diff options
Diffstat (limited to 'source/gui.c')
-rw-r--r-- | source/gui.c | 919 |
1 files changed, 919 insertions, 0 deletions
diff --git a/source/gui.c b/source/gui.c new file mode 100644 index 0000000..1d14fa3 --- /dev/null +++ b/source/gui.c @@ -0,0 +1,919 @@ +/* + 3dsspec - Nintendo 3DS Spectrum emulator. + + Copyright (C) 2021 Ian Cowburn <ianc@noddybox.co.uk> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/> + + $Id: gui.c 75 2010-11-19 14:52:49Z ianc $ +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <3ds.h> + +#include <sys/types.h> +#include <dirent.h> + +#include "framebuffer.h" +#include "spec.h" +#include "keyboard.h" +#include "config.h" + + +/* ---------------------------------------- PRIVATE INTERFACES - PATH HANDLING +*/ +#define FSEL_FILENAME_LEN 20 +#define FSEL_LINES 24 +#define FSEL_MAX_FILES 1024 + +#define FSEL_LIST_Y 10 +#define FSEL_LIST_H FSEL_LINES*8 + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +typedef struct +{ + char name[FSEL_FILENAME_LEN+1]; + int is_dir; + int size; +} FSEL_File; + +static FSEL_File fsel[FSEL_MAX_FILES]; + + +static void CheckPath(char *path) +{ + size_t l; + + l = strlen(path); + + if (l == 1) + { + path[0] = '/'; + } + else + { + if (path[l-1] != '/') + { + path[l] = '/'; + path[l+1] = 0; + } + } +} + + +static void AddPath(char *path, const char *dir) +{ + if (strcmp(dir,"..") == 0) + { + size_t l; + + l = strlen(path); + + if (l > 1) + { + path[--l] = 0; + + while(l && path[l] != '/') + { + path[l--] = 0; + } + } + } + else + { + strcat(path,dir); + strcat(path,"/"); + } +} + + + + +static int SortFiles(const void *a, const void *b) +{ + const FSEL_File *f1; + const FSEL_File *f2; + + f1 = (const FSEL_File *)a; + f2 = (const FSEL_File *)b; + + if (strcmp(f1->name, "..") == 0) + { + return -1; + } + else if (strcmp(f2->name, "..") == 0) + { + return 1; + } + else if (f1->is_dir == f2->is_dir) + { + return strcasecmp(f1->name, f2->name); + } + else if (f1->is_dir) + { + return -1; + } + else + { + return 1; + } +} + + +static int ValidFilename(const char *name, int is_dir, const char *filter) +{ + size_t l; + size_t f; + + l = strlen(name); + + if (l > FSEL_FILENAME_LEN) + return 0; + + if (strcmp(name,".") == 0) + return 0; + + if (is_dir || !filter) + return 1; + + f = strlen(filter); + + if (l > f) + { + if (strcasecmp(name+l-f,filter) == 0) + { + return 1; + } + } + + return 0; +} + + +static int LoadDir(const char *path, const char *filter) +{ + DIR *dir; + struct dirent *ent; + struct stat st; + int no = 0; + char whole_path[FILENAME_MAX]; + + if ((dir = opendir(path))) + { + if (strcmp(path, "/") != 0) + { + strcpy(fsel[no].name, ".."); + fsel[no].is_dir = TRUE; + fsel[no].size = 0; + no++; + } + + while(no < FSEL_MAX_FILES && (ent = readdir(dir))) + { + strcpy(whole_path, path); + strcat(whole_path, "/"); + strcat(whole_path, ent->d_name); + + stat(whole_path, &st); + + if (ValidFilename(ent->d_name, (st.st_mode & S_IFDIR), filter)) + { + strcpy(fsel[no].name,ent->d_name); + fsel[no].is_dir = (st.st_mode & S_IFDIR); + fsel[no].size = (int)st.st_size; + no++; + } + } + + closedir(dir); + + qsort(fsel,no,sizeof fsel[0],SortFiles); + } + + return no; +} + + +/* ---------------------------------------- PUBLIC INTERFACES +*/ +int GUI_Menu(const char *opts[]) +{ + int x,y; + int h; + int w; + int no; + int sel; + int f; + int done; + int defer; + Framebuffer lower; + + w=0; + h=0; + sel=0; + done=FALSE; + defer=FALSE; + + for(no=0;opts[no];no++) + { + h+=16; + + if (strlen(opts[no])>w) + { + w=strlen(opts[no]); + } + } + + FB_StartFrame(NULL, &lower); + + w=w*8+16; + + x=lower.width/2-w/2; + y=lower.height - 3; + + while(!done) + { + u32 key=0; + + do + { + FB_StartFrame(NULL, &lower); + + FB_Clear(&lower, COL_BLACK); + + FB_Box(&lower,x,y,w,h,COL_WHITE); + FB_FillBox(&lower,x+1,y-sel*16+1,w-2,14,COL_GUISELECT); + + for(f=0;f<no;f++) + { + FB_Centre(&lower, opts[f],y-4-f*16,COL_WHITE,COL_TRANSPARENT); + } + + FB_EndFrame(); + } while(!defer && !(key=hidKeysDownRepeat())); + + if (defer) + { + do + { + gspWaitForVBlank(); + hidScanInput(); + } while (hidKeysHeld()&KEY_TOUCH); + done=TRUE; + } + else + { + if (key & (KEY_A|KEY_B|KEY_X|KEY_Y)) + { + done=TRUE; + } + else if ((key & KEY_UP) && sel) + { + sel--; + } + else if ((key & KEY_DOWN) && sel<no-1) + { + sel++; + } + else if (key & KEY_TOUCH) + { + touchPosition tp; + + hidTouchRead(&tp); + + if (tp.px>=x && tp.px<(x+w) && tp.py>=3 && tp.py<(3+h)) + { + defer=TRUE; + sel=(tp.py-3)/16; + } + } + } + } + + return sel; +} + + +void GUI_Alert(int fatal, const char *text) +{ + char line[80]; + int h; + const char *p; + char *d; + Framebuffer lower; + + FB_StartFrame(NULL, &lower); + + h=40; + p=text; + + while(*p) + { + if (*p++=='\n') + { + h+=8; + } + } + + FB_Clear(&lower, COL_BLACK); + FB_Box(&lower,0,lower.height-1,lower.width-1,lower.height-1,COL_WHITE); + + p=text; + h=lower.height-12; + d=line; + + while(*p) + { + if (*p=='\n') + { + *d++=0; + p++; + FB_Centre(&lower,line,h,COL_WHITE,COL_TRANSPARENT); + h-=8; + d=line; + } + else + { + *d++=*p++; + } + } + + if (d>line) + { + *d=0; + FB_Centre(&lower,line,h,COL_WHITE,COL_TRANSPARENT); + h-=8; + } + + if (!fatal) + { + FB_Centre(&lower, "PRESS ANY BUTTON OR SCREEN",h-16, + COL_YELLOW,COL_TRANSPARENT); + + FB_EndFrame(); + + while(!hidKeysDown()) + { + gspWaitForVBlank(); + hidScanInput(); + } + + while(hidKeysHeld()) + { + gspWaitForVBlank(); + hidScanInput(); + } + } + else + { + FB_Centre(&lower,"PLEASE RESET YOUR CONSOLE",h-16, + COL_YELLOW,COL_TRANSPARENT); + + FB_EndFrame(); + + while(1) + { + gspWaitForVBlank(); + } + } +} + + +void GUI_Config(void) +{ + int sel; + DSSPEC_ConfigItem f; + int done; + int save; + Framebuffer lower; + + sel = 0; + done = FALSE; + save = FALSE; + + while(!done) + { + u32 key=0; + + do + { + FB_StartFrame(NULL, &lower); + + FB_Clear(&lower,COL_BLACK); + + FB_Centre(&lower,"Up/Down to select",40, + COL_YELLOW,COL_TRANSPARENT); + FB_Centre(&lower,"A to toggle",32,COL_YELLOW,COL_TRANSPARENT); + FB_Centre(&lower,"Or use touchscreen",24, + COL_YELLOW,COL_TRANSPARENT); + FB_Centre(&lower,"START to finish",16,COL_YELLOW,COL_TRANSPARENT); + + FB_Centre(&lower,"SELECT to finish and save",8, + COL_YELLOW,COL_TRANSPARENT); + + for(f=0;f<DSSPEC_NUM_CONFIG_ITEMS;f++) + { + FB_Print(&lower,ConfigDesc(f),14,lower.height-(20+f*14), + COL_WHITE,COL_TRANSPARENT); + } + + + for(f=0;f<DSSPEC_NUM_CONFIG_ITEMS;f++) + { + FB_FillBox(&lower,2,lower.height-(20+f*14-1),10,10, + DSSPEC_Config[f] ? COL_WHITE : COL_BLACK); + + FB_Box(&lower,2,lower.height-(20+f*14-1),10,10,COL_GREY); + } + + FB_Box(&lower,0,lower.height-(20+sel*14-3),lower.width-1,14, + COL_GUISELECT); + + FB_EndFrame(); + } while(!(key=hidKeysDownRepeat())); + + if (key & KEY_START) + { + done=TRUE; + } + else if (key & KEY_SELECT) + { + done=TRUE; + save=TRUE; + } + else if (key & KEY_A) + { + DSSPEC_Config[sel] = !DSSPEC_Config[sel]; + } + else if ((key & KEY_UP) && sel) + { + sel--; + } + else if ((key & KEY_DOWN) && sel<DSSPEC_NUM_CONFIG_ITEMS-1) + { + sel++; + } + else if (key & KEY_TOUCH) + { + touchPosition tp; + int nsel; + + touchRead(&tp); + + nsel = (tp.py-18)/14; + + if (nsel>=0 && nsel<DSSPEC_NUM_CONFIG_ITEMS) + { + sel = nsel; + DSSPEC_Config[sel] = !DSSPEC_Config[sel]; + } + } + } + + if (save) + { + SaveConfig(); + } +} + + +int GUI_FileSelect(char pwd[], char selected_file[], const char *filter) +{ + int no; + int sel; + int top; + int bar_size; + double bar_step; + int done; + int ret; + FB_Colour paper; + int off; + int f; + int drag; + int drag_start; + Framebuffer lower; + + CheckPath(pwd); + no = LoadDir(pwd,filter); + + sel = 0; + top = 0; + done = FALSE; + ret = FALSE; + drag = FALSE; + drag_start = 0; + + while(!done) + { + u32 key=0; + + FB_StartFrame(NULL, &lower); + + FB_Clear(&lower,COL_BLACK); + + FB_printf(&lower,0,lower.height-1,COL_BLACK,COL_LIGHTGREY, + "%-32.32s",pwd); + + FB_Centre(&lower,"Use pad and A to select",32, + COL_YELLOW,COL_TRANSPARENT); + FB_Centre(&lower,"L and R to page up/down",24, + COL_YELLOW,COL_TRANSPARENT); + FB_Centre(&lower,"Or use touchscreen",16,COL_YELLOW,COL_TRANSPARENT); + FB_Centre(&lower,"B to cancel",8,COL_YELLOW,COL_TRANSPARENT); + + if (no<=FSEL_LINES) + { + bar_step = 0; + bar_size = FSEL_LIST_H; + } + else + { + bar_step = FSEL_LIST_H/(double)no; + bar_size = bar_step*FSEL_LINES; + } + + for (f=0;f<FSEL_LINES;f++) + { + off = f + top; + + if (off<no) + { + if (off == sel) + { + paper = COL_GUISELECT; + } + else + { + paper = COL_BLACK; + } + + FB_printf(&lower,8,lower.height-(FSEL_LIST_Y+f*8), + COL_WHITE,paper, + "%-*s %s", + FSEL_FILENAME_LEN, + fsel[off].name, + fsel[off].is_dir ? "DIR" : " "); + } + else + { + FB_printf(&lower,8,lower.height-(FSEL_LIST_Y+f*8), + COL_WHITE,COL_BLACK, + "%-*s %s", + FSEL_FILENAME_LEN, + off==0 ? "No Files!" : "", + " "); + } + } + + FB_FillBox(&lower,240,lower.height-(FSEL_LIST_Y),16,FSEL_LIST_H, + COL_DARKGREY); + FB_FillBox(&lower,240,lower.height-(FSEL_LIST_Y+top*bar_step),16, + bar_size,COL_WHITE); + + FB_EndFrame(); + + if (drag) + { + touchPosition tp = {0}; + int diff = 0; + + while (((key=keysHeld()) & KEY_TOUCH) && diff == 0) + { + hidScanInput(); + touchRead(&tp); + diff = tp.py - drag_start; + gspWaitForVBlank(); + } + + if (key & KEY_TOUCH) + { + int new_top; + + new_top = top + diff / bar_step; + + if (new_top > (no - FSEL_LINES)) + { + new_top = no - FSEL_LINES; + } + + if (new_top < 0) + { + new_top = 0; + } + + if (new_top != top) + { + top = new_top; + sel = top; + drag_start = tp.py; + } + } + else + { + drag = FALSE; + } + } + + if (!drag) + { + int activate = FALSE; + + key=hidKeysDownRepeat(); + + if (key & KEY_TOUCH) + { + touchPosition tp; + + touchRead(&tp); + + if (tp.py >= FSEL_LIST_Y && tp.py <= (FSEL_LIST_Y+FSEL_LIST_H)) + { + if (tp.px > 239) + { + drag = TRUE; + drag_start = tp.py; + } + else + { + int new_sel; + + new_sel = top + (tp.py - FSEL_LIST_Y)/8; + + if (new_sel < no) + { + if (new_sel == sel) + { + activate = TRUE; + } + else + { + sel = new_sel; + } + } + } + } + } + else if (key & KEY_UP) + { + if (sel) + { + sel--; + + if (sel<top) + { + top--; + } + } + } + else if (key & KEY_DOWN) + { + if (sel < (no-1)) + { + sel++; + + if (sel >= (top+FSEL_LINES)) + { + top++; + } + } + } + else if (key & KEY_L) + { + if (sel) + { + sel-=FSEL_LINES; + + if (sel < 0) + { + sel = 0; + } + + top = sel; + } + } + else if (key & KEY_R) + { + if (sel < (no-1)) + { + sel+=FSEL_LINES; + + if (sel > (no-1)) + { + sel = no-1; + } + + top = sel - FSEL_LINES + 1; + + if (top < 0) + { + top = 0; + } + } + } + else if (key & KEY_A) + { + activate = TRUE; + } + else if (key & KEY_B) + { + done = TRUE; + } + + if (activate) + { + if (fsel[sel].is_dir) + { + AddPath(pwd,fsel[sel].name); + + no = LoadDir(pwd,filter); + + sel = 0; + top = 0; + + if (no<=FSEL_LINES) + { + bar_step = 0; + bar_size = FSEL_LIST_H; + } + else + { + bar_step = FSEL_LIST_H/(double)no; + bar_size = bar_step*FSEL_LINES; + } + } + else + { + done = TRUE; + ret = TRUE; + + strcpy(selected_file,pwd); + strcat(selected_file,fsel[sel].name); + } + } + } + } + + while (hidKeysHeld()) + { + hidScanInput(); + gspWaitForVBlank(); + } + + return ret; +} + + +int GUI_InputName(const char *prompt, const char *ext, char name[], int maxlen) +{ + struct + { + SoftKey key; + int ascii; + } keymap[] = + { + {SK_1, '1'}, + {SK_2, '2'}, + {SK_3, '3'}, + {SK_4, '4'}, + {SK_5, '5'}, + {SK_6, '6'}, + {SK_7, '7'}, + {SK_8, '8'}, + {SK_9, '9'}, + {SK_0, '0'}, + {SK_A, 'A'}, + {SK_B, 'B'}, + {SK_C, 'C'}, + {SK_D, 'D'}, + {SK_E, 'E'}, + {SK_F, 'F'}, + {SK_G, 'G'}, + {SK_H, 'H'}, + {SK_I, 'I'}, + {SK_J, 'J'}, + {SK_K, 'K'}, + {SK_L, 'L'}, + {SK_M, 'M'}, + {SK_N, 'N'}, + {SK_O, 'O'}, + {SK_P, 'P'}, + {SK_Q, 'Q'}, + {SK_R, 'R'}, + {SK_S, 'S'}, + {SK_T, 'T'}, + {SK_U, 'U'}, + {SK_V, 'V'}, + {SK_W, 'W'}, + {SK_X, 'X'}, + {SK_Y, 'Y'}, + {SK_Z, 'Z'}, + {0, 0} + }; + + SoftKeyEvent ev; + int done = FALSE; + int accept = FALSE; + Framebuffer upper; + + name[0] = 0; + + while(!done) + { + FB_StartFrame(&upper, NULL); + + FB_Clear(&upper, COL_WHITE); + SK_DisplayKeyboard(); + + FB_printf(&upper, 0, 16, COL_BLACK, COL_TRANSPARENT, "%s", prompt); + FB_printf(&upper, 0, 8, COL_BLACK, COL_TRANSPARENT, "\"%s\001L\001\"", + name); + + FB_Print(&upper, "PRESS ENTER TO ACCEPT", 0, 32, + COL_BLACK, COL_TRANSPARENT); + FB_Print(&upper, "PRESS PERIOD TO BACKSPACE", 0, 40, + COL_BLACK, COL_TRANSPARENT); + FB_Print(&upper, "PRESS SPACE/BREAK TO CANCEL", 0, 48, + COL_BLACK, COL_TRANSPARENT); + + FB_EndFrame(); + + while(SK_GetBareEvent(&ev)) + { + if (!ev.pressed) + { + size_t l; + int f; + int ascii; + + l = strlen(name); + + switch(ev.key) + { + case SK_PERIOD: + if (l) + { + name[--l] = 0; + } + break; + + case SK_SPACE: + done = TRUE; + accept = FALSE; + break; + + case SK_NEWLINE: + done = TRUE; + accept = TRUE; + break; + + default: + if (l < maxlen) + { + f = 0; + ascii = 0; + + while(!ascii && keymap[f].ascii) + { + if (ev.key == keymap[f].key) + { + ascii = keymap[f].ascii; + } + + f++; + } + + if (ascii) + { + name[l++] = ascii; + name[l] = 0; + } + } + break; + } + } + } + } + + return accept; +} + |