summaryrefslogtreecommitdiff
path: root/source/gui.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/gui.c')
-rw-r--r--source/gui.c919
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;
+}
+