/* * imgmover.c * * Simple C application using raylib to move images between directories. * * History: * 20250119 Creation. Menu bar. * 20250123 Load font. * 20250213 Support sticky drop-down menus. * 20250216 Modularize menu handling. * 20250222 Able to list files. * 20250223 Draw pane titles and main dir list. * 20250224 Draw right dir list and store elem positions. * 20250225 Draw image placeholders * 20250226 Fix rightside positions * 20250228 Navigate directories * 20250301 Aesthetic fixes for leftside. * Basic image loading. * Show big image on hover. * Preserve aspect ratio of big image. * 20250305 Scrollable left pane. * 20250308 Show statustooltip on image hover. * Scrollwheel support for leftside. * Fix thumb aspect ratio. * 20250311 Fix bug because of stray CloseWindow() call. * Add show using all window with right button. * 20250316 Add android target support. * Delay loading images if over fps deadline. * Scroll by finger. Big image on double click. * 20250319 Add-dirdata button. * 20250320 Fix add-dirdata button appearance. * Dirdata colors. Select dirdata. * 20250329 Background loading of thumbnails. * 20250330 Refine background loading. * 20250413 Background loading for windows target. * * Author: Dario Rodriguez dario@darionomono.com * (c) Dario Rodriguez 2025 * This program is licensed under the terms of GNU GPL v2.1+ */ #include #include #include #include #include #include #include #include #include #include "raylib.h" #include "roboto_regular.c" #define UTF8DOWNARROW "\xe2\x86\x86" /* U+2186 in UTF-8 */ #ifndef ANDROID #define SIMANDROID #endif #define TARGETFPS 30 #if defined(ANDROID) || defined(SIMANDROID) #define ROOTDIR "/sdcard/" #define DEFAULTWIDTH 2400 #define DEFAULTHEIGHT 1080 #define LEFTSIZE 1600 #define DEFAULTDIRDATAHEIGHT 492 #define DEFAULTDIRDATATRIANGLEW 100 #define LEFTIMAGESIDELEN 326 #define FONTSIZE 64 #define FONTBIGSIZE 96 #define FONTHUGESIZE 128 #define ADDREMOVEDIRDATAHEIGHT 128 #else #define ROOTDIR "/var/www/default/animeshot/" #define DEFAULTWIDTH 1280 #define DEFAULTHEIGHT 768 #define LEFTSIZE 720 #define DEFAULTDIRDATAHEIGHT 150 #define DEFAULTDIRDATATRIANGLEW 35 #define LEFTIMAGESIDELEN 125 #define FONTSIZE 18 #define FONTBIGSIZE 32 #define FONTHUGESIZE 48 #define ADDREMOVEDIRDATAHEIGHT 128 #endif #define SCROLLTHRESHOLD (LEFTIMAGESIDELEN/3) #if defined(SIMANDROID) #undef ROOTDIR #define ROOTDIR "/var/www/default/animeshot/" #endif #define WHEELSTEP LEFTIMAGESIDELEN #define SEP "/" #define BLOCKLISTINGBUF 2048 #define BLOCKLISTINGELEMS 1024 #define BLOCKDIRDATA 16 #define SIZEBGLOAD 256 #ifndef FILLXY #define FILLXY(xywh,valx,valy) (xywh).x=(valx),(xywh).y=(valy) #endif #ifndef FILLWH #define FILLWH(xywh,valw,valh) (xywh).w=(valw),(xywh).h=(valh) #endif #ifndef FILLXYWH #define FILLXYWH(xywh,valx,valy,valw,valh) (xywh).x=(valx),(xywh).y=(valy),(xywh).w=(valw),(xywh).h=(valh) #endif #ifndef UNROLLXYWH #define UNROLLXYWH(xywh) (xywh).x,(xywh).y,(xywh).w,(xywh).h #endif #ifndef UNROLLWHXY #define UNROLLWHXY(xywh) (xywh).w,(xywh).h,(xywh).x,(xywh).y #endif #ifndef RD #define RD 0 #endif #ifndef WR #define WR 1 #endif #if !defined(__linux__) && !defined(ANDROID) /* the old raylib used in the windows build lacks this function */ bool IsImageValid(Image image) { bool result = false; if ((image.data != NULL) && // Validate pixel data available (image.width > 0) && // Validate image width (image.height > 0) && // Validate image height (image.format > 0) && // Validate image format (image.mipmaps > 0)) result = true; // Validate image mipmaps (at least 1 for basic mipmap level) return result; } #endif typedef struct xywh_t { int x; int y; int w; int h; } xywh_t; typedef struct font_t { Font font; int height; } font_t; typedef struct menudata_t { char *title; xywh_t xywh; int sizeoptions; char **options; xywh_t optionsxywh; int flag_open; int flag_stickyopen; int currentoption; } menudata_t; typedef struct menubar_t { int height; int sizemenudata; menudata_t **menudata; font_t *ptrfont; } menubar_t; typedef struct thumb_t { xywh_t xywh; xywh_t screenxywh; Texture2D texture; int has_texture; int has_failedload; } thumb_t; typedef struct listingdata_t { thumb_t left; thumb_t right; char *name; /* first byte in the name is the type, next is the name, i.e. a directory is "dhome" and a file is "f.bashrc" (this is done for easier sorting) */ } listingdata_t; typedef struct listing_t { int sizeelems; int usedelems; listingdata_t *elems; int sizebuf; int usedbuf; char *buf; int has_leftxywh; int has_rightxywh; xywh_t lastleftxywh; } listing_t; typedef struct dirdata_t { int height; char *dirname; listing_t listing; int leftscrollpos; } dirdata_t; typedef struct texture_t { char currentpath[2048]; Texture2D texture; int texturew; int textureh; int has_texture; int has_failedload; xywh_t source; /* be able to detect a "double click" */ } texture_t; typedef struct bgload_t { /* main/thread ownership management */ int lended_to_thread; // written from main, read from thread int thread_finished; // written from thread, read from main int is_todo; // written from main, read from thread int has_mark; // written/read from main /* data only accessed from owner */ char path[1024]; // to use only from owner Image image; // to use only from owner int has_data; // to use only from owner int has_failedload; // to use only from owner } bgload_t; typedef struct bg_t { int sizebgload; bgload_t *bgload; int pipe[2]; pthread_t thread; pthread_attr_t tattr; int flag_threadstarted; } bg_t; typedef struct body_t { char *rootdir; xywh_t xywh; xywh_t backxywh; int leftsize; int sizedirdata; dirdata_t **dirdata; int currentdirdata; int rightscrollpos; font_t *ptrfont; font_t *ptrfontbig; font_t *ptrfonthuge; texture_t texture; int is_displayingtexture; texture_t bigtexture; int flag_drawbigtexture; xywh_t dirdataadd; Font roundedbox; bg_t *bg; } body_t; typedef struct im_t { int windowinit; int w; int h; menubar_t *menubar; body_t *body; font_t *font; font_t *fontbig; font_t *fonthuge; } im_t; im_t *im_init(char *menus, char *rootdir); void im_free(im_t *im); font_t *im_font_init(int size); void im_font_free(font_t *font); menubar_t *im_menubar_init(char *menus, font_t *font); void im_menubar_free(menubar_t *menubar); int im_menubar_mouse(menubar_t *menubar, Vector2 mousepos, int lmbpressed, int lmbreleased, int lmbdown, int *click_avail, char **sel_menu, char **sel_submenu); int im_menubar_draw(menubar_t *menubar, int windowwidth, int windowheight, int *needs_nextredraw); body_t *im_body_init(int x, int y, font_t *font, font_t *fontbig, font_t *fonthuge, int leftsize, char *rootdir, int windowwidth, int windowheight); void im_body_free(body_t *body); int im_body_add(body_t *body,char *dir); int im_body_mouse(body_t *body, Vector2 mousepos, Vector2 wheel, int lmbpressed, int lmbreleased, int lmbdown, int *click_avail); int im_body_draw(body_t *body, Vector2 mousepos, int lmbdown, int rmbdown, int windowwidth, int windowheight, int *needs_nextredraw, int scrollspeed); int listing_get(listing_t *listing, char *pathprefix, char *path, int flag_sort); void listing_freedata(listing_t *listing); int listing_fillxywh(listing_t *listing, font_t *font, int w, int sidelen, int is_left); int texture_load(texture_t *texture, char *fullpath, int maxw, int maxh, bg_t *bg); int texture_draw(texture_t *texture, int x0, int y0, int maxw, int maxh); int texture_freedata(texture_t *texture); int imutil_menu_count(char *menus); char *imutil_menu_get(char *menus, int targetn, int *len); int imutil_submenu_count(char *menus); char *imutil_submenu_get(char *menus, int targetn, int *len); char *imutil_strduplen(char *str, int len); int is_imutil_insidexywh(Vector2 pos, xywh_t *xywh, int margin); int imutil_aspectmaximize(int w, int h, int maxw, int maxh, int *neww, int *newh); int menudata_pos2option(menudata_t *menudata, Vector2 pos); int *getcodepoints(int *sizecodepoints); int is_imagefilename(char *filename); Image imutil_loadimage(const char *filename); void imutil_fpsreset(void); int imutil_fpsleft(void); long long imutil_milliseconds(void); bg_t *bg_init(int sizebgload); void bg_free(bg_t *bg); int bg_resetmarks(bg_t *bg); bgload_t *bg_get(bg_t *bg, char *path); int bg_add(bg_t *bg, char *path); int bg_freeunmarked(bg_t *bg); void *bg_thread(void *); static int mypipe(int fds[2]); int main(int argc, char *argv[]) { im_t *im; Vector2 mousepos,wheel,oldmousepos,scrollstartpos; int flag_ignorelmb; int lmbpressed,lmbreleased,lmbdown,rmbdown,oldlmbdown,oldrmbdown; int click_avail; int has_mousechanges; int needs_nextredraw; long long scrollstart; long long scrolllast; int scrollspeed; int is_scrolling; int leftscrollposstart; char *sel_menu,*sel_submenu; if((im=im_init("Fichero\nAjustes\nSalir\n\nEditar\nNuevo directorio\n\nAyuda\nInformación sobre el programa\n\n",ROOTDIR))==NULL) { return(1); } flag_ignorelmb=0; mousepos=(Vector2) {.x=0,.y=0}; lmbdown=rmbdown=-1; needs_nextredraw=1; scrollstart=0; is_scrolling=0; while(!WindowShouldClose()) { imutil_fpsreset(); oldmousepos=mousepos; mousepos=GetMousePosition(); wheel=GetMouseWheelMoveV(); lmbpressed=IsMouseButtonPressed(0); lmbreleased=IsMouseButtonReleased(0); oldlmbdown=lmbdown; lmbdown=IsMouseButtonDown(0); oldrmbdown=rmbdown; rmbdown=IsMouseButtonDown(1); click_avail=1; has_mousechanges=(lmbdown!=oldlmbdown || rmbdown!=oldrmbdown || mousepos.x!=oldmousepos.x || mousepos.y!=oldmousepos.y || wheel.x!=0 || wheel.y!=0)?1:0; needs_nextredraw=0; /* process scrolling */ if(lmbdown==1 && oldlmbdown==0 && scrollstart==0 && mousepos.y>im->body->xywh.y) { scrollstart=imutil_milliseconds(); scrollstartpos=mousepos; leftscrollposstart=im->body->dirdata[im->body->currentdirdata]->leftscrollpos; } if(scrollstart!=0 && lmbdown==0) { scrollstart=0; if(is_scrolling) click_avail=0; /* this click is the mouseup of the scroll */ } is_scrolling=(scrollstart==0)?0:is_scrolling; if(is_scrolling==0 && scrollstart!=0) { float t; t=scrollstartpos.y-mousepos.y; t=(t<0)?-t:t; if(t>SCROLLTHRESHOLD) { is_scrolling=1; scrolllast=0; } t=scrollstartpos.x-mousepos.x; t=(t<0)?-t:t; if(t>SCROLLTHRESHOLD) is_scrolling=0,scrollstart=0; } if(is_scrolling) { long long tcur,tdif; long long ycur; tcur=imutil_milliseconds(); tdif=tcur-scrolllast; ycur=scrollstartpos.y-mousepos.y; scrollspeed=(tdif>0)?(oldmousepos.y-mousepos.y)*100000/tdif:0; im->body->dirdata[im->body->currentdirdata]->leftscrollpos=leftscrollposstart+ycur; scrolllast=tcur; } if(is_scrolling==0 && scrollspeed!=0) { scrollspeed=scrollspeed*4/5; im->body->dirdata[im->body->currentdirdata]->leftscrollpos+=scrollspeed; } /* process clicks on menus */ if(click_avail) { sel_menu=sel_submenu=NULL; im_menubar_mouse(im->menubar, mousepos, lmbpressed, lmbreleased, lmbdown, &click_avail, &sel_menu, &sel_submenu); if(sel_menu!=NULL && sel_submenu!=NULL) { #if 1 fprintf(stderr,"SELECTED: \"%s\"->\"%s\"\n",sel_menu,sel_submenu); #endif if(strcmp(sel_submenu,"Salir")==0) break; /* exit from main loop */ } } if(click_avail) im_body_mouse(im->body, mousepos, wheel, lmbpressed, lmbreleased, lmbdown, &click_avail); /* draw screen contents */ BeginDrawing(); ClearBackground(RAYWHITE); im_body_draw(im->body,mousepos,lmbdown,rmbdown,im->w,im->h,&needs_nextredraw,scrollspeed); im_menubar_draw(im->menubar,im->w,im->h,&needs_nextredraw); #if 0 { int i,j; Vector2 v2; listing_t listing={0}; if(listing_get(&listing,"..",".",1)==0) { i=2; v2.x=(float) (im->font->height/2); v2.y=(float) ((im->font->height+im->font->height/2)*i+(im->font->height/4)); DrawTextEx(im->font->font ,SEP ,v2 ,im->font->height ,0 ,((Color){ 45, 45, 45, 255 }) ); for(j=0;jfont->height/2); v2.y=(float) ((im->font->height+im->font->height/2)*i+(im->font->height/4)); DrawTextEx(im->font->font ,listing.elems[j].name ,v2 ,im->font->height ,0 ,c ); } } } #endif if(has_mousechanges==0 && needs_nextredraw==0 && scrollspeed==0) { /* Wait for new events when calling EndDrawing() */ EnableEventWaiting(); } else { /* EndDrawing() doesn't wait for new events, returns immediately */ DisableEventWaiting(); } EndDrawing(); } im_free(im),im=NULL; return(0); } im_t * im_init(char *menus, char *rootdir) { im_t *im; if(menus==NULL) return(NULL); /* sanity check failed */ if((im=calloc(1,sizeof(im_t)))==NULL) { im_free(im),im=NULL; return(NULL); /* insuf. mem. */ } /* init window */ SetTraceLogLevel(LOG_ERROR); InitWindow((im->w=DEFAULTWIDTH),(im->h=DEFAULTHEIGHT),"Image Mover"); im->windowinit=1; SetTargetFPS(30); /* init fonts and contents */ if((im->font=im_font_init(FONTSIZE))==NULL || (im->fontbig=im_font_init(FONTBIGSIZE))==NULL || (im->fonthuge=im_font_init(FONTHUGESIZE))==NULL || (im->menubar=im_menubar_init(menus,im->font))==NULL || (im->body=im_body_init(0,im->menubar->height, im->font, im->fontbig, im->fonthuge, LEFTSIZE, rootdir,im->w,im->h))==NULL ) { im_free(im),im=NULL; return(NULL); /* insuf. mem. */ } /* add the starting directory */ im_body_add(im->body,""); return(im); } void im_free(im_t *im) { if(im==NULL) return; if(im->menubar!=NULL) im_menubar_free(im->menubar),im->menubar=NULL; if(im->body!=NULL) im_body_free(im->body),im->body=NULL; if(im->font!=NULL) im_font_free(im->font),im->font=NULL; if(im->fontbig!=NULL) im_font_free(im->fontbig),im->fontbig=NULL; if(im->fonthuge!=NULL) im_font_free(im->fonthuge),im->fonthuge=NULL; if(im->windowinit) CloseWindow(),im->windowinit=0; free(im),im=NULL; return; } font_t * im_font_init(int size) { font_t *font; int sizecodepoints; int *codepoints; if((font=calloc(1,sizeof(font_t)))==NULL) return(NULL); /* insuf. mem. */ font->height=size; codepoints=getcodepoints(&sizecodepoints); font->font=LoadFontFromMemory(".ttf",(const unsigned char *)roboto_regular,sizeof(roboto_regular)-1,font->height,codepoints,sizecodepoints); return(font); } void im_font_free(font_t *font) { if(font==NULL) return; /* NOTE: Cannot call UnloadFont(font->font) as the data was not malloc'd; see https://github.com/raysan5/raylib/blob/master/examples/others/embedded_files_loading.c */ free(font),font=NULL; return; } menubar_t * im_menubar_init(char *menus, font_t *font) { int i,j; char *str,*substr; int len,sublen; menubar_t *menubar; menudata_t *menudata; if(menus==NULL || font==NULL) return(NULL); /* sanity check failed */ if((menubar=calloc(1,sizeof(menubar_t)))==NULL || (menubar->sizemenudata=imutil_menu_count(menus))<=0 || (menubar->menudata=calloc(menubar->sizemenudata,sizeof(menudata_t)))==NULL ) { im_menubar_free(menubar),menubar=NULL; return(NULL); /* insuf. mem. */ } menubar->ptrfont=font; menubar->height=font->height+font->height/2; /* init menus */ for(i=0;isizemenudata;i++) { if((menudata=menubar->menudata[i]=calloc(1,sizeof(menudata_t)))==NULL || (str=imutil_menu_get(menus,i,&len))==NULL || (menudata->title=imutil_strduplen(str,len))==NULL || (menudata->sizeoptions=imutil_submenu_count(str))<=0 || (menudata->options=calloc(menudata->sizeoptions,sizeof(char *)))==NULL ) { im_menubar_free(menubar),menubar=NULL; return(NULL); /* insuf. mem. */ } for(j=0;jsizeoptions;j++) { if((substr=imutil_submenu_get(str,j,&sublen))==NULL || (menudata->options[j]=imutil_strduplen(substr,sublen))==NULL ) { im_menubar_free(menubar),menubar=NULL; return(NULL); /* insuf. mem. */ } } } #if 0 /* test imutil_menu_xxx */ int n,m,l,ml,len,mlen; char *ptr,*mptr; for(n=0,l=imutil_menu_count(menus);n"); for(m=0,ml=imutil_submenu_count(ptr);msizemenudata;i++) { fprintf(stderr,"menu[%i]:\"%s\"->",i,menubar->menudata[i]->title); for(j=0;jmenudata[i]->sizeoptions;j++) fprintf(stderr,"|\"%s\"",menubar->menudata[i]->options[j]); fprintf(stderr,"\n"); } #endif return(menubar); } void im_menubar_free(menubar_t *menubar) { int i,j; menudata_t *menudata; if(menubar==NULL) return; if(menubar->menudata!=NULL) { for(i=0;isizemenudata;i++) { if((menudata=menubar->menudata[i])==NULL) continue; if(menudata->title!=NULL) free(menudata->title),menudata->title=NULL; if(menudata->options!=NULL) { for(j=0;jsizeoptions;j++) { if(menudata->options[j]!=NULL) free(menudata->options[j]),menudata->options[j]=NULL; } free(menudata->options),menudata->options=NULL,menudata->sizeoptions=0; } free(menubar->menudata[i]),menubar->menudata[i]=NULL,menudata=NULL; } free(menubar->menudata),menubar->menudata=NULL,menubar->sizemenudata=0; } free(menubar),menubar=NULL; return; } int im_menubar_mouse(menubar_t *menubar, Vector2 mousepos, int lmbpressed, int lmbreleased, int lmbdown, int *click_avail, char **sel_menu, char **sel_submenu) { int flag_outsideall; int i,j; if(menubar==NULL || click_avail==NULL || sel_menu==NULL || sel_submenu==NULL) return(-1); *click_avail=1; *sel_menu=NULL; *sel_submenu=NULL; flag_outsideall=1; for(i=0;isizemenudata;i++) { int insidetitle,currentoption; insidetitle=is_imutil_insidexywh(mousepos,&(menubar->menudata[i]->xywh),0); currentoption=menudata_pos2option(menubar->menudata[i],mousepos); flag_outsideall=(currentoption!=-1 || insidetitle)?0:flag_outsideall; if(lmbreleased && insidetitle) { for(j=0;jsizemenudata;j++) { menubar->menudata[j]->flag_stickyopen=(j==i)?1:0; menubar->menudata[j]->flag_open=0; menubar->menudata[j]->currentoption=-1; } } else if((lmbpressed || lmbdown) && insidetitle) { for(j=0;jsizemenudata;j++) { menubar->menudata[j]->flag_open=(j==i)?1:0; menubar->menudata[j]->flag_stickyopen=0; menubar->menudata[j]->currentoption=-1; } } else if((lmbdown || menubar->menudata[i]->flag_stickyopen) && currentoption!=-1) { for(j=0;jsizemenudata;j++) { if(lmbreleased==0 || j!=i || menubar->menudata[i]->flag_stickyopen==0) { menubar->menudata[j]->flag_open=(j==i)?menubar->menudata[i]->flag_open:0; menubar->menudata[j]->flag_stickyopen=(j==i)?menubar->menudata[i]->flag_stickyopen:0; menubar->menudata[j]->currentoption=(j==i)?currentoption:-1; } else { menubar->menudata[j]->flag_open=0; menubar->menudata[j]->flag_stickyopen=0; menubar->menudata[j]->currentoption=-1; /* has selected this submenu */ *click_avail=0; *sel_menu=menubar->menudata[j]->title; *sel_submenu=menubar->menudata[j]->options[currentoption]; } } } else if(menubar->menudata[i]->flag_stickyopen && currentoption==-1) { if(lmbreleased) { menubar->menudata[i]->flag_open=0; menubar->menudata[i]->flag_stickyopen=0; } menubar->menudata[i]->currentoption=-1; } else if(lmbreleased && currentoption!=-1) { for(j=0;jsizemenudata;j++) { menubar->menudata[j]->flag_open=0; menubar->menudata[j]->flag_stickyopen=0; menubar->menudata[j]->currentoption=-1; } /* has selected this submenu */ *click_avail=0; *sel_menu=menubar->menudata[i]->title; *sel_submenu=menubar->menudata[i]->options[currentoption]; } else if(lmbdown==0) { menubar->menudata[i]->flag_open=0; menubar->menudata[i]->flag_stickyopen=0; menubar->menudata[i]->currentoption=-1; } } if(flag_outsideall) { for(j=0;jsizemenudata;j++) { menubar->menudata[j]->currentoption=-1; } } /* update click_avail */ for(j=0;jsizemenudata;j++) { if(menubar->menudata[j]->flag_open || menubar->menudata[j]->flag_stickyopen) { *click_avail=0; break; } } return(0); } int im_menubar_draw(menubar_t *menubar, int windowwidth, int windowheight, int *needs_nextredraw) { int i,j,k,x; menudata_t *menudata; font_t *font; if(menubar==NULL) return(-1); /* sanity check failed */ font=menubar->ptrfont; DrawRectangle(0,0,windowwidth, font->height+font->height/2, (Color){ 235, 235, 235, 235 } ); for(i=0,x=0;isizemenudata;i++) { Vector2 v2; menudata=menubar->menudata[i]; v2=MeasureTextEx(font->font,menudata->title,font->height,0); FILLXYWH(menudata->xywh,x,0,((int)v2.x)+font->height,font->height+font->height/2); v2.x=(float) (menudata->xywh.x+font->height/2); v2.y=(float) (menudata->xywh.y+font->height/4); DrawTextEx(font->font ,menudata->title ,v2 ,font->height ,0 ,(Color){ 45, 45, 45, 255 } ); if(menudata->flag_open || menudata->flag_stickyopen) { int underline_height=3; int maxw; DrawRectangle(menudata->xywh.x,menudata->xywh.y+menudata->xywh.h-underline_height,menudata->xywh.w,underline_height, (Color){ 53,132,228,255 } ); for(j=0,maxw=0;jsizeoptions;j++) { v2=MeasureTextEx(font->font,menudata->options[j],font->height,0); maxw=(((int)(v2.x))>maxw)?((int)(v2.x)):maxw; } maxw=(maxw<(menudata->xywh.w+font->height))?(menudata->xywh.w+font->height):maxw; maxw+=font->height; FILLXYWH(menudata->optionsxywh,menudata->xywh.x+1,menudata->xywh.y+menudata->xywh.h+2,maxw,(font->height+font->height/2)*menudata->sizeoptions); DrawLine(menudata->optionsxywh.x-1,menudata->optionsxywh.y-2,menudata->optionsxywh.x+menudata->optionsxywh.w+2,menudata->optionsxywh.y-2,(Color){ 255,255,255,255 } ); DrawLine(menudata->optionsxywh.x-1,menudata->optionsxywh.y,menudata->optionsxywh.x-1,menudata->optionsxywh.y+menudata->optionsxywh.h+1,(Color){ 255,255,255,255 } ); DrawLine(menudata->optionsxywh.x+menudata->optionsxywh.w+2,menudata->optionsxywh.y,menudata->optionsxywh.x+menudata->optionsxywh.w+2,menudata->optionsxywh.y+menudata->optionsxywh.h+1,(Color){ 192,192,192,255 } ); DrawLine(menudata->optionsxywh.x-1,menudata->optionsxywh.y+menudata->optionsxywh.h+1,menudata->optionsxywh.x+menudata->optionsxywh.w+2,menudata->optionsxywh.y+menudata->optionsxywh.h+1,(Color){ 192,192,192,255 } ); DrawRectangle(menudata->optionsxywh.x,menudata->optionsxywh.y,menudata->optionsxywh.w,menudata->optionsxywh.h,(Color){ 235, 235, 235, 235 }); for(k=0;ksizeoptions;k++) { Color c; c=(k==menudata->currentoption)?((Color){ 255,255,255,255 }):((Color){ 45, 45, 45, 255 }); if(k==menudata->currentoption) DrawRectangle(menudata->optionsxywh.x+1,menudata->optionsxywh.y+(font->height+(font->height/2))*k,menudata->optionsxywh.w-2,font->height+font->height/2,(Color){ 53,132,228,255 }); v2.x=(float) (menudata->optionsxywh.x+font->height/2); v2.y=(float) (menudata->optionsxywh.y+(font->height/4)+(font->height+(font->height/2))*k); DrawTextEx(font->font ,menudata->options[k] ,v2 ,font->height ,0 ,c ); } } else { FILLXYWH(menudata->optionsxywh,0,0,0,0); } x=menudata->xywh.x+menudata->xywh.w; } return(0); } body_t * im_body_init(int x, int y, font_t *font, font_t *fontbig, font_t *fonthuge, int leftsize, char *rootdir, int windowwidth, int windowheight) { body_t *body; static char sep[]={SEP}; if(font==NULL || fontbig==NULL || fonthuge==NULL || rootdir==NULL) return(NULL); /* sanity check failed */ if((body=calloc(1,sizeof(body_t)))==NULL || (body->rootdir=strdup(rootdir))==NULL || (body->bg=bg_init(SIZEBGLOAD))==NULL ) { im_body_free(body),body=NULL; return(NULL); } FILLXYWH(body->xywh,x,y,windowwidth-x,windowheight-y); body->leftsize=leftsize; body->ptrfont=font; body->ptrfontbig=fontbig; body->ptrfonthuge=fonthuge; if(body->rootdir[0]!='\0' && strcmp(body->rootdir,SEP)!=0 && body->rootdir[strlen(body->rootdir)-1]==sep[0]) body->rootdir[strlen(body->rootdir)-1]='\0'; /* rootdir doesn't need the final '/' */ /* init rounded box glyph (a really big zero) */ { int codepoints[]={'O','+','-'}; body->roundedbox=LoadFontFromMemory(".ttf",(const unsigned char *)roboto_regular,sizeof(roboto_regular)-1,ADDREMOVEDIRDATAHEIGHT*4,codepoints,sizeof(codepoints)/sizeof(codepoints[0])); } return(body); } void im_body_free(body_t *body) { int i; dirdata_t *dirdata; if(body==NULL) return; /* nothing to do */ if(body->dirdata!=NULL) { for(i=0;isizedirdata;i++) { if((dirdata=body->dirdata[i])==NULL) continue; if(dirdata->dirname!=NULL) free(dirdata->dirname),dirdata->dirname=NULL; listing_freedata(&(dirdata->listing)); free(dirdata),dirdata=body->dirdata[i]=NULL; } free(body->dirdata),body->dirdata=NULL,body->sizedirdata=0; } texture_freedata(&(body->texture)); texture_freedata(&(body->bigtexture)); if(body->rootdir!=NULL) free(body->rootdir),body->rootdir=NULL; /* NOTE: Cannot call UnloadFont(body->roundedbox) as the data was not malloc'd; see https://github.com/raysan5/raylib/blob/master/examples/others/embedded_files_loading.c */ if(body->bg!=NULL) bg_free(body->bg),body->bg=NULL; free(body),body=NULL; return; } int im_body_add(body_t *body,char *dir) { int i; dirdata_t *dirdata; if(body==NULL || dir==NULL) return(-1); /* sanity check failed */ for(i=0;isizedirdata;i++) { if(body->dirdata[i]==NULL) break; } if(i==body->sizedirdata) { /* dirdata container full, add more slots */ dirdata_t **newdirdata; if((newdirdata=realloc(body->dirdata,sizeof(dirdata_t *)*(body->sizedirdata+BLOCKDIRDATA)))==NULL) return(-1); /* insuf. mem. */ body->dirdata=newdirdata; memset(body->dirdata+body->sizedirdata,0,sizeof(dirdata_t *)*BLOCKDIRDATA); body->sizedirdata+=BLOCKDIRDATA; } if((dirdata=body->dirdata[i]=calloc(1,sizeof(dirdata_t)))==NULL || (dirdata->dirname=strdup(dir))==NULL ) { if(dirdata!=NULL) free(dirdata),dirdata=body->dirdata[i]=NULL; return(-1); /* insuf. mem. */ } dirdata->height=DEFAULTDIRDATAHEIGHT; dirdata->dirname=strdup(dir); listing_get(&(dirdata->listing),body->rootdir,dir,1); return(0); } int im_body_mouse(body_t *body, Vector2 mousepos, Vector2 wheel, int lmbpressed, int lmbreleased, int lmbdown, int *click_avail) { int i; char *ptr; dirdata_t *dirdata; listingdata_t *ld; int margin; if(body==NULL || click_avail==NULL || body->currentdirdata<0 || body->currentdirdata>=body->sizedirdata || body->dirdata[body->currentdirdata]==NULL) return(-1); /* sanity check error */ dirdata=body->dirdata[body->currentdirdata]; /* wheel */ body->dirdata[body->currentdirdata]->leftscrollpos-=(int)wheel.y*WHEELSTEP; body->dirdata[body->currentdirdata]->leftscrollpos=(body->dirdata[body->currentdirdata]->leftscrollpos<0)?0:body->dirdata[body->currentdirdata]->leftscrollpos; if(body->dirdata[body->currentdirdata]->leftscrollpos>dirdata->listing.lastleftxywh.y) body->dirdata[body->currentdirdata]->leftscrollpos=dirdata->listing.lastleftxywh.y; /* check if we have to process a click */ if(*click_avail==0 || lmbreleased==0) return(0); /* nothing else to do */ /* show image in full screen */ if(body->flag_drawbigtexture) { if(lmbreleased) body->flag_drawbigtexture=0; return(0); /* nothing else to do */ } margin=body->ptrfont->height/4; if(body->is_displayingtexture && lmbreleased && is_imutil_insidexywh(mousepos,&(body->texture.source),margin)) { body->flag_drawbigtexture=1; return(0); /* nothing else to do */ } /* leftside backbutton */ if(is_imutil_insidexywh(mousepos,&(body->backxywh),0)) { static char sep[]={SEP}; if((ptr=strrchr(dirdata->dirname,sep[0]))!=NULL) { /* previous dir */ *ptr='\0'; } else { /* root dir */ dirdata->dirname[0]='\0'; } listing_get(&(dirdata->listing),body->rootdir,dirdata->dirname,1); body->dirdata[body->currentdirdata]->leftscrollpos=0; *click_avail=0; return(0); } /* leftside directories */ for(i=0;ilisting.usedelems;i++) { ld=dirdata->listing.elems+i; if(ld->name[0]!='d') continue; if(is_imutil_insidexywh(mousepos,&(ld->left.screenxywh),0)) { char *newname,*oldprefix; int l,l0,l1; static char sep[]={SEP}; oldprefix=(strcmp(dirdata->dirname,SEP)==0)?"":dirdata->dirname; l0=strlen(oldprefix); l1=strlen(ld->name+1); l=l0+((l0>0)?1:0)+l1+1; if((newname=malloc(l))==NULL) return(-1); /* insuf. mem. */ if(l0>0) { memcpy(newname,oldprefix,l0); newname[l0]=sep[0]; } memcpy(newname+l0+((l0>0)?1:0),ld->name+1,l1); newname[l0+((l0>0)?1:0)+l1]='\0'; free(dirdata->dirname),dirdata->dirname=NULL; dirdata->dirname=newname; listing_get(&(dirdata->listing),body->rootdir,dirdata->dirname,1); body->dirdata[body->currentdirdata]->leftscrollpos=0; *click_avail=0; return(0); } } /* dirdata select */ if(mousepos.x>body->leftsize && mousepos.y>body->xywh.y) { int righty; for(righty=body->xywh.y,i=0;isizedirdata;i++) { if(body->dirdata[i]==NULL) continue; if(mousepos.y>=righty && mousepos.y<(righty+body->dirdata[i]->height) && i!=body->currentdirdata) { body->currentdirdata=i; *click_avail=0; return(0); } righty+=body->dirdata[i]->height; } } /* detect click on "add dirdata" button */ if(is_imutil_insidexywh(mousepos,&(body->dirdataadd),0)) { im_body_add(body,body->dirdata[body->currentdirdata]->dirname); *click_avail=0; return(0); } /* dirdata remove */ #warning TODO return(0); } int im_body_draw(body_t *body, Vector2 mousepos, int lmbdown, int rmbdown, int windowwidth, int windowheight, int *needs_nextredraw, int scrollspeed) { int i,k,margin,righty; int lastx,lasty; dirdata_t *dirdata; listingdata_t *elem; Vector2 v2,m2; font_t *font,*fontbig,*fonthuge; int is_leftside; int flag_skiprightside; int flag_skipall; int xoff,yoff; thumb_t *thumb; char statustooltip[1024]; if(body==NULL) return(-1); font=body->ptrfont; fontbig=body->ptrfontbig; fonthuge=body->ptrfonthuge; FILLXYWH(body->backxywh,0,0,0,0); margin=font->height/4; body->is_displayingtexture=0; /* if we are displaying a full screenimage... */ if(body->flag_drawbigtexture && body->texture.has_texture) { /* draw image in full screen */ int maxw,maxh; maxw=windowwidth; maxh=windowheight-body->xywh.y; if(body->bigtexture.has_texture==0 || strcmp(body->bigtexture.currentpath,body->texture.currentpath)!=0) texture_load(&(body->bigtexture),body->texture.currentpath,maxw,maxh,body->bg); if(body->bigtexture.has_texture && strcmp(body->bigtexture.currentpath,body->texture.currentpath)==0) { int x0,y0; x0=0; y0=body->xywh.y; texture_draw(&(body->bigtexture),x0,y0,maxw,maxh); return(0); /* all done */ } else { body->flag_drawbigtexture=0; /* error loading big texture, draw screen normally */ } } /* calculate positions */ for(i=0;isizedirdata;i++) { if(body->dirdata[i]==NULL) continue; if(body->dirdata[i]->listing.has_rightxywh==0) { int sidelen; sidelen=body->dirdata[i]->height-(fontbig->height+fontbig->height/4+font->height+margin*4+fontbig->height/4); listing_fillxywh(&(body->dirdata[i]->listing),body->ptrfont,body->xywh.w-(body->xywh.x+body->leftsize),sidelen,0); body->dirdata[i]->listing.has_rightxywh=1; } if(i==body->currentdirdata && body->dirdata[i]->listing.has_leftxywh==0) { listing_fillxywh(&(body->dirdata[i]->listing),body->ptrfont,body->leftsize,LEFTIMAGESIDELEN,1); body->dirdata[i]->listing.has_leftxywh=1; } } /* draw left side background */ DrawRectangle(body->xywh.x,body->xywh.y,body->leftsize, body->xywh.h, (Color){ 215, 215, 215, 255 } ); /* draw right side background */ DrawRectangle(body->xywh.x+body->leftsize,body->xywh.y,body->xywh.w-body->leftsize, body->xywh.h, (Color){ 227, 227, 227, 255 } ); /* reset lazy load marks */ bg_resetmarks(body->bg); /* first pass, draw leftside, second pass, draw all of rightside */ statustooltip[0]='\0'; for(is_leftside=1,flag_skiprightside=flag_skipall=0;is_leftside>=0 && flag_skiprightside==0 && flag_skipall==0;is_leftside--) { for(i=(is_leftside)?body->currentdirdata:0,righty=body->xywh.y;flag_skipall==0 && ((is_leftside && i==body->currentdirdata) || (!is_leftside && isizedirdata));i++) { int sidelen; if((dirdata=body->dirdata[i])==NULL) continue; if(is_leftside && !(i==body->currentdirdata)) continue; /* this element is not in leftside */ sidelen=(is_leftside)?LEFTIMAGESIDELEN:dirdata->height-(fontbig->height+fontbig->height/4+font->height+margin*4+fontbig->height/4); /* draw left side back arrow if is_leftside and not in root dir */ if(is_leftside && !(dirdata->dirname[0]=='\0' || strcmp(dirdata->dirname,SEP)==0)) { m2=MeasureTextEx(fontbig->font,UTF8DOWNARROW,fontbig->height,0); v2.x=(float) (body->xywh.x+fontbig->height/2); v2.y=(float) (body->xywh.y+fontbig->height/4+(fontbig->height-v2.x)/2)-body->dirdata[body->currentdirdata]->leftscrollpos; FILLXYWH(body->backxywh,v2.x-fontbig->height/4,v2.y-fontbig->height/8,m2.x+fontbig->height/4,m2.x+fontbig->height/4); #if 0 DrawTexture(fontbig->font.texture, 0, 0, WHITE); /* font glyphs -- see https://github.com/raysan5/raylib/issues/2022 */ DrawRectangle(UNROLLXYWH(body->backxywh),((Color){ 0,255,0,255 })); /* hit zone */ #endif if((v2.y+fontbig->height)>=0) DrawTextPro(fontbig->font,UTF8DOWNARROW,(Vector2){v2.x+m2.x,v2.y},(Vector2){0,0},90.0,fontbig->height,0,(Color){65,65,65,255}); } if(is_leftside) { /* ...dirname */ v2.y=(float) (body->xywh.y+fontbig->height/4-body->dirdata[body->currentdirdata]->leftscrollpos); if((v2.y+fontbig->height)>=0) { m2=MeasureTextEx(fontbig->font,dirdata->dirname,fontbig->height,0); v2.x=(float) (body->xywh.x+fontbig->height/2)+(body->leftsize-(body->xywh.x+fontbig->height)-m2.x)/2; DrawTextEx(fontbig->font ,dirdata->dirname ,v2 ,fontbig->height ,0 ,(Color){ 65, 65, 65, 255 } ); } } else { /* rightside */ /* ...bg */ DrawRectangle(body->xywh.x+body->leftsize,righty,body->xywh.w-body->leftsize, dirdata->height,(i==body->currentdirdata)?((Color){ 168, 168, 168, 255 }):((Color){ 227, 227, 227, 255 })); /* ...bottom separator */ DrawRectangle(body->xywh.x+body->leftsize,righty+dirdata->height-1,body->xywh.w-body->leftsize, 1, (Color){ 221, 221, 221, 255 } ); /* ...dirname */ DrawTextEx(fontbig->font ,dirdata->dirname ,(Vector2) {body->xywh.x+body->leftsize+fontbig->height/2,righty+fontbig->height/4} ,fontbig->height ,0 ,(i==body->currentdirdata)?((Color){ 240, 240, 240, 255 }):((Color){ 65, 65, 65, 255 }) ); } /* directories */ xoff=((is_leftside)?0:body->leftsize); yoff=((is_leftside)?body->xywh.y:righty)+fontbig->height/4+fontbig->height+font->height/4-(is_leftside?body->dirdata[body->currentdirdata]->leftscrollpos:0); if(is_leftside && dirdata->dirname[0]=='\0') yoff-=fontbig->height/4+fontbig->height; for(k=0,lastx=lasty=0;klisting.usedelems;k++) { elem=dirdata->listing.elems+k; thumb=(is_leftside)?&(elem->left):&(elem->right); if(elem->name[0]!='d' || strcmp(elem->name,"d.")==0 || strcmp(elem->name,"d..")==0) { if(elem->name[0]=='d') FILLXYWH(thumb->screenxywh,0,0,0,0); continue; } if((thumb->xywh.y+yoff)>(body->xywh.y+body->xywh.h) || thumb->xywh.w==0 || thumb->xywh.h==0) { #warning TODO: if !is_leftside, draw "..." in huge font using lastx,lasty as reference break; } if((thumb->xywh.y+yoff+thumb->xywh.h)<0) { FILLXYWH(thumb->screenxywh,0,0,0,0); continue; } FILLXYWH(thumb->screenxywh,thumb->xywh.x+xoff,thumb->xywh.y+yoff,thumb->xywh.w,thumb->xywh.h); DrawRectangleLines(UNROLLXYWH(thumb->screenxywh),((Color){ 65, 65, 65, 255 })); DrawTextEx(font->font,elem->name+1,(Vector2){thumb->screenxywh.x+margin,thumb->screenxywh.y+margin},font->height,0,((Color){ 65, 65, 65, 255 })); lastx=thumb->screenxywh.x+thumb->screenxywh.w,lasty=thumb->screenxywh.y+thumb->screenxywh.h; } for(;klisting.usedelems;k++) { elem=dirdata->listing.elems+k; if(elem->name[0]!='d') continue; thumb=(is_leftside)?&(elem->left):&(elem->right); FILLXYWH(thumb->screenxywh,0,0,0,0); } /* files */ for(k=0,lastx=lasty=0;klisting.usedelems;k++) { Texture2D *te; xywh_t *xywh; elem=dirdata->listing.elems+k; thumb=(is_leftside)?&(elem->left):&(elem->right); xywh=&(thumb->xywh); te=&(thumb->texture); int has_imagedrawn; if(elem->name[0]!='f' && elem->name[0]!='l') continue; if((xywh->y+yoff)>(body->xywh.y+body->xywh.h) || xywh->w==0 || xywh->h==0) { #warning TODO: if !is_leftside, draw "..." in huge font using lastx,lasty as reference break; } if(is_leftside && is_imutil_insidexywh(mousepos,&(thumb->screenxywh),margin)) { strncpy(statustooltip,elem->name+1,sizeof(statustooltip)); statustooltip[sizeof(statustooltip)-1]='\0'; } /* show image */ has_imagedrawn=0; if(is_imagefilename(elem->name+1) && !(thumb->screenxywh.y>(body->xywh.y+body->xywh.h) || (thumb->screenxywh.y+thumb->screenxywh.h)xywh.y)) { if(thumb->has_texture==0 && thumb->has_failedload==0) { bgload_t *bgload; char fullpath[2048]; *needs_nextredraw=1; snprintf(fullpath,sizeof(fullpath),"%s/%s/%s",body->rootdir,dirdata->dirname,elem->name+1); fullpath[sizeof(fullpath)-1]='\0'; bgload=bg_get(body->bg,fullpath); if(bgload!=NULL && bgload->has_data!=0) { Image im; im=ImageCopy(bgload->image); if(IsImageValid(im)) { int neww,newh; Image im2,impixel; #if 1 fprintf(stderr,"Loaded %s\n",fullpath); { int oldw=im.width,oldh=im.height; #endif im2=GenImageColor(sidelen,sidelen,(Color){0,0,0,255}); imutil_aspectmaximize(im.width,im.height,sidelen,sidelen,&neww,&newh); ImageResize(&im,neww,newh); impixel=ImageCopy(im); ImageResize(&impixel,1,1); #if 1 if(neww>sidelen || newh>sidelen) fprintf(stderr,"elem:\"%s\" sidelen:%i old:%ix%i new:%ix%i\n",elem->name+1,sidelen,oldw,oldh,neww,newh); } #endif ImageDraw(&im2,impixel,(Rectangle) {0,0,1,1},(Rectangle) {0,0,sidelen,sidelen},(Color){255,255,255,255}); ImageDraw(&im2,im,(Rectangle) {0,0,neww,newh},(Rectangle) {(sidelen-neww)/2,(sidelen-newh)/2,neww,newh},(Color){255,255,255,255}); *te=LoadTextureFromImage(im2); UnloadImage(im); UnloadImage(im2); thumb->has_texture=1; } else { thumb->has_failedload=1; } } else if(bgload==NULL) { bg_add(body->bg,fullpath); } } if(thumb->has_texture!=0) { FILLXYWH(thumb->screenxywh,xywh->x+xoff,xywh->y+yoff,xywh->w,xywh->h); DrawTexture(*te,thumb->screenxywh.x,thumb->screenxywh.y,WHITE); has_imagedrawn=1; lastx=xywh->x+xywh->w,lasty=xywh->y+xywh->h+yoff; if(is_leftside && rmbdown==0 && is_imutil_insidexywh(mousepos,&(thumb->screenxywh),margin) && scrollspeed==0) { /* draw image in rightside */ char fullpath[2048]; int maxw,maxh; maxw=windowwidth-(body->leftsize-DEFAULTDIRDATATRIANGLEW); maxh=windowheight-body->xywh.y; snprintf(fullpath,sizeof(fullpath),"%s/%s/%s",body->rootdir,dirdata->dirname,elem->name+1); fullpath[sizeof(fullpath)-1]='\0'; if((body->texture.has_texture==0 && !(body->texture.has_failedload && strcmp(body->texture.currentpath,fullpath)==0)) || strcmp(body->texture.currentpath,fullpath)!=0 ) { texture_load(&(body->texture),fullpath,maxw,maxh,body->bg); } if(body->texture.has_texture && strcmp(body->texture.currentpath,fullpath)==0) { int x0,y0; x0=body->leftsize-DEFAULTDIRDATATRIANGLEW; y0=body->xywh.y; texture_draw(&(body->texture),x0,y0,maxw,maxh); body->is_displayingtexture=1; memcpy(&(body->texture.source),&(thumb->screenxywh),sizeof(body->texture.source)); flag_skiprightside=1; } } } } if(has_imagedrawn==0) { char *ptr; char shortname[1024]; xywh_t *pos; int l; font_t *myfont=(is_leftside)?fonthuge:font; pos=is_leftside?&(elem->left.xywh):&(elem->right.xywh); FILLXYWH(thumb->screenxywh,xywh->x+xoff,xywh->y+yoff,xywh->w,xywh->h); DrawRectangle(UNROLLXYWH(thumb->screenxywh),((Color){0,0,0,64})); if((ptr=strchr(elem->name+1,'.'))!=NULL) { m2=MeasureTextEx(myfont->font,ptr,myfont->height,0); DrawTextEx(myfont->font,ptr,(Vector2){xywh->x+xoff+(sidelen-m2.x)/2,xywh->y+yoff+(font->height)/2+(xywh->w-myfont->height)/2},myfont->height,0,(Color){ 0,0,0,96 }); } ptr=(ptr==NULL)?elem->name+1:ptr; l=(ptr-(elem->name+1)); l=(l>=sizeof(shortname))?sizeof(shortname)-1:l; memcpy(shortname,elem->name+1,l); shortname[l]='\0'; DrawRectangle(pos->x+xoff,pos->y+yoff,pos->w,font->height+font->height/2,((Color){0,0,0,64})); DrawTextEx(font->font,shortname,(Vector2){pos->x+xoff+font->height/4,pos->y+yoff+font->height/4},font->height,0,(Color){ 192,192,192,255 }); lastx=xywh->x+xoff+xywh->w,lasty=xywh->y+yoff+xywh->h; } } for(;klisting.usedelems;k++) { elem=dirdata->listing.elems+k; if(elem->name[0]!='f' && elem->name[0]!='l') continue; thumb=(is_leftside)?&(elem->left):&(elem->right); FILLXYWH(thumb->screenxywh,0,0,0,0); } /* ...finishing touchs */ if(is_leftside) { ; } else { if(i==body->currentdirdata) { /* draw right side "current" marker inside left side area */ DrawTriangle((Vector2){((float)body->xywh.x)+body->leftsize,((float)righty)}, (Vector2){((float)body->xywh.x)+body->leftsize-DEFAULTDIRDATATRIANGLEW,((float)righty)+dirdata->height/2}, (Vector2){((float)body->xywh.x)+body->leftsize,((float)righty)+dirdata->height-1}, (Color){ 168, 168, 168, 255 } ); } /* advance to next element */ righty+=dirdata->height; } } } if(flag_skiprightside==0 && (righty+DEFAULTDIRDATAHEIGHT)<=(body->xywh.y+body->xywh.h)) { GlyphInfo gi; int xoff; int margin; margin=20; FILLXYWH(body->dirdataadd,(body->xywh.x+body->xywh.w-ADDREMOVEDIRDATAHEIGHT-margin),righty+margin,ADDREMOVEDIRDATAHEIGHT,ADDREMOVEDIRDATAHEIGHT); gi=GetGlyphInfo(body->roundedbox,'O'); xoff=0; DrawTexturePro(body->roundedbox.texture ,(Rectangle){0.0+xoff,0.0,(float)gi.image.width+5,(float)gi.image.height+2} ,(Rectangle) { (float) body->dirdataadd.x, (float) body->dirdataadd.y, (float) body->dirdataadd.w, (float)body->dirdataadd.h} ,(Vector2){0.0,0.0} ,0.0 ,(Color){ 168, 168, 168, 255 } ); DrawRectangle(body->dirdataadd.x+13+12,body->dirdataadd.y+13,body->dirdataadd.w-26-24, (float)body->dirdataadd.h-26,(Color){ 168, 168, 168, 255 }); DrawRectangle(body->dirdataadd.x+13,body->dirdataadd.y+13+12,body->dirdataadd.w-26, (float)body->dirdataadd.h-26-24,(Color){ 168, 168, 168, 255 }); xoff+=gi.image.width+4; gi=GetGlyphInfo(body->roundedbox,'+'); DrawTexturePro(body->roundedbox.texture ,(Rectangle){xoff,0.0,(float)gi.image.width,(float)gi.image.height} ,(Rectangle) { (float) body->dirdataadd.x+30-2, (float) body->dirdataadd.y+30, (float) body->dirdataadd.w-60, (float)body->dirdataadd.h-60} ,(Vector2){0.0,0.0} ,0.0 ,(Color){ 227, 227, 227, 255 } ); } else { memset(&(body->dirdataadd),0,sizeof(body->dirdataadd)); } if(statustooltip[0]!='\0') { m2=MeasureTextEx(font->font,statustooltip,font->height,0); DrawRectangle(0,windowheight-1-margin*2-font->height,m2.x+margin*2,font->height+margin*2,((Color){0,0,0,96})); DrawTextEx(font->font,statustooltip,(Vector2){margin,windowheight-1-margin-font->height},font->height,0,(Color){ 255,255,255,128 }); } /* free not used bg load items (only if idle, as it takes time) */ if(scrollspeed==0 && *needs_nextredraw==0) bg_freeunmarked(body->bg); return(0); } int texture_load(texture_t *texture, char *fullpath, int maxw, int maxh, bg_t *bg) { Image im; int neww,newh; bgload_t *bgload; if(texture==NULL || fullpath==NULL) return(-1); /* sanity check failed */ if(texture->has_texture) { UnloadTexture(texture->texture); texture->currentpath[0]='\0'; texture->has_texture=0; texture->has_failedload=0; } texture->currentpath[0]='\0'; if(bg!=NULL && (bgload=bg_get(bg,texture->currentpath))!=NULL && bgload->has_data!=0) { im=ImageCopy(bgload->image); } else { im=imutil_loadimage(fullpath); } if(IsImageValid(im)) { imutil_aspectmaximize(im.width,im.height,maxw,maxh,&neww,&newh); ImageResize(&im,neww,newh); texture->texture=LoadTextureFromImage(im); UnloadImage(im); strncpy(texture->currentpath,fullpath,sizeof(texture->currentpath)); texture->currentpath[sizeof(texture->currentpath)-1]='\0'; texture->has_texture=1; texture->has_failedload=0; texture->texturew=neww; texture->textureh=newh; } else { strncpy(texture->currentpath,fullpath,sizeof(texture->currentpath)); texture->currentpath[sizeof(texture->currentpath)-1]='\0'; texture->has_texture=0; texture->has_failedload=1; } return(0); } int texture_draw(texture_t *texture, int x0, int y0, int maxw, int maxh) { if(texture==NULL || texture->has_texture==0) return(-1); /* sanity check failed */ DrawRectangle(x0,y0,maxw,maxh,(Color){ 215, 215, 215, 255 } ); DrawTexture(texture->texture,x0+(maxw-texture->texturew)/2,y0+(maxh-texture->textureh)/2,WHITE); return(0); } int texture_freedata(texture_t *texture) { if(texture==NULL) return(-1); /* sanity check failed */ if(texture->has_texture) { UnloadTexture(texture->texture); texture->currentpath[0]='\0'; texture->has_texture=0; texture->has_failedload=0; } return(0); } int imutil_menu_count(char *menus) { int n; char *ptr,*next; if(menus==NULL) return(0); /* sanity check error */ for(ptr=menus,n=0;(next=strchr(ptr,'\n'))!=NULL;ptr=next+1) { if(next[1]=='\n') { next++; n++; } } return(n); } char * imutil_menu_get(char *menus, int targetn, int *len) { int n; char *ptr,*next,*end; int is_title; if(len!=NULL) *len=0; if(menus==NULL || targetn<0) return(NULL); /* sanity check error */ end=menus+strlen(menus); for(ptr=menus,is_title=1,n=0;(next=strchr(ptr,'\n'))!=NULL;ptr=next+1) { if(targetn==n && is_title==1) { if(len!=NULL) *len=next-ptr; return(ptr); /* found, return start of menu string */ } is_title=0; if(next[1]=='\n') { n++; next++; is_title=1; } } return(end); /* not found, return end of menus */ } int imutil_submenu_count(char *menus) { int subn; char *ptr,*next; if(menus==NULL) return(0); /* sanity check error */ next=strchr(menus,'\n'); if(next==NULL) return(0); /* no title */ for(subn=0,ptr=next+1;(next=strchr(ptr,'\n'))!=NULL;ptr=next+1) { subn++; if(next[1]=='\n') break; } return(subn); } char * imutil_submenu_get(char *menus, int targetsubn, int *len) { char *ptr,*next,*end; int subn; if(len!=NULL) *len=0; if(menus==NULL || targetsubn<0) return(NULL); /* sanity check error */ end=menus+strlen(menus); next=strchr(menus,'\n'); if(next==NULL) return(end); /* no title */ for(ptr=next+1,subn=0;(next=strchr(ptr,'\n'))!=NULL;ptr=next+1,subn++) { if(targetsubn==subn) { if(len!=NULL) *len=next-ptr; return(ptr); } if(next[1]=='\n') break; /* "\n\n" marks the end of submenus */ } return(end); } char * imutil_strduplen(char *str, int len) { char *res; if(len<0 || (str==NULL && len!=0)) return(NULL); if((res=malloc(len+1))==NULL) return(NULL); memcpy(res,str,len); res[len]='\0'; return(res); } int is_imutil_insidexywh(Vector2 pos, xywh_t *xywh, int margin) { if(xywh==NULL) return(0); /* sanity check error */ if(pos.x>=(float)(xywh->x-margin) && pos.x<=(float)(xywh->x+xywh->w+margin) && pos.y>=(float)(xywh->y-margin) && pos.y<=(float)(xywh->y+xywh->h+margin) ) { return(1); } return(0); } int imutil_aspectmaximize(int w, int h, int maxw, int maxh, int *neww, int *newh) { if(neww==NULL || newh==NULL || w==0 || h==0 || maxw==0 || maxh==0) return(-1); if((w/h)>(maxw/maxh) || (maxw==maxh && w>h)) { *neww=maxw; *newh=h*maxw/w; } else { /* (w/h)<=(maxw/maxh) */ *newh=maxh; *neww=w*maxh/h; } return(0); } int menudata_pos2option(menudata_t *menudata, Vector2 pos) { int n,h; if(menudata==NULL || (menudata->flag_open==0 && menudata->flag_stickyopen==0) || is_imutil_insidexywh(pos, &(menudata->optionsxywh),0)==0 ) { return(-1); } h=(menudata->sizeoptions==0)?0:menudata->optionsxywh.h/menudata->sizeoptions; n=(((int)pos.y)-menudata->optionsxywh.y)/h; return(n); } int * getcodepoints(int *sizecodepoints) { static int codepoints[]={ /* Basic Latin */ 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, /* Latin-1 Supplement */ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, /* down arrow (U+2186 -- this font doesn't have arrows, this is old roman numeral 50 */ (0x21<<8)|0x86, }; if(sizecodepoints!=NULL) *sizecodepoints=(int) (sizeof(codepoints)/sizeof(codepoints[0])); return(codepoints); } static int strptrcmp(void *a,void *b) { return(strcmp(((listingdata_t *)a)->name,((listingdata_t *)b)->name)); } int listing_get(listing_t *listing, char *pathprefix, char *parampath, int flag_sort) { int i,l; DIR *d; struct dirent *de; unsigned char dtype; char path[1024]; listingdata_t *ld; if(listing==NULL) return(-1); /* sanity check failed */ /* free old textures */ for(i=0;iusedelems;i++) { ld=listing->elems+i; if(ld->left.has_texture) { UnloadTexture(ld->left.texture); ld->left.has_texture=0; ld->left.has_failedload=0; } if(ld->right.has_texture) { UnloadTexture(ld->right.texture); ld->right.has_texture=0; ld->right.has_failedload=0; } } /* reset struct */ listing->usedelems=listing->usedbuf=0; memset(listing->elems,0,sizeof(listingdata_t)*listing->sizeelems); listing->has_leftxywh=listing->has_rightxywh=0; /* fill listing */ if(pathprefix==NULL && parampath==NULL) return(-1); /* nothing to fill */ snprintf(path,sizeof(path),"%s%s%s",(pathprefix!=NULL)?pathprefix:"",SEP,(parampath!=NULL)?parampath:""); path[sizeof(path)-1]='\0'; if((d=opendir(path))==NULL) return(-1); /* dir not found */ while((de=readdir(d))!=NULL) { l=strlen(de->d_name); dtype='u'; #ifdef _DIRENT_HAVE_D_TYPE dtype=(de->d_type==DT_BLK)?'b' :(de->d_type==DT_CHR)?'c' :(de->d_type==DT_DIR)?'d' :(de->d_type==DT_FIFO)?'p' :(de->d_type==DT_LNK)?'l' :(de->d_type==DT_REG)?'f' :(de->d_type==DT_SOCK)?'s' :'u'; #endif if(dtype=='u') { char stpath[2048]; struct stat st; mode_t m; snprintf(stpath,sizeof(stpath),"%s%s%s",path,SEP,de->d_name); stpath[sizeof(stpath)-1]='\0'; if(stat(stpath,&st)==0 && (m=(st.st_mode&S_IFMT))!=0) { dtype=(m==S_IFBLK)?'b' :(m==S_IFCHR)?'c' :(m==S_IFDIR)?'d' :(m==S_IFIFO)?'p' #ifdef S_IFLNK :(m==S_IFLNK)?'l' #endif :(m==S_IFREG)?'f' #ifdef S_IFSOCK :(m==S_IFSOCK)?'s' #endif :'u'; } } /* check for space for this elem (and get space if necessary) */ if(listing->usedelems==listing->sizeelems) { listingdata_t *newelems; if((newelems=realloc(listing->elems,sizeof(listingdata_t)*(listing->sizeelems+BLOCKLISTINGELEMS)))==NULL) { closedir(d),d=NULL; return(-1); /* insuf. mem. */ } listing->elems=newelems; memset(listing->elems+listing->sizeelems,0,sizeof(listingdata_t)*BLOCKLISTINGELEMS); listing->sizeelems+=BLOCKLISTINGELEMS; } /* check for space for this elem data (and get space if necessary) */ if((listing->sizebuf+l+2)>listing->sizebuf) { int i; char *newbuf; int newsize; int off; newsize=listing->sizebuf+l+2+BLOCKLISTINGBUF-1; newsize/=BLOCKLISTINGBUF; newsize*=BLOCKLISTINGBUF; if((newbuf=realloc(listing->buf,newsize))==NULL) { closedir(d),d=NULL; return(-1); /* insuf. mem. */ } /* the elem data buffer has a new pointer, fix previous entries */ for(i=0;iusedelems;i++) { off=(listing->elems[i].name)-listing->buf; listing->elems[i].name=newbuf+off; } listing->buf=newbuf; listing->sizebuf+=BLOCKLISTINGELEMS; } /* store the data */ listing->elems[listing->usedelems++].name=listing->buf+listing->usedbuf; listing->buf[listing->usedbuf++]=dtype; memcpy(listing->buf+listing->usedbuf,de->d_name,l+1); listing->usedbuf+=l+1; } closedir(d),d=NULL; if(flag_sort) qsort(listing->elems,listing->usedelems,sizeof(listingdata_t ),(int (*)(const void *, const void *)) strptrcmp); return(0); } void listing_freedata(listing_t *listing) { if(listing==NULL) return; /* nothing to do */ if(listing->elems!=NULL) { int i; listingdata_t *ld; for(i=0;iusedelems;i++) { ld=listing->elems+i; if(ld->left.has_texture) { UnloadTexture(ld->left.texture); ld->left.has_texture=0; ld->left.has_failedload=0; } if(ld->right.has_texture) { UnloadTexture(ld->right.texture); ld->right.has_texture=0; ld->right.has_failedload=0; } } free(listing->elems),listing->elems=NULL,listing->sizeelems=listing->usedelems=0; } if(listing->buf!=NULL) free(listing->buf),listing->buf=NULL,listing->sizebuf=listing->usedbuf=0; return; } int listing_fillxywh(listing_t *listing, font_t *font, int w, int sidelen, int is_leftside) { int x0,y0,x1; int k,x,y; Vector2 m2; int margin; listingdata_t *elem; thumb_t *thumb; if(listing==NULL || font==NULL) return(-1); /* sanity check failed */ margin=font->height/4; if(is_leftside) memset(&(listing->lastleftxywh),0,sizeof(listing->lastleftxywh)); /* directories */ if(is_leftside) { x0=font->height/2; x1=w-DEFAULTDIRDATATRIANGLEW-font->height/2; y0=0; } else { x0=FONTBIGSIZE/2; x1=w-font->height/2; y0=0; } for(k=0,x=x0,y=y0;kusedelems;k++) { elem=listing->elems+k; thumb=(is_leftside)?&(elem->left):&(elem->right); if(elem->name[0]!='d' || strcmp(elem->name,"d.")==0 || strcmp(elem->name,"d..")==0) continue; m2=MeasureTextEx(font->font,elem->name+1,font->height,0); if((x+margin*2+m2.x)>x1 && !(is_leftside==0 && y>y0)) x=x0,y+=font->height+margin*2+font->height/4; if(is_leftside==0 && y>y0) { FILLXYWH(thumb->xywh,0,0,0,0); continue; } FILLXYWH(thumb->xywh,x,y,margin*2+m2.x,margin*2+font->height); if(is_leftside && (thumb->xywh.y+thumb->xywh.h)>(listing->lastleftxywh.y+listing->lastleftxywh.h)) memcpy(&(listing->lastleftxywh),&(thumb->xywh),sizeof(xywh_t)); x+=margin*2+m2.x+font->height/4; } y+=((x==x0)?0:font->height+margin*2+font->height/4); /* files */ if(is_leftside) y0=y; else y0+=margin*2+font->height+margin; for(k=0,x=x0,y=y0;kusedelems;k++) { elem=listing->elems+k; thumb=(is_leftside)?&(elem->left):&(elem->right); if(elem->name[0]!='f' && elem->name[0]!='l') continue; if(!is_leftside && y>y0) { FILLXYWH(thumb->xywh,0,0,0,0); continue; } if((x+margin*2+sidelen)>x1 && !(is_leftside==0 && y>y0)) x=x0,y+=sidelen+margin*2; if(is_leftside==0 && y>y0) { FILLXYWH(thumb->xywh,0,0,0,0); continue; } FILLXYWH(thumb->xywh,x,y,sidelen,sidelen); if(is_leftside && (thumb->xywh.y+thumb->xywh.h)>(listing->lastleftxywh.y+listing->lastleftxywh.h)) memcpy(&(listing->lastleftxywh),&(thumb->xywh),sizeof(xywh_t)); x+=margin*2+sidelen; } return(0); } int is_imagefilename(char *filename) { char *ptr; int i; char *knownext[]={".jpg",".jpeg",".JPG",".JPEG",".Jpg",".Jpeg" ,".png",".PNG",".Png" ,".tga",".TGA",".Tga" ,".bmp",".BMP",".Bmp" ,".psd",".PSD",".Psd" ,".gif",".GIF",".Gif" ,".hdr",".HDR",".Hdr" ,".pic",".PIC",".Pic" ,".pnm",".PNM",".Pnm" }; if(filename==NULL) return(0); /* sanity check failed */ if((ptr=strrchr(filename,'.'))==NULL) return(0); /* no extension found */ for(i=0;i<(sizeof(knownext)/sizeof(knownext[0]));i++) { if(strcmp(ptr,knownext[i])==0) return(1); /* it has a known ext */ } return(0); /* not in the knownext list */ } #ifdef ANDROID Image imutil_loadimage(const char *filename) { unsigned char *filedata=NULL; FILE *f=NULL; struct stat st; Image img; char *ext; if((f=fopen(filename,"r"))==NULL || fstat(fileno(f),&st)!=0 || st.st_size<=0 || (filedata=(unsigned char *)malloc(st.st_size))==NULL || fread(filedata,1,st.st_size,f)!=st.st_size ) { if(f!=NULL) fclose(f),f=NULL; if(filedata!=NULL) free(filedata),filedata=NULL; return((Image){0}); } fclose(f); ext=strchr(filename,'.'); ext=(ext==NULL)?filename+strlen(filename):ext; img=LoadImageFromMemory(ext,filedata,st.st_size); free(filedata),filedata=NULL; return(img); } #else Image imutil_loadimage(const char *filename) { return(LoadImage(filename)); } #endif static void intimutil_fpsdata(struct timeval **deadline) { static struct timeval mydeadline; *deadline=&(mydeadline); return; } void imutil_fpsreset(void) { struct timeval *deadline; long deadlineincr; intimutil_fpsdata(&deadline); gettimeofday(deadline,NULL); deadlineincr=1000000L/TARGETFPS; deadline->tv_usec+=deadlineincr; deadline->tv_sec+=(deadline->tv_usec)/1000000L; deadline->tv_usec%=1000000L; } int imutil_fpsleft(void) { struct timeval *deadline,now; intimutil_fpsdata(&deadline); gettimeofday(&now,NULL); if(deadline->tv_sectv_sec==now.tv_sec && deadline->tv_usecpipe[0]=bg->pipe[1]=-1)!=-1 || mypipe(bg->pipe)!=0 || (bg->bgload=calloc(sizebgload,sizeof(bgload_t)))==NULL || (bg->sizebgload=sizebgload)!=sizebgload || pthread_attr_init(&(bg->tattr))!=0 || pthread_create(&(bg->thread),&(bg->tattr),bg_thread,(void *)bg)!=0 || (bg->flag_threadstarted=1)!=1 ) { bg_free(bg); return(NULL); } return(bg); } void bg_free(bg_t *bg) { int i; if(bg==NULL) return; /* nothing to do */ if(bg->flag_threadstarted) { char dummy=1; write(bg->pipe[WR],&dummy,1); pthread_join(bg->thread,NULL); bg->flag_threadstarted=0; } if(bg->pipe[0]!=-1) close(bg->pipe[0]),bg->pipe[0]=-1; if(bg->pipe[1]!=-1) close(bg->pipe[1]),bg->pipe[1]=-1; if(bg->bgload!=NULL) { bgload_t *bgload; for(i=0,bgload=bg->bgload;isizebgload;i++,bgload++) { if(bgload->has_data) { UnloadImage(bgload->image); bgload->has_data=0; } } free(bg->bgload),bg->bgload=NULL,bg->sizebgload=0; } return; } int bg_resetmarks(bg_t *bg) { int i; bgload_t *bgload; if(bg==NULL) return(-1); for(i=0,bgload=bg->bgload;isizebgload;i++,bgload++) bgload->has_mark=0; return(0); } bgload_t * bg_get(bg_t *bg, char *path) { int i; bgload_t *bgload; if(bg==NULL) return(NULL); for(i=0,bgload=bg->bgload;isizebgload;i++,bgload++) { if(bgload->thread_finished && bgload->has_data && strcmp(path,bgload->path)==0) { bgload->has_mark=1; #if 1 fprintf(stderr,"bg_get: \"%s\"\n",bgload->path); #endif return(bgload); } } return(NULL); } int bg_add(bg_t *bg, char *path) { int i; bgload_t *bgload; int dummy; if(bg==NULL) return(-1); for(i=0,bgload=bg->bgload;isizebgload;i++,bgload++) { if(bgload->lended_to_thread && strcmp(path,bgload->path)==0) { bgload->is_todo=1; bgload->has_mark=1; dummy=0; write(bg->pipe[WR],&dummy,1); return(0); /* already on list */ } } for(i=0,bgload=bg->bgload;isizebgload;i++,bgload++) { if(bgload->lended_to_thread==0) { memset(bgload,0,sizeof(bgload_t)); strncpy(bgload->path,path,sizeof(bgload->path)); bgload->path[sizeof(bgload->path)-1]='\0'; bgload->is_todo=1; bgload->has_mark=1; dummy=0; bgload->lended_to_thread=1; write(bg->pipe[WR],&dummy,1); return(0); /* added to list */ } } return(-1); /* couldn't add */ } int bg_freeunmarked(bg_t *bg) { int i; bgload_t *bgload; if(bg==NULL) return(-1); for(i=0,bgload=bg->bgload;isizebgload;i++,bgload++) { if(bgload->lended_to_thread && bgload->thread_finished && bgload->has_mark==0) { if(bgload->has_data) { #if 1 fprintf(stderr,"bg: Unloading: \"%s\"\n",bgload->path); #endif UnloadImage(bgload->image); bgload->has_data=0; } #if 1 else { fprintf(stderr,"bg: Cancelling: \"%s\"\n",bgload->path); } #endif memset(bgload,0,sizeof(bgload_t)); } } return(0); } void * bg_thread(void *parambg) { bg_t *bg; char dummy; bg=(bg_t *)parambg; int i; bgload_t *bgload; while(1) { read(bg->pipe[RD],&dummy,1); if(dummy!=0) break; /* was told to exit */ for(i=0,bgload=bg->bgload;isizebgload;i++,bgload++) { if(bgload->lended_to_thread==0) continue; if(bgload->is_todo==0) { bgload->thread_finished=1; continue; } if(bgload->has_data==0 && bgload->has_failedload==0) { bgload->image=imutil_loadimage(bgload->path); if(IsImageValid(bgload->image)) bgload->has_data=1; else bgload->has_failedload=1; bgload->thread_finished=1; } } } pthread_exit(NULL); } #if !defined(__linux__) && !defined(ANDROID) #include "win32_pipe.h" static int mypipe(int fds[2]) { return(win32_pipe(fds)); } #else static int mypipe(char fds[2]) { return(pipe(fds)); } #endif