Skip to content

Commit b41e000

Browse files
committed
rg_gui: Improved the shadow options system so that it can now work with very large list of constant items, instead of blowing the stack
The file picker dialog has always been inadequate with its limited capacity, this will make it easier to fix it. Which we need for the updater.
1 parent 7ecd674 commit b41e000

File tree

1 file changed

+71
-64
lines changed

1 file changed

+71
-64
lines changed

components/retro-go/rg_gui.c

Lines changed: 71 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -730,26 +730,23 @@ void rg_gui_draw_dialog(const char *title, const rg_gui_option_t *options, size_
730730
y += font_height + 6;
731731
}
732732

733-
int top_i = 0;
734-
int end_i = 0;
733+
int list_top_i = 0;
734+
int list_end_i = 0;
735735

736736
// Find top of page that contains selection
737-
if (sel >= 0 && sel < options_count)
737+
for (int yy = y, i = 0; i <= sel && i < options_count; i++)
738738
{
739-
for (int yy = y, i = 0; i <= sel; i++)
739+
yy += row_height[i];
740+
if (yy >= box_y + box_height)
740741
{
741-
yy += row_height[i];
742-
if (yy >= box_y + box_height)
743-
{
744-
if (sel < i)
745-
break;
746-
yy = y;
747-
top_i = i;
748-
}
742+
if (sel < i)
743+
break;
744+
yy = y + row_height[i];
745+
list_top_i = i;
749746
}
750747
}
751748

752-
for (int i = top_i; i < options_count; i++)
749+
for (int i = list_top_i; i < options_count; i++)
753750
{
754751
uint16_t color, fg, bg;
755752
int xx = x + row_padding_x;
@@ -770,7 +767,7 @@ void rg_gui_draw_dialog(const char *title, const rg_gui_option_t *options, size_
770767
if (y + row_height[i] >= box_y + box_height)
771768
break;
772769

773-
end_i = i;
770+
list_end_i = i;
774771

775772
if (options[i].flags == RG_DIALOG_FLAG_HIDDEN)
776773
continue;
@@ -809,7 +806,7 @@ void rg_gui_draw_dialog(const char *title, const rg_gui_option_t *options, size_
809806
rg_gui_draw_rect(box_x - 1, box_y - 1, box_width + 2, box_height + 2, 1, gui.style.box_border, C_NONE);
810807

811808
// Basic scroll indicators are overlayed at the end...
812-
if (top_i > 0)
809+
if (list_top_i > 0)
813810
{
814811
int x = box_x + box_width - 10;
815812
int y = box_y + box_padding + 2;
@@ -818,7 +815,7 @@ void rg_gui_draw_dialog(const char *title, const rg_gui_option_t *options, size_
818815
rg_gui_draw_rect(x + 2, y - 4, 2, 2, 0, 0, gui.style.scrollbar);
819816
}
820817

821-
if (end_i + 1 < options_count)
818+
if (list_end_i + 1 < options_count)
822819
{
823820
int x = box_x + box_width - 10;
824821
int y = box_y + box_height - 6;
@@ -847,45 +844,55 @@ void rg_gui_draw_message(const char *format, ...)
847844

848845
intptr_t rg_gui_dialog(const char *title, const rg_gui_option_t *options_const, int selected_index)
849846
{
847+
rg_gui_option_t *options = (rg_gui_option_t *)options_const;
850848
size_t options_count = get_dialog_items_count(options_const);
851-
int sel = selected_index < 0 ? (options_count + selected_index) : selected_index;
852-
int sel_old = -1;
853-
bool redraw = false;
854-
855-
// Constrain initial cursor and skip FLAG_SKIP items
856-
sel = RG_MIN(RG_MAX(0, sel), options_count - 1);
857-
858-
// We create a copy of options because the callbacks might modify it (ie option->value)
859-
rg_gui_option_t options[options_count + 1];
860-
// The text_buffer is used for mutable option->values. Because values tend to be small, we can make
861-
// some assumptions. This is a terrible system that is prone to corruption. But we're stuck with it.
862-
size_t text_buffer_size = RG_MAX(options_count * 32, 1024);
863-
char *text_buffer = malloc(text_buffer_size);
864-
char *text_buffer_ptr = text_buffer;
865-
866-
memcpy(options, options_const, sizeof(options));
867849

850+
// In many cases we must create a copy of the options array because it can be mutated by the callbacks
851+
// (typically option->value and option->flags). No callback in the array = no way of it being mutable.
852+
// The entire text_buffer system is very brittle and prone to corruption. We get away with it for now
853+
// because most values are less than our assumed 32 bytes...
854+
size_t shadow_options_count = 0;
855+
size_t shadow_text_buffer_size = 0;
868856
for (size_t i = 0; i < options_count; i++)
869857
{
870-
rg_gui_option_t *option = &options[i];
871-
if (!option->label)
872-
option->label = "";
873-
if (option->value && text_buffer_ptr)
858+
if (options_const[i].update_cb)
859+
shadow_options_count = options_count;
860+
// if (options_const[i].value)
861+
// shadow_text_buffer_size += strlen(options_const[i].value) + 1;
862+
}
863+
rg_gui_option_t shadow_options[shadow_options_count + 1];
864+
char *shadow_text_buffer = NULL;
865+
if (shadow_options_count > 0)
866+
{
867+
options = memcpy(shadow_options, options_const, sizeof(shadow_options));
868+
shadow_text_buffer_size = RG_MAX(options_count * 32, 1024);
869+
shadow_text_buffer = malloc(shadow_text_buffer_size);
870+
char *text_buffer_ptr = shadow_text_buffer;
871+
for (size_t i = 0; i < shadow_options_count; i++)
872+
{
873+
rg_gui_option_t *option = &shadow_options[i];
874+
if (!text_buffer_ptr || !option->value || !option->update_cb)
875+
continue;
874876
option->value = strcpy(text_buffer_ptr, option->value);
875-
if (option->update_cb)
876877
option->update_cb(option, RG_DIALOG_INIT);
877-
if (option->value && text_buffer_ptr)
878-
text_buffer_ptr += RG_MAX(strlen(option->value), 31) + 1;
878+
text_buffer_ptr += RG_MAX(strlen(text_buffer_ptr), 31) + 1;
879+
}
879880
}
880881

881-
rg_gui_draw_status_bars();
882-
rg_gui_draw_dialog(title, options, options_count, sel);
883-
rg_input_wait_for_key(RG_KEY_ALL, false, 1000);
884-
rg_task_delay(80);
882+
if (selected_index < 0)
883+
selected_index += options_count;
885884

886885
rg_gui_event_t event = RG_DIALOG_VOID;
887886
uint32_t joystick = 0, joystick_old;
888887
uint64_t joystick_last = 0;
888+
bool redraw = false;
889+
int sel = RG_MIN(RG_MAX(0, selected_index), options_count - 1);
890+
int sel_old = -1;
891+
892+
rg_gui_draw_status_bars();
893+
rg_gui_draw_dialog(title, options, options_count, sel);
894+
rg_input_wait_for_key(RG_KEY_ALL, false, 1000);
895+
rg_task_delay(80);
889896

890897
while (event != RG_DIALOG_SELECT && event != RG_DIALOG_CANCEL)
891898
{
@@ -981,7 +988,8 @@ intptr_t rg_gui_dialog(const char *title, const rg_gui_option_t *options_const,
981988

982989
rg_input_wait_for_key(joystick, false, 1000);
983990
rg_display_force_redraw();
984-
free(text_buffer);
991+
// free(shadow_options);
992+
free(shadow_text_buffer);
985993

986994
if (event == RG_DIALOG_CANCEL || sel < 0)
987995
return RG_DIALOG_CANCELLED;
@@ -1014,7 +1022,7 @@ void rg_gui_alert(const char *title, const char *message)
10141022

10151023
typedef struct
10161024
{
1017-
rg_gui_option_t options[22];
1025+
rg_gui_option_t *options;
10181026
size_t count;
10191027
bool (*validator)(const char *path);
10201028
} file_picker_opts_t;
@@ -1024,51 +1032,50 @@ static int file_picker_cb(const rg_scandir_t *entry, void *arg)
10241032
file_picker_opts_t *f = arg;
10251033
if (f->validator && !(f->validator)(entry->path))
10261034
return RG_SCANDIR_SKIP;
1027-
char *path = strdup(entry->path);
1028-
f->options[f->count].arg = (intptr_t)path;
1029-
f->options[f->count].flags = RG_DIALOG_FLAG_NORMAL;
1030-
f->options[f->count].label = rg_basename(path);
1031-
f->count++;
1032-
if (f->count > 18)
1035+
rg_gui_option_t *options = realloc(f->options, (f->count + 2) * sizeof(rg_gui_option_t));
1036+
if (!options)
10331037
return RG_SCANDIR_STOP;
1038+
char *name = strdup(entry->basename);
1039+
f->options = options;
1040+
f->options[f->count++] = (rg_gui_option_t){(intptr_t)name, name, NULL, RG_DIALOG_FLAG_NORMAL, NULL};
10341041
return RG_SCANDIR_CONTINUE;
10351042
}
10361043

10371044
char *rg_gui_file_picker(const char *title, const char *path, bool (*validator)(const char *path), bool none_option)
10381045
{
10391046
file_picker_opts_t options = {
1040-
.options = {},
1047+
.options = calloc(8, sizeof(rg_gui_option_t)),
10411048
.count = 0,
10421049
.validator = validator,
10431050
};
1051+
char *filepath = NULL;
10441052

10451053
if (!title)
10461054
title = _("Select file");
10471055

10481056
if (none_option)
1049-
{
10501057
options.options[options.count++] = (rg_gui_option_t){0, _("<None>"), NULL, RG_DIALOG_FLAG_NORMAL, NULL};
1051-
// options.options[options.count++] = (rg_gui_option_t)RG_DIALOG_SEPARATOR;
1052-
}
10531058

10541059
if (!rg_storage_scandir(path, file_picker_cb, &options, 0) || options.count < 1)
10551060
{
10561061
rg_gui_alert(title, _("Folder is empty."));
1057-
return NULL;
1062+
goto cleanup;
10581063
}
1059-
10601064
options.options[options.count] = (rg_gui_option_t)RG_DIALOG_END;
10611065

1062-
char *filepath = (char *)rg_gui_dialog(title, options.options, 0);
1063-
1064-
if (filepath != (void *)RG_DIALOG_CANCELLED)
1065-
filepath = strdup(filepath ? filepath : "");
1066-
else
1067-
filepath = NULL;
1066+
char *filename = (char *)rg_gui_dialog(title, options.options, 0);
1067+
if (filename != (void *)RG_DIALOG_CANCELLED)
1068+
{
1069+
char buffer[RG_PATH_MAX] = "";
1070+
if (filename)
1071+
snprintf(buffer, RG_PATH_MAX, "%s/%s", path, filename);
1072+
filepath = strdup(buffer);
1073+
}
10681074

1075+
cleanup:
10691076
for (size_t i = 0; i < options.count; ++i)
10701077
free((void *)(options.options[i].arg));
1071-
1078+
free(options.options);
10721079
return filepath;
10731080
}
10741081

0 commit comments

Comments
 (0)