mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-14 00:37:21 +00:00
Merge branch 'dev' into astra/3746-mfp-detect
This commit is contained in:
commit
d638b9b1a4
5 changed files with 361 additions and 107 deletions
10
applications/debug/text_box_element_test/application.fam
Normal file
10
applications/debug/text_box_element_test/application.fam
Normal file
|
@ -0,0 +1,10 @@
|
|||
App(
|
||||
appid="text_box_element_test",
|
||||
name="Text Box Element Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="text_box_element_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
fap_category="Debug",
|
||||
)
|
|
@ -71,7 +71,7 @@ static void text_box_test_input_callback(InputEvent* input_event, void* ctx) {
|
|||
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
|
||||
int32_t text_box_test_app(void* p) {
|
||||
int32_t text_box_element_test_app(void* p) {
|
||||
UNUSED(p);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent));
|
||||
furi_check(event_queue);
|
|
@ -1,8 +1,8 @@
|
|||
App(
|
||||
appid="text_box_test",
|
||||
name="Text Box Test",
|
||||
appid="text_box_view_test",
|
||||
name="Text Box View Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="text_box_test_app",
|
||||
entry_point="text_box_view_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
133
applications/debug/text_box_view_test/text_box_view_test.c
Normal file
133
applications/debug/text_box_view_test/text_box_view_test.c
Normal file
|
@ -0,0 +1,133 @@
|
|||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/text_box.h>
|
||||
#include <gui/view_stack.h>
|
||||
|
||||
#define TAG "TextBoxViewTest"
|
||||
|
||||
typedef struct {
|
||||
TextBoxFont font;
|
||||
TextBoxFocus focus;
|
||||
const char* text;
|
||||
} TextBoxViewTestContent;
|
||||
|
||||
static const TextBoxViewTestContent text_box_view_test_content_arr[] = {
|
||||
{
|
||||
.font = TextBoxFontText,
|
||||
.focus = TextBoxFocusStart,
|
||||
.text = "Hello, let's test text box. Press Right and Left to switch content",
|
||||
},
|
||||
{
|
||||
.font = TextBoxFontText,
|
||||
.focus = TextBoxFocusStart,
|
||||
.text =
|
||||
"Verify that symbols don't overlap borders: llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllend",
|
||||
},
|
||||
{
|
||||
.font = TextBoxFontText,
|
||||
.focus = TextBoxFocusStart,
|
||||
.text =
|
||||
"\n\n\n Start from several newline chars. Verify that scrolling doesn't break.\n\n\n\n\nThe end",
|
||||
},
|
||||
{
|
||||
.font = TextBoxFontText,
|
||||
.focus = TextBoxFocusStart,
|
||||
.text =
|
||||
"Let's test big text.\n\n The ARM Cortex-M is a group of 32-bit RISC ARM processor cores licensed by ARM Limited. These cores are optimized for low-cost and energy-efficient integrated circuits, which have been embedded in tens of billions of consumer devices.[1] Though they are most often the main component of microcontroller chips, sometimes they are embedded inside other types of chips too. The Cortex-M family consists of Cortex-M0,[2] Cortex-M0+,[3] Cortex-M1,[4] Cortex-M3,[5] Cortex-M4,[6] Cortex-M7,[7] Cortex-M23,[8] Cortex-M33,[9] Cortex-M35P,[10] Cortex-M52,[11] Cortex-M55,[12] Cortex-M85.[13] A floating-point unit (FPU) option is available for Cortex-M4 / M7 / M33 / M35P / M52 / M55 / M85 cores, and when included in the silicon these cores are sometimes known as \"Cortex-MxF\", where 'x' is the core variant.\n\nThe ARM Cortex-M family are ARM microprocessor cores that are designed for use in microcontrollers, ASICs, ASSPs, FPGAs, and SoCs. Cortex-M cores are commonly used as dedicated microcontroller chips, but also are hidden inside of SoC chips as power management controllers, I/O controllers, system controllers, touch screen controllers, smart battery controllers, and sensor controllers. The main difference from Cortex-A cores is that Cortex-M cores have no memory management unit (MMU) for virtual memory, considered essential for full-fledged operating systems. Cortex-M programs instead run bare metal or on one of the many real-time operating systems which support a Cortex-M.Though 8-bit microcontrollers were very popular in the past, Cortex-M has slowly been chipping away at the 8-bit market as the prices of low-end Cortex-M chips have moved downward. Cortex-M have become a popular replacements for 8-bit chips in applications that benefit from 32-bit math operations, and replacing older legacy ARM cores such as ARM7 and ARM9.",
|
||||
},
|
||||
{
|
||||
.font = TextBoxFontText,
|
||||
.focus = TextBoxFocusEnd,
|
||||
.text =
|
||||
"The same but with EndFocus\n\n The ARM Cortex-M is a group of 32-bit RISC ARM processor cores licensed by ARM Limited. These cores are optimized for low-cost and energy-efficient integrated circuits, which have been embedded in tens of billions of consumer devices.[1] Though they are most often the main component of microcontroller chips, sometimes they are embedded inside other types of chips too. The Cortex-M family consists of Cortex-M0,[2] Cortex-M0+,[3] Cortex-M1,[4] Cortex-M3,[5] Cortex-M4,[6] Cortex-M7,[7] Cortex-M23,[8] Cortex-M33,[9] Cortex-M35P,[10] Cortex-M52,[11] Cortex-M55,[12] Cortex-M85.[13] A floating-point unit (FPU) option is available for Cortex-M4 / M7 / M33 / M35P / M52 / M55 / M85 cores, and when included in the silicon these cores are sometimes known as \"Cortex-MxF\", where 'x' is the core variant.\n\nThe ARM Cortex-M family are ARM microprocessor cores that are designed for use in microcontrollers, ASICs, ASSPs, FPGAs, and SoCs. Cortex-M cores are commonly used as dedicated microcontroller chips, but also are hidden inside of SoC chips as power management controllers, I/O controllers, system controllers, touch screen controllers, smart battery controllers, and sensor controllers. The main difference from Cortex-A cores is that Cortex-M cores have no memory management unit (MMU) for virtual memory, considered essential for full-fledged operating systems. Cortex-M programs instead run bare metal or on one of the many real-time operating systems which support a Cortex-M.Though 8-bit microcontrollers were very popular in the past, Cortex-M has slowly been chipping away at the 8-bit market as the prices of low-end Cortex-M chips have moved downward. Cortex-M have become a popular replacements for 8-bit chips in applications that benefit from 32-bit math operations, and replacing older legacy ARM cores such as ARM7 and ARM9.",
|
||||
},
|
||||
{
|
||||
.font = TextBoxFontHex,
|
||||
.focus = TextBoxFocusEnd,
|
||||
.text =
|
||||
"0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999",
|
||||
},
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
TextBox* text_box;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
size_t current_content_i;
|
||||
} TextBoxViewTest;
|
||||
|
||||
static void text_box_update_view(TextBoxViewTest* instance) {
|
||||
text_box_reset(instance->text_box);
|
||||
|
||||
const TextBoxViewTestContent* content =
|
||||
&text_box_view_test_content_arr[instance->current_content_i];
|
||||
text_box_set_font(instance->text_box, content->font);
|
||||
text_box_set_focus(instance->text_box, content->focus);
|
||||
text_box_set_text(instance->text_box, content->text);
|
||||
}
|
||||
|
||||
static bool text_box_switch_view_input_callback(InputEvent* event, void* context) {
|
||||
bool consumed = false;
|
||||
TextBoxViewTest* instance = context;
|
||||
size_t contents_cnt = COUNT_OF(text_box_view_test_content_arr);
|
||||
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyRight) {
|
||||
if(instance->current_content_i < contents_cnt - 1) {
|
||||
instance->current_content_i++;
|
||||
text_box_update_view(instance);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
if(instance->current_content_i > 0) {
|
||||
instance->current_content_i--;
|
||||
text_box_update_view(instance);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event->key == InputKeyBack) {
|
||||
view_dispatcher_stop(instance->view_dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
int32_t text_box_view_test_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_enable_queue(view_dispatcher);
|
||||
|
||||
TextBoxViewTest instance = {
|
||||
.text_box = text_box_alloc(),
|
||||
.current_content_i = 0,
|
||||
.view_dispatcher = view_dispatcher,
|
||||
};
|
||||
|
||||
text_box_update_view(&instance);
|
||||
|
||||
View* text_box_switch_view = view_alloc();
|
||||
view_set_input_callback(text_box_switch_view, text_box_switch_view_input_callback);
|
||||
view_set_context(text_box_switch_view, &instance);
|
||||
|
||||
ViewStack* view_stack = view_stack_alloc();
|
||||
view_stack_add_view(view_stack, text_box_switch_view);
|
||||
view_stack_add_view(view_stack, text_box_get_view(instance.text_box));
|
||||
|
||||
view_dispatcher_add_view(view_dispatcher, 0, view_stack_get_view(view_stack));
|
||||
view_dispatcher_switch_to_view(view_dispatcher, 0);
|
||||
|
||||
view_dispatcher_run(view_dispatcher);
|
||||
|
||||
view_dispatcher_remove_view(view_dispatcher, 0);
|
||||
view_dispatcher_free(view_dispatcher);
|
||||
view_stack_free(view_stack);
|
||||
view_free(text_box_switch_view);
|
||||
text_box_free(instance.text_box);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -4,8 +4,13 @@
|
|||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TEXT_BOX_MAX_SYMBOL_WIDTH (10)
|
||||
#define TEXT_BOX_LINE_WIDTH (120)
|
||||
#define TEXT_BOX_TEXT_WIDTH (120)
|
||||
#define TEXT_BOX_TEXT_HEIGHT (56)
|
||||
#define TEXT_BOX_MAX_LINES_PER_SCREEN (10)
|
||||
|
||||
#define TEXT_BOX_LINES_SCROLL_SPEED_MEDIUM (3)
|
||||
#define TEXT_BOX_LINES_SCROLL_SPEED_FAST (5)
|
||||
#define TEXT_BOX_LINES_SCROLL_SPEED_SATURATION (9)
|
||||
|
||||
struct TextBox {
|
||||
View* view;
|
||||
|
@ -14,13 +19,19 @@ struct TextBox {
|
|||
};
|
||||
|
||||
typedef struct {
|
||||
const char* text;
|
||||
char* text_pos;
|
||||
FuriString* text_formatted;
|
||||
int32_t scroll_pos;
|
||||
int32_t scroll_num;
|
||||
TextBoxFont font;
|
||||
TextBoxFocus focus;
|
||||
const char* text;
|
||||
|
||||
int32_t scroll_pos;
|
||||
int32_t scroll_num;
|
||||
int32_t lines_on_screen;
|
||||
|
||||
int32_t line_offset;
|
||||
int32_t text_offset;
|
||||
FuriString* text_on_screen;
|
||||
FuriString* text_line;
|
||||
|
||||
bool formatted;
|
||||
} TextBoxModel;
|
||||
|
||||
|
@ -29,20 +40,11 @@ static void text_box_process_down(TextBox* text_box, uint8_t lines) {
|
|||
text_box->view,
|
||||
TextBoxModel * model,
|
||||
{
|
||||
if(model->scroll_pos < model->scroll_num - lines) {
|
||||
if(model->scroll_pos + lines < model->scroll_num) {
|
||||
model->scroll_pos += lines;
|
||||
for(uint8_t i = 0; i < lines; i++) {
|
||||
// Search next line start
|
||||
while(*model->text_pos++ != '\n')
|
||||
;
|
||||
}
|
||||
} else if(lines > 1) {
|
||||
lines = model->scroll_num - model->scroll_pos - 1;
|
||||
model->scroll_pos = model->scroll_num - 1;
|
||||
for(uint8_t i = 0; i < lines; i++) {
|
||||
// Search next line start
|
||||
while(*model->text_pos++ != '\n')
|
||||
;
|
||||
} else {
|
||||
if(model->scroll_num > 0) {
|
||||
model->scroll_pos = model->scroll_num - 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -54,69 +56,196 @@ static void text_box_process_up(TextBox* text_box, uint8_t lines) {
|
|||
text_box->view,
|
||||
TextBoxModel * model,
|
||||
{
|
||||
if(model->scroll_pos > lines - 1) {
|
||||
if(model->scroll_pos - lines > 0) {
|
||||
model->scroll_pos -= lines;
|
||||
for(uint8_t i = 0; i < lines; i++) {
|
||||
// Reach last symbol of previous line
|
||||
model->text_pos--;
|
||||
// Search previous line start
|
||||
while((model->text_pos != model->text) && (*(--model->text_pos) != '\n'))
|
||||
;
|
||||
if(*model->text_pos == '\n') {
|
||||
model->text_pos++;
|
||||
}
|
||||
}
|
||||
} else if(lines > 1) {
|
||||
lines = model->scroll_pos;
|
||||
} else {
|
||||
model->scroll_pos = 0;
|
||||
model->text_pos = (char*)model->text;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) {
|
||||
size_t i = 0;
|
||||
size_t line_width = 0;
|
||||
const char* str = model->text;
|
||||
size_t line_num = 0;
|
||||
static bool text_box_view_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
while(str[i] != '\0') {
|
||||
char symb = str[i++];
|
||||
if(symb != '\n') {
|
||||
TextBox* text_box = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
|
||||
int32_t scroll_speed = 1;
|
||||
if(text_box->button_held_for_ticks > TEXT_BOX_LINES_SCROLL_SPEED_FAST) {
|
||||
if(text_box->button_held_for_ticks % 2) {
|
||||
scroll_speed = 0;
|
||||
} else {
|
||||
scroll_speed =
|
||||
(text_box->button_held_for_ticks > TEXT_BOX_LINES_SCROLL_SPEED_SATURATION) ?
|
||||
TEXT_BOX_LINES_SCROLL_SPEED_FAST :
|
||||
TEXT_BOX_LINES_SCROLL_SPEED_MEDIUM;
|
||||
}
|
||||
}
|
||||
|
||||
if(event->key == InputKeyDown) {
|
||||
text_box_process_down(text_box, scroll_speed);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyUp) {
|
||||
text_box_process_up(text_box, scroll_speed);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
text_box->button_held_for_ticks++;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
text_box->button_held_for_ticks = 0;
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static bool text_box_end_of_text_reached(TextBoxModel* model) {
|
||||
return model->text[model->text_offset] == '\0';
|
||||
}
|
||||
|
||||
static bool text_box_start_of_text_reached(TextBoxModel* model) {
|
||||
return model->text_offset == 0;
|
||||
}
|
||||
|
||||
static void text_box_seek_next_line(Canvas* canvas, TextBoxModel* model) {
|
||||
size_t line_width = 0;
|
||||
|
||||
while(!text_box_end_of_text_reached(model)) {
|
||||
char symb = model->text[model->text_offset];
|
||||
if(symb == '\n') {
|
||||
model->text_offset++;
|
||||
break;
|
||||
} else {
|
||||
size_t glyph_width = canvas_glyph_width(canvas, symb);
|
||||
if(line_width + glyph_width > TEXT_BOX_LINE_WIDTH) {
|
||||
line_num++;
|
||||
line_width = 0;
|
||||
furi_string_push_back(model->text_formatted, '\n');
|
||||
if(line_width + glyph_width > TEXT_BOX_TEXT_WIDTH) {
|
||||
break;
|
||||
}
|
||||
line_width += glyph_width;
|
||||
} else {
|
||||
line_num++;
|
||||
line_width = 0;
|
||||
model->text_offset++;
|
||||
}
|
||||
furi_string_push_back(model->text_formatted, symb);
|
||||
}
|
||||
line_num++;
|
||||
model->text = furi_string_get_cstr(model->text_formatted);
|
||||
model->text_pos = (char*)model->text;
|
||||
size_t lines_on_screen = 56 / canvas_current_font_height(canvas);
|
||||
if(model->focus == TextBoxFocusEnd && line_num > lines_on_screen) {
|
||||
// Set text position to 5th line from the end
|
||||
const char* end = model->text + furi_string_size(model->text_formatted);
|
||||
for(size_t i = 0; i < line_num - lines_on_screen; i++) {
|
||||
while(model->text_pos < end) {
|
||||
if(*model->text_pos++ == '\n') break;
|
||||
}
|
||||
}
|
||||
|
||||
static void text_box_seek_end_of_prev_line(TextBoxModel* model) {
|
||||
do {
|
||||
if(text_box_start_of_text_reached(model)) break;
|
||||
model->text_offset--;
|
||||
if(text_box_start_of_text_reached(model)) break;
|
||||
if(model->text[model->text_offset] == '\n') {
|
||||
model->text_offset--;
|
||||
}
|
||||
} while(false);
|
||||
}
|
||||
|
||||
static void text_box_seek_prev_paragraph(TextBoxModel* model) {
|
||||
while(!text_box_start_of_text_reached(model)) {
|
||||
if(model->text[model->text_offset] == '\n') {
|
||||
model->text_offset++;
|
||||
break;
|
||||
}
|
||||
model->text_offset--;
|
||||
}
|
||||
}
|
||||
|
||||
static void text_box_seek_prev_line(Canvas* canvas, TextBoxModel* model) {
|
||||
int32_t start_text_offset = model->text_offset;
|
||||
|
||||
text_box_seek_end_of_prev_line(model);
|
||||
text_box_seek_prev_paragraph(model);
|
||||
|
||||
int32_t current_text_offset = model->text_offset;
|
||||
while(true) {
|
||||
text_box_seek_next_line(canvas, model);
|
||||
if(model->text_offset == start_text_offset) {
|
||||
break;
|
||||
}
|
||||
current_text_offset = model->text_offset;
|
||||
}
|
||||
model->text_offset = current_text_offset;
|
||||
}
|
||||
|
||||
static void text_box_move_line_offset(Canvas* canvas, TextBoxModel* model, int32_t line_offset) {
|
||||
if(line_offset >= 0) {
|
||||
for(int32_t i = 0; i < line_offset; i++) {
|
||||
text_box_seek_next_line(canvas, model);
|
||||
}
|
||||
model->scroll_num = line_num - (lines_on_screen - 1);
|
||||
model->scroll_pos = line_num - lines_on_screen;
|
||||
} else {
|
||||
model->scroll_num = MAX(line_num - (lines_on_screen - 1), 0u);
|
||||
model->scroll_pos = 0;
|
||||
for(int32_t i = 0; i < (-line_offset); i++) {
|
||||
text_box_seek_prev_line(canvas, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void text_box_update_screen_text(Canvas* canvas, TextBoxModel* model) {
|
||||
furi_string_reset(model->text_on_screen);
|
||||
furi_string_reset(model->text_line);
|
||||
|
||||
int32_t start_text_offset = model->text_offset;
|
||||
|
||||
for(int32_t i = 0; i < model->lines_on_screen; i++) {
|
||||
int32_t current_line_text_offset = model->text_offset;
|
||||
text_box_seek_next_line(canvas, model);
|
||||
int32_t next_line_text_offset = model->text_offset;
|
||||
furi_string_set_strn(
|
||||
model->text_line,
|
||||
&model->text[current_line_text_offset],
|
||||
next_line_text_offset - current_line_text_offset);
|
||||
size_t str_len = furi_string_size(model->text_line);
|
||||
if(furi_string_get_char(model->text_line, str_len - 1) != '\n') {
|
||||
furi_string_push_back(model->text_line, '\n');
|
||||
}
|
||||
furi_string_cat(model->text_on_screen, model->text_line);
|
||||
|
||||
if(text_box_end_of_text_reached(model)) break;
|
||||
current_line_text_offset = next_line_text_offset;
|
||||
}
|
||||
|
||||
model->text_offset = start_text_offset;
|
||||
}
|
||||
|
||||
static void text_box_update_text_on_screen(Canvas* canvas, TextBoxModel* model) {
|
||||
int32_t line_offset = model->scroll_pos - model->line_offset;
|
||||
text_box_move_line_offset(canvas, model, line_offset);
|
||||
text_box_update_screen_text(canvas, model);
|
||||
model->line_offset = model->scroll_pos;
|
||||
}
|
||||
|
||||
static void text_box_prepare_model(Canvas* canvas, TextBoxModel* model) {
|
||||
int32_t lines_num = 0;
|
||||
model->text_offset = 0;
|
||||
model->scroll_num = 0;
|
||||
model->scroll_pos = 0;
|
||||
model->line_offset = 0;
|
||||
model->lines_on_screen = TEXT_BOX_TEXT_HEIGHT / canvas_current_font_height(canvas);
|
||||
|
||||
// Cache text offset to quick final text offset update if TextBoxFocusEnd is set
|
||||
int32_t window_offset[TEXT_BOX_MAX_LINES_PER_SCREEN] = {};
|
||||
do {
|
||||
window_offset[lines_num % model->lines_on_screen] = model->text_offset;
|
||||
text_box_seek_next_line(canvas, model);
|
||||
lines_num++;
|
||||
} while(!text_box_end_of_text_reached(model));
|
||||
lines_num++;
|
||||
|
||||
if(model->focus == TextBoxFocusEnd) {
|
||||
if(lines_num > model->lines_on_screen) {
|
||||
model->text_offset = window_offset[(lines_num - 1) % model->lines_on_screen];
|
||||
}
|
||||
} else {
|
||||
model->text_offset = 0;
|
||||
}
|
||||
|
||||
if(lines_num > model->lines_on_screen) {
|
||||
model->scroll_num = lines_num - model->lines_on_screen;
|
||||
model->scroll_pos = (model->focus == TextBoxFocusEnd) ? model->scroll_num - 1 : 0;
|
||||
}
|
||||
|
||||
text_box_update_screen_text(canvas, model);
|
||||
model->line_offset = model->scroll_pos;
|
||||
}
|
||||
|
||||
static void text_box_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
TextBoxModel* model = _model;
|
||||
|
||||
|
@ -132,44 +261,17 @@ static void text_box_view_draw_callback(Canvas* canvas, void* _model) {
|
|||
}
|
||||
|
||||
if(!model->formatted) {
|
||||
text_box_insert_endline(canvas, model);
|
||||
text_box_prepare_model(canvas, model);
|
||||
model->formatted = true;
|
||||
}
|
||||
|
||||
elements_slightly_rounded_frame(canvas, 0, 0, 124, 64);
|
||||
elements_multiline_text(canvas, 3, 11, model->text_pos);
|
||||
elements_scrollbar(canvas, model->scroll_pos, model->scroll_num);
|
||||
}
|
||||
|
||||
static bool text_box_view_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
TextBox* text_box = context;
|
||||
bool consumed = false;
|
||||
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
|
||||
int32_t scroll_speed = 1;
|
||||
if(text_box->button_held_for_ticks > 5) {
|
||||
if(text_box->button_held_for_ticks % 2) {
|
||||
scroll_speed = 0;
|
||||
} else {
|
||||
scroll_speed = text_box->button_held_for_ticks > 9 ? 5 : 3;
|
||||
}
|
||||
}
|
||||
|
||||
if(event->key == InputKeyDown) {
|
||||
text_box_process_down(text_box, scroll_speed);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyUp) {
|
||||
text_box_process_up(text_box, scroll_speed);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
text_box->button_held_for_ticks++;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
text_box->button_held_for_ticks = 0;
|
||||
consumed = true;
|
||||
if(model->line_offset != model->scroll_pos) {
|
||||
text_box_update_text_on_screen(canvas, model);
|
||||
}
|
||||
return consumed;
|
||||
elements_multiline_text(canvas, 3, 11, furi_string_get_cstr(model->text_on_screen));
|
||||
}
|
||||
|
||||
TextBox* text_box_alloc(void) {
|
||||
|
@ -185,7 +287,8 @@ TextBox* text_box_alloc(void) {
|
|||
TextBoxModel * model,
|
||||
{
|
||||
model->text = NULL;
|
||||
model->text_formatted = furi_string_alloc_set("");
|
||||
model->text_on_screen = furi_string_alloc();
|
||||
model->text_line = furi_string_alloc();
|
||||
model->formatted = false;
|
||||
model->font = TextBoxFontText;
|
||||
},
|
||||
|
@ -198,7 +301,13 @@ void text_box_free(TextBox* text_box) {
|
|||
furi_check(text_box);
|
||||
|
||||
with_view_model(
|
||||
text_box->view, TextBoxModel * model, { furi_string_free(model->text_formatted); }, true);
|
||||
text_box->view,
|
||||
TextBoxModel * model,
|
||||
{
|
||||
furi_string_free(model->text_on_screen);
|
||||
furi_string_free(model->text_line);
|
||||
},
|
||||
true);
|
||||
view_free(text_box->view);
|
||||
free(text_box);
|
||||
}
|
||||
|
@ -216,9 +325,15 @@ void text_box_reset(TextBox* text_box) {
|
|||
TextBoxModel * model,
|
||||
{
|
||||
model->text = NULL;
|
||||
furi_string_set(model->text_formatted, "");
|
||||
model->font = TextBoxFontText;
|
||||
model->focus = TextBoxFocusStart;
|
||||
furi_string_reset(model->text_line);
|
||||
furi_string_reset(model->text_on_screen);
|
||||
model->line_offset = 0;
|
||||
model->text_offset = 0;
|
||||
model->lines_on_screen = 0;
|
||||
model->scroll_num = 0;
|
||||
model->scroll_pos = 0;
|
||||
model->formatted = false;
|
||||
},
|
||||
true);
|
||||
|
@ -227,16 +342,12 @@ void text_box_reset(TextBox* text_box) {
|
|||
void text_box_set_text(TextBox* text_box, const char* text) {
|
||||
furi_check(text_box);
|
||||
furi_check(text);
|
||||
size_t str_length = strlen(text);
|
||||
size_t formating_margin = str_length * TEXT_BOX_MAX_SYMBOL_WIDTH / TEXT_BOX_LINE_WIDTH;
|
||||
|
||||
with_view_model(
|
||||
text_box->view,
|
||||
TextBoxModel * model,
|
||||
{
|
||||
model->text = text;
|
||||
furi_string_reset(model->text_formatted);
|
||||
furi_string_reserve(model->text_formatted, str_length + formating_margin);
|
||||
model->formatted = false;
|
||||
},
|
||||
true);
|
||||
|
|
Loading…
Reference in a new issue