diff --git a/firmware/application/apps/ui_fileman.cpp b/firmware/application/apps/ui_fileman.cpp index 28dc5c55..d2d0ab84 100644 --- a/firmware/application/apps/ui_fileman.cpp +++ b/firmware/application/apps/ui_fileman.cpp @@ -62,7 +62,7 @@ std::string truncate(const fs::path& path, size_t max_length) { } // Inserts the entry into the entry list sorted directories first then by file name. -void insert_sorted(std::vector& entries, fileman_entry&& entry) { +void insert_sorted(std::list& entries, fileman_entry&& entry) { auto it = std::lower_bound( std::begin(entries), std::end(entries), entry, [](const fileman_entry& lhs, const fileman_entry& rhs) { @@ -149,31 +149,64 @@ namespace ui { void FileManBaseView::load_directory_contents(const fs::path& dir_path) { current_path = dir_path; entry_list.clear(); + menu_view.clear(); auto filtering = !extension_filter.empty(); bool cxx_file = path_iequal(cxx_ext, extension_filter); + bool wasfull = false; // reached the limit or not text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24)); for (const auto& entry : fs::directory_iterator(dir_path, u"*")) { // Hide files starting with '.' (hidden / tmp). + if (entry_list.size() >= max_items_loaded) { + wasfull = true; + break; // hard limit. size() complexyt constant in list from c++11, and for vector it constant. + } if (!show_hidden_files && is_hidden_file(entry.path())) continue; if (fs::is_regular_file(entry.status())) { if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path()))) - insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false}); + insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false}); } else if (fs::is_directory(entry.status())) { - insert_sorted(entry_list, {entry.path(), 0, true}); + insert_sorted(entry_list, {entry.path().string(), 0, true}); } } + // paginating + auto list_size = entry_list.size(); + nb_pages = 1 + (list_size / items_per_page); + size_t start = pagination * items_per_page; + size_t stop = start + items_per_page; + if (list_size > start) { + if (list_size < stop) + stop = list_size; + entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end()); + entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start)); + } + // Add "parent" directory if not at the root. - if (!dir_path.empty()) - entry_list.insert(entry_list.begin(), {parent_dir_path, 0, true}); + if (!dir_path.empty() && pagination == 0) + entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true}); + + // add next page + if (list_size > start + items_per_page) { + entry_list.push_back({str_next, (uint32_t)pagination + 1, true}); + } else { + // add warning if reached limit + if (wasfull) { + entry_list.push_back({str_full, max_items_loaded, true}); + } + } + + // add prev page + if (pagination > 0) { + entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true}); + } } fs::path FileManBaseView::get_selected_full_path() const { - if (get_selected_entry().path == parent_dir_path) + if (get_selected_entry().path == parent_dir_path.string()) return current_path.parent_path(); return current_path / get_selected_entry().path; @@ -181,7 +214,10 @@ fs::path FileManBaseView::get_selected_full_path() const { const fileman_entry& FileManBaseView::get_selected_entry() const { // TODO: return reference to an "empty" entry on OOB? - return entry_list[menu_view.highlighted_index()]; + auto it = entry_list.begin(); + if (menu_view.highlighted_index() >= 1) std::advance(it, menu_view.highlighted_index()); + return *it; + // return entry_list[menu_view.highlighted_index()]; } FileManBaseView::FileManBaseView( @@ -230,7 +266,7 @@ void FileManBaseView::push_dir(const fs::path& path) { current_path /= path; saved_index_stack.push_back(menu_view.highlighted_index()); menu_view.set_highlighted(0); - reload_current(); + reload_current(true); } } @@ -239,29 +275,56 @@ void FileManBaseView::pop_dir() { return; current_path = current_path.parent_path(); - reload_current(); + reload_current(true); menu_view.set_highlighted(saved_index_stack.back()); saved_index_stack.pop_back(); } +std::string get_extension(std::string t) { + const auto index = t.find_last_of(u'.'); + if (index == t.npos) { + return {}; + } else { + return t.substr(index); + } +} + +std::string get_stem(std::string t) { + const auto index = t.find_last_of(u'.'); + if (index == t.npos) { + return t; + } else { + return t.substr(0, index); + } +} +std::string get_filename(std::string _s) { + const auto index = _s.find_last_of("/"); + if (index == _s.npos) { + return _s; + } else { + return _s.substr(index + 1); + } +} void FileManBaseView::refresh_list() { if (on_refresh_widgets) on_refresh_widgets(false); - auto prev_highlight = menu_view.highlighted_index(); + prev_highlight = menu_view.highlighted_index(); menu_view.clear(); for (const auto& entry : entry_list) { - auto entry_name = truncate(entry.path, 20); + auto entry_name = std::string{entry.path.length() <= 20 ? entry.path : entry.path.substr(0, 20)}; if (entry.is_directory) { - auto size_str = - (entry.path == parent_dir_path) - ? "" - : to_string_dec_uint(file_count(current_path / entry.path)); + std::string size_str{}; + if (entry.path == str_next || entry.path == str_back) { + size_str = to_string_dec_uint(1 + entry.size) + "/" + to_string_dec_uint(nb_pages); // show computed number of pages + } else { + size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path)); + } menu_view.add_item( - {entry_name + std::string(21 - entry_name.length(), ' ') + size_str, + {entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str, ui::Color::yellow(), &bitmap_icon_dir, [this](KeyEvent key) { @@ -270,11 +333,11 @@ void FileManBaseView::refresh_list() { }}); } else { - const auto& assoc = get_assoc(entry.path.extension()); + const auto& assoc = get_assoc(get_extension(entry.path)); auto size_str = to_string_file_size(entry.size); menu_view.add_item( - {entry_name + std::string(21 - entry_name.length(), ' ') + size_str, + {entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str, assoc.color, assoc.icon, [this](KeyEvent key) { @@ -282,16 +345,13 @@ void FileManBaseView::refresh_list() { on_select_entry(key); }}); } - - // HACK: Should page menu items instead of limiting the number. - if (menu_view.item_count() >= max_items_shown) - break; } menu_view.set_highlighted(prev_highlight); } -void FileManBaseView::reload_current() { +void FileManBaseView::reload_current(bool reset_pagination) { + if (reset_pagination) pagination = 0; load_directory_contents(current_path); refresh_list(); } @@ -327,6 +387,21 @@ FileLoadView::FileLoadView( on_select_entry = [this](KeyEvent) { if (get_selected_entry().is_directory) { + if (get_selected_entry().path == str_full) { + return; + } + if (get_selected_entry().path == str_back) { + pagination--; + menu_view.set_highlighted(0); + reload_current(false); + return; + } + if (get_selected_entry().path == str_next) { + pagination++; + menu_view.set_highlighted(0); + reload_current(false); + return; + } push_dir(get_selected_entry().path); } else { if (on_changed) @@ -416,10 +491,10 @@ void FileManagerView::on_rename(std::string_view hint) { auto& entry = get_selected_entry(); // Append the hint to the filename stem as a rename suggestion. - name_buffer = entry.path.stem().string(); + name_buffer = get_stem(entry.path); if (!hint.empty()) name_buffer += "_" + std::string{hint}; - name_buffer += entry.path.extension().string(); + name_buffer += get_extension(entry.path); // Set the rename cursor to before the extension to make renaming simpler. uint32_t cursor_pos = (uint32_t)name_buffer.length(); @@ -440,11 +515,11 @@ void FileManagerView::on_rename(std::string_view hint) { auto new_name = renamed_path.replace_extension(partner.extension()); rename_file(partner, current_path / new_name); } - reload_current(); + reload_current(false); }); if (!has_partner) - reload_current(); + reload_current(false); }); } @@ -454,7 +529,7 @@ void FileManagerView::on_delete() { return; } - auto name = get_selected_entry().path.filename().string(); + auto name = get_filename(get_selected_entry().path); nav_.push( "Delete", "Delete " + name + "\nAre you sure?", YESNO, [this](bool choice) { @@ -466,11 +541,11 @@ void FileManagerView::on_delete() { [this](const fs::path& partner, bool should_delete) { if (should_delete) delete_file(partner); - reload_current(); + reload_current(true); }); if (!has_partner) - reload_current(); + reload_current(true); } }); } @@ -495,7 +570,7 @@ void FileManagerView::on_clean() { std::filesystem::path current_full_path = path_name / file_name; delete_file(current_full_path); } - reload_current(); + reload_current(true); } }); } @@ -504,7 +579,7 @@ void FileManagerView::on_new_dir() { name_buffer = ""; text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) { make_new_directory(current_path / dir_name); - reload_current(); + reload_current(true); }); } @@ -528,14 +603,14 @@ void FileManagerView::on_paste() { clipboard_path = fs::path{}; clipboard_mode = ClipboardMode::None; menu_view.focus(); - reload_current(); + reload_current(true); } void FileManagerView::on_new_file() { name_buffer = ""; text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) { make_new_file(current_path / file_name); - reload_current(); + reload_current(true); }); } @@ -558,7 +633,7 @@ bool FileManagerView::handle_file_open() { return true; } else if (path_iequal(bmp_ext, ext)) { nav_.push(path); - reload_current(); + reload_current(false); return true; } else if (path_iequal(rem_ext, ext)) { nav_.push(path); @@ -570,7 +645,7 @@ bool FileManagerView::handle_file_open() { bool FileManagerView::selected_is_valid() const { return !entry_list.empty() && - get_selected_entry().path != parent_dir_path; + get_selected_entry().path != parent_dir_path.string(); } FileManagerView::FileManagerView( @@ -602,7 +677,7 @@ FileManagerView::FileManagerView( }); menu_view.on_highlight = [this]() { - if (menu_view.highlighted_index() >= max_items_shown - 1) { + if (menu_view.highlighted_index() >= max_items_loaded - 1) { // todo check this if correct text_date.set_style(&Styles::red); text_date.set("Too many files!"); } else { @@ -617,13 +692,30 @@ FileManagerView::FileManagerView( refresh_list(); on_select_entry = [this](KeyEvent key) { - if (key == KeyEvent::Select && get_selected_entry().is_directory) { - push_dir(get_selected_entry().path); - } else if (key == KeyEvent::Select && handle_file_open()) { - return; - } else { - button_rename.focus(); + if (key == KeyEvent::Select) { + if (get_selected_entry().is_directory) { + if (get_selected_entry().path == str_full) { + return; + } + if (get_selected_entry().path == str_back) { + pagination--; + menu_view.set_highlighted(0); + reload_current(false); + return; + } + if (get_selected_entry().path == str_next) { + pagination++; + menu_view.set_highlighted(0); + reload_current(false); + return; + } + push_dir(get_selected_entry().path); + return; + } else if (handle_file_open()) { + return; + } } + button_rename.focus(); }; button_rename.on_select = [this]() { diff --git a/firmware/application/apps/ui_fileman.hpp b/firmware/application/apps/ui_fileman.hpp index 06ee942c..18c25d1f 100644 --- a/firmware/application/apps/ui_fileman.hpp +++ b/firmware/application/apps/ui_fileman.hpp @@ -31,7 +31,7 @@ namespace ui { struct fileman_entry { - std::filesystem::path path{}; + std::string path{}; uint32_t size{}; bool is_directory{}; }; @@ -61,8 +61,12 @@ class FileManBaseView : public View { void push_dir(const std::filesystem::path& path); protected: - static constexpr size_t max_filename_length = 64; - static constexpr size_t max_items_shown = 100; + uint32_t prev_highlight = 0; + uint8_t pagination = 0; + uint8_t nb_pages = 1; + static constexpr size_t max_filename_length = 20; + static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't load more + static constexpr size_t items_per_page = 20; struct file_assoc_t { std::filesystem::path extension; @@ -87,7 +91,7 @@ class FileManBaseView : public View { void pop_dir(); void refresh_list(); - void reload_current(); + void reload_current(bool reset_pagination = false); void load_directory_contents(const std::filesystem::path& dir_path); const file_assoc_t& get_assoc(const std::filesystem::path& ext) const; @@ -97,11 +101,14 @@ class FileManBaseView : public View { std::function on_select_entry{nullptr}; std::function on_refresh_widgets{nullptr}; + const std::string str_back{"<--"}; + const std::string str_next{"-->"}; + const std::string str_full{"Can't load more.."}; const std::filesystem::path parent_dir_path{u".."}; std::filesystem::path current_path{u""}; std::filesystem::path extension_filter{u""}; - std::vector entry_list{}; + std::list entry_list{}; std::vector saved_index_stack{}; bool show_hidden_files{false};