diff --git a/firmware/application/apps/ui_fileman.cpp b/firmware/application/apps/ui_fileman.cpp index 554895cd5..edb9368e1 100644 --- a/firmware/application/apps/ui_fileman.cpp +++ b/firmware/application/apps/ui_fileman.cpp @@ -390,6 +390,7 @@ std::string get_stem(std::string t) { return t.substr(0, index); } } + std::string get_filename(std::string _s) { const auto index = _s.find_last_of("/"); if (index == _s.npos) { @@ -398,6 +399,7 @@ std::string get_filename(std::string _s) { return _s.substr(index + 1); } } + void FileManBaseView::refresh_list() { if (on_refresh_widgets) on_refresh_widgets(false); @@ -408,8 +410,6 @@ void FileManBaseView::refresh_list() { menu_view.clear(); for (const auto& entry : entry_list) { - auto entry_name = std::string{entry.path.length() <= max_filename_length ? entry.path : entry.path.substr(0, max_filename_length)}; - if (entry.is_directory) { std::string size_str{}; if (entry.path == str_next || entry.path == str_back) { @@ -418,8 +418,10 @@ void FileManBaseView::refresh_list() { size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path)); } + // CRITICAL: Use "\t" (Tab) to separate the path and the size! + // Do not use spaces or padding here. The menu will handle the alignment. menu_view.add_item( - {entry_name.substr(0, max_filename_length) + std::string((max_filename_length + 1) - entry_name.length(), ' ') + size_str, + {entry.path + "\t" + size_str, Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_dir, [this](KeyEvent key) { @@ -431,8 +433,9 @@ void FileManBaseView::refresh_list() { const auto& assoc = get_assoc(get_extension(entry.path)); auto size_str = to_string_file_size(entry.size); + // CRITICAL: Use "\t" (Tab) to separate the path and the size! menu_view.add_item( - {entry_name.substr(0, max_filename_length) + std::string((max_filename_length + 1) - entry_name.length(), ' ') + size_str, + {entry.path + "\t" + size_str, assoc.color, assoc.icon, [this](KeyEvent key) { diff --git a/firmware/application/apps/ui_flash_utility.cpp b/firmware/application/apps/ui_flash_utility.cpp index c9ff3f081..2c8486c15 100644 --- a/firmware/application/apps/ui_flash_utility.cpp +++ b/firmware/application/apps/ui_flash_utility.cpp @@ -32,7 +32,7 @@ static const char* hackrf_magic = "HACKRFFW"; #define FIRST_CHECKSUM_NIGHTLY 240125 Thread* FlashUtilityView::thread{nullptr}; -static constexpr size_t max_filename_length = 26; +static constexpr size_t max_filename_length = 60; // max length of filename bool valid_firmware_file(std::filesystem::path::string_type path) { File firmware_file; diff --git a/firmware/application/ui/ui_menu.cpp b/firmware/application/ui/ui_menu.cpp index 98e2988bf..c775ee46a 100644 --- a/firmware/application/ui/ui_menu.cpp +++ b/firmware/application/ui/ui_menu.cpp @@ -22,6 +22,7 @@ #include "ui_menu.hpp" #include "rtc_time.hpp" +#include namespace ui { @@ -29,57 +30,112 @@ namespace ui { void MenuItemView::set_item(MenuItem* item_) { item = item_; + scroll_offset = 0; + can_scroll = false; } void MenuItemView::highlight() { set_highlighted(true); + scroll_offset = 0; set_dirty(); } void MenuItemView::unhighlight() { set_highlighted(false); + scroll_offset = 0; set_dirty(); } -void MenuItemView::paint(Painter& painter) { - Coord offset_x{}; +void MenuItemView::set_scroll_offset(size_t offset) { + if (can_scroll && scroll_offset != offset) { + scroll_offset = offset; + set_dirty(); + } +} +void MenuItemView::paint(Painter& painter) { if (!item) return; - const auto r = screen_rect(); - + const auto rect = screen_rect(); const auto paint_style = (highlighted() && (parent()->has_focus() || keep_highlight)) ? style().invert() : style(); - const auto font_height = paint_style.font.line_height(); + const int char_width = paint_style.font.char_width(); + const int line_height = paint_style.font.line_height(); + + if (char_width == 0 || line_height == 0) return; + + const int margin_x = char_width / 2; ui::Color final_item_color = (highlighted() && (parent()->has_focus() || keep_highlight)) ? paint_style.foreground : item->color; ui::Color final_bg_color = (highlighted() && (parent()->has_focus() || keep_highlight)) ? item->color : paint_style.background; if (final_item_color.v == final_bg_color.v) final_item_color = paint_style.foreground; - painter.fill_rectangle( - r, - final_bg_color); + painter.fill_rectangle(rect, final_bg_color); + + Coord offset_x = 0; if (item->bitmap) { painter.draw_bitmap( - {r.location().x() + 4, r.location().y() + 4}, + {rect.location().x() + 4, rect.location().y() + 4}, *item->bitmap, final_item_color, final_bg_color); offset_x = 26; - } else + } else { offset_x = 0; + } - Style text_style{ - .font = paint_style.font, - .background = final_bg_color, - .foreground = final_item_color}; + Style text_style{.font = paint_style.font, .background = final_bg_color, .foreground = final_item_color}; - painter.draw_string( - {r.location().x() + offset_x, r.location().y() + (r.size().height() - font_height) / 2}, - text_style, - item->text); + std::string_view full_text = item->text; + std::string_view file_name = full_text; + std::string_view file_size_text = ""; + + auto tab_pos = full_text.find('\t'); + if (tab_pos != std::string_view::npos) { + file_size_text = full_text.substr(tab_pos + 1); + file_name = full_text.substr(0, tab_pos); + } + + int available_width_px = rect.width() - offset_x; + if (available_width_px <= 0) return; + + size_t max_name_chars = available_width_px / char_width; + if (max_name_chars == 0) return; + + if (!file_size_text.empty() && max_name_chars > file_size_text.length()) { + max_name_chars = max_name_chars - file_size_text.length() - 1; + } + + if (max_name_chars == 0) return; + + can_scroll = (file_name.length() > max_name_chars); + + std::string_view display_name = file_name; + if (file_name.length() > max_name_chars) { + if (highlighted()) { + size_t max_scroll = file_name.length() - max_name_chars; + size_t actual_offset = scroll_offset % (max_scroll + 1); + display_name = file_name.substr(actual_offset, max_name_chars); + } else { + display_name = file_name.substr(0, max_name_chars); + } + } + + Coord text_y = rect.location().y() + (rect.height() - line_height) / 2; + + painter.draw_string({rect.location().x() + offset_x, text_y}, text_style, display_name); + + if (!file_size_text.empty()) { + int file_size_width = static_cast(file_size_text.length()) * char_width; + int file_size_x = rect.width() - file_size_width - margin_x; + + // Csak akkor rajzoljuk ki, ha pozitív koordinátára esik és nem takarja el az ikont/nevet + if (file_size_x > offset_x) { + painter.draw_string({rect.location().x() + static_cast(file_size_x), text_y}, text_style, file_size_text); + } + } } /* MenuView **************************************************************/ @@ -134,6 +190,20 @@ void MenuView::set_parent_rect(const Rect new_parent_rect) { update_items(); } +void MenuView::increment_scroll() { + if (menu_items.empty()) return; + const size_t view_index = highlighted_item - offset; + if (view_index >= menu_item_views.size()) { + return; + } + // The MenuView checks if the currently highlighted item needs scrolling + scroll_offset++; + auto* view = item_view(view_index); + if (view) { + view->set_scroll_offset(scroll_offset); + } +} + void MenuView::on_tick_second() { if (more && blink) arrow_more.set_foreground(Theme::getInstance()->bg_darkest->foreground); @@ -141,8 +211,9 @@ void MenuView::on_tick_second() { arrow_more.set_foreground(Theme::getInstance()->bg_darkest->background); blink = !blink; - arrow_more.set_dirty(); + + increment_scroll(); } void MenuView::clear() { @@ -153,6 +224,7 @@ void MenuView::clear() { menu_items.shrink_to_fit(); highlighted_item = 0; offset = 0; + reset_scroll(); } size_t MenuView::item_count() const { @@ -225,6 +297,8 @@ bool MenuView::set_highlighted(int32_t new_value) { item_view(highlighted_item - offset)->highlight(); } + reset_scroll(); + if (on_highlight) on_highlight(); diff --git a/firmware/application/ui/ui_menu.hpp b/firmware/application/ui/ui_menu.hpp index 25597fcfc..a4faec372 100644 --- a/firmware/application/ui/ui_menu.hpp +++ b/firmware/application/ui/ui_menu.hpp @@ -67,9 +67,13 @@ class MenuItemView : public Widget { void highlight(); void unhighlight(); + void set_scroll_offset(size_t offset); + private: MenuItem* item{nullptr}; bool keep_highlight = false; + size_t scroll_offset{0}; + bool can_scroll{false}; }; class MenuView : public View { @@ -108,6 +112,10 @@ class MenuView : public View { void update_items(); void on_tick_second(); + // SCROLLING + void increment_scroll(); + inline void reset_scroll() { scroll_offset = 0; } + bool keep_highlight{false}; SignalToken signal_token_tick_second{}; @@ -126,6 +134,8 @@ class MenuView : public View { size_t displayed_max{0}; size_t highlighted_item{0}; size_t offset{0}; + + size_t scroll_offset{0}; }; } /* namespace ui */