From 32ef1536d70c067acff216cc9fb3c4a2f2bff4d2 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Fri, 24 Jun 2022 00:38:05 +0100 Subject: [PATCH] jfileselect: add a "save as" option --- include/justui/jfileselect.h | 14 +++++ src/jfileselect.c | 103 ++++++++++++++++++++++++++++++++--- src/jscene.c | 3 +- src/jwidget.c | 3 +- 4 files changed, 111 insertions(+), 12 deletions(-) diff --git a/include/justui/jfileselect.h b/include/justui/jfileselect.h index bddfe0a..1f282b1 100644 --- a/include/justui/jfileselect.h +++ b/include/justui/jfileselect.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -34,6 +35,12 @@ typedef struct { /* Full path to file last selected with EXE */ char *selected_file; + /* "Save As" input field */ + jinput *saveas_input; + /* Whether the "Save As" option is shown */ + bool saveas; + /* Whether we are currently using the input field */ + bool input_mode; /* Current cursor position (0 .. folder_entries-1) */ int16_t cursor; @@ -62,6 +69,13 @@ extern uint16_t JFILESELECT_CANCELED; any events in this state; a path must first be set before use. */ jfileselect *jfileselect_create(void *parent); +/* jfileselect_set_saveas(): Select whether a "Save as" option is presented + + If true, the browser will show a "" entry in every + directory. The result of jfileselect_selected_file(), in this case, might be + a file that does not yet exist. */ +void jfileselect_set_saveas(jfileselect *fs, bool save_as); + /* jfileselect_browse(): Browse a folder This function loads the specified folder and allows the user to select a diff --git a/src/jfileselect.c b/src/jfileselect.c index 526cc17..58ccc72 100644 --- a/src/jfileselect.c +++ b/src/jfileselect.c @@ -28,7 +28,7 @@ struct fileinfo { char *name; /* File size in bytes if file, number of entries if folder */ int size; - /* Type from [struct dirent] */ + /* Type from [struct dirent], -1 for the "Save As" entry */ int type; }; @@ -41,11 +41,21 @@ jfileselect *jfileselect_create(void *parent) jwidget_init(&fs->widget, jfileselect_type_id, parent); + jinput *input = jinput_create("Filename: ", 32, fs); + if(!input) { + free(fs); + return NULL; + } + jwidget_set_floating(input, true); + jwidget_set_visible(input, false); + fs->path = NULL; fs->entries = NULL; fs->entry_count = 0; fs->selected_file = NULL; + fs->saveas_input = input; + fs->saveas = false; fs->cursor = -1; fs->scroll = 0; @@ -77,6 +87,30 @@ static void set_finfo(jfileselect *fs, struct fileinfo *finfo, int n) fs->entry_count = n; } +void jfileselect_set_saveas(jfileselect *fs, bool save_as) +{ + fs->saveas = save_as; +} + +//--- +// Input utilities +//--- + +static void start_input(jfileselect *fs) +{ + fs->input_mode = true; + jinput_clear(fs->saveas_input); + jwidget_event(fs->saveas_input, (jevent){ .type = JWIDGET_FOCUS_IN }); + fs->widget.update = true; +} + +static void stop_input(jfileselect *fs) +{ + fs->input_mode = false; + jwidget_event(fs->saveas_input, (jevent){ .type = JWIDGET_FOCUS_OUT }); + fs->widget.update = true; +} + //--- // Getters and setters //--- @@ -161,12 +195,19 @@ static int count_accepted_entries(DIR *dp) static int compare_entries(void const *i1_0, void const *i2_0) { struct fileinfo const *i1 = i1_0, *i2 = i2_0; - int d1 = (i1->type == DT_DIR); - int d2 = (i2->type == DT_DIR); /* Group directories first */ + int d1 = (i1->type == DT_DIR); + int d2 = (i2->type == DT_DIR); if(d1 != d2) return d2 - d1; + + /* Then the "Save As" entry */ + int sa1 = (i1->type == -1); + int sa2 = (i2->type == -1); + if(sa1 != sa2) + return sa2 - sa1; + /* Then group by name */ return strcmp(i1->name, i2->name); } @@ -181,7 +222,7 @@ static bool load_folder_switch(jfileselect *fs, char *path) return false; /* Count entries */ - int n = count_accepted_entries(dp); + int n = count_accepted_entries(dp) + fs->saveas; /* Allocate memory for the fileinfo structures */ struct fileinfo *finfo = malloc(n * sizeof *finfo); @@ -227,6 +268,13 @@ static bool load_folder_switch(jfileselect *fs, char *path) i++; } + /* Add the saveas entry */ + if(fs->saveas) { + finfo[n-1].name = strdup(""); + finfo[n-1].type = -1; + finfo[n-1].size = -1; + } + qsort(finfo, n, sizeof *finfo, compare_entries); closedir(dp); @@ -258,6 +306,7 @@ bool jfileselect_browse(jfileselect *fs, char const *path) fs->cursor = 0; fs->scroll = 0; + stop_input(fs); return true; } @@ -288,6 +337,7 @@ static void jfileselect_poly_layout(void *fs0) { jfileselect *fs = fs0; count_visible_lines(fs); + fs->saveas_input->widget.w = jwidget_content_width(fs) - 4; } static void jfileselect_poly_render(void *fs0, int x, int y) @@ -306,14 +356,22 @@ static void jfileselect_poly_render(void *fs0, int x, int y) struct fileinfo *info = &finfo[fs->scroll + i]; bool isfolder = (info->type == DT_DIR); - int line_y = y + line_height * i; - if(selected) - drect(x, line_y, x + cw - 1, line_y + line_height - 1, C_BLACK); - /* Round `line_spacing / 2` down so there is more spacing below */ + int line_y = y + line_height * i; int text_y = line_y + (fs->line_spacing + 0) / 2; int fg = selected ? C_WHITE : C_BLACK; + if(selected && fs->input_mode) { + /* Little bit of a hack */ + fs->saveas_input->widget.visible = true; + jwidget_render(fs->saveas_input, x+2, text_y); + fs->saveas_input->widget.visible = false; + continue; + } + + if(selected) + drect(x, line_y, x + cw - 1, line_y + line_height - 1, C_BLACK); + dprint(x+2, text_y, fg, "%s%s", info->name, isfolder ? "/" : ""); if(fs->show_file_size && info->size >= 0) { dprint_opt(x + cw - 3, text_y, fg, C_NONE, DTEXT_RIGHT, DTEXT_TOP, @@ -330,6 +388,30 @@ static bool jfileselect_poly_event(void *fs0, jevent e) if(!fs->path || !fs->entries) return false; + if(e.type == JINPUT_CANCELED && e.source == fs->saveas_input) { + stop_input(fs); + return true; + } + else if(e.type == JINPUT_VALIDATED && e.source == fs->saveas_input) { + stop_input(fs); + fs->selected_file = path_down(fs->path,jinput_value(fs->saveas_input)); + if(fs->selected_file) { + jwidget_emit(fs,(jevent){ .type = JFILESELECT_VALIDATED }); + return true; + } + else return false; + } + /* Send all events to the input when in input mode (without requiring + access to the jscene to actually move the focus */ + else if(fs->input_mode) { + bool b = jwidget_event(fs->saveas_input, e); + if(b) + fs->widget.update = true; + /* We do capture all key events if not used by the input, so F-keys are + disabled/etc */ + return b || e.type == JWIDGET_KEY; + } + if(e.type == JWIDGET_KEY) { key_event_t ev = e.key; if(ev.type != KEYEV_DOWN && ev.type != KEYEV_HOLD) @@ -376,6 +458,7 @@ static bool jfileselect_poly_event(void *fs0, jevent e) else if(key == KEY_EXE) { struct fileinfo *finfo = fs->entries; struct fileinfo *i = &finfo[fs->cursor]; + if(i->type == DT_DIR) { char *child = path_down(fs->path, i->name); if(child) { @@ -385,6 +468,10 @@ static bool jfileselect_poly_event(void *fs0, jevent e) return true; } } + else if(fs->saveas && i->type == -1) { + start_input(fs); + return true; + } else { fs->selected_file = path_down(fs->path, i->name); if(fs->selected_file) { diff --git a/src/jscene.c b/src/jscene.c index 8b5c1d2..4088e24 100644 --- a/src/jscene.c +++ b/src/jscene.c @@ -99,8 +99,7 @@ void jscene_queue_event(jscene *s, jevent e) { /* Prevent filling and overflowing the queue */ int next = (s->queue_next + 1) % JSCENE_QUEUE_SIZE; - if(next == s->queue_first) - { + if(next == s->queue_first) { s->lost_events++; return; } diff --git a/src/jwidget.c b/src/jwidget.c index 0f7772f..75d1115 100644 --- a/src/jwidget.c +++ b/src/jwidget.c @@ -656,8 +656,7 @@ bool jwidget_event(void *w0, jevent e) { J_CAST(w) jwidget_poly const *poly = widget_types[w->type]; - if(poly->event) return poly->event(w, e); - return false; + return poly->event && poly->event(w, e); } void jwidget_emit(void *w0, jevent e)