Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 142 additions & 3 deletions src/ui/components/trinary_input_char.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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.
*
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/ui/components/trinary_input_char.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
6 changes: 5 additions & 1 deletion src/ui/components/trinary_input_string.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down