From 4ea485aae60f866dc649a981385c76e39675d0d6 Mon Sep 17 00:00:00 2001 From: 956MB Date: Sun, 5 Jan 2025 15:51:19 -0600 Subject: [PATCH 1/3] Asset Packs: Warning for RAM usage - Checks asset packs if they contain Fonts/Icons and adds "Size Warning" list entry that opens new scenes. - First scene is just a warning with text explaining that fonts and icons remain loaded and use up memory. - Second scene uses the file list widget to display all the selected asset packs fonts and icons, as well as their sizes. This information is just about being extra informative. No actions can be done on any of the files shown. --- applications/main/momentum_app/momentum_app.c | 6 + applications/main/momentum_app/momentum_app.h | 4 +- .../scenes/momentum_app_scene_config.h | 2 + .../momentum_app_scene_interface_graphics.c | 46 +++- ...pp_scene_interface_graphics_pack_warning.c | 60 +++++ ...ene_interface_graphics_pack_warning_info.c | 107 +++++++++ applications/services/gui/modules/widget.c | 22 +- applications/services/gui/modules/widget.h | 23 ++ .../modules/widget_elements/widget_element.h | 1 + .../widget_element_file_list.c | 225 ++++++++++++++++++ .../widget_elements/widget_element_i.h | 14 +- applications/services/gui/modules/widget_i.h | 10 + applications/services/storage/storage.h | 21 ++ .../services/storage/storage_external_api.c | 73 ++++++ targets/f7/api_symbols.csv | 2 + 15 files changed, 604 insertions(+), 12 deletions(-) create mode 100644 applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning.c create mode 100644 applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning_info.c create mode 100644 applications/services/gui/modules/widget_elements/widget_element_file_list.c create mode 100644 applications/services/gui/modules/widget_i.h diff --git a/applications/main/momentum_app/momentum_app.c b/applications/main/momentum_app/momentum_app.c index 9b929ab70..50874f716 100644 --- a/applications/main/momentum_app/momentum_app.c +++ b/applications/main/momentum_app/momentum_app.c @@ -313,6 +313,10 @@ MomentumApp* momentum_app_alloc() { view_dispatcher_add_view( app->view_dispatcher, MomentumAppViewDialogEx, dialog_ex_get_view(app->dialog_ex)); + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, MomentumAppViewWidget, widget_get_view(app->widget)); + // Settings init app->asset_pack_index = 0; @@ -441,6 +445,8 @@ void momentum_app_free(MomentumApp* app) { popup_free(app->popup); view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewDialogEx); dialog_ex_free(app->dialog_ex); + view_dispatcher_remove_view(app->view_dispatcher, MomentumAppViewWidget); + widget_free(app->widget); // View Dispatcher and Scene Manager view_dispatcher_free(app->view_dispatcher); diff --git a/applications/main/momentum_app/momentum_app.h b/applications/main/momentum_app/momentum_app.h index 776f21b67..f43e07ade 100644 --- a/applications/main/momentum_app/momentum_app.h +++ b/applications/main/momentum_app/momentum_app.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -63,7 +64,7 @@ typedef struct { NumberInput* number_input; Popup* popup; DialogEx* dialog_ex; - + Widget* widget; CharList_t asset_pack_names; uint8_t asset_pack_index; CharList_t mainmenu_app_labels; @@ -107,6 +108,7 @@ typedef enum { MomentumAppViewNumberInput, MomentumAppViewPopup, MomentumAppViewDialogEx, + MomentumAppViewWidget, } MomentumAppView; bool momentum_app_apply(MomentumApp* app); diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_config.h b/applications/main/momentum_app/scenes/momentum_app_scene_config.h index c9eb6e996..804b09d1c 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_config.h +++ b/applications/main/momentum_app/scenes/momentum_app_scene_config.h @@ -2,6 +2,8 @@ ADD_SCENE(momentum_app, start, Start) ADD_SCENE(momentum_app, interface, Interface) ADD_SCENE(momentum_app, interface_graphics, InterfaceGraphics) ADD_SCENE(momentum_app, interface_graphics_pack, InterfaceGraphicsPack) +ADD_SCENE(momentum_app, interface_graphics_pack_warning, InterfaceGraphicsPackWarning) +ADD_SCENE(momentum_app, interface_graphics_pack_warning_info, InterfaceGraphicsPackWarningInfo) ADD_SCENE(momentum_app, interface_mainmenu, InterfaceMainmenu) ADD_SCENE(momentum_app, interface_mainmenu_add, InterfaceMainmenuAdd) ADD_SCENE(momentum_app, interface_mainmenu_add_main, InterfaceMainmenuAddMain) diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics.c b/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics.c index b3b781ec3..2bac8710e 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics.c @@ -2,11 +2,29 @@ enum VarItemListIndex { VarItemListIndexAssetPack, + VarItemListIndexPackWarning, VarItemListIndexAnimSpeed, VarItemListIndexCycleAnims, VarItemListIndexUnlockAnims, }; +static bool check_asset_pack_folders(const char* pack_name) { + if(pack_name == NULL) return false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* path = furi_string_alloc(); + bool result = false; + + furi_string_printf(path, "%s/%s/Icons", ASSET_PACKS_PATH, pack_name); + if(storage_dir_exists(storage, furi_string_get_cstr(path))) result = true; + if(!result) { + furi_string_printf(path, "%s/%s/Fonts", ASSET_PACKS_PATH, pack_name); + if(storage_dir_exists(storage, furi_string_get_cstr(path))) result = true; + } + + furi_string_free(path); + return result; +} + void momentum_app_scene_interface_graphics_var_item_list_callback(void* context, uint32_t index) { MomentumApp* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, index); @@ -24,6 +42,12 @@ static void momentum_app_scene_interface_graphics_asset_pack_changed(VariableIte app->asset_pack_index = index; app->save_settings = true; app->apply_pack = true; + + // Workaround to force scene reload to rebuild the list with/without pack warning and + // return to 0 index ("Asset Pack"). + scene_manager_previous_scene(app->scene_manager); + scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceGraphics); + scene_manager_set_scene_state(app->scene_manager, MomentumAppSceneInterfaceGraphics, 0); } const char* const anim_speed_names[] = { @@ -118,6 +142,10 @@ void momentum_app_scene_interface_graphics_on_enter(void* context) { VariableItem* item; uint8_t value_index; + const char* selected_asset_pack = + app->asset_pack_index == 0 ? + "Default" : + *CharList_get(app->asset_pack_names, app->asset_pack_index - 1); item = variable_item_list_add( var_item_list, "Asset Pack", @@ -125,11 +153,14 @@ void momentum_app_scene_interface_graphics_on_enter(void* context) { momentum_app_scene_interface_graphics_asset_pack_changed, app); variable_item_set_current_value_index(item, app->asset_pack_index); - variable_item_set_current_value_text( - item, - app->asset_pack_index == 0 ? - "Default" : - *CharList_get(app->asset_pack_names, app->asset_pack_index - 1)); + variable_item_set_current_value_text(item, selected_asset_pack); + + if(app->asset_pack_index > 0) { + if(check_asset_pack_folders(selected_asset_pack)) { + item = variable_item_list_add(var_item_list, "Size Warning", 0, NULL, app); + variable_item_set_current_value_text(item, ">"); + } + } item = variable_item_list_add( var_item_list, @@ -183,6 +214,11 @@ bool momentum_app_scene_interface_graphics_on_event(void* context, SceneManagerE switch(event.event) { case VarItemListIndexAssetPack: scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceGraphicsPack); + break; + case VarItemListIndexPackWarning: + scene_manager_next_scene( + app->scene_manager, MomentumAppSceneInterfaceGraphicsPackWarning); + break; default: break; } diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning.c b/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning.c new file mode 100644 index 000000000..dd0a4c177 --- /dev/null +++ b/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning.c @@ -0,0 +1,60 @@ +#include "../momentum_app.h" + +void momentum_app_scene_interface_graphics_pack_warning_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + MomentumApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void momentum_app_scene_interface_graphics_pack_warning_on_enter(void* context) { + MomentumApp* app = context; + + widget_add_button_element( + app->widget, + GuiButtonTypeLeft, + "Back", + momentum_app_scene_interface_graphics_pack_warning_widget_callback, + app); + widget_add_button_element( + app->widget, + GuiButtonTypeRight, + "Info", + momentum_app_scene_interface_graphics_pack_warning_widget_callback, + app); + + widget_add_text_box_element( + app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, "\e#Size Warning\e#", false); + + const char* body = + "Your selected pack contains Fonts & Icons, which remain loaded and use up memory"; + widget_add_string_multiline_element( + app->widget, 0, 18, AlignLeft, AlignTop, FontSecondary, body); + + view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewWidget); +} + +bool momentum_app_scene_interface_graphics_pack_warning_on_event( + void* context, + SceneManagerEvent event) { + MomentumApp* app = context; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + return scene_manager_previous_scene(app->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene( + app->scene_manager, MomentumAppSceneInterfaceGraphicsPackWarningInfo); + return true; + } + } + return false; +} + +void momentum_app_scene_interface_graphics_pack_warning_on_exit(void* context) { + MomentumApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning_info.c b/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning_info.c new file mode 100644 index 000000000..6aec95459 --- /dev/null +++ b/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning_info.c @@ -0,0 +1,107 @@ +#include "../momentum_app.h" + +void momentum_app_scene_interface_graphics_pack_warning_info_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + MomentumApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void momentum_app_scene_interface_graphics_pack_warning_info_on_enter(void* context) { + MomentumApp* app = context; + + widget_add_button_element( + app->widget, + GuiButtonTypeLeft, + "Exit", + momentum_app_scene_interface_graphics_pack_warning_info_widget_callback, + app); + + const char* dirs[] = {"Fonts", "Icons"}; + const char* font_exts[] = {".u8f"}; + const char* icon_exts[] = {".bm", ".bmx"}; + const char** exts[] = {font_exts, icon_exts}; + const size_t ext_counts[] = {COUNT_OF(font_exts), COUNT_OF(icon_exts)}; + const char* selected_pack = + app->asset_pack_index == 0 ? + "Default" : + *CharList_get(app->asset_pack_names, app->asset_pack_index - 1); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* path = furi_string_alloc(); + FuriString** paths = NULL; + size_t num_paths = 0; + + for(size_t i = 0; i < COUNT_OF(dirs); i++) { + FuriString** files = NULL; + size_t num_files = 0; + + furi_string_printf(path, "%s/%s/%s", ASSET_PACKS_PATH, selected_pack, dirs[i]); + if(storage_dir_exists(storage, furi_string_get_cstr(path))) { + if(storage_list_dir( + storage, + furi_string_get_cstr(path), + &files, + &num_files, + true, + exts[i], + ext_counts[i])) { + paths = realloc(paths, (num_paths + num_files) * sizeof(FuriString*)); + memcpy(&paths[num_paths], files, num_files * sizeof(FuriString*)); + num_paths += num_files; + free(files); + } + } + } + + size_t f_count = 0; + size_t i_count = 0; + for(size_t i = 0; i < num_paths; i++) { + if(furi_string_start_with_str(paths[i], ASSET_PACKS_PATH)) { + if(furi_string_search_str(paths[i], "/Fonts/") != FURI_STRING_FAILURE) { + f_count++; + } else if(furi_string_search_str(paths[i], "/Icons/") != FURI_STRING_FAILURE) { + i_count++; + } + } + } + + FuriString* title = furi_string_alloc(); + if(f_count > 0 && i_count > 0) { + furi_string_printf(title, "%zu Fonts, %zu Icons", f_count, i_count); + } else if(f_count > 0) { + furi_string_printf(title, "%zu Fonts", f_count); + } else if(i_count > 0) { + furi_string_printf(title, "%zu Icons", i_count); + } + + widget_add_string_element( + app->widget, 35, 57, AlignLeft, AlignCenter, FontPrimary, furi_string_get_cstr(title)); + furi_string_free(title); + + widget_add_file_list_element(app->widget, 0, 12, 4, paths, num_paths, 0, 52, true); + + view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewWidget); +} + +bool momentum_app_scene_interface_graphics_pack_warning_info_on_event( + void* context, + SceneManagerEvent event) { + MomentumApp* app = context; + + if((event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeLeft) || + event.type == SceneManagerEventTypeBack) { + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, MomentumAppSceneInterfaceGraphics); + return true; + } + return false; +} + +void momentum_app_scene_interface_graphics_pack_warning_info_on_exit(void* context) { + MomentumApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/services/gui/modules/widget.c b/applications/services/gui/modules/widget.c index 8efb9601d..5996f6ba8 100644 --- a/applications/services/gui/modules/widget.c +++ b/applications/services/gui/modules/widget.c @@ -5,11 +5,6 @@ ARRAY_DEF(ElementArray, WidgetElement*, M_PTR_OPLIST); // NOLINT -struct Widget { - View* view; - void* context; -}; - typedef struct { ElementArray_t element; } GuiWidgetModel; @@ -119,6 +114,23 @@ static void widget_add_element(Widget* widget, WidgetElement* element) { true); } +WidgetElement* widget_add_file_list_element( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t lines, + FuriString** files, + size_t count, + uint8_t scrollbar_y, + uint8_t scrollbar_height, + bool show_size) { + furi_assert(widget); + WidgetElement* file_list_element = widget_element_file_list_create( + widget, x, y, lines, files, count, scrollbar_y, scrollbar_height, show_size); + widget_add_element(widget, file_list_element); + return file_list_element; +} + WidgetElement* widget_add_string_multiline_element( Widget* widget, uint8_t x, diff --git a/applications/services/gui/modules/widget.h b/applications/services/gui/modules/widget.h index 51ce2785f..c912d20f7 100644 --- a/applications/services/gui/modules/widget.h +++ b/applications/services/gui/modules/widget.h @@ -41,6 +41,29 @@ void widget_reset(Widget* widget); */ View* widget_get_view(Widget* widget); +/** Add File List Element + * + * @param widget Widget instance + * @param x x coordinate + * @param y y coordinate + * @param lines Number of lines visible + * @param files Array of FuriString pointers + * @param count Number of files + * @param scrollbar_y Y coordinate of the scrollbar + * @param scrollbar_height Height of the scrollbar + * @param show_size Show file size + */ +WidgetElement* widget_add_file_list_element( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t lines, + FuriString** files, + size_t count, + uint8_t scrollbar_y, + uint8_t scrollbar_height, + bool show_size); + /** Add Multi String Element * * @param widget Widget instance diff --git a/applications/services/gui/modules/widget_elements/widget_element.h b/applications/services/gui/modules/widget_elements/widget_element.h index 473fabd04..15faa4a46 100644 --- a/applications/services/gui/modules/widget_elements/widget_element.h +++ b/applications/services/gui/modules/widget_elements/widget_element.h @@ -5,6 +5,7 @@ #pragma once +#include "input/input.h" #ifdef __cplusplus extern "C" { #endif diff --git a/applications/services/gui/modules/widget_elements/widget_element_file_list.c b/applications/services/gui/modules/widget_elements/widget_element_file_list.c new file mode 100644 index 000000000..f51547fcd --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_file_list.c @@ -0,0 +1,225 @@ +#include "assets_icons.h" +#include "path.h" +#include "widget_element_i.h" +#include +#include +#include "archive/archive_i.h" +#include "assets_icons.h" + +#define SCROLL_INTERVAL (333) +#define SCROLL_DELAY (2) + +const char* units_short[] = {"B", "K", "M", "G", "T"}; + +typedef struct { + FuriString* path; + const Icon* icon; + char size_num[8]; + char size_unit[2]; +} FileListItem; + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t lines; + FileListItem* files; + size_t count; + size_t offset; + uint8_t scrollbar_y; + bool show_size; + uint8_t scrollbar_height; + size_t scroll_counter; + FuriTimer* scroll_timer; +} FileListModel; + +static void format_file_size( + uint64_t size, + char* num_buf, + size_t num_size, + char* unit_buf, + size_t unit_size) { + double formatted_size = size; + uint8_t unit = 0; + + while(formatted_size >= 1024 && unit < COUNT_OF(units_short) - 1) { + formatted_size /= 1024; + unit++; + } + + if(unit == 0) { + snprintf(num_buf, num_size, "%d", (int)formatted_size); + } else { + snprintf(num_buf, num_size, "%.1f", (double)formatted_size); + } + snprintf(unit_buf, unit_size, "%s", units_short[unit]); +} + +static void widget_element_file_list_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + FileListModel* model = element->model; + size_t items_visible = MIN(model->lines, model->count); + + canvas_set_font(canvas, FontSecondary); + for(size_t i = 0; i < items_visible; i++) { + size_t idx = model->offset + i; + if(idx < model->count) { + canvas_draw_icon( + canvas, model->x + 2, model->y + (i * FRAME_HEIGHT) - 9, model->files[idx].icon); + + size_t inner_x = 123; + if(model->show_size && model->files[idx].size_num[0] != '\0') { + canvas_set_font(canvas, FontPrimary); + uint16_t num_width = canvas_string_width(canvas, model->files[idx].size_num); + canvas_set_font(canvas, FontSecondary); + uint16_t unit_width = canvas_string_width(canvas, model->files[idx].size_unit); + uint16_t total_width = num_width + unit_width; + inner_x = model->x + (128 - model->x) - total_width - 5; + inner_x--; + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str( + canvas, inner_x, model->y + (i * FRAME_HEIGHT), model->files[idx].size_num); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str( + canvas, + inner_x + num_width + 1, + model->y + (i * FRAME_HEIGHT), + model->files[idx].size_unit); + } + + size_t scroll_counter = model->scroll_counter; + scroll_counter = + i == 0 ? (model->count > model->lines && + (scroll_counter < SCROLL_DELAY ? 0 : scroll_counter - SCROLL_DELAY)) : + 0; + + elements_scrollable_text_line( + canvas, + model->x + 15, + model->y + (i * FRAME_HEIGHT), + inner_x - 19, + model->files[idx].path, + scroll_counter, + i != 0 || model->count <= model->lines); + } + } + + if(model->count > model->lines) { + elements_scrollbar_pos( + canvas, + 128, + model->scrollbar_y, + model->scrollbar_height, + model->offset, + model->count - (model->lines - 1)); + } +} + +static bool widget_element_file_list_input(InputEvent* event, WidgetElement* element) { + furi_assert(element); + FileListModel* model = element->model; + bool consumed = false; + + if(model->count > model->lines && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { + if(event->key == InputKeyUp) { + model->offset = (model->offset > 0) ? model->offset - 1 : model->count - model->lines; + model->scroll_counter = 0; + consumed = true; + } else if(event->key == InputKeyDown) { + model->offset = ((model->offset + model->lines) < model->count) ? model->offset + 1 : + 0; + model->scroll_counter = 0; + consumed = true; + } + } + + return consumed; +} + +static void widget_element_file_list_timer_callback(void* context) { + WidgetElement* element = context; + FileListModel* file_model = element->model; + file_model->scroll_counter++; + with_view_model(element->parent->view, void* _model, { UNUSED(_model); }, true); +} + +static void widget_element_file_list_free(WidgetElement* element) { + furi_assert(element); + FileListModel* model = element->model; + furi_timer_stop(model->scroll_timer); + furi_timer_free(model->scroll_timer); + for(size_t i = 0; i < model->count; i++) { + furi_string_free(model->files[i].path); + } + free(model->files); + free(model); + free(element); +} + +WidgetElement* widget_element_file_list_create( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t lines, + FuriString** files, + size_t count, + uint8_t scrollbar_y, + uint8_t scrollbar_height, + bool show_size) { + // Allocate and init model + FileListModel* model = malloc(sizeof(FileListModel)); + model->x = x; + model->y = y; + model->lines = lines; + model->count = count; + model->scrollbar_y = scrollbar_y; + model->scrollbar_height = scrollbar_height; + model->show_size = show_size; + model->offset = 0; + model->scroll_counter = 0; + model->files = malloc(sizeof(FileListItem) * count); + + Storage* storage = furi_record_open(RECORD_STORAGE); + FileInfo info; + for(size_t i = 0; i < count; i++) { + model->files[i].path = furi_string_alloc(); + path_extract_filename(files[i], model->files[i].path, false); + model->files[i].size_num[0] = '\0'; + model->files[i].size_unit[0] = '\0'; + + if(storage_dir_exists(storage, furi_string_get_cstr(files[i]))) { + model->files[i].icon = &I_dir_10px; + } else { + const char* ext = strrchr(furi_string_get_cstr(model->files[i].path), '.'); + model->files[i].icon = (ext && strcasecmp(ext, ".js") == 0) ? &I_js_script_10px : + &I_unknown_10px; + } + + if(show_size) { + storage_common_stat(storage, furi_string_get_cstr(files[i]), &info); + format_file_size( + info.size, + model->files[i].size_num, + sizeof(model->files[i].size_num), + model->files[i].size_unit, + sizeof(model->files[i].size_unit)); + } + } + furi_record_close(RECORD_STORAGE); + + // Allocate and init Element + WidgetElement* element = malloc(sizeof(WidgetElement)); + element->draw = widget_element_file_list_draw; + element->input = widget_element_file_list_input; + element->free = widget_element_file_list_free; + element->parent = widget; + element->model = model; + + model->scroll_timer = + furi_timer_alloc(widget_element_file_list_timer_callback, FuriTimerTypePeriodic, element); + furi_timer_start(model->scroll_timer, SCROLL_INTERVAL); + + return element; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_i.h b/applications/services/gui/modules/widget_elements/widget_element_i.h index 9a873fef7..f6ff6579b 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_i.h +++ b/applications/services/gui/modules/widget_elements/widget_element_i.h @@ -5,7 +5,7 @@ #pragma once -#include "../widget.h" +#include "../widget_i.h" #include "widget_element.h" #include #include @@ -34,6 +34,18 @@ struct WidgetElement { Widget* parent; }; +/** Create file list element */ +WidgetElement* widget_element_file_list_create( + Widget* widget, + uint8_t x, + uint8_t y, + uint8_t lines, + FuriString** files, + size_t count, + uint8_t scrollbar_y, + uint8_t scrollbar_height, + bool show_size); + /** Create multi string element */ WidgetElement* widget_element_string_multiline_create( uint8_t x, diff --git a/applications/services/gui/modules/widget_i.h b/applications/services/gui/modules/widget_i.h new file mode 100644 index 000000000..d4f0a57eb --- /dev/null +++ b/applications/services/gui/modules/widget_i.h @@ -0,0 +1,10 @@ +#pragma once + +#include "widget.h" +#include +#include + +struct Widget { + View* view; + void* context; +}; diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 6d707fa9d..4bb064b76 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -274,6 +274,27 @@ bool storage_dir_rewind(File* file); */ bool storage_dir_exists(Storage* storage, const char* path); +/** + * @brief List the contents of a directory. + * + * @param storage pointer to a storage API instance. + * @param path pointer to a zero-terminated string containing the path of the directory in question. + * @param files pointer to an array of FuriString pointers to contain the file names. + * @param num_files pointer to the number of files in the directory. + * @param ignore_dirs if true, only files will be returned, not directories. + * @param include_ext pointer to an array of zero-terminated strings of extensions to include. + * @param ext_count the number of extensions in the include_ext array. + * @return true if the directory was successfully listed, false otherwise. + */ +bool storage_list_dir( + Storage* storage, + const char* path, + FuriString*** files, + size_t* num_files, + bool ignore_dirs, + const char** include_ext, + size_t ext_count); + /******************* Common Functions *******************/ /** diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 95119561c..7cfb70ad0 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -440,6 +440,79 @@ bool storage_dir_exists(Storage* storage, const char* path) { return exist; } + +bool storage_list_dir( + Storage* storage, + const char* path, + FuriString*** files, + size_t* num_files, + bool ignore_dirs, + const char** include_ext, + size_t ext_count) { + furi_check(storage); + furi_check(path); + furi_check(files); + furi_check(num_files); + + FileInfo fileinfo; + bool result = false; + char* name = malloc(MAX_NAME_LENGTH); + File* dir = storage_file_alloc(storage); + + do { + if(!storage_dir_open(dir, path)) break; + + while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { + if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; + + if(file_info_is_dir(&fileinfo)) { + if(!ignore_dirs) { + *files = realloc(*files, (*num_files + 1) * sizeof(FuriString*)); + (*files)[*num_files] = furi_string_alloc_printf("%s/%s", path, name); + (*num_files)++; + } + FuriString* subpath = furi_string_alloc_printf("%s/%s", path, name); + storage_list_dir( + storage, + furi_string_get_cstr(subpath), + files, + num_files, + ignore_dirs, + include_ext, + ext_count); + furi_string_free(subpath); + } else { + bool add_file = (include_ext == NULL || ext_count == 0); + + if(!add_file) { + const char* ext = strrchr(name, '.'); + if(ext != NULL) { + for(size_t i = 0; i < ext_count; i++) { + if(strcasecmp(ext, include_ext[i]) == 0) { + add_file = true; + break; + } + } + } + } + + if(add_file) { + *files = realloc(*files, (*num_files + 1) * sizeof(FuriString*)); + (*files)[*num_files] = furi_string_alloc_printf("%s/%s", path, name); + (*num_files)++; + } + } + } + result = true; + } while(false); + + storage_dir_close(dir); + storage_file_free(dir); + free(name); + + return result; +} + /****************** COMMON ******************/ FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp) { diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f5e79d7ee..8576602f0 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3333,6 +3333,7 @@ Function,+,storage_get_next_filename,void,"Storage*, const char*, const char*, c Function,+,storage_get_pubsub,FuriPubSub*,Storage* Function,+,storage_int_backup,FS_Error,"Storage*, const char*" Function,+,storage_int_restore,FS_Error,"Storage*, const char*, StorageNameConverter" +Function,+,storage_list_dir,_Bool,"Storage*, const char*, FuriString***, size_t*, _Bool, const char**, size_t" Function,+,storage_sd_format,FS_Error,Storage* Function,+,storage_sd_info,FS_Error,"Storage*, SDInfo*" Function,+,storage_sd_mount,FS_Error,Storage* @@ -3836,6 +3837,7 @@ Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" Function,-,wctomb,int,"char*, wchar_t" Function,+,widget_add_button_element,WidgetElement*,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" +Function,+,widget_add_file_list_element,WidgetElement*,"Widget*, uint8_t, uint8_t, uint8_t, FuriString**, size_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_frame_element,WidgetElement*,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,widget_add_icon_element,WidgetElement*,"Widget*, uint8_t, uint8_t, const Icon*" Function,+,widget_add_string_element,WidgetElement*,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" From 6ee3228aae669778c88b90dfa6dd9b12040b7409 Mon Sep 17 00:00:00 2001 From: 956MB Date: Tue, 7 Jan 2025 01:10:00 -0600 Subject: [PATCH 2/3] fix: scroll_counter eval order causing scroll to stop --- .../gui/modules/widget_elements/widget_element_file_list.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/gui/modules/widget_elements/widget_element_file_list.c b/applications/services/gui/modules/widget_elements/widget_element_file_list.c index f51547fcd..9aecbb4a9 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_file_list.c +++ b/applications/services/gui/modules/widget_elements/widget_element_file_list.c @@ -90,9 +90,9 @@ static void widget_element_file_list_draw(Canvas* canvas, WidgetElement* element size_t scroll_counter = model->scroll_counter; scroll_counter = - i == 0 ? (model->count > model->lines && - (scroll_counter < SCROLL_DELAY ? 0 : scroll_counter - SCROLL_DELAY)) : - 0; + i == 0 && model->count > model->lines ? + (scroll_counter < SCROLL_DELAY ? 0 : scroll_counter - SCROLL_DELAY) : + 0; elements_scrollable_text_line( canvas, From 1213f6171e9b199da1c90d8d1dadfa8c6e2dc058 Mon Sep 17 00:00:00 2001 From: 956MB Date: Tue, 7 Jan 2025 22:32:35 -0600 Subject: [PATCH 3/3] fix: align warning text to the center --- ...m_app_scene_interface_graphics_pack_warning.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning.c b/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning.c index dd0a4c177..a0a2d5cb5 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_interface_graphics_pack_warning.c @@ -25,14 +25,18 @@ void momentum_app_scene_interface_graphics_pack_warning_on_enter(void* context) "Info", momentum_app_scene_interface_graphics_pack_warning_widget_callback, app); - widget_add_text_box_element( - app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, "\e#Size Warning\e#", false); - - const char* body = - "Your selected pack contains Fonts & Icons, which remain loaded and use up memory"; + app->widget, 0, 0, 128, 22, AlignCenter, AlignCenter, "\e#Size Warning\e#", false); widget_add_string_multiline_element( - app->widget, 0, 18, AlignLeft, AlignTop, FontSecondary, body); + app->widget, + 64, + 33, + AlignCenter, + AlignCenter, + FontSecondary, + "Your selected pack contains\n" + "Fonts & Icons, which remain\n" + "loaded and use up memory."); view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewWidget); }