From 7b32709887034b88654af5e9afa423b1068e5024 Mon Sep 17 00:00:00 2001 From: alessandro-opensource Date: Sun, 16 Nov 2025 23:00:47 +0000 Subject: [PATCH] src/ui/components: Added -Feature: Hitting back during "keypad" input goes back to previous group of characters #862- --- src/ui/components/trinary_input_char.c | 145 ++++++++++++++++++++++- src/ui/components/trinary_input_char.h | 6 + src/ui/components/trinary_input_string.c | 6 +- 3 files changed, 153 insertions(+), 4 deletions(-) diff --git a/src/ui/components/trinary_input_char.c b/src/ui/components/trinary_input_char.c index e20dd64e35..bae5010bae 100644 --- a/src/ui/components/trinary_input_char.c +++ b/src/ui/components/trinary_input_char.c @@ -40,9 +40,18 @@ typedef struct { // excluding null terminator #define MAX_CHARS 33 +// Maximum depth for navigation stack +#define MAX_NAVIGATION_DEPTH 4 + // Each of the three groups can occupy roughly a third of the width. static const UG_S16 _group_width = SCREEN_WIDTH / 3; +// Stack entry for navigation history +typedef struct { + char alphabet[MAX_CHARS + 1]; + UG_S16 horiz_space; +} navigation_stack_entry_t; + typedef struct { void (*character_chosen_cb)(component_t*, char); @@ -60,9 +69,98 @@ typedef struct { // Horizontal space between characters in a group. UG_S16 horiz_space; + // Navigation stack to support going back to previous alphabet + navigation_stack_entry_t navigation_stack[MAX_NAVIGATION_DEPTH]; + size_t navigation_stack_size; + const UG_FONT* font; } data_t; +/** + * Push current alphabet onto navigation stack before navigating to a sub-alphabet. + */ +static void _navigation_stack_push(data_t* data, const char* current_alphabet) +{ + if (data->navigation_stack_size >= MAX_NAVIGATION_DEPTH) { + // Stack is full, shift all entries down and add new one at the top + for (size_t i = 0; i < MAX_NAVIGATION_DEPTH - 1; i++) { + data->navigation_stack[i] = data->navigation_stack[i + 1]; + } + data->navigation_stack_size = MAX_NAVIGATION_DEPTH - 1; + } + + // Add current alphabet to stack + navigation_stack_entry_t* entry = &data->navigation_stack[data->navigation_stack_size]; + snprintf(entry->alphabet, sizeof(entry->alphabet), "%s", current_alphabet); + entry->horiz_space = data->horiz_space; + data->navigation_stack_size++; +} + +/** + * Pop previous alphabet from navigation stack and return it. + * Returns false if stack is empty. + */ +static bool _navigation_stack_pop(data_t* data, char* out_alphabet, UG_S16* out_horiz_space) +{ + if (data->navigation_stack_size == 0) { + return false; + } + + data->navigation_stack_size--; + navigation_stack_entry_t* entry = &data->navigation_stack[data->navigation_stack_size]; + snprintf(out_alphabet, MAX_CHARS + 1, "%s", entry->alphabet); + *out_horiz_space = entry->horiz_space; + return true; +} + +/** + * Clear the navigation stack. + */ +static void _navigation_stack_clear(data_t* data) +{ + data->navigation_stack_size = 0; + memset(data->navigation_stack, 0, sizeof(data->navigation_stack)); +} + +/** + * Rebuild the full alphabet from left, middle, and right parts. + */ +static void _rebuild_full_alphabet(const data_t* data, char* out_buffer, size_t buffer_size) +{ + const size_t l = strlens(data->left_alphabet); + const size_t m = strlens(data->middle_alphabet); + const size_t r = strlens(data->right_alphabet); + size_t total = l + m + r; + + if (total > buffer_size - 1) { + total = buffer_size - 1; + } + + size_t off = 0; + size_t to_copy = (l > total - off) ? (total - off) : l; + memcpy(out_buffer + off, data->left_alphabet, to_copy); + off += to_copy; + + to_copy = (m > total - off) ? (total - off) : m; + memcpy(out_buffer + off, data->middle_alphabet, to_copy); + off += to_copy; + + to_copy = (r > total - off) ? (total - off) : r; + memcpy(out_buffer + off, data->right_alphabet, to_copy); + off += to_copy; + + out_buffer[off] = '\0'; +} + +/** + * Internal helper to set the alphabet without clearing the navigation stack. + * Used during navigation to preserve the stack. + */ +static void _set_alphabet_internal( + component_t* component, + const char* alphabet_input, + UG_S16 horiz_space); + /** * Called when a selection on one of the alphabet options has been made. * @@ -86,9 +184,18 @@ static void _alphabet_selected(component_t* component, const char* alphabet) memset(data->elements, 0, sizeof(data->elements)); data->character_chosen_cb(component, alphabet[0]); data->in_progress = false; + /* Clear navigation stack when a character is chosen */ + _navigation_stack_clear(data); } else { /* Select a sub-alphabet. */ - trinary_input_char_set_alphabet(component, alphabet, data->horiz_space); + + /* Rebuild current full alphabet and push it before going deeper */ + char prev_alphabet[MAX_CHARS + 1]; + _rebuild_full_alphabet(data, prev_alphabet, sizeof(prev_alphabet)); + _navigation_stack_push(data, prev_alphabet); + + /* Use internal function to preserve the navigation stack */ + _set_alphabet_internal(component, alphabet, data->horiz_space); data->in_progress = true; } } @@ -209,7 +316,7 @@ static void _put_string( } } -void trinary_input_char_set_alphabet( +static void _set_alphabet_internal( component_t* component, const char* alphabet_input, UG_S16 horiz_space) @@ -314,10 +421,22 @@ void trinary_input_char_set_alphabet( (int)right_size, alphabet + left_size + middle_size); - data->in_progress = false; data->alphabet_is_empty = len == 0; } +void trinary_input_char_set_alphabet( + component_t* component, + const char* alphabet_input, + UG_S16 horiz_space) +{ + data_t* data = (data_t*)component->data; + /* Clear navigation stack when alphabet is set externally */ + _navigation_stack_clear(data); + data->in_progress = false; + + _set_alphabet_internal(component, alphabet_input, horiz_space); +} + /********************************** Create Instance **********************************/ component_t* trinary_input_char_create( @@ -348,6 +467,26 @@ component_t* trinary_input_char_create( return component; } +bool trinary_input_char_go_back(component_t* component) +{ + data_t* data = (data_t*)component->data; + + char previous_alphabet[MAX_CHARS + 1]; + UG_S16 previous_horiz_space; + + if (_navigation_stack_pop(data, previous_alphabet, &previous_horiz_space)) { + // Successfully popped from stack, restore previous alphabet + // Use internal function to preserve the remaining stack + _set_alphabet_internal(component, previous_alphabet, previous_horiz_space); + data->in_progress = data->navigation_stack_size > 0; // Still in progress if stack not empty + return true; + } + + // Stack was empty, can't go back further + data->in_progress = false; + return false; +} + bool trinary_input_char_in_progress(component_t* component) { const data_t* data = (data_t*)component->data; diff --git a/src/ui/components/trinary_input_char.h b/src/ui/components/trinary_input_char.h index ac82eeb10d..5851d4a9cf 100644 --- a/src/ui/components/trinary_input_char.h +++ b/src/ui/components/trinary_input_char.h @@ -48,6 +48,12 @@ void trinary_input_char_set_alphabet( */ bool trinary_input_char_in_progress(component_t* component); +/** + * Go back to the previous alphabet in the navigation stack. + * @return true if successfully went back, false if navigation stack was empty. + */ +bool trinary_input_char_go_back(component_t* component); + /** * @return whether the alphabet provided is the empty set. */ diff --git a/src/ui/components/trinary_input_string.c b/src/ui/components/trinary_input_string.c index 43de3fd268..fe0275f9d0 100644 --- a/src/ui/components/trinary_input_string.c +++ b/src/ui/components/trinary_input_string.c @@ -366,7 +366,11 @@ static void _back(void* user_data) component_t* self = (component_t*)user_data; data_t* data = (data_t*)self->data; if (trinary_input_char_in_progress(data->trinary_char_component)) { - _set_alphabet(self, false); + // Try to go back in navigation stack first + if (!trinary_input_char_go_back(data->trinary_char_component)) { + // If stack was empty, reset to full alphabet + _set_alphabet(self, false); + } return; } if (data->string_index == 0) {