/*
* boxui.c
*
* Multiplatform UI library for SDL2.
*
* (c) 2023 Dario Rodriguez <antartica@box-ui.org>
* Licensed under the terms of the GNU GPL v3.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include "SDL.h"
#include "SDL_rwops.h"
#include "SDL_ttf.h"
#include "boxui.h"
#include "notosans_regular_ttf.c"
#define DEFAULT_FPS 30
typedef struct intboxui_t {
char *title;
int defaultw,defaulth;
int fps;
long long fpsepoch;
long long fpsepochframeno;
void *userptr;
int isdirty;
long long lastrenderedframeno;
int sizeafterbuf;
int usedafterbuf;
char *afterbuf;
/* temporary members (to be able to test) */
int screenw;
int screenh;
SDL_Window *win;
SDL_Renderer *renderer;
SDL_RWops *defaultfontdata;
TTF_Font *defaultfont;
int defaultfontheight;
int defaultfontwidth;
char currenttext[1024];
char currentclickevent[1024];
char currentactionstr[1024];
int flag_currentactionstrused;
/* end of temporary members (to be able to test) */
} intboxui_t;
int intboxui_waittick(intboxui_t *boxui);
int intboxui_render(intboxui_t *boxui);
int intboxui_after_doexpired(intboxui_t *boxui);
boxui_t *
boxui_init(char *title,int defaultw, int defaulth, int flags)
{
intboxui_t *boxui;
title=(title==NULL)?"":title;
if((boxui=malloc(sizeof(intboxui_t)))==NULL)
return(NULL); /* insuf. mem for boxui struct */
memset(boxui,0,sizeof(intboxui_t));
if((boxui->title=strdup(title))==NULL) {
boxui_free((boxui_t *)boxui),boxui=NULL;
return(NULL); /* insuf. mem for title string */
}
/* temporary initialization (to be able to test) */
if(SDL_Init(SDL_INIT_VIDEO)<0
|| (boxui->win=SDL_CreateWindow(title,
SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,
defaultw,defaulth,0))==NULL
|| (boxui->renderer=SDL_CreateRenderer(boxui->win,-1,
SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC))==NULL) {
boxui_free(boxui),boxui=NULL;
return(NULL); /* ERROR: Couldn't init video */
}
boxui->screenw=defaultw,boxui->screenh=defaulth;
if((boxui->defaultfontdata=SDL_RWFromConstMem(notosans_regular_ttf,notosans_regular_ttf_len))==NULL
|| TTF_Init()==-1) {
boxui_free(boxui),boxui=NULL;
return(NULL); /* ERROR: Couldn't init font data */
}
SDL_RWseek(boxui->defaultfontdata,0,RW_SEEK_SET);
if(boxui->defaultfontdata==NULL
|| (boxui->defaultfont=TTF_OpenFontRW(boxui->defaultfontdata,0,/* default font size */ 16))==NULL) {
boxui_free(boxui),boxui=NULL;
return(NULL); /* ERROR: Couldn't init defaultfont */
}
/* font width and height */
{
SDL_Surface *s;
SDL_Color c={0,0,0,0};
/* width */
if((s=TTF_RenderUTF8_Blended(boxui->defaultfont,"m",c))==NULL) {
boxui_free(boxui),boxui=NULL;
return(NULL); /* ERROR: Couldn't query font width */
}
boxui->defaultfontwidth=s->w;
SDL_FreeSurface(s),s=NULL;
/* height */
if((s=TTF_RenderUTF8_Blended(boxui->defaultfont,"gjpqy_",c))==NULL) {
boxui_free(boxui),boxui=NULL;
return(NULL); /* ERROR: Couldn't query font height */
}
boxui->defaultfontheight=s->h;
SDL_FreeSurface(s),s=NULL;
}
/* end of temporary initialization (to be able to test) */
/* return the struct */
return((boxui_t *)boxui);
}
void
boxui_free(boxui_t *paramboxui)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL)
return; /* sanity check failed */
if(boxui->title!=NULL)
free(boxui->title),boxui->title=NULL;
/* temporary initialization freeing */
if(boxui->defaultfont!=NULL)
TTF_CloseFont(boxui->defaultfont),boxui->defaultfont=NULL;
if(TTF_WasInit())
TTF_Quit();
if(boxui->defaultfontdata!=NULL)
SDL_FreeRW(boxui->defaultfontdata),boxui->defaultfontdata=NULL;
if(boxui->renderer!=NULL)
SDL_DestroyRenderer(boxui->renderer),boxui->renderer=NULL;
if(boxui->win!=NULL)
SDL_DestroyWindow(boxui->win),boxui->win=NULL;
SDL_Quit();
/* end of temporary initialization freeing */
free(boxui),boxui=NULL,paramboxui=NULL;
return;
}
int
boxui_setuserptr(boxui_t *paramboxui, void *userptr)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL)
return(-1); /* sanity check failed */
boxui->userptr=userptr;
return(0);
}
void *
boxui_getuserptr(boxui_t *paramboxui)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL)
return(NULL); /* sanity check failed */
return(boxui->userptr);
}
int
boxui_setfps(boxui_t *paramboxui, int fps)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL || fps<1)
return(-1); /* sanity check failed */
boxui->fps=fps;
#if SDL_VERSION_ATLEAST(2,0,18)
boxui->fpsepoch=(long long)SDL_GetTicks64();
#else
boxui->fpsepoch=(long long)SDL_GetTicks();
#endif
boxui->fpsepochframeno=boxui->lastrenderedframeno;
return(0);
}
int
boxui_getfps(boxui_t *paramboxui)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL)
return(DEFAULT_FPS); /* sanity check failed */
return((boxui->fps==0)?DEFAULT_FPS:boxui->fps);
}
int
boxui_tick(boxui_t *paramboxui)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL) {
SDL_Delay(1);
return(-1); /* sanity check failed */
}
if(boxui->isdirty) {
boxui_ghostrender((boxui_t *)boxui);
intboxui_waittick(boxui);
intboxui_render(boxui);
} else {
intboxui_waittick(boxui);
}
boxui->lastrenderedframeno++;
return(0);
}
int
boxui_ghostrender(boxui_t *boxui)
{
#warning TODO
return(-1);
}
int
boxui_refreshevents(boxui_t *paramboxui)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL)
return(-1); /* sanity check failed */
SDL_PumpEvents();
return(0);
}
int
boxui_getevent(boxui_t *paramboxui, SDL_Event *event)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL || event==NULL)
return(-1); /* sanity check failed */
while(SDL_PeepEvents(event,1,SDL_GETEVENT,SDL_FIRSTEVENT,SDL_LASTEVENT)>0) {
#if 1
/* test */
if(event->type==SDL_MOUSEBUTTONUP) {
if(boxui->currentclickevent[0]!='\0') {
boxui_putactionstr((boxui_t *)boxui,boxui->currentclickevent,NULL);
continue; /* we have used the event */
}
}
/* end of test */
#endif
return(0); /* event found and put into "event" */
}
return(-1); /* no events remaining */
}
int
boxui_event_isquit(boxui_t *paramboxui, SDL_Event *event)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL || event==NULL)
return(0); /* sanity check failed */
if(event->type==SDL_WINDOWEVENT && event->window.event==SDL_WINDOWEVENT_CLOSE)
return(1);
return(0);
}
int
boxui_event_iskeydown(boxui_t *paramboxui, SDL_Event *event, int *keycode)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL || event==NULL)
return(0); /* sanity check failed */
if(event->type==SDL_KEYDOWN) {
if(keycode!=NULL)
*keycode=event->key.keysym.sym;
return(1);
}
return(0);
}
int
boxui_event_isresize(boxui_t *paramboxui, SDL_Event *event, int *neww, int *newh)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL || event==NULL)
return(0); /* sanity check failed */
if(event->type==SDL_WINDOWEVENT_RESIZED
|| event->type==SDL_WINDOWEVENT_SIZE_CHANGED) {
if(neww!=NULL)
*neww=event->window.data1;
if(newh!=NULL)
*newh=event->window.data2;
return(1);
}
return(0);
}
void
boxui_resize(boxui_t *boxui, int neww, int newh)
{
#warning TODO
return;
}
int
boxui_putactionstr(boxui_t *paramboxui, char *firstelem, ...)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL || firstelem==NULL)
return(-1); /* sanity check failed */
/* test */
strncpy(boxui->currentactionstr,firstelem,sizeof(boxui->currentactionstr));
boxui->currentactionstr[sizeof(boxui->currentactionstr)-1]='\0';
boxui->flag_currentactionstrused=0;
/* end of test */
#warning TODO
return(-1);
}
char *
boxui_getactionstr(boxui_t *paramboxui, char **secondelemtoend)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL)
return(NULL); /* sanity check failed */
/* test */
if(boxui->flag_currentactionstrused || boxui->currentactionstr[0]=='\0')
return(NULL);
boxui->flag_currentactionstrused=1;
return(boxui->currentactionstr);
/* end of test */
#warning TODO
return(NULL);
}
char *
boxui_add(boxui_t *paramboxui, char *filedebug, int linedebug, char *classwiname, char *format, ...)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL || classwiname==NULL || format==NULL)
return(NULL); /* sanity check failed */
/* test */
if(strchr(format,'%')==NULL)
boxui_config(boxui,NULL,-1,classwiname,format);
/* end of test */
#warning TODO
return(NULL);
}
char *
boxui_config(boxui_t *paramboxui, char *filedebug, int linedebug, char *winame, char *format, ...)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL || winame==NULL || format==NULL)
return(NULL); /* sanity check failed */
/* test */
{
char *start,*end;
int l;
/* text */
if((start=strchr(format,'\"'))!=NULL
&& (start=start+1)!=NULL
&& (end=strchr(start,'\"'))!=NULL) {
l=end-start;
l=(l>sizeof(boxui->currenttext))?sizeof(boxui->currenttext)-1:l;
memcpy(boxui->currenttext,start,l);
boxui->currenttext[l]='\0';
boxui->isdirty=1;
}
/* click */
for(start=strchr(format,'-');start!=NULL;start=strchr(start+1,'-')) {
if(memcmp(start,"-click ",7)==0) {
start+=7;
end=strchr(start,' ');
end=(end==NULL)?start+strlen(start):end;
l=end-start;
l=(l>sizeof(boxui->currentclickevent))?sizeof(boxui->currentclickevent)-1:l;
memcpy(boxui->currentclickevent,start,l);
boxui->currentclickevent[l]='\0';
}
}
}
/* end of test */
#warning TODO
return(NULL);
}
char *
boxui_pack(boxui_t *boxui, char *filedebug, int linedebug, char *winame, char *format, ...)
{
#warning TODO
return(NULL);
}
int
intboxui_waittick(intboxui_t *paramboxui)
{
long long delay;
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL) {
SDL_Delay(1);
return(-1); /* sanity check failed */
}
#if SDL_VERSION_ATLEAST(2,0,18)
delay=((long long)SDL_GetTicks64());
#else
delay=((long long)SDL_GetTicks());
#endif
delay-=boxui->fpsepoch;
delay=(((boxui->lastrenderedframeno-boxui->fpsepochframeno)+1)*1000/((boxui->fps==0)?DEFAULT_FPS:boxui->fps))-delay;
if(delay>0) {
SDL_Delay(delay);
} else if(delay<-1000) {
/* reset epoch if unsyncronized by more than 1s */
#if SDL_VERSION_ATLEAST(2,0,18)
boxui->fpsepoch=((long long)SDL_GetTicks64());
#else
boxui->fpsepoch=((long long)SDL_GetTicks());
#endif
boxui->fpsepochframeno=boxui->lastrenderedframeno;
}
intboxui_after_doexpired(boxui);
return(0);
}
int
intboxui_render(intboxui_t *paramboxui)
{
intboxui_t *boxui=(intboxui_t *)paramboxui;
if(boxui==NULL)
return(-1); /* sanity check failed */
/* test */
SDL_SetRenderDrawColor(boxui->renderer,0xff,0xff,0xff,0xff);
SDL_RenderClear(boxui->renderer);
if(boxui->currenttext[0]!='\0') {
SDL_Surface *fgsurface=NULL;
SDL_Texture *fg=NULL;
SDL_Rect dstrect;
SDL_Color c={0x00,0x00,0x00,0xff};
if((fgsurface=TTF_RenderUTF8_Blended(boxui->defaultfont,boxui->currenttext,c))!=NULL
&& (fg=SDL_CreateTextureFromSurface(boxui->renderer,fgsurface))!=NULL) {
dstrect.x=(boxui->screenw-fgsurface->w)/2;
dstrect.y=(boxui->screenh-fgsurface->h)/2;
dstrect.w=fgsurface->w,dstrect.h=fgsurface->h;
SDL_RenderCopy(boxui->renderer,fg,NULL,&dstrect);
}
if(fgsurface!=NULL)
SDL_FreeSurface(fgsurface),fgsurface=NULL;
if(fg!=NULL)
SDL_DestroyTexture(fg),fg=NULL;
}
SDL_RenderPresent(boxui->renderer);
boxui->isdirty=0;
/* end of test */
#warning TODO
return(0);
}
int
intboxui_after_doexpired(intboxui_t *boxui)
{
#warning TODO
return(0);
}