/* * boxui.c * * Multiplatform UI library for SDL3. * * (c) 2023-2024 Dario Rodriguez * Licensed under the terms of the MIT/X license. */ #include #include #include #include #include #include #include #include "SDL3/SDL.h" #include "SDL3/SDL_iostream.h" #include "SDL3_ttf/SDL_ttf.h" #include "boxui.h" #include "notosans_regular_ttf.c" #define DEFAULTFONTSIZE 16 #define MAXPENDINGSIGNAL 64 #define BLOCKSIGNALACTION 16 #define BLOCKWINDOW 8 #ifdef DEBUG_BOXUI #define DEBUGFPRINTF(a) fprintf a #else #define DEBUGFPRINTF(a) #endif typedef enum datatype_t { datatype_literal=0, /* inline string */ datatype_escapedquotedliteral, /* (only used when parsing) inline quoted string */ datatype_quotedliteral, /* quoted string (%s or unescaping escapedquotedliteral) */ datatype_integer, /* (only used when parsing) %i */ datatype_textvariable, /* %s%i */ datatype_asset, /* %p%i%i */ datatype_callback, /* %p%p */ } datatype_t; typedef struct signalaction_t { int nsignal; char *actionstr; } signalaction_t; typedef struct signaldata_t { int readpos; int writepos; int buf[MAXPENDINGSIGNAL]; int sizeactions; int usedactions; signalaction_t *actions; } signaldata_t; typedef struct intboxui_t { SDL_Window *window; SDL_Renderer *renderer; int width; int height; signaldata_t signaldata; SDL_IOStream *defaultfontdata; TTF_Font *defaultfont; int defaultfontwidth; int defaultfontheight; void *userptr; int isdirty; int haschanged; SDL_Texture *texture; /* temporary members (to be able to test) */ char currenttext[1024]; char currentclickevent[1024]; char currentactionstr[1024]; int flag_currentactionstrused; } intboxui_t; static void intboxui_signalhandler_gen(int nsignal, int is_set, intboxui_t *boxuiset); static int intboxui_render(intboxui_t *boxui); static char *intboxuiutil_getnext(char *data, int *posdata, va_list valist, int *resl, datatype_t *datatype, char **ptr1, char **ptr2, int *int1, int *int2, int *in_error); static char *datatypedup_literalquotedliteral(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2); static int datatypedup_int(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2, int defaultint, int *in_error); static int datatypedup_istrue(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2, int *in_error); static int memstrcmp(char *ptr, int l, char *str); boxui_t * boxui_init(SDL_Window *window, SDL_Renderer *renderer, int width, int height) { intboxui_t *boxui; if((boxui=malloc(sizeof(intboxui_t)))==NULL) { SDL_Log("boxui: insufficient memory for main structure\n"); return(NULL); /* insuf. mem for boxui struct */ } memset(boxui,0,sizeof(intboxui_t)); if(!TTF_Init() || (boxui->defaultfontdata=SDL_IOFromConstMem(notosans_regular_ttf,notosans_regular_ttf_len))==NULL || (boxui->defaultfont=TTF_OpenFontIO(boxui->defaultfontdata,0,DEFAULTFONTSIZE))==NULL ) { boxui_free(boxui),boxui=NULL; SDL_Log("boxui: Couldn't initialize TTF: %s\n",SDL_GetError()); return(NULL); } boxui->window=window; boxui->renderer=renderer; boxui->width=width; boxui->height=height; boxui->isdirty=1; /* font width and height */ { int w,h; /* width */ TTF_GetStringSize(boxui->defaultfont,"m",0,&w,&h); boxui->defaultfontwidth=w; /* height */ boxui->defaultfontheight=TTF_GetFontHeight(boxui->defaultfont); } return((boxui_t *)boxui); } void boxui_free(boxui_t *paramboxui) { int i; intboxui_t *boxui; if((boxui=(intboxui_t *)paramboxui)==NULL) return; /* nothing to do */ intboxui_signalhandler_gen(0,1,NULL); if(boxui->signaldata.actions!=NULL) { signalaction_t *action; for(i=0,action=boxui->signaldata.actions ;isignaldata.usedactions ;i++,action++ ) { if(action->actionstr!=NULL) free(action->actionstr),action->actionstr=NULL; } free(boxui->signaldata.actions),boxui->signaldata.actions=NULL,boxui->signaldata.sizeactions=boxui->signaldata.usedactions=0; } if(boxui->defaultfont!=NULL) TTF_CloseFont(boxui->defaultfont),boxui->defaultfont=NULL; if(boxui->defaultfontdata!=NULL) SDL_CloseIO(boxui->defaultfontdata),boxui->defaultfontdata=NULL; if(boxui->texture!=NULL) SDL_DestroyTexture(boxui->texture),boxui->texture=NULL; free(boxui),boxui=NULL; return; } int boxui_resize(boxui_t *paramboxui, int width, int height) { intboxui_t *boxui=(intboxui_t *)paramboxui; if(boxui==NULL) { /* XXX (OLD): DEBUGFPRINTF((stderr,"WARNING: %s%s%i: boxui_resize: called with NULL boxui", (filedubug!=NULL)?filedebug:"",(filedubug!=NULL)?":":"",linedebug)); */ return(-1); } boxui->width=width; boxui->height=height; boxui->isdirty=1; return(0); } int boxui_setuserptr(boxui_t *boxui, void *userptr) { if(boxui==NULL) return(-1); ((intboxui_t *)boxui)->userptr=userptr; return(0); } void * boxui_getuserptr(boxui_t *boxui) { if(boxui==NULL) return(NULL); return(((intboxui_t *)boxui)->userptr); } static void intboxui_signalhandler_gen(int nsignal, int is_set, intboxui_t *boxuiset) { static signaldata_t *signaldata; int pos; if(is_set) { signaldata=(boxuiset!=NULL)?&(boxuiset->signaldata):NULL; return; } if(signaldata==NULL) return; /* no handler */ pos=signaldata->writepos; pos%=MAXPENDINGSIGNAL; signaldata->buf[pos]=nsignal; pos++; pos%=MAXPENDINGSIGNAL; signaldata->writepos=pos; return; } void intboxui_signalhandler(int nsignal) { intboxui_signalhandler_gen(nsignal, 0, NULL); return; } int boxui_setsignal(boxui_t *paramboxui, int nsignal, char *actionstr) { intboxui_t *boxui=(intboxui_t *)paramboxui; int i; signaldata_t *signaldata; char *newstr; if(boxui==NULL || actionstr==NULL) return(-1); /* sanity check failed */ intboxui_signalhandler_gen(0,1,boxui); signaldata=&(((intboxui_t*)boxui)->signaldata); /* check if signal already registered */ for(i=0;iusedactions;i++) { if(signaldata->actions[i].nsignal==nsignal) { if(strcmp(actionstr,signaldata->actions[i].actionstr)==0) return(0); /* already configured */ if((newstr=strdup(actionstr))==NULL) return(-1); /* mem. insuf. */ free(signaldata->actions[i].actionstr),signaldata->actions[i].actionstr=NULL; signaldata->actions[i].actionstr=newstr; return(0); /* all done */ } } /* enlarge array if necessary */ if(signaldata->usedactions==signaldata->sizeactions) { signalaction_t *newactions; int reqsize=sizeof(signalaction_t)*(signaldata->sizeactions+BLOCKSIGNALACTION); if((newactions=realloc(signaldata->actions,reqsize))==NULL) return(-1); /* mem. insuf. */ signaldata->actions=newactions,newactions=NULL; memset(signaldata->actions+signaldata->sizeactions,0,sizeof(signalaction_t)*BLOCKSIGNALACTION); signaldata->sizeactions+=BLOCKSIGNALACTION; } /* add element */ if((signaldata->actions[signaldata->usedactions].actionstr=strdup(actionstr))==NULL) return(-1); /* mem. insuf. */ signaldata->actions[signaldata->usedactions].nsignal=nsignal; signaldata->usedactions++; /* install signal handler */ return(0); } int boxui_event(boxui_t *paramboxui, SDL_Event *event) { #warning TODO: we do not have the position of the texture in the window intboxui_t *boxui=(intboxui_t *)paramboxui; if(boxui==NULL || event==NULL) return(-1); /* sanity check failed */ if(event->type==SDL_EVENT_WINDOW_SHOWN || event->type==SDL_EVENT_WINDOW_EXPOSED || event->type==SDL_EVENT_WINDOW_RESIZED || event->type==SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED || event->type==SDL_EVENT_WINDOW_MINIMIZED || event->type==SDL_EVENT_WINDOW_MAXIMIZED || event->type==SDL_EVENT_WINDOW_RESTORED || event->type==SDL_EVENT_WINDOW_OCCLUDED || event->type==SDL_EVENT_WINDOW_ENTER_FULLSCREEN || event->type==SDL_EVENT_WINDOW_LEAVE_FULLSCREEN ) { boxui->isdirty=1; } else #if 1 /* test */ if(event->type==SDL_EVENT_MOUSE_BUTTON_UP) { if(boxui->currentclickevent[0]!='\0') { boxui_putaction((boxui_t *)boxui,boxui->currentclickevent,NULL); } } /* end of test */ #endif return(0); } int boxui_tick(boxui_t *paramboxui) { intboxui_t *boxui=(intboxui_t *)paramboxui; if(boxui==NULL) return(-1); /* sanity check failed */ if(boxui->isdirty) intboxui_render(boxui); return(0); } int boxui_haschanged(boxui_t *paramboxui) { intboxui_t *boxui=(intboxui_t *)paramboxui; if(boxui==NULL) return(0); /* sanity check failed */ return(boxui->isdirty || boxui->haschanged); } SDL_Texture * boxui_gettexture(boxui_t *paramboxui) { intboxui_t *boxui=(intboxui_t *)paramboxui; if(boxui==NULL) return(NULL); #if 0 fprintf(stderr,"GETTEXTURE\n"); #endif if(boxui->texture==NULL || boxui->isdirty) { boxui->isdirty=1; boxui_tick((boxui_t *) boxui); if(boxui->texture==NULL) { SDL_Log("boxui: Couldn't generate texture\n"); return(NULL); } } boxui->haschanged=0; return(boxui->texture); } char * boxui_getaction(boxui_t *paramboxui, char **actiondata) { 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); } int boxui_putaction(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_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); return(classwiname); /* end of test */ #warning TODO } 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'; } } } return(winame); /* end of test */ #warning TODO } int boxuiutil_datacmp(char *datastr,char *key,char *value) { #warning TODO return(-1); } int boxui_pack(boxui_t *boxui, char *filedebug, int linedebug, char *winame, char *format, ...) { #warning TODO return(0); } static int intboxui_render(intboxui_t *paramboxui) { intboxui_t *boxui=(intboxui_t *)paramboxui; if(boxui==NULL) return(-1); /* sanity check failed */ /* test */ #if 0 fprintf(stderr,"RENDER: START\n"); #endif if(boxui->texture==NULL) { if((boxui->texture=SDL_CreateTexture(boxui->renderer,SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_TARGET,boxui->width,boxui->height))==NULL) { SDL_Log("boxui: couldn't create output texture\n"); return(-1); } } SDL_SetRenderTarget(boxui->renderer,boxui->texture); SDL_SetRenderDrawColor(boxui->renderer,0xff,0xff,0xff,0xff); SDL_RenderClear(boxui->renderer); if(boxui->currenttext[0]!='\0') { SDL_Surface *fgsurface=NULL; SDL_Texture *fgtexture=NULL; SDL_FRect dstrect; SDL_Color bg={0x00,0x00,0x00,0x00}; SDL_Color fg={0x00,0x00,0x00,0xff}; if((fgsurface=TTF_RenderText_Shaded(boxui->defaultfont,boxui->currenttext,0,fg,bg))!=NULL && (fgtexture=SDL_CreateTextureFromSurface(boxui->renderer,fgsurface))!=NULL) { #if 0 fprintf(stderr,"RENDER: text (w:%i h:%i)\n",fgsurface->w,fgsurface->h); #endif dstrect.x=(boxui->width-fgsurface->w)/2; dstrect.y=(boxui->height-fgsurface->h)/2; dstrect.w=fgsurface->w,dstrect.h=fgsurface->h; #if 0 fprintf(stderr,"RENDER: text (w:%i h:%i) dstrect:%ix%i+%i+%i\n",fgsurface->w,fgsurface->h,dstrect.w,dstrect.h,dstrect.x,dstrect.y); #endif SDL_RenderTexture(boxui->renderer,fgtexture,NULL,&dstrect); } if(fgsurface!=NULL) SDL_DestroySurface(fgsurface),fgsurface=NULL; if(fgtexture!=NULL) SDL_DestroyTexture(fgtexture),fgtexture=NULL; } SDL_SetRenderTarget(boxui->renderer,NULL); boxui->isdirty=0; boxui->haschanged=1; #if 0 fprintf(stderr,"RENDER: START\n"); #endif /* end of test */ #warning TODO return(0); } static char * intboxuiutil_getnext(char *data, int *posdata, va_list valist, int *resl, datatype_t *datatype, char **ptr1, char **ptr2, int *int1, int *int2, int *in_error) { char *ptr; char *res; ptr=data+*posdata; /* skip trailing spaces */ while(*ptr==' ') ptr++; if(*ptr!='-') { *in_error=1; DEBUGFPRINTF((stderr,"WARNING: intboxuiutil_getnext(): wrong data in \"%s\", analyzing \"%s\", '-' not found at \"%s\"\n",data,data+*posdata,ptr)); return(NULL); } /* get keyword (including the '-') */ res=ptr; while(*ptr!=' ' && *ptr!='\0') ptr++; *resl=ptr-res; /* skip the space */ ptr++; /* get value */ if(*ptr!='\"' && *ptr!='%') { *datatype=datatype_literal; *ptr1=ptr; while(*ptr!=' ' && *ptr!='\0') ptr++; *int1=ptr-*ptr1; } else if(*ptr=='\"') { int flag_esc; *datatype=datatype_escapedquotedliteral; ptr++; *ptr1=ptr; for(flag_esc=0;!(flag_esc==0 && *ptr=='\"') && *ptr!='\0';ptr++) { if(flag_esc) flag_esc=0; else if(*ptr=='\\') flag_esc=1; } if(*ptr!='\"') { *in_error=1; DEBUGFPRINTF((stderr,"WARNING: intboxuiutil_getnext(): wrong data in \"%s\", analyzing \"%s\", unterminated string at \"%s\"\n",data,data+*posdata,*ptr1)); return(NULL); } *int1=ptr-*ptr1; ptr++; } else if(ptr[0]=='%' && ptr[1]=='s' && (ptr[2]=='\0' || ptr[2]==' ')) { *datatype=datatype_quotedliteral; *ptr1=va_arg(valist, char *); *int1=strlen(*ptr1); ptr+=2; } else if(ptr[0]=='%' && ptr[1]=='i' && (ptr[2]=='\0' || ptr[2]==' ')) { *datatype=datatype_integer; *int1=va_arg(valist, int); ptr+=2; } else if(ptr[0]=='%' && ptr[1]=='s' && ptr[2]=='%' && ptr[3]=='i' && (ptr[4]=='\0' || ptr[4]==' ')) { *datatype=datatype_textvariable; *ptr1=va_arg(valist, char *); *int1=va_arg(valist, int); ptr+=4; } else if(ptr[0]=='%' && ptr[1]=='p' && ptr[2]=='%' && ptr[3]=='i' && ptr[4]=='%' && ptr[5]=='i' && (ptr[6]=='\0' || ptr[6]==' ')) { *datatype=datatype_asset; *ptr1=va_arg(valist, char *); *int1=va_arg(valist, int); *int2=va_arg(valist, int); ptr+=6; } else if(ptr[0]=='%' && ptr[1]=='p' && ptr[2]=='%' && ptr[3]=='p' && (ptr[4]=='\0' || ptr[4]==' ')) { *datatype=datatype_callback; *ptr1=va_arg(valist, char *); *ptr2=va_arg(valist, char *); ptr+=4; } else { *in_error=1; DEBUGFPRINTF((stderr,"WARNING: intboxuiutil_getnext(): wrong data in \"%s\", analyzing \"%s\", unrecognized value type at \"%s\"\n",data,data+*posdata,ptr)); return(NULL); } /* save the current position and return the key */ *posdata=ptr-data; return(res); } static char * datatypedup_literalquotedliteral(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2) { char *res,*aux; int t,i; int flag_esc; res=NULL; int req; /* two passes; first calculates needed storage space, second copies the data */ for(t=0,req=0;t<2;t++) { if(datatype==datatype_literal || datatype==datatype_quotedliteral) { req=int1+1; if(t) { memcpy(res,ptr1,req); ptr1[int1]='\0'; } } else if(datatype==datatype_escapedquotedliteral) { for(req=0,aux=ptr1,flag_esc=0,i=0;!(flag_esc==0 && *aux=='\"') && *aux!='\0' && i0 || req==0;) { if(t) res[(l-2)-req]=(i%10)+'0'; req++; i/=10; } if(t) res[l-1]='\0'; req++; } else { return(NULL); /* type is not amenable to strdup */ } if(t==0) { if((res=malloc(req))==NULL) return(NULL); /* insuf. mem. */ } } return(res); } static int datatypedup_int(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2, int defaultint, int *in_error) { if(in_error!=NULL) *in_error=0; if(datatype==datatype_integer) return(int1); else if(datatype==datatype_literal || datatype==datatype_escapedquotedliteral || datatype==datatype_quotedliteral) { int i,res; if(int1==0) return(defaultint); /* no data */ for(i=0,res=0;i='0' && ptr1[i]<='9')) { if(in_error!=NULL) *in_error=1; return(defaultint); } res=(res*10)+(ptr1[i]-'0'); } return(res); } if(in_error!=NULL) *in_error=1; return(defaultint); } static int datatypedup_istrue(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2, int *in_error) { if(in_error!=NULL) *in_error=0; if(datatype==datatype_literal || datatype==datatype_escapedquotedliteral || datatype==datatype_quotedliteral) { if(memstrcmp(ptr1,int1,"true")==0) return(1); return(0); } if(in_error!=NULL) *in_error=1; return(0); } static int memstrcmp(char *ptr, int l, char *str) { int res; res=memcmp(ptr,str,l); if(res==0 && str[l]=='\0') return(0); return((res==0)?1:res); }