/*
 * 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.
 *
 * Author: Dario Rodriguez dario@darionomono.com
 * (c) Dario Rodriguez 2025
 * This program is licensed under the terms of GNU GPL v2.1+
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>

#include "raylib.h"
#include "roboto_regular.c"

#define DEFAULTWIDTH 1280
#define DEFAULTHEIGHT 768

#define UTF8DOWNARROW "\xe2\x86\x86" /* U+2186 in UTF-8 */

#define LEFTSIZE 720
#define DEFAULTDIRDATAHEIGHT 150
#define DEFAULTDIRDATATRIANGLEW 35
#define LEFTIMAGESIDELEN 125

#define FONTSIZE 18
#define FONTBIGSIZE 32
#define FONTHUGESIZE 48

#define WHEELSTEP LEFTIMAGESIDELEN

#define ROOTDIR "/var/www/default/animeshot/"

#define SEP "/"

#define BLOCKLISTINGBUF 2048
#define BLOCKLISTINGELEMS 1024
#define BLOCKDIRDATA 16

#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 __linux__
/* 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;
} dirdata_t;

typedef struct body_t {
        char *rootdir;
        xywh_t xywh;
        xywh_t backxywh;
        int leftsize;
        int sizedirdata;
        dirdata_t **dirdata;
        int currentdirdata;
        int leftscrollpos;
        int rightscrollpos;
        font_t *ptrfont;
        font_t *ptrfontbig;
        font_t *ptrfonthuge;
        char currenttexture[2048];
        Texture2D texture;
        int texturew;
        int textureh;
        int has_texture;
        int has_failedload;
} 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);

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 windowwidth, int windowheight);

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 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 im_util_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);

int
main(int argc, char *argv[])
{
        im_t *im;
        Vector2 mousepos,wheel;
        int flag_ignorelmb;
        int lmbpressed,lmbreleased,lmbdown;
        int click_avail;
        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;
        while(!WindowShouldClose()) {
                mousepos=GetMousePosition();
                wheel=GetMouseWheelMoveV();
                lmbpressed=IsMouseButtonPressed(0);
                lmbreleased=IsMouseButtonReleased(0);
                lmbdown=IsMouseButtonDown(0);
                click_avail=1;
                /* 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(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,im->w,im->h);
                im_menubar_draw(im->menubar,im->w,im->h);
#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;j<listing.usedelems;j++) {
     Color c;
     i++;
     c=((Color){ 45, 45, 45, 255 });
     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
       ,listing.elems[j].name
       ,v2
       ,im->font->height
       ,0
       ,c
     );
   }
 }
}
#endif
                EndDrawing();
        }
        CloseWindow();
        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 0 /* not working as intended */
        if(im->windowinit)
                CloseWindow(),im->windowinit=0;
#endif
        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;i<menubar->sizemenudata;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;j<menudata->sizeoptions;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<l;n++) {
                ptr=imutil_menu_get(menus,n,&len);
                fprintf(stderr,"menu[%i]:\"",n);
                fwrite(ptr,1,len,stderr);
                fprintf(stderr,"\"->");
                for(m=0,ml=imutil_submenu_count(ptr);m<ml;m++) {
                        mptr=imutil_submenu_get(ptr,m,&mlen);
                        fprintf(stderr,"|\"");
                        fwrite(mptr,1,mlen,stderr);
                        fprintf(stderr,"\"");
                }
                fprintf(stderr,"\n");
        }
#endif
#if 0
        /* test menudata */
        for(i=0;i<menubar->sizemenudata;i++) {
                fprintf(stderr,"menu[%i]:\"%s\"->",i,menubar->menudata[i]->title);
                for(j=0;j<menubar->menudata[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;i<menubar->sizemenudata;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;j<menudata->sizeoptions;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;i<menubar->sizemenudata;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;j<menubar->sizemenudata;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;j<menubar->sizemenudata;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;j<menubar->sizemenudata;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;j<menubar->sizemenudata;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;j<menubar->sizemenudata;j++) {
                        menubar->menudata[j]->currentoption=-1;
                }
        }
        /* update click_avail */
        for(j=0;j<menubar->sizemenudata;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 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;i<menubar->sizemenudata;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;j<menudata->sizeoptions;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;k<menudata->sizeoptions;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
        ) {
                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 '/' */
        return(body);
}

void
im_body_free(body_t *body)
{
        int i;
        dirdata_t *dirdata;
        if(body==NULL)
                return; /* nothing to do */
        if(body->rootdir!=NULL)
                free(body->rootdir),body->rootdir=NULL;
        if(body->dirdata!=NULL) {
                for(i=0;i<body->sizedirdata;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;
        }
        if(body->has_texture) {
                UnloadTexture(body->texture);
                body->currenttexture[0]='\0';
                body->has_texture=0;
                body->has_failedload=0;
        }
        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;i<body->sizedirdata;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;
        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->leftscrollpos-=(int)wheel.y*WHEELSTEP;
        body->leftscrollpos=(body->leftscrollpos<0)?0:body->leftscrollpos;
        if(body->leftscrollpos>dirdata->listing.lastleftxywh.y)
                body->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 */
        /* 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->leftscrollpos=0;
                *click_avail=0;
                return(0);
        }
        /* leftside directories */
        for(i=0;i<dirdata->listing.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->leftscrollpos=0;
                        *click_avail=0;
                        return(0);
                }
        }
#warning TODO
        return(0);
}

int
im_body_draw(body_t *body, Vector2 mousepos, int lmbdown, int windowwidth, int windowheight)
{
        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 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;
        /* calculate positions */
        for(i=0;i<body->sizedirdata;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 } );
        /* first pass, draw leftside, second pass, draw all of rightside */
        statustooltip[0]='\0';
        for(is_leftside=1,flag_skiprightside=0;is_leftside>=0 && flag_skiprightside==0;is_leftside--) {
                for(i=(is_leftside)?body->currentdirdata:0,righty=body->xywh.y;(is_leftside && i==body->currentdirdata) || (!is_leftside && i<body->sizedirdata);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->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->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
                                  ,(Color){ 240, 240, 240, 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->leftscrollpos:0);
                        if(is_leftside && dirdata->dirname[0]=='\0')
                                yoff-=fontbig->height/4+fontbig->height;
                        for(k=0,lastx=lasty=0;k<dirdata->listing.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(;k<dirdata->listing.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;k<dirdata->listing.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 1
                                if(is_imagefilename(elem->name+1)) {
                                        if(thumb->has_texture==0) {
                                                Image im;
                                                char fullpath[2048];
                                                snprintf(fullpath,sizeof(fullpath),"%s/%s/%s",body->rootdir,dirdata->dirname,elem->name+1);
                                                fullpath[sizeof(fullpath)-1]='\0';
                                                im=LoadImage(fullpath);
                                                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});
                                                        im_util_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;
                                                }
                                        }
                                        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 && lmbdown==0 && is_imutil_insidexywh(mousepos,&(thumb->screenxywh),margin)) {
                                                        /* draw image in rightside */
                                                        char path[2048];
                                                        int maxw,maxh;
                                                        snprintf(path,sizeof(path),"%s/%s",dirdata->dirname,elem->name+1);
                                                        path[sizeof(path)-1]='\0';
                                                        maxw=windowwidth-(body->leftsize-DEFAULTDIRDATATRIANGLEW);
                                                        maxh=windowheight-body->xywh.y;
                                                        if((body->has_texture==0 && !(body->has_failedload && strcmp(body->currenttexture,path)==0))
                                                          || strcmp(body->currenttexture,path)!=0
                                                        ) {
                                                                Image im;
                                                                int neww,newh;
                                                                char fullpath[2048];
                                                                if(body->has_texture) {
                                                                        UnloadTexture(body->texture);
                                                                        body->currenttexture[0]='\0';
                                                                        body->has_texture=0;
                                                                        body->has_failedload=0;
                                                                }
                                                                snprintf(fullpath,sizeof(fullpath),"%s/%s/%s",body->rootdir,dirdata->dirname,elem->name+1);
                                                                fullpath[sizeof(fullpath)-1]='\0';
                                                                im=LoadImage(fullpath);
                                                                if(IsImageValid(im)) {
                                                                        im_util_aspectmaximize(im.width,im.height,maxw,maxh,&neww,&newh);
                                                                        ImageResize(&im,neww,newh);
                                                                        body->texture=LoadTextureFromImage(im);
                                                                        UnloadImage(im);
                                                                        strncpy(body->currenttexture,fullpath,sizeof(body->currenttexture));
                                                                        body->currenttexture[sizeof(body->currenttexture)-1]='\0';
                                                                        body->has_texture=1;
                                                                        body->has_failedload=0;
                                                                        body->texturew=neww;
                                                                        body->textureh=newh;
                                                                } else {
                                                                        strncpy(body->currenttexture,fullpath,sizeof(body->currenttexture));
                                                                        body->currenttexture[sizeof(body->currenttexture)-1]='\0';
                                                                        body->has_texture=0;
                                                                        body->has_failedload=1;
                                                                }
                                                        }
                                                        if(body->has_texture) {
                                                                int x0,y0;
                                                                x0=body->leftsize-DEFAULTDIRDATATRIANGLEW;
                                                                y0=body->xywh.y;
                                                                DrawRectangle(x0,y0,maxw,maxh,(Color){ 215, 215, 215, 255 } );
                                                                DrawTexture(body->texture,x0+(maxw-body->texturew)/2,y0+(maxh-body->textureh)/2,WHITE);
                                                                flag_skiprightside=1;
                                                        }
                                                }
                                        }
                                }
#endif
                                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(;k<dirdata->listing.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(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 });
        }
        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
im_util_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;i<listing->usedelems;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;i<listing->usedelems;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;i<listing->usedelems;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;k<listing->usedelems;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;k<listing->usedelems;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 */
}