Added a text input that only accepts full numbers (int) (#3350)
* Added a text input that only accepts full numbers (int) * Added to Gui sdk_headers and api_symbols in f7 and f18 * Fixed _Bool declarations in symbols csv * renamed int_input to number_input * Changed name & added example fap * Added a text input that only accepts full numbers (int) * Added to Gui sdk_headers and api_symbols in f7 and f18 * Changed name & added example fap * update for clearing views * GUI: Fix array out of bounds in menu exit (#3604) * GUI: Fix array out of bounds in menu exit * Gui: fix incorrect empty menu handling * Gui: add missing item check in menu ok handling * Gui: remove dead code from menu module * nfc app: add legacy keys for plantain cards (#3602) * refactoring test app, part 1 * Refactor test app, part 2 * Minor updates while travelling * Switched from const char to FuriString. Using Temp module copy for development to spare compile time * Option to limit number output with min and max values * Preparations for option to change number sign from + to - * Preparations for option to change number sign from + to - * Preparing for testing * counter automatic API version change * added trailing comma in application.fam ... because the lint check wants it¿ * removed unused callback NumberChangedCallback * change uint8_t to size_t in number_input_backspace_cb * Removal of unused view_stack in demo app * copied module to app folder for faster development (remove later) * Replaced all uint8_t with size_t... removed unused logic for selected_row < 0 * Optimize use of canvas_set_color * Remove alloc/free of furistring that actually is a pointer * Dynamic Header text with min/max in Example * Removed the need of useSign in Model * Number_input Removed sign from model, started transfer from text to int32_t * number_input FuriString in input_show_number * number_input FuriString in input_show_number * limiting inputs for min/max values * limiting inputs for min/max values * number_input change save button on invalid numbers * input_number update demo app to allow change of min/max * number input fine tuning * number_input, Remove temp development folder * number_input, fbt format * Bump CSV Files * Clear input if value is zero * number_input: handle null on header text * number_input: change keyboard values to char * number input: Remove static on char for header text, change numbers to INT32_MIN/INT32_MAX * number_input: removal of dead code * number_input: fix for crash if number_input not opened before free * number_input: added icon for example app * number_input: Replaced view for show_number with DialogEx * Number_input: FBT Format * number_input: bump csv versions * number_input: allow negative input if max_value is 0 * Number_input: linting / format * Removed dead code, fbt format * Examples: cleanup number input code * Examples: moar code cleanup in number input, simplify as much as possible, highlight incorrect input handling * Gui: correctly handle INT_MAX and INT_MIN * Gui: fix memory leak in number input module Co-authored-by: David Lee <david.lee@arcmedia.ch> Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com> Co-authored-by: WillyJL <49810075+Willy-JL@users.noreply.github.com> Co-authored-by: gornekich <n.gorbadey@gmail.com>
7
applications/examples/example_number_input/ReadMe.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Number Input
|
||||
|
||||
Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input.
|
||||
|
||||
Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -.
|
||||
|
||||
It is also possible to define a header text, shown in this example app with the 3 different input options.
|
10
applications/examples/example_number_input/application.fam
Normal file
@ -0,0 +1,10 @@
|
||||
App(
|
||||
appid="example_number_input",
|
||||
name="Example: Number Input",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_number_input",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="example_number_input_10px.png",
|
||||
fap_category="Examples",
|
||||
)
|
@ -0,0 +1,79 @@
|
||||
#include "example_number_input.h"
|
||||
|
||||
bool example_number_input_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool example_number_input_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static ExampleNumberInput* example_number_input_alloc() {
|
||||
ExampleNumberInput* app = malloc(sizeof(ExampleNumberInput));
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&example_number_input_scene_handlers, app);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, example_number_input_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, example_number_input_back_event_callback);
|
||||
|
||||
app->number_input = number_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
ExampleNumberInputViewIdNumberInput,
|
||||
number_input_get_view(app->number_input));
|
||||
|
||||
app->dialog_ex = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
ExampleNumberInputViewIdShowNumber,
|
||||
dialog_ex_get_view(app->dialog_ex));
|
||||
|
||||
app->current_number = 5;
|
||||
app->min_value = INT32_MIN;
|
||||
app->max_value = INT32_MAX;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void example_number_input_free(ExampleNumberInput* app) {
|
||||
furi_assert(app);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
|
||||
dialog_ex_free(app->dialog_ex);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
number_input_free(app->number_input);
|
||||
|
||||
scene_manager_free(app->scene_manager);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
|
||||
//Remove whatever is left
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t example_number_input(void* p) {
|
||||
UNUSED(p);
|
||||
ExampleNumberInput* app = example_number_input_alloc();
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneShowNumber);
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
example_number_input_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/number_input.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#include "scenes/example_number_input_scene.h"
|
||||
|
||||
typedef struct ExampleNumberInputShowNumber ExampleNumberInputShowNumber;
|
||||
|
||||
typedef enum {
|
||||
ExampleNumberInputViewIdShowNumber,
|
||||
ExampleNumberInputViewIdNumberInput,
|
||||
} ExampleNumberInputViewId;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
SceneManager* scene_manager;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
|
||||
NumberInput* number_input;
|
||||
DialogEx* dialog_ex;
|
||||
|
||||
int32_t current_number;
|
||||
int32_t min_value;
|
||||
int32_t max_value;
|
||||
} ExampleNumberInput;
|
After Width: | Height: | Size: 87 B |
@ -0,0 +1,30 @@
|
||||
#include "example_number_input_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const example_number_input_on_enter_handlers[])(void*) = {
|
||||
#include "example_number_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const example_number_input_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "example_number_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const example_number_input_on_exit_handlers[])(void* context) = {
|
||||
#include "example_number_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers example_number_input_scene_handlers = {
|
||||
.on_enter_handlers = example_number_input_on_enter_handlers,
|
||||
.on_event_handlers = example_number_input_on_event_handlers,
|
||||
.on_exit_handlers = example_number_input_on_exit_handlers,
|
||||
.scene_num = ExampleNumberInputSceneNum,
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) ExampleNumberInputScene##id,
|
||||
typedef enum {
|
||||
#include "example_number_input_scene_config.h"
|
||||
ExampleNumberInputSceneNum,
|
||||
} ExampleNumberInputScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers example_number_input_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "example_number_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "example_number_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "example_number_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
@ -0,0 +1,4 @@
|
||||
ADD_SCENE(example_number_input, input_number, InputNumber)
|
||||
ADD_SCENE(example_number_input, show_number, ShowNumber)
|
||||
ADD_SCENE(example_number_input, input_max, InputMax)
|
||||
ADD_SCENE(example_number_input, input_min, InputMin)
|
@ -0,0 +1,39 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
void example_number_input_scene_input_max_callback(void* context, int32_t number) {
|
||||
ExampleNumberInput* app = context;
|
||||
app->max_value = number;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_max_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
number_input_set_header_text(number_input, "Enter the maximum value");
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
example_number_input_scene_input_max_callback,
|
||||
context,
|
||||
app->max_value,
|
||||
app->min_value,
|
||||
INT32_MAX);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_input_max_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_max_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
void example_number_input_scene_input_min_callback(void* context, int32_t number) {
|
||||
ExampleNumberInput* app = context;
|
||||
app->min_value = number;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_min_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
number_input_set_header_text(number_input, "Enter the minimum value");
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
example_number_input_scene_input_min_callback,
|
||||
context,
|
||||
app->min_value,
|
||||
INT32_MIN,
|
||||
app->max_value);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_input_min_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_min_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
void example_number_input_scene_input_number_callback(void* context, int32_t number) {
|
||||
ExampleNumberInput* app = context;
|
||||
app->current_number = number;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_number_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
char str[50];
|
||||
snprintf(str, sizeof(str), "Set Number (%ld - %ld)", app->min_value, app->max_value);
|
||||
|
||||
number_input_set_header_text(number_input, str);
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
example_number_input_scene_input_number_callback,
|
||||
context,
|
||||
app->current_number,
|
||||
app->min_value,
|
||||
app->max_value);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_input_number_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) { //Back button pressed
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_number_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
static void
|
||||
example_number_input_scene_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
ExampleNumberInput* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
static void example_number_input_scene_update_view(void* context) {
|
||||
ExampleNumberInput* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "The number is", 64, 0, AlignCenter, AlignTop);
|
||||
|
||||
static char buffer[12]; //needs static for extended lifetime
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%ld", app->current_number);
|
||||
dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter);
|
||||
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Min");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Max");
|
||||
dialog_ex_set_center_button_text(dialog_ex, "Change");
|
||||
|
||||
dialog_ex_set_result_callback(dialog_ex, example_number_input_scene_confirm_dialog_callback);
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
}
|
||||
|
||||
void example_number_input_scene_show_number_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
|
||||
example_number_input_scene_update_view(app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_show_number_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultCenter:
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputNumber);
|
||||
consumed = true;
|
||||
break;
|
||||
case DialogExResultLeft:
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMin);
|
||||
consumed = true;
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMax);
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_show_number_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
@ -19,6 +19,7 @@ App(
|
||||
"view_holder.h",
|
||||
"modules/button_menu.h",
|
||||
"modules/byte_input.h",
|
||||
"modules/number_input.h",
|
||||
"modules/popup.h",
|
||||
"modules/text_input.h",
|
||||
"modules/widget.h",
|
||||
|
443
applications/services/gui/modules/number_input.c
Normal file
@ -0,0 +1,443 @@
|
||||
#include "number_input.h"
|
||||
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
struct NumberInput {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char text;
|
||||
const size_t x;
|
||||
const size_t y;
|
||||
} NumberInputKey;
|
||||
|
||||
typedef struct {
|
||||
FuriString* header;
|
||||
FuriString* text_buffer;
|
||||
|
||||
int32_t current_number;
|
||||
int32_t max_value;
|
||||
int32_t min_value;
|
||||
|
||||
NumberInputCallback callback;
|
||||
void* callback_context;
|
||||
|
||||
size_t selected_row;
|
||||
size_t selected_column;
|
||||
} NumberInputModel;
|
||||
|
||||
static const size_t keyboard_origin_x = 7;
|
||||
static const size_t keyboard_origin_y = 31;
|
||||
static const size_t keyboard_row_count = 2;
|
||||
static const char enter_symbol = '\r';
|
||||
static const char backspace_symbol = '\b';
|
||||
static const char sign_symbol = '-';
|
||||
|
||||
static const NumberInputKey keyboard_keys_row_1[] = {
|
||||
{'0', 0, 12},
|
||||
{'1', 11, 12},
|
||||
{'2', 22, 12},
|
||||
{'3', 33, 12},
|
||||
{'4', 44, 12},
|
||||
{backspace_symbol, 103, 4},
|
||||
};
|
||||
|
||||
static const NumberInputKey keyboard_keys_row_2[] = {
|
||||
{'5', 0, 26},
|
||||
{'6', 11, 26},
|
||||
{'7', 22, 26},
|
||||
{'8', 33, 26},
|
||||
{'9', 44, 26},
|
||||
{sign_symbol, 55, 17},
|
||||
{enter_symbol, 95, 17},
|
||||
};
|
||||
|
||||
static size_t number_input_get_row_size(size_t row_index) {
|
||||
size_t row_size = 0;
|
||||
|
||||
switch(row_index + 1) {
|
||||
case 1:
|
||||
row_size = COUNT_OF(keyboard_keys_row_1);
|
||||
break;
|
||||
case 2:
|
||||
row_size = COUNT_OF(keyboard_keys_row_2);
|
||||
break;
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
return row_size;
|
||||
}
|
||||
|
||||
static const NumberInputKey* number_input_get_row(size_t row_index) {
|
||||
const NumberInputKey* row = NULL;
|
||||
|
||||
switch(row_index + 1) {
|
||||
case 1:
|
||||
row = keyboard_keys_row_1;
|
||||
break;
|
||||
case 2:
|
||||
row = keyboard_keys_row_2;
|
||||
break;
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
static void number_input_draw_input(Canvas* canvas, NumberInputModel* model) {
|
||||
const size_t text_x = 8;
|
||||
const size_t text_y = 25;
|
||||
|
||||
elements_slightly_rounded_frame(canvas, 6, 14, 116, 15);
|
||||
|
||||
canvas_draw_str(canvas, text_x, text_y, furi_string_get_cstr(model->text_buffer));
|
||||
}
|
||||
|
||||
static bool number_input_use_sign(NumberInputModel* model) {
|
||||
//only show sign button if allowed number range needs it
|
||||
if(model->min_value < 0 && model->max_value >= 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void number_input_backspace_cb(NumberInputModel* model) {
|
||||
size_t text_length = furi_string_utf8_length(model->text_buffer);
|
||||
if(text_length < 1 || (text_length < 2 && model->current_number <= 0)) {
|
||||
return;
|
||||
}
|
||||
furi_string_set_strn(
|
||||
model->text_buffer, furi_string_get_cstr(model->text_buffer), text_length - 1);
|
||||
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||
}
|
||||
|
||||
static void number_input_handle_up(NumberInputModel* model) {
|
||||
if(model->selected_row > 0) {
|
||||
model->selected_row--;
|
||||
if(model->selected_column > number_input_get_row_size(model->selected_row) - 1) {
|
||||
model->selected_column = number_input_get_row_size(model->selected_row) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void number_input_handle_down(NumberInputModel* model) {
|
||||
if(model->selected_row < keyboard_row_count - 1) {
|
||||
if(model->selected_column >= number_input_get_row_size(model->selected_row) - 1) {
|
||||
model->selected_column = number_input_get_row_size(model->selected_row + 1) - 1;
|
||||
}
|
||||
model->selected_row += 1;
|
||||
}
|
||||
const NumberInputKey* keys = number_input_get_row(model->selected_row);
|
||||
if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
|
||||
model->selected_column--;
|
||||
}
|
||||
}
|
||||
|
||||
static void number_input_handle_left(NumberInputModel* model) {
|
||||
if(model->selected_column > 0) {
|
||||
model->selected_column--;
|
||||
} else {
|
||||
model->selected_column = number_input_get_row_size(model->selected_row) - 1;
|
||||
}
|
||||
const NumberInputKey* keys = number_input_get_row(model->selected_row);
|
||||
if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
|
||||
model->selected_column--;
|
||||
}
|
||||
}
|
||||
|
||||
static void number_input_handle_right(NumberInputModel* model) {
|
||||
if(model->selected_column < number_input_get_row_size(model->selected_row) - 1) {
|
||||
model->selected_column++;
|
||||
} else {
|
||||
model->selected_column = 0;
|
||||
}
|
||||
const NumberInputKey* keys = number_input_get_row(model->selected_row);
|
||||
if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
|
||||
model->selected_column++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_number_too_large(NumberInputModel* model) {
|
||||
int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||
if(value > (int64_t)model->max_value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_number_too_small(NumberInputModel* model) {
|
||||
int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||
if(value < (int64_t)model->min_value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void number_input_sign(NumberInputModel* model) {
|
||||
int32_t number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||
if(number == 0 && furi_string_cmp_str(model->text_buffer, "-") != 0) {
|
||||
furi_string_set_str(model->text_buffer, "-");
|
||||
return;
|
||||
}
|
||||
number = number * -1;
|
||||
furi_string_printf(model->text_buffer, "%ld", number);
|
||||
if(is_number_too_large(model) || is_number_too_small(model)) {
|
||||
furi_string_printf(model->text_buffer, "%ld", model->current_number);
|
||||
return;
|
||||
}
|
||||
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||
if(model->current_number == 0) {
|
||||
furi_string_set_str(model->text_buffer, ""); //show empty if 0, better for usability
|
||||
}
|
||||
}
|
||||
|
||||
static void number_input_add_digit(NumberInputModel* model, char* newChar) {
|
||||
furi_string_cat_str(model->text_buffer, newChar);
|
||||
if((model->max_value >= 0 && is_number_too_large(model)) ||
|
||||
(model->min_value < 0 && is_number_too_small(model))) {
|
||||
//you still need to be able to type invalid numbers in some cases to reach valid numbers on later keypress
|
||||
furi_string_printf(model->text_buffer, "%ld", model->current_number);
|
||||
return;
|
||||
}
|
||||
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||
if(model->current_number == 0) {
|
||||
furi_string_reset(model->text_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static void number_input_handle_ok(NumberInputModel* model) {
|
||||
char selected = number_input_get_row(model->selected_row)[model->selected_column].text;
|
||||
char temp_str[2] = {selected, '\0'};
|
||||
if(selected == enter_symbol) {
|
||||
if(is_number_too_large(model) || is_number_too_small(model)) {
|
||||
return; //Do nothing if number outside allowed range
|
||||
}
|
||||
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||
model->callback(model->callback_context, model->current_number);
|
||||
} else if(selected == backspace_symbol) {
|
||||
number_input_backspace_cb(model);
|
||||
} else if(selected == sign_symbol) {
|
||||
number_input_sign(model);
|
||||
} else {
|
||||
number_input_add_digit(model, temp_str);
|
||||
}
|
||||
}
|
||||
|
||||
static void number_input_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
NumberInputModel* model = _model;
|
||||
|
||||
number_input_draw_input(canvas, model);
|
||||
|
||||
if(!furi_string_empty(model->header)) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 2, 9, furi_string_get_cstr(model->header));
|
||||
}
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
// Draw keyboard
|
||||
for(size_t row = 0; row < keyboard_row_count; row++) {
|
||||
const size_t column_count = number_input_get_row_size(row);
|
||||
const NumberInputKey* keys = number_input_get_row(row);
|
||||
|
||||
for(size_t column = 0; column < column_count; column++) {
|
||||
if(keys[column].text == sign_symbol && !number_input_use_sign(model)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(keys[column].text == enter_symbol) {
|
||||
if(is_number_too_small(model) || is_number_too_large(model)) {
|
||||
//in some cases you need to be able to type a number smaller/larger than the limits (expl. min = 50, clear all and editor must allow to type 9 and later 0 for 90)
|
||||
if(model->selected_row == row && model->selected_column == column) {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeySaveBlockedSelected_24x11);
|
||||
} else {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeySaveBlocked_24x11);
|
||||
}
|
||||
} else {
|
||||
if(model->selected_row == row && model->selected_column == column) {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeySaveSelected_24x11);
|
||||
} else {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeySave_24x11);
|
||||
}
|
||||
}
|
||||
} else if(keys[column].text == backspace_symbol) {
|
||||
if(model->selected_row == row && model->selected_column == column) {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeyBackspaceSelected_16x9);
|
||||
} else {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeyBackspace_16x9);
|
||||
}
|
||||
} else if(keys[column].text == sign_symbol) {
|
||||
if(model->selected_row == row && model->selected_column == column) {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeySignSelected_21x11);
|
||||
} else {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
&I_KeySign_21x11);
|
||||
}
|
||||
} else {
|
||||
if(model->selected_row == row && model->selected_column == column) {
|
||||
canvas_draw_box(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x - 3,
|
||||
keyboard_origin_y + keys[column].y - 10,
|
||||
11,
|
||||
13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
|
||||
canvas_draw_glyph(
|
||||
canvas,
|
||||
keyboard_origin_x + keys[column].x,
|
||||
keyboard_origin_y + keys[column].y,
|
||||
keys[column].text);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool number_input_view_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
NumberInput* number_input = context;
|
||||
|
||||
bool consumed = false;
|
||||
|
||||
// Fetch the model
|
||||
NumberInputModel* model = view_get_model(number_input->view);
|
||||
|
||||
if(event->type == InputTypeShort || event->type == InputTypeLong ||
|
||||
event->type == InputTypeRepeat) {
|
||||
consumed = true;
|
||||
switch(event->key) {
|
||||
case InputKeyLeft:
|
||||
number_input_handle_left(model);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
number_input_handle_right(model);
|
||||
break;
|
||||
case InputKeyUp:
|
||||
number_input_handle_up(model);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
number_input_handle_down(model);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
number_input_handle_ok(model);
|
||||
break;
|
||||
default:
|
||||
consumed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// commit view
|
||||
view_commit_model(number_input->view, consumed);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
NumberInput* number_input_alloc(void) {
|
||||
NumberInput* number_input = malloc(sizeof(NumberInput));
|
||||
number_input->view = view_alloc();
|
||||
view_set_context(number_input->view, number_input);
|
||||
view_allocate_model(number_input->view, ViewModelTypeLocking, sizeof(NumberInputModel));
|
||||
view_set_draw_callback(number_input->view, number_input_view_draw_callback);
|
||||
view_set_input_callback(number_input->view, number_input_view_input_callback);
|
||||
|
||||
with_view_model(
|
||||
number_input->view,
|
||||
NumberInputModel * model,
|
||||
{
|
||||
model->header = furi_string_alloc();
|
||||
model->text_buffer = furi_string_alloc();
|
||||
},
|
||||
true);
|
||||
|
||||
return number_input;
|
||||
}
|
||||
|
||||
void number_input_free(NumberInput* number_input) {
|
||||
furi_check(number_input);
|
||||
with_view_model(
|
||||
number_input->view,
|
||||
NumberInputModel * model,
|
||||
{
|
||||
furi_string_free(model->header);
|
||||
furi_string_free(model->text_buffer);
|
||||
},
|
||||
true);
|
||||
view_free(number_input->view);
|
||||
free(number_input);
|
||||
}
|
||||
|
||||
View* number_input_get_view(NumberInput* number_input) {
|
||||
furi_check(number_input);
|
||||
return number_input->view;
|
||||
}
|
||||
|
||||
void number_input_set_result_callback(
|
||||
NumberInput* number_input,
|
||||
NumberInputCallback callback,
|
||||
void* callback_context,
|
||||
int32_t current_number,
|
||||
int32_t min_value,
|
||||
int32_t max_value) {
|
||||
furi_check(number_input);
|
||||
|
||||
current_number = CLAMP(current_number, max_value, min_value);
|
||||
|
||||
with_view_model(
|
||||
number_input->view,
|
||||
NumberInputModel * model,
|
||||
{
|
||||
model->callback = callback;
|
||||
model->callback_context = callback_context;
|
||||
model->current_number = current_number;
|
||||
furi_string_printf(model->text_buffer, "%ld", current_number);
|
||||
model->min_value = min_value;
|
||||
model->max_value = max_value;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void number_input_set_header_text(NumberInput* number_input, const char* text) {
|
||||
furi_check(number_input);
|
||||
with_view_model(
|
||||
number_input->view,
|
||||
NumberInputModel * model,
|
||||
{ furi_string_set(model->header, text); },
|
||||
true);
|
||||
}
|
69
applications/services/gui/modules/number_input.h
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @file number_input.h
|
||||
* GUI: Integer string keyboard view module API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Number input anonymous structure */
|
||||
typedef struct NumberInput NumberInput;
|
||||
|
||||
/** Callback to be called on save button press */
|
||||
typedef void (*NumberInputCallback)(void* context, int32_t number);
|
||||
|
||||
/** Allocate and initialize Number input.
|
||||
*
|
||||
* This Number input is used to enter Numbers (Integers).
|
||||
*
|
||||
* @return NumberInput instance pointer
|
||||
*/
|
||||
NumberInput* number_input_alloc(void);
|
||||
|
||||
/** Deinitialize and free byte input
|
||||
*
|
||||
* @param number_input Number input instance
|
||||
*/
|
||||
void number_input_free(NumberInput* number_input);
|
||||
|
||||
/** Get byte input view
|
||||
*
|
||||
* @param number_input byte input instance
|
||||
*
|
||||
* @return View instance that can be used for embedding
|
||||
*/
|
||||
View* number_input_get_view(NumberInput* number_input);
|
||||
|
||||
/** Set byte input result callback
|
||||
*
|
||||
* @param number_input byte input instance
|
||||
* @param input_callback input callback fn
|
||||
* @param callback_context callback context
|
||||
* @param[in] current_number The current number
|
||||
* @param min_value Min number value
|
||||
* @param max_value Max number value
|
||||
*/
|
||||
|
||||
void number_input_set_result_callback(
|
||||
NumberInput* number_input,
|
||||
NumberInputCallback input_callback,
|
||||
void* callback_context,
|
||||
int32_t current_number,
|
||||
int32_t min_value,
|
||||
int32_t max_value);
|
||||
|
||||
/** Set byte input header text
|
||||
*
|
||||
* @param number_input byte input instance
|
||||
* @param text text to be shown
|
||||
*/
|
||||
void number_input_set_header_text(NumberInput* number_input, const char* text);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
BIN
assets/icons/Keyboard/KeySaveBlockedSelected_24x11.png
Normal file
After Width: | Height: | Size: 110 B |
BIN
assets/icons/Keyboard/KeySaveBlocked_24x11.png
Normal file
After Width: | Height: | Size: 110 B |
Before Width: | Height: | Size: 94 B After Width: | Height: | Size: 96 B |
Before Width: | Height: | Size: 110 B After Width: | Height: | Size: 110 B |
BIN
assets/icons/Keyboard/KeySignSelected_21x11.png
Normal file
After Width: | Height: | Size: 95 B |
BIN
assets/icons/Keyboard/KeySign_21x11.png
Normal file
After Width: | Height: | Size: 109 B |
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,72.0,,
|
||||
Version,+,72.1,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
@ -13,6 +13,7 @@ Header,+,applications/services/gui/icon_i.h,,
|
||||
Header,+,applications/services/gui/modules/button_menu.h,,
|
||||
Header,+,applications/services/gui/modules/button_panel.h,,
|
||||
Header,+,applications/services/gui/modules/byte_input.h,,
|
||||
Header,+,applications/services/gui/modules/number_input.h,,
|
||||
Header,+,applications/services/gui/modules/dialog_ex.h,,
|
||||
Header,+,applications/services/gui/modules/empty_screen.h,,
|
||||
Header,+,applications/services/gui/modules/file_browser.h,,
|
||||
@ -722,6 +723,11 @@ Function,+,byte_input_free,void,ByteInput*
|
||||
Function,+,byte_input_get_view,View*,ByteInput*
|
||||
Function,+,byte_input_set_header_text,void,"ByteInput*, const char*"
|
||||
Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t"
|
||||
Function,+,number_input_alloc,NumberInput*,
|
||||
Function,+,number_input_free,void,NumberInput*
|
||||
Function,+,number_input_get_view,View*,NumberInput*
|
||||
Function,+,number_input_set_header_text,void,"NumberInput*, const char*"
|
||||
Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t"
|
||||
Function,-,bzero,void,"void*, size_t"
|
||||
Function,+,calloc,void*,"size_t, size_t"
|
||||
Function,+,canvas_clear,void,Canvas*
|
||||
|
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,72.0,,
|
||||
Version,+,72.1,,
|
||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||
@ -20,6 +20,7 @@ Header,+,applications/services/gui/modules/file_browser.h,,
|
||||
Header,+,applications/services/gui/modules/file_browser_worker.h,,
|
||||
Header,+,applications/services/gui/modules/loading.h,,
|
||||
Header,+,applications/services/gui/modules/menu.h,,
|
||||
Header,+,applications/services/gui/modules/number_input.h,,
|
||||
Header,+,applications/services/gui/modules/popup.h,,
|
||||
Header,+,applications/services/gui/modules/submenu.h,,
|
||||
Header,+,applications/services/gui/modules/text_box.h,,
|
||||
@ -2815,6 +2816,11 @@ Function,+,notification_internal_message_block,void,"NotificationApp*, const Not
|
||||
Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*"
|
||||
Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*"
|
||||
Function,-,nrand48,long,unsigned short[3]
|
||||
Function,+,number_input_alloc,NumberInput*,
|
||||
Function,+,number_input_free,void,NumberInput*
|
||||
Function,+,number_input_get_view,View*,NumberInput*
|
||||
Function,+,number_input_set_header_text,void,"NumberInput*, const char*"
|
||||
Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t"
|
||||
Function,-,on_exit,int,"void (*)(int, void*), void*"
|
||||
Function,+,onewire_host_alloc,OneWireHost*,const GpioPin*
|
||||
Function,+,onewire_host_free,void,OneWireHost*
|
||||
|
|