diff --git a/Data/system/fonts/MaterialSymbolsRounded.codepoints b/Data/system/fonts/MaterialSymbolsRounded.codepoints index 39ee896a7..dca40c2a0 100644 --- a/Data/system/fonts/MaterialSymbolsRounded.codepoints +++ b/Data/system/fonts/MaterialSymbolsRounded.codepoints @@ -21,25 +21,38 @@ check e668 circle ef4a close e5cd cloud f15c +content_copy e14d +content_cut e14e +content_paste e14f +create_new_folder e2cc delete e92e deployed_code f720 +description e873 devices e326 display_settings eb97 download f090 +draft e66d +edit f097 edit_note e745 +eject e8fb electric_bolt ec1c folder e2c7 +folder_open e2c8 forum e8af gamepad e30f +hard_disk f3da help e8fd hub e9f4 image e3f4 +info e88e keyboard_alt f028 keyboard_arrow_up e316 language ea07 lightbulb e90f lightstrip fff75 +link e250 lists e9b9 +location_off e0c7 location_on f1db logo_dev ead6 mail e159 @@ -54,11 +67,16 @@ network_wifi_2_bar ebd6 network_wifi_3_bar ebe1 note_add e89c palette e40a +phone_android f2db +play_arrow e037 power_settings_new f8c7 refresh e5d5 +save e161 +save_as eb60 sd_card e623 sd_card_alert f057 search ef7a +select_window_2 f4c8 settings e8b8 signal_wifi_0_bar f0b0 signal_wifi_4_bar f065 diff --git a/Data/system/fonts/MaterialSymbolsRounded.ttf b/Data/system/fonts/MaterialSymbolsRounded.ttf index 039d1b3d0..2f507af2c 100644 Binary files a/Data/system/fonts/MaterialSymbolsRounded.ttf and b/Data/system/fonts/MaterialSymbolsRounded.ttf differ diff --git a/Devices/m5stack-tab5/Source/devices/devices_v2.cpp b/Devices/m5stack-tab5/Source/devices/devices_v2.cpp index 0db9ce0ab..4a5aa4eea 100644 --- a/Devices/m5stack-tab5/Source/devices/devices_v2.cpp +++ b/Devices/m5stack-tab5/Source/devices/devices_v2.cpp @@ -48,8 +48,8 @@ void tab5_create_devices_v2(Device* i2c0) { .ldo_voltage_mv = 2500, .dsi_bus_id = 0, .num_data_lanes = 2, - .lane_bit_rate_mbps = 965, // ST7123 lane bitrate per M5Stack BSP - .dpi_clock_freq_mhz = 70, + .lane_bit_rate_mbps = 1040, // Deviates from M5Stack BSP to fix flickering + .dpi_clock_freq_mhz = 80, .hsync_pulse_width = 2, .hsync_back_porch = 40, .hsync_front_porch = 40, diff --git a/Devices/m5stack-tab5/Source/devices/devices_v3.cpp b/Devices/m5stack-tab5/Source/devices/devices_v3.cpp index cc4267b0b..4d391624a 100644 --- a/Devices/m5stack-tab5/Source/devices/devices_v3.cpp +++ b/Devices/m5stack-tab5/Source/devices/devices_v3.cpp @@ -49,8 +49,8 @@ void tab5_create_devices_v3(Device* i2c0) { .ldo_voltage_mv = 2500, .dsi_bus_id = 0, .num_data_lanes = 2, - .lane_bit_rate_mbps = 965, // ST7121 lane bitrate per M5Stack BSP (same as ST7123) - .dpi_clock_freq_mhz = 70, + .lane_bit_rate_mbps = 1040, // Deviates from M5Stack BSP to fix flickering + .dpi_clock_freq_mhz = 80, .hsync_pulse_width = 2, .hsync_back_porch = 40, .hsync_front_porch = 40, diff --git a/Modules/lvgl-module/generate-icons.py b/Modules/lvgl-module/generate-icons.py index c983bd9cb..d0727d0ba 100755 --- a/Modules/lvgl-module/generate-icons.py +++ b/Modules/lvgl-module/generate-icons.py @@ -194,6 +194,10 @@ def generate_icon_names(codepoint_map: dict, codepoint_names: list, variable_nam "play_arrow", "eject", "create_new_folder", + # Notes app + "save", + "save_as", + "folder_open", ] # Get more from https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded diff --git a/Modules/lvgl-module/include/lvgl/icons/names.h b/Modules/lvgl-module/include/lvgl/icons/names.h index 9d08db392..05dbaacbf 100644 --- a/Modules/lvgl-module/include/lvgl/icons/names.h +++ b/Modules/lvgl-module/include/lvgl/icons/names.h @@ -9,7 +9,7 @@ extern "C" { #endif /** Changes whenever the icon TTF subset, its codepoints file or the icon name lists change */ -#define LVGL_ICON_FONT_VERSION 0xB2450B17u +#define LVGL_ICON_FONT_VERSION 0xD5578751u // Icon names per icon font, resolved through the .codepoints file next to the TTF extern const char* const lvgl_icon_shared_names[]; diff --git a/Modules/lvgl-module/include/lvgl/icons/shared.h b/Modules/lvgl-module/include/lvgl/icons/shared.h index fa63b1527..516ab56e2 100644 --- a/Modules/lvgl-module/include/lvgl/icons/shared.h +++ b/Modules/lvgl-module/include/lvgl/icons/shared.h @@ -66,3 +66,6 @@ #define LVGL_ICON_SHARED_PLAY_ARROW "\xEE\x80\xB7" #define LVGL_ICON_SHARED_EJECT "\xEE\xA3\xBB" #define LVGL_ICON_SHARED_CREATE_NEW_FOLDER "\xEE\x8B\x8C" +#define LVGL_ICON_SHARED_SAVE "\xEE\x85\xA1" +#define LVGL_ICON_SHARED_SAVE_AS "\xEE\xAD\xA0" +#define LVGL_ICON_SHARED_FOLDER_OPEN "\xEE\x8B\x88" diff --git a/Modules/lvgl-module/source/icon_names.c b/Modules/lvgl-module/source/icon_names.c index e0cf41aa8..d5df0af30 100644 --- a/Modules/lvgl-module/source/icon_names.c +++ b/Modules/lvgl-module/source/icon_names.c @@ -68,6 +68,9 @@ const char* const lvgl_icon_shared_names[] = { "play_arrow", "eject", "create_new_folder", + "save", + "save_as", + "folder_open", }; const size_t lvgl_icon_shared_name_count = sizeof(lvgl_icon_shared_names) / sizeof(lvgl_icon_shared_names[0]); diff --git a/Tactility/Source/app/notes/Notes.cpp b/Tactility/Source/app/notes/Notes.cpp index 802487923..7d40de977 100644 --- a/Tactility/Source/app/notes/Notes.cpp +++ b/Tactility/Source/app/notes/Notes.cpp @@ -15,7 +15,13 @@ #include #include +#include +#include +#include +#include #include +#include +#include #include #include #include @@ -33,7 +39,9 @@ struct Context { TaskEventGroup* eventGroup = nullptr; lv_obj_t* uiCurrentFileName = nullptr; - lv_obj_t* uiDropDownMenu = nullptr; + lv_obj_t* uiToolbar = nullptr; + lv_obj_t* uiOverflowButton = nullptr; + lv_obj_t* uiOverlay = nullptr; lv_obj_t* uiNoteText = nullptr; std::string filePath; @@ -77,50 +85,100 @@ bool saveFile(Context* ctx, const std::string& path) { return result; } -void appNotesEventCb(lv_event_t* e) { +void hideOverlay(Context* ctx) { + if (lv_obj_is_hidden(ctx->uiOverlay)) { + return; + } + lv_obj_set_hidden(ctx->uiOverlay, true); + + // Keys continue on the button that opened the overlay + lv_group_t* group = lv_group_get_default(); + if (group != nullptr && lv_group_get_focused(group) == ctx->uiOverlay) { + lv_group_focus_obj(ctx->uiToolbar); + lv_gridnav_set_focused(ctx->uiToolbar, ctx->uiOverflowButton, LV_ANIM_OFF); + } +} + +void onNewPressed(lv_event_t* e) { auto* ctx = static_cast(lv_event_get_user_data(e)); - lv_event_code_t code = lv_event_get_code(e); - lv_obj_t* obj = lv_event_get_target_obj(e); - - if (code == LV_EVENT_VALUE_CHANGED) { - if (obj == ctx->uiDropDownMenu) { - switch (lv_dropdown_get_selected(obj)) { - case 0: // New - resetFileContent(ctx); - break; - case 1: // Save - if (!ctx->filePath.empty()) { - lvgl_lock(); - ctx->saveBuffer = lv_textarea_get_text(ctx->uiNoteText); - lvgl_unlock(); - saveFile(ctx, ctx->filePath); - } - break; - case 2: // Save as... - lvgl_lock(); - ctx->saveBuffer = lv_textarea_get_text(ctx->uiNoteText); - lvgl_unlock(); - ctx->saveFileLaunchId = fileselection::startForExistingOrNewFile(ctx->appInstanceId, ctx->saveResultStream, ctx->saveResultBuffer, sizeof(ctx->saveResultBuffer), ctx->eventGroup); - LOG_I(TAG, "launched with id %u", ctx->saveFileLaunchId); - break; - case 3: // Load - ctx->loadFileLaunchId = fileselection::startForExistingFile(ctx->appInstanceId, ctx->loadResultStream, ctx->loadResultBuffer, sizeof(ctx->loadResultBuffer), ctx->eventGroup); - LOG_I(TAG, "launched with id %u", ctx->loadFileLaunchId); - break; - } - } else { - auto* cont = lv_event_get_current_target_obj(e); - if (obj == cont) return; - if (lv_obj_get_child(cont, 1)) { - ctx->saveFileLaunchId = fileselection::startForExistingOrNewFile(ctx->appInstanceId, ctx->saveResultStream, ctx->saveResultBuffer, sizeof(ctx->saveResultBuffer), ctx->eventGroup); - LOG_I(TAG, "launched with id %u", ctx->saveFileLaunchId); - } else { //Reset - resetFileContent(ctx); - } + hideOverlay(ctx); + resetFileContent(ctx); +} + +void onSavePressed(lv_event_t* e) { + auto* ctx = static_cast(lv_event_get_user_data(e)); + hideOverlay(ctx); + ctx->saveBuffer = lv_textarea_get_text(ctx->uiNoteText); + saveFile(ctx, ctx->filePath); +} + +void onSaveAsPressed(lv_event_t* e) { + auto* ctx = static_cast(lv_event_get_user_data(e)); + hideOverlay(ctx); + ctx->saveBuffer = lv_textarea_get_text(ctx->uiNoteText); + ctx->saveFileLaunchId = fileselection::startForExistingOrNewFile(ctx->appInstanceId, ctx->saveResultStream, ctx->saveResultBuffer, sizeof(ctx->saveResultBuffer), ctx->eventGroup); + LOG_I(TAG, "launched with id %u", ctx->saveFileLaunchId); +} + +void onOpenPressed(lv_event_t* e) { + auto* ctx = static_cast(lv_event_get_user_data(e)); + hideOverlay(ctx); + ctx->loadFileLaunchId = fileselection::startForExistingFile(ctx->appInstanceId, ctx->loadResultStream, ctx->loadResultBuffer, sizeof(ctx->loadResultBuffer), ctx->eventGroup); + LOG_I(TAG, "launched with id %u", ctx->loadFileLaunchId); +} + +void addOverlayButton(Context* ctx, const char* icon, lv_event_cb_t callback) { + auto* button = lvgl_icon_button_create(ctx->uiOverlay); + auto* label = lv_label_create(button); + lv_obj_set_style_text_font(label, lvgl_get_shared_icon_default_font(), LV_STATE_DEFAULT); + lv_label_set_text(label, icon); + lv_obj_add_event_cb(button, callback, LV_EVENT_SHORT_CLICKED, ctx); +} + +void showOverlay(Context* ctx) { + lv_obj_clean(ctx->uiOverlay); + addOverlayButton(ctx, LVGL_ICON_SHARED_NOTE_ADD, onNewPressed); + // Saving needs a file, which "save as" picks + if (!ctx->filePath.empty()) { + addOverlayButton(ctx, LVGL_ICON_SHARED_SAVE, onSavePressed); + } + addOverlayButton(ctx, LVGL_ICON_SHARED_SAVE_AS, onSaveAsPressed); + addOverlayButton(ctx, LVGL_ICON_SHARED_FOLDER_OPEN, onOpenPressed); + + lv_obj_set_hidden(ctx->uiOverlay, false); + lv_obj_align_to(ctx->uiOverlay, ctx->uiToolbar, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + lv_group_t* group = lv_group_get_default(); + if (group != nullptr) { + lv_group_focus_obj(ctx->uiOverlay); + lv_gridnav_set_focused(ctx->uiOverlay, lv_obj_get_child(ctx->uiOverlay, 0), LV_ANIM_OFF); + // Touch shows no selection, until a key moves the focus + lv_indev_t* indev = lv_indev_active(); + if (indev != nullptr && lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER) { + lvgl_focus_hide_key_selection(group); } } } +void onOverflowPressed(lv_event_t* e) { + auto* ctx = static_cast(lv_event_get_user_data(e)); + if (lv_obj_is_hidden(ctx->uiOverlay)) { + showOverlay(ctx); + } else { + hideOverlay(ctx); + } +} + +void onOverlayKey(lv_event_t* e) { + if (lv_event_get_key(e) == LV_KEY_ESC) { + hideOverlay(static_cast(lv_event_get_user_data(e))); + } +} + +void onNoteTextPressed(lv_event_t* e) { + hideOverlay(static_cast(lv_event_get_user_data(e))); +} + void createWidgets(lv_obj_t* parent, void* userData) { auto* ctx = static_cast(userData); @@ -128,16 +186,10 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); - lv_obj_t* toolbar = lvgl_toolbar_create(parent, "Notes"); - lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); - - ctx->uiDropDownMenu = lv_dropdown_create(toolbar); - lv_dropdown_set_options(ctx->uiDropDownMenu, LV_SYMBOL_FILE " New File\n" LV_SYMBOL_SAVE " Save\n" LV_SYMBOL_SAVE " Save As...\n" LV_SYMBOL_DIRECTORY " Open File"); - lv_dropdown_set_text(ctx->uiDropDownMenu, "Menu"); - lv_dropdown_set_symbol(ctx->uiDropDownMenu, LV_SYMBOL_DOWN); - lv_dropdown_set_selected_highlight(ctx->uiDropDownMenu, false); - lv_obj_align(ctx->uiDropDownMenu, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(ctx->uiDropDownMenu, appNotesEventCb, LV_EVENT_VALUE_CHANGED, ctx); + ctx->uiToolbar = lvgl_toolbar_create(parent, "Notes"); + lv_obj_align(ctx->uiToolbar, LV_ALIGN_TOP_MID, 0, 0); + ctx->uiOverflowButton = lvgl_toolbar_add_text_button_action(ctx->uiToolbar, LVGL_ICON_SHARED_MORE_VERT, onOverflowPressed, ctx); + lv_obj_set_style_text_font(ctx->uiOverflowButton, lvgl_get_shared_icon_default_font(), LV_STATE_DEFAULT); lv_obj_t* wrapper = lv_obj_create(parent); lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); @@ -158,17 +210,18 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_style_bg_color(ctx->uiNoteText, lv_color_hex(0x262626), LV_PART_MAIN); } lv_textarea_set_placeholder_text(ctx->uiNoteText, "Notes..."); + lv_obj_add_event_cb(ctx->uiNoteText, onNoteTextPressed, LV_EVENT_PRESSED, ctx); lv_obj_t* footer = lv_obj_create(wrapper); lv_obj_set_flex_flow(footer, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(footer, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + // Shows the screen background + lv_obj_set_style_bg_opa(footer, LV_OPA_TRANSP, LV_PART_MAIN); if (lv_display_get_color_format(lv_obj_get_display(parent)) == LV_COLOR_FORMAT_L8) { - lv_obj_set_style_bg_color(footer, lv_color_hex(0xEEEEEE), LV_PART_MAIN); lv_obj_set_style_border_width(footer, 1, LV_PART_MAIN); lv_obj_set_style_border_color(footer, lv_theme_get_color_secondary(footer), LV_PART_MAIN); lv_obj_set_style_border_side(footer, LV_BORDER_SIDE_TOP, LV_PART_MAIN); } else { - lv_obj_set_style_bg_color(footer, lv_color_hex(0x262626), LV_PART_MAIN); lv_obj_set_style_border_width(footer, 0, LV_PART_MAIN); } lv_obj_set_width(footer, LV_PCT(100)); @@ -183,6 +236,19 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_label_set_text(ctx->uiCurrentFileName, "Untitled"); lv_obj_align(ctx->uiCurrentFileName, LV_ALIGN_CENTER, 0, 0); + // Floating over the text area, below the toolbar + ctx->uiOverlay = lvgl_card_create(parent); + lv_obj_set_floating(ctx->uiOverlay, true); + lv_obj_set_size(ctx->uiOverlay, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(ctx->uiOverlay, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(ctx->uiOverlay, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_scrollable(ctx->uiOverlay, false); + lvgl_obj_add_edge_padding(ctx->uiOverlay); + // The arrow keys move between the overlay's buttons as one row + lvgl_grid_navigation_add(ctx->uiOverlay); + lv_obj_add_event_cb(ctx->uiOverlay, onOverlayKey, LV_EVENT_KEY, ctx); + lv_obj_set_hidden(ctx->uiOverlay, true); + if (!ctx->filePath.empty()) { openFile(ctx, ctx->filePath); }