From f08dd2fd03db28324e391b30909a92f99d16abaa Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 14 May 2023 01:44:00 +0300 Subject: [PATCH] Text Input improvements, added cursor and ability to set minimal length thanks to WillyJL --- .../services/gui/modules/text_input.c | 160 +++++++++++++----- .../services/gui/modules/text_input.h | 2 + firmware/targets/f7/api_symbols.csv | 1 + 3 files changed, 124 insertions(+), 39 deletions(-) diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 5fd306aa6..1152f496c 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -18,7 +18,12 @@ typedef struct { const char* header; char* text_buffer; size_t text_buffer_size; + size_t minimum_length; bool clear_default_text; + FuriString* temp_str; + + bool cursor_select; + size_t cursor_pos; TextInputCallback callback; void* callback_context; @@ -146,9 +151,14 @@ static char char_to_uppercase(const char letter) { } static void text_input_backspace_cb(TextInputModel* model) { - uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer); - if(text_length > 0) { - model->text_buffer[text_length - 1] = 0; + if(model->clear_default_text) { + model->text_buffer[0] = 0; + model->cursor_pos = 0; + } else if(model->cursor_pos > 0) { + furi_string_set_str(model->temp_str, model->text_buffer); + furi_string_replace_at(model->temp_str, model->cursor_pos - 1, 1, ""); + model->cursor_pos--; + strcpy(model->text_buffer, furi_string_get_cstr(model->temp_str)); } } @@ -158,7 +168,7 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { uint8_t needed_string_width = canvas_width(canvas) - 8; uint8_t start_pos = 4; - const char* text = model->text_buffer; + model->cursor_pos = model->cursor_pos > text_length ? text_length : model->cursor_pos; canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); @@ -166,25 +176,43 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str(canvas, 2, 8, model->header); elements_slightly_rounded_frame(canvas, 1, 12, 126, 15); - if(canvas_string_width(canvas, text) > needed_string_width) { - canvas_draw_str(canvas, start_pos, 22, "..."); - start_pos += 6; - needed_string_width -= 8; - } - - while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) { - text++; + FuriString* str = model->temp_str; + if(model->text_buffer) { + furi_string_set_str(str, model->text_buffer); + } else { + furi_string_reset(str); } + const char* cstr = furi_string_get_cstr(str); if(model->clear_default_text) { elements_slightly_rounded_box( - canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10); + canvas, start_pos - 1, 14, canvas_string_width(canvas, cstr) + 2, 10); canvas_set_color(canvas, ColorWhite); } else { - canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 22, "|"); - canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 22, "|"); + furi_string_replace_at(str, model->cursor_pos, 0, "|"); } - canvas_draw_str(canvas, start_pos, 22, text); + + if(model->cursor_pos > 0 && canvas_string_width(canvas, cstr) > needed_string_width) { + canvas_draw_str(canvas, start_pos, 22, "..."); + start_pos += 6; + needed_string_width -= 8; + for(uint32_t off = 0; + !furi_string_empty(str) && canvas_string_width(canvas, cstr) > needed_string_width && + off < model->cursor_pos; + off++) { + furi_string_right(str, 1); + } + } + + if(canvas_string_width(canvas, cstr) > needed_string_width) { + needed_string_width -= 4; + while(!furi_string_empty(str) && canvas_string_width(canvas, cstr) > needed_string_width) { + furi_string_left(str, furi_string_size(str) - 1); + } + furi_string_cat_str(str, "..."); + } + + canvas_draw_str(canvas, start_pos, 22, cstr); canvas_set_font(canvas, FontKeyboard); @@ -193,9 +221,12 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { const TextInputKey* keys = get_row(row); for(size_t column = 0; column < column_count; column++) { + bool selected = !model->cursor_select && model->selected_row == row && + model->selected_column == column; + if(keys[column].text == ENTER_KEY) { canvas_set_color(canvas, ColorBlack); - if(model->selected_row == row && model->selected_column == column) { + if(selected) { canvas_draw_icon( canvas, keyboard_origin_x + keys[column].x, @@ -210,7 +241,7 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { } } else if(keys[column].text == BACKSPACE_KEY) { canvas_set_color(canvas, ColorBlack); - if(model->selected_row == row && model->selected_column == column) { + if(selected) { canvas_draw_icon( canvas, keyboard_origin_x + keys[column].x, @@ -224,7 +255,7 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { &I_KeyBackspace_16x9); } } else { - if(model->selected_row == row && model->selected_column == column) { + if(selected) { canvas_set_color(canvas, ColorBlack); canvas_draw_box( canvas, @@ -273,12 +304,17 @@ static void text_input_handle_up(TextInput* text_input, TextInputModel* model) { if(model->selected_column > get_row_size(model->selected_row) - 6) { model->selected_column = model->selected_column + 1; } + } else { + model->cursor_select = true; + model->clear_default_text = false; } } static void text_input_handle_down(TextInput* text_input, TextInputModel* model) { UNUSED(text_input); - if(model->selected_row < keyboard_row_count - 1) { + if(model->cursor_select) { + model->cursor_select = false; + } else if(model->selected_row < keyboard_row_count - 1) { model->selected_row++; if(model->selected_column > get_row_size(model->selected_row) - 4) { model->selected_column = model->selected_column - 1; @@ -288,7 +324,11 @@ static void text_input_handle_down(TextInput* text_input, TextInputModel* model) static void text_input_handle_left(TextInput* text_input, TextInputModel* model) { UNUSED(text_input); - if(model->selected_column > 0) { + if(model->cursor_select) { + if(model->cursor_pos > 0) { + model->cursor_pos = CLAMP(model->cursor_pos - 1, strlen(model->text_buffer), 0u); + } + } else if(model->selected_column > 0) { model->selected_column--; } else { model->selected_column = get_row_size(model->selected_row) - 1; @@ -297,14 +337,19 @@ static void text_input_handle_left(TextInput* text_input, TextInputModel* model) static void text_input_handle_right(TextInput* text_input, TextInputModel* model) { UNUSED(text_input); - if(model->selected_column < get_row_size(model->selected_row) - 1) { + if(model->cursor_select) { + model->cursor_pos = CLAMP(model->cursor_pos + 1, strlen(model->text_buffer), 0u); + } else if(model->selected_column < get_row_size(model->selected_row) - 1) { model->selected_column++; } else { model->selected_column = 0; } } -static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, bool shift) { +static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, InputType type) { + if(model->cursor_select) return; + bool shift = type == InputTypeLong; + bool repeat = type == InputTypeRepeat; char selected = get_selected_char(model); size_t text_length = strlen(model->text_buffer); @@ -314,24 +359,34 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b model->text_buffer, model->validator_text, model->validator_callback_context))) { model->validator_message_visible = true; furi_timer_start(text_input->timer, furi_kernel_get_tick_frequency() * 4); - } else if(model->callback != 0 && text_length > 0) { + } else if(model->callback != 0 && text_length >= model->minimum_length) { model->callback(model->callback_context); } - } else if(selected == BACKSPACE_KEY) { - text_input_backspace_cb(model); } else { - if(model->clear_default_text) { - text_length = 0; - } - if(text_length < (model->text_buffer_size - 1)) { - if(shift != (text_length == 0)) { - selected = char_to_uppercase(selected); + if(selected == BACKSPACE_KEY) { + text_input_backspace_cb(model); + } else if(!repeat) { + if(model->clear_default_text) { + text_length = 0; + } + if(text_length < (model->text_buffer_size - 1)) { + if(shift != (text_length == 0)) { + selected = char_to_uppercase(selected); + } + const char replace[2] = {selected, 0}; + if(model->clear_default_text) { + furi_string_set_str(model->temp_str, replace); + model->cursor_pos = 1; + } else { + furi_string_set_str(model->temp_str, model->text_buffer); + furi_string_replace_at(model->temp_str, model->cursor_pos, 0, replace); + model->cursor_pos++; + } + strcpy(model->text_buffer, furi_string_get_cstr(model->temp_str)); } - model->text_buffer[text_length] = selected; - model->text_buffer[text_length + 1] = 0; } + model->clear_default_text = false; } - model->clear_default_text = false; } static bool text_input_view_input_callback(InputEvent* event, void* context) { @@ -363,7 +418,7 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { text_input_handle_right(text_input, model); break; case InputKeyOk: - text_input_handle_ok(text_input, model, false); + text_input_handle_ok(text_input, model, event->type); break; default: consumed = false; @@ -385,7 +440,7 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { text_input_handle_right(text_input, model); break; case InputKeyOk: - text_input_handle_ok(text_input, model, true); + text_input_handle_ok(text_input, model, event->type); break; case InputKeyBack: text_input_backspace_cb(model); @@ -409,6 +464,9 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { case InputKeyRight: text_input_handle_right(text_input, model); break; + case InputKeyOk: + text_input_handle_ok(text_input, model, event->type); + break; case InputKeyBack: text_input_backspace_cb(model); break; @@ -448,7 +506,13 @@ TextInput* text_input_alloc() { with_view_model( text_input->view, TextInputModel * model, - { model->validator_text = furi_string_alloc(); }, + { + model->validator_text = furi_string_alloc(); + model->temp_str = furi_string_alloc(); + model->minimum_length = 1; + model->cursor_pos = 0; + model->cursor_select = false; + }, false); text_input_reset(text_input); @@ -461,7 +525,10 @@ void text_input_free(TextInput* text_input) { with_view_model( text_input->view, TextInputModel * model, - { furi_string_free(model->validator_text); }, + { + furi_string_free(model->validator_text); + furi_string_free(model->temp_str); + }, false); // Send stop command @@ -483,7 +550,10 @@ void text_input_reset(TextInput* text_input) { model->header = ""; model->selected_row = 0; model->selected_column = 0; + model->minimum_length = 1; model->clear_default_text = false; + model->cursor_pos = 0; + model->cursor_select = false; model->text_buffer = NULL; model->text_buffer_size = 0; model->callback = NULL; @@ -517,15 +587,27 @@ void text_input_set_result_callback( model->text_buffer = text_buffer; model->text_buffer_size = text_buffer_size; model->clear_default_text = clear_default_text; + model->cursor_select = false; if(text_buffer && text_buffer[0] != '\0') { + model->cursor_pos = strlen(text_buffer); // Set focus on Save model->selected_row = 2; model->selected_column = 8; + } else { + model->cursor_pos = 0; } }, true); } +void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length) { + with_view_model( + text_input->view, + TextInputModel * model, + { model->minimum_length = minimum_length; }, + true); +} + void text_input_set_validator( TextInput* text_input, TextInputValidatorCallback callback, diff --git a/applications/services/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h index 218df3141..65207532e 100644 --- a/applications/services/gui/modules/text_input.h +++ b/applications/services/gui/modules/text_input.h @@ -70,6 +70,8 @@ void text_input_set_validator( TextInputValidatorCallback callback, void* callback_context); +void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length); + TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input); void* text_input_get_validator_callback_context(TextInput* text_input); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 7eac9ab00..b79c8271b 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -3399,6 +3399,7 @@ Function,+,text_input_get_validator_callback_context,void*,TextInput* Function,+,text_input_get_view,View*,TextInput* Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" Function,-,tga_save,void,const char*