/* 3ds81 - Nintendo 3DS ZX81 emulator. Copyright (C) 2021 Ian Cowburn 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 $Id: gui.c 75 2010-11-19 14:52:49Z ianc $ */ #include #include #include #include <3ds.h> #include #include #include "framebuffer.h" #include "zx81.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=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; DS81_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=0 && nsel (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+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; } int GUI_Input(const char *prompt, char text[], int maxlen) { struct { SoftKey key; int ascii; int shift; } keymap[] = { {SK_1, '1', 0}, {SK_2, '2', 0}, {SK_3, '3', 0}, {SK_4, '4', 0}, {SK_5, '5', 0}, {SK_6, '6', 0}, {SK_7, '7', 0}, {SK_8, '8', 0}, {SK_9, '9', 0}, {SK_0, '0', 8}, {SK_A, 'A', 0}, {SK_B, 'B', '*'}, {SK_C, 'C', '?'}, {SK_D, 'D', 0}, {SK_E, 'E', 0}, {SK_F, 'F', 0}, {SK_G, 'G', 0}, {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', 0}, {SK_R, 'R', 0}, {SK_S, 'S', 0}, {SK_T, 'T', 0}, {SK_U, 'U', '$'}, {SK_V, 'V', '/'}, {SK_W, 'W', 0}, {SK_X, 'X', ';'}, {SK_Y, 'Y', 0}, {SK_Z, 'Z', ':'}, {SK_PERIOD, '.', ','}, {0, 0, 0} }; SoftKeyEvent ev; int done = FALSE; int accept = FALSE; int shifted; Framebuffer upper; text[0] = 0; shifted = SK_KeyPressed(SK_SHIFT); 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\"", text); FB_Print(&upper, "PRESS ENTER TO ACCEPT", 0, 32, COL_BLACK, COL_TRANSPARENT); FB_Print(&upper, "PRESS SPACE/BREAK TO CANCEL", 0, 40, COL_BLACK, COL_TRANSPARENT); FB_EndFrame(); while(SK_GetBareEvent(&ev)) { if (ev.pressed) { switch(ev.key) { case SK_SHIFT: shifted = TRUE; break; default: break; } } if (!ev.pressed) { size_t l; int f; int ascii; l = strlen(text); switch(ev.key) { case SK_SHIFT: shifted = FALSE; break; case SK_SPACE: done = TRUE; accept = FALSE; break; case SK_NEWLINE: done = TRUE; accept = TRUE; break; default: f = 0; ascii = 0; while(!ascii && keymap[f].ascii) { if (ev.key == keymap[f].key) { if (shifted) { ascii = keymap[f].shift; } else { ascii = keymap[f].ascii; } } f++; } if (ascii == 8) { if (l) { text[--l] = 0; } } else if (ascii) { if (l < maxlen) { text[l++] = ascii; text[l] = 0; } } break; } } } } return accept; }