From a832bd433dfaf690a9da2e0a39be1094c5817db0 Mon Sep 17 00:00:00 2001 From: Kyle Reed Date: Thu, 11 May 2023 13:46:38 -0700 Subject: [PATCH] Fileman icon toolbar (#975) * Add cut/copy/paste menu with icons * Add icons, file limit to fileman --- firmware/application/apps/ui_fileman.cpp | 106 +++++++++++------ firmware/application/apps/ui_fileman.hpp | 73 +++++++++--- firmware/application/apps/ui_settings.cpp | 2 +- firmware/application/bitmap.hpp | 131 ++++++++++++++++++++++ firmware/application/file.cpp | 31 +++-- firmware/application/file.hpp | 7 +- firmware/application/ui/ui_menu.cpp | 4 + firmware/application/ui/ui_menu.hpp | 1 + firmware/common/ui_widget.cpp | 11 +- firmware/common/ui_widget.hpp | 3 +- firmware/graphics/icon_copy.png | Bin 0 -> 293 bytes firmware/graphics/icon_cut.png | Bin 0 -> 286 bytes firmware/graphics/icon_new_dir.png | Bin 0 -> 258 bytes firmware/graphics/icon_new_file.png | Bin 0 -> 253 bytes firmware/graphics/icon_paste.png | Bin 0 -> 286 bytes firmware/graphics/icon_trash.png | Bin 0 -> 231 bytes 16 files changed, 306 insertions(+), 63 deletions(-) create mode 100644 firmware/graphics/icon_copy.png create mode 100644 firmware/graphics/icon_cut.png create mode 100644 firmware/graphics/icon_new_dir.png create mode 100644 firmware/graphics/icon_new_file.png create mode 100644 firmware/graphics/icon_paste.png create mode 100644 firmware/graphics/icon_trash.png diff --git a/firmware/application/apps/ui_fileman.cpp b/firmware/application/apps/ui_fileman.cpp index 9ce4b307..58f4e895 100644 --- a/firmware/application/apps/ui_fileman.cpp +++ b/firmware/application/apps/ui_fileman.cpp @@ -22,7 +22,6 @@ /* TODO: * - Paging menu items - * - Copy/Move */ #include @@ -146,6 +145,22 @@ bool partner_file_prompt( return true; } +fs::path get_unique_filename(const fs::path& path, const fs::path& file) { + auto stem = file.stem(); + auto ext = file.extension(); + auto serial = 1; + fs::path new_path = file; + + while (fs::file_exists(path / new_path)) { + new_path = stem; + new_path += fs::path{ u"_" }; + new_path += to_string_dec_int(serial++); + new_path += ext; + } + + return new_path; +} + } namespace ui { @@ -198,6 +213,7 @@ FileManBaseView::FileManBaseView( add_children({ &labels, &text_current, + &text_info, &button_exit }); @@ -287,8 +303,13 @@ void FileManBaseView::refresh_list() { } }); } + + // HACK: Should page menu items instead of limiting the number. + if (menu_view.item_count() >= max_items_shown) + break; } - + + text_info.set(menu_view.item_count() >= max_items_shown ? "Too many files!" : ""); menu_view.set_highlighted(prev_highlight); } @@ -465,26 +486,30 @@ void FileManagerView::on_new_dir() { }); } +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(); + }); +} + void FileManagerView::on_paste() { - auto stem = copy_path.stem(); - auto ext = copy_path.extension(); - auto serial = 1; - fs::path new_path = copy_path.filename(); - - // Create a unique name. - while (fs::file_exists(current_path / new_path)) { - new_path = stem; - new_path += fs::path{ u"_" }; - new_path += to_string_dec_int(serial++); - new_path += ext; - } - // TODO: handle partner file. Need to fix nav stack first. - auto result = copy_file(copy_path, current_path / new_path); + auto new_name = get_unique_filename(current_path, clipboard_path.filename()); + fs::filesystem_error result; + + if (clipboard_mode == ClipboardMode::Cut) + result = rename_file(clipboard_path, current_path / new_name); + + else if (clipboard_mode == ClipboardMode::Copy) + result = copy_file(clipboard_path, current_path / new_name); + if (result.code() != FR_OK) nav_.display_modal("Paste Failed", result.what()); - copy_path = fs::path{ }; + clipboard_path = fs::path{ }; + clipboard_mode = ClipboardMode::None; menu_view.focus(); reload_current(); } @@ -497,9 +522,12 @@ bool FileManagerView::selected_is_valid() const { void FileManagerView::refresh_widgets(const bool v) { button_rename.hidden(v); button_delete.hidden(v); - button_new_dir.hidden(v); + button_cut.hidden(v); button_copy.hidden(v); button_paste.hidden(v); + button_new_dir.hidden(v); + button_new_file.hidden(v); + set_dirty(); } @@ -521,13 +549,14 @@ FileManagerView::FileManagerView( &text_date, &button_rename, &button_delete, - &button_new_dir, + &button_cut, &button_copy, - &button_paste + &button_paste, + &button_new_dir, + &button_new_file }); menu_view.on_highlight = [this]() { - // TODO: enable/disable buttons. if (selected_is_valid()) text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path()))); else @@ -544,32 +573,45 @@ FileManagerView::FileManagerView( } }; - button_rename.on_select = [this](Button&) { + button_rename.on_select = [this]() { if (selected_is_valid()) on_rename(); }; - button_delete.on_select = [this](Button&) { + button_delete.on_select = [this]() { if (selected_is_valid()) on_delete(); }; - button_new_dir.on_select = [this](Button&) { - on_new_dir(); + button_cut.on_select = [this]() { + if (selected_is_valid() && !get_selected_entry().is_directory) { + clipboard_path = get_selected_full_path(); + clipboard_mode = ClipboardMode::Cut; + } else + nav_.display_modal("Cut", "Can't cut that."); }; - button_copy.on_select = [this](Button&) { - if (selected_is_valid() && !get_selected_entry().is_directory) - copy_path = get_selected_full_path(); - else + button_copy.on_select = [this]() { + if (selected_is_valid() && !get_selected_entry().is_directory) { + clipboard_path = get_selected_full_path(); + clipboard_mode = ClipboardMode::Copy; + } else nav_.display_modal("Copy", "Can't copy that."); }; - button_paste.on_select = [this](Button&) { - if (!copy_path.empty()) + button_paste.on_select = [this]() { + if (clipboard_mode != ClipboardMode::None) on_paste(); else - nav_.display_modal("Paste", "Copy a file first."); + nav_.display_modal("Paste", "Cut or copy a file first."); + }; + + button_new_dir.on_select = [this]() { + on_new_dir(); + }; + + button_new_file.on_select = [this]() { + on_new_file(); }; } diff --git a/firmware/application/apps/ui_fileman.hpp b/firmware/application/apps/ui_fileman.hpp index 220a7f9c..dc66581b 100644 --- a/firmware/application/apps/ui_fileman.hpp +++ b/firmware/application/apps/ui_fileman.hpp @@ -42,6 +42,12 @@ enum class EmptyReason : uint8_t { NoSDC }; +enum class ClipboardMode : uint8_t { + None, + Cut, + Copy +}; + class FileManBaseView : public View { public: FileManBaseView( @@ -56,6 +62,7 @@ public: protected: static constexpr size_t max_filename_length = 64; + static constexpr size_t max_items_shown = 128; struct file_assoc_t { std::filesystem::path extension; @@ -109,6 +116,12 @@ protected: { 0, 2 * 8, 240, 26 * 8 }, true }; + + // HACK: for item count limit. + Text text_info { + { 1 * 8, 35 * 8, 15 * 8, 16 }, + "" + }; Button button_exit { { 21 * 8, 34 * 8, 9 * 8, 32 }, @@ -194,13 +207,15 @@ public: private: // Passed by ref to other views needing lifetime extension. std::string name_buffer { }; - std::filesystem::path copy_path { }; + std::filesystem::path clipboard_path { }; + ClipboardMode clipboard_mode { ClipboardMode::None }; void refresh_widgets(const bool v); void on_rename(); void on_delete(); - void on_new_dir(); void on_paste(); + void on_new_dir(); + void on_new_file(); // True if the selected entry is a real file item. bool selected_is_valid() const; @@ -214,29 +229,53 @@ private: "" }; - Button button_rename { - { 0 * 8, 29 * 8, 9 * 8, 32 }, - "Rename" + NewButton button_rename { + { 0 * 8, 29 * 8, 4 * 8, 32 }, + { }, + &bitmap_icon_rename, + Color::dark_blue() }; - Button button_delete { - { 21 * 4, 29 * 8, 9 * 8, 32 }, - "Delete" + NewButton button_delete { + { 4 * 8, 29 * 8, 4 * 8, 32 }, + { }, + &bitmap_icon_trash, + Color::red() }; - Button button_new_dir { - { 21 * 8, 29 * 8, 9 * 8, 32 }, - "New Dir" + NewButton button_cut { + { 9 * 8, 29 * 8, 4 * 8, 32 }, + { }, + &bitmap_icon_cut, + Color::dark_grey() }; - Button button_copy { - { 0 * 8, 34 * 8, 9 * 8, 32 }, - "Copy" + NewButton button_copy { + { 13 * 8, 29 * 8, 4 * 8, 32 }, + { }, + &bitmap_icon_copy, + Color::dark_grey() }; - Button button_paste { - { 21 * 4, 34 * 8, 9 * 8, 32 }, - "Paste" + NewButton button_paste { + { 17 * 8, 29 * 8, 4 * 8, 32 }, + { }, + &bitmap_icon_paste, + Color::dark_grey() + }; + + NewButton button_new_dir { + { 22 * 8, 29 * 8, 4 * 8, 32 }, + { }, + &bitmap_icon_new_dir, + Color::green() + }; + + NewButton button_new_file { + { 26 * 8, 29 * 8, 4 * 8, 32 }, + { }, + &bitmap_icon_new_file, + Color::green() }; }; diff --git a/firmware/application/apps/ui_settings.cpp b/firmware/application/apps/ui_settings.cpp index fbba2f0d..ce7782c8 100644 --- a/firmware/application/apps/ui_settings.cpp +++ b/firmware/application/apps/ui_settings.cpp @@ -446,7 +446,7 @@ namespace ui { else { auto result = delete_file( pmem_flag_file ); - if( result != 0 ) + if( result.code() != FR_OK ) { text_pmem_status.set("!err. deleting pmem flagfile!"); } diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index 08d15e00..d21e002e 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -2543,6 +2543,137 @@ static constexpr Bitmap bitmap_target { { 16, 16 }, bitmap_target_data }; +static constexpr uint8_t bitmap_icon_trash_data[] = { + 0x00, 0x00, + 0xC0, 0x01, + 0x20, 0x02, + 0xFC, 0x1F, + 0x00, 0x00, + 0xA8, 0x0A, + 0xA8, 0x0A, + 0xA8, 0x0A, + 0xA8, 0x0A, + 0xA8, 0x0A, + 0xA8, 0x0A, + 0xA8, 0x0A, + 0xA8, 0x0A, + 0x08, 0x08, + 0xF0, 0x07, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_icon_trash { + { 16, 16 }, bitmap_icon_trash_data +}; + +static constexpr uint8_t bitmap_icon_copy_data[] = { + 0x00, 0x00, + 0xFC, 0x00, + 0x84, 0x01, + 0xC4, 0x0F, + 0x74, 0x18, + 0x44, 0x38, + 0x44, 0x78, + 0x74, 0x40, + 0x44, 0x44, + 0x44, 0x44, + 0x74, 0x5F, + 0x44, 0x44, + 0x44, 0x44, + 0x7C, 0x40, + 0xC0, 0x7F, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_icon_copy { + { 16, 16 }, bitmap_icon_copy_data +}; + +static constexpr uint8_t bitmap_icon_cut_data[] = { + 0x00, 0x00, + 0x10, 0x10, + 0x30, 0x18, + 0x20, 0x08, + 0x60, 0x0C, + 0x40, 0x04, + 0xC0, 0x06, + 0x80, 0x00, + 0x80, 0x01, + 0x80, 0x01, + 0xC0, 0x03, + 0x78, 0x1E, + 0x44, 0x22, + 0x44, 0x22, + 0x44, 0x22, + 0x38, 0x1C, +}; +static constexpr Bitmap bitmap_icon_cut { + { 16, 16 }, bitmap_icon_cut_data +}; + +static constexpr uint8_t bitmap_icon_paste_data[] = { + 0x00, 0x00, + 0xE0, 0x00, + 0x18, 0x03, + 0xE4, 0x04, + 0x04, 0x04, + 0x04, 0x04, + 0x84, 0x3F, + 0x84, 0x20, + 0x84, 0x2E, + 0x84, 0x20, + 0x84, 0x2E, + 0x84, 0x20, + 0x84, 0x2E, + 0xF8, 0x20, + 0x80, 0x3F, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_icon_paste { + { 16, 16 }, bitmap_icon_paste_data +}; + +static constexpr uint8_t bitmap_icon_new_dir_data[] = { + 0x00, 0x00, + 0x1E, 0x00, + 0x21, 0x00, + 0xE1, 0x7F, + 0x01, 0xC0, + 0x81, 0x81, + 0x81, 0x81, + 0x81, 0x81, + 0xF1, 0x8F, + 0xF1, 0x8F, + 0x81, 0x81, + 0x81, 0x81, + 0x81, 0x81, + 0x03, 0xC0, + 0xFE, 0x7F, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_icon_new_dir { + { 16, 16 }, bitmap_icon_new_dir_data +}; + +static constexpr uint8_t bitmap_icon_new_file_data[] = { + 0x00, 0x00, + 0xFC, 0x07, + 0x04, 0x0C, + 0x04, 0x1C, + 0x04, 0x3C, + 0x84, 0x21, + 0x84, 0x21, + 0x84, 0x21, + 0xF4, 0x2F, + 0xF4, 0x2F, + 0x84, 0x21, + 0x84, 0x21, + 0x84, 0x21, + 0x04, 0x20, + 0xFC, 0x3F, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_icon_new_file { + { 16, 16 }, bitmap_icon_new_file_data +}; } /* namespace ui */ diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp index 49a7692d..652d7627 100644 --- a/firmware/application/file.cpp +++ b/firmware/application/file.cpp @@ -197,18 +197,21 @@ std::vector scan_root_directories(const std::filesystem:: return directory_list; } -uint32_t delete_file(const std::filesystem::path& file_path) { - return f_unlink(reinterpret_cast(file_path.c_str())); +std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path) { + return { f_unlink(reinterpret_cast(file_path.c_str())) }; } -uint32_t rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name) { - return f_rename(reinterpret_cast(file_path.c_str()), reinterpret_cast(new_name.c_str())); +std::filesystem::filesystem_error rename_file( + const std::filesystem::path& file_path, + const std::filesystem::path& new_name +) { + return { f_rename(reinterpret_cast(file_path.c_str()), reinterpret_cast(new_name.c_str())) }; } std::filesystem::filesystem_error copy_file( const std::filesystem::path& file_path, - const std::filesystem::path& dest_path) -{ + const std::filesystem::path& dest_path +) { File src; File dst; constexpr size_t buffer_size = 512; @@ -242,8 +245,20 @@ FATTimestamp file_created_date(const std::filesystem::path& file_path) { return { filinfo.fdate, filinfo.ftime }; } -uint32_t make_new_directory(const std::filesystem::path& dir_path) { - return f_mkdir(reinterpret_cast(dir_path.c_str())); +std::filesystem::filesystem_error make_new_file( + const std::filesystem::path& file_path +) { + File f; + auto result = f.create(file_path); + return result.is_valid() + ? result.value() + : std::filesystem::filesystem_error{ }; +} + +std::filesystem::filesystem_error make_new_directory( + const std::filesystem::path& dir_path +) { + return { f_mkdir(reinterpret_cast(dir_path.c_str())) }; } namespace std { diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index 7ea12df9..6b3a9df1 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -252,11 +252,12 @@ struct FATTimestamp { uint16_t FAT_time; }; -uint32_t delete_file(const std::filesystem::path& file_path); -uint32_t rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name); +std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path); +std::filesystem::filesystem_error rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name); std::filesystem::filesystem_error copy_file(const std::filesystem::path& file_path, const std::filesystem::path& dest_path); FATTimestamp file_created_date(const std::filesystem::path& file_path); -uint32_t make_new_directory(const std::filesystem::path& dir_path); +std::filesystem::filesystem_error make_new_file(const std::filesystem::path& file_path); +std::filesystem::filesystem_error make_new_directory(const std::filesystem::path& dir_path); std::vector scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension); std::vector scan_root_directories(const std::filesystem::path& directory); diff --git a/firmware/application/ui/ui_menu.cpp b/firmware/application/ui/ui_menu.cpp index 4b548aa6..3a307c17 100644 --- a/firmware/application/ui/ui_menu.cpp +++ b/firmware/application/ui/ui_menu.cpp @@ -167,6 +167,10 @@ void MenuView::clear() { offset = 0; } +size_t MenuView::item_count() const { + return menu_items.size(); +} + void MenuView::add_item(MenuItem new_item) { menu_items.push_back(new_item); diff --git a/firmware/application/ui/ui_menu.hpp b/firmware/application/ui/ui_menu.hpp index 9aa1c6cd..bc6d2bc4 100644 --- a/firmware/application/ui/ui_menu.hpp +++ b/firmware/application/ui/ui_menu.hpp @@ -83,6 +83,7 @@ public: void add_item(MenuItem new_item); void add_items(std::initializer_list new_items); void clear(); + size_t item_count() const; MenuItemView* item_view(size_t index) const; diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index e9dad8fc..0eb0dba6 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -1143,9 +1143,18 @@ NewButton::NewButton( Rect parent_rect, std::string text, const Bitmap* bitmap +) : NewButton { parent_rect, text, bitmap, Color::dark_cyan() } +{ } + +NewButton::NewButton( + Rect parent_rect, + std::string text, + const Bitmap* bitmap, + Color color ) : Widget { parent_rect }, text_ { text }, - bitmap_ (bitmap) + bitmap_ { bitmap }, + color_ { color } { set_focusable(true); } diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 4192d47a..6c5b847d 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -466,6 +466,7 @@ public: NewButton(const NewButton&) = delete; NewButton& operator=(const NewButton&) = delete; NewButton(Rect parent_rect, std::string text, const Bitmap* bitmap); + NewButton(Rect parent_rect, std::string text, const Bitmap* bitmap, Color color); NewButton( ) : NewButton { { }, { }, { } } { @@ -486,8 +487,8 @@ public: private: std::string text_; - Color color_ = Color::dark_cyan(); const Bitmap* bitmap_; + Color color_; }; class Image : public Widget { diff --git a/firmware/graphics/icon_copy.png b/firmware/graphics/icon_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..1aaef7c5d5ed61c76ff2bfc53f37efa097af7ff2 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$e`aNA7Lo5W> zP7dTdpupjrdh_r4H`n_0VjX>^&6Kz}jlD)9xu^2|`PV7W=9sE0WoVvfXSjN{A?f$- zNpTFKTayd}xFviT~9PeBILj>Pi& zeG8^azSiJ$xOjF)@a1*?WE(@bGCf{pW%6grC-G8)Ry{_Ox97NjdfF@bKis(mSp9e?}97sZ(p$M(o&ACb2n$bxS=%;`F|sVhn-hUJYumcOSMZ zf8g{k{ww2d&z26hFIJPYPK$EfyPE7!$-b*^{gTb>FHhLANinG31i8)A)z4*}Q$iB} Dt8P}S literal 0 HcmV?d00001 diff --git a/firmware/graphics/icon_new_dir.png b/firmware/graphics/icon_new_dir.png new file mode 100644 index 0000000000000000000000000000000000000000..00cb545bb933768b4915f37b21e137637c07c744 GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$eay(reLo`H} z21IimFyL6d`1}5%>5mE`wVKYXcl&&y@1hQK{aR72&39LJ-F(1lGxc+h=&ZT%Jx95H zuBgVg+fAN0e>!)|sSUb6WO{vX=qUXEy1%8M=zW9CZNCieH6G7>Kh(|mIH{29^6H>x axA!or_vx}<99 UVzv8vC?90Cr>mdKI;Vst04>c$v;Y7A literal 0 HcmV?d00001 diff --git a/firmware/graphics/icon_paste.png b/firmware/graphics/icon_paste.png new file mode 100644 index 0000000000000000000000000000000000000000..95d540e89dd162e8bf11e8b407ba9682b7fe2987 GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$eIy_w*Lo5WZ zPQJ}`K!L~k?T!EcHyQJ zxp6I+pr9y{)w50X!UF9pdu~m)QZMU!kRZ!i`Ss@0AO->M#Lp`(EN4()UG-p^L+cIB zce;)9OByV~~k!W~T0?{UEn_y85}Sb4q9e E04RM}IsgCw literal 0 HcmV?d00001 diff --git a/firmware/graphics/icon_trash.png b/firmware/graphics/icon_trash.png new file mode 100644 index 0000000000000000000000000000000000000000..21ffba7a8723665e2948c4807eefc7589eec1c8a GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$e0zF+ELo`H_ z6BGmul2-ly|KHwa!bHJton2KsvKFuyNw4F}Vhgj}p`_5PaeIPkLZY+ILDxg31_vWu wjND8GBm)9ccPL5w%BWT@Jn87O$Rv<~A^eN-cjoi&)IpYby85}Sb4q9e0K057r2qf` literal 0 HcmV?d00001