Merge pull request #950 from kallanreed/fileman_ux2

Fileman fit and finish
This commit is contained in:
gullradriel 2023-05-03 22:32:00 +02:00 committed by GitHub
commit 0742fc169d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 42 deletions

View File

@ -22,7 +22,6 @@
/* TODO: /* TODO:
* - Paging menu items * - Paging menu items
* - UI with empty SD card
* - Copy/Move * - Copy/Move
*/ */
@ -101,6 +100,9 @@ void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
// Returns the partner file path or an empty path if no partner is found. // Returns the partner file path or an empty path if no partner is found.
fs::path get_partner_file(fs::path path) { fs::path get_partner_file(fs::path path) {
if (fs::is_directory(path))
return { };
const fs::path txt_path{ u".TXT" }; const fs::path txt_path{ u".TXT" };
const fs::path c16_path{ u".C16" }; const fs::path c16_path{ u".C16" };
auto ext = path.extension(); auto ext = path.extension();
@ -113,12 +115,13 @@ fs::path get_partner_file(fs::path path) {
return { }; return { };
path.replace_extension(ext); path.replace_extension(ext);
return file_exists(path) ? path : fs::path{ }; return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{ };
} }
// Modal prompt to update the partner file if it exists. // Modal prompt to update the partner file if it exists.
// Runs continuation on_partner_action to update the partner file. // Runs continuation on_partner_action to update the partner file.
// Returns true is a partner is found, otherwise false. // Returns true is a partner is found, otherwise false.
// Path must be the full path to the file.
bool partner_file_prompt( bool partner_file_prompt(
NavigationView& nav, NavigationView& nav,
const fs::path& path, const fs::path& path,
@ -147,6 +150,8 @@ bool partner_file_prompt(
namespace ui { namespace ui {
/* FileManBaseView ***********************************************************/
void FileManBaseView::load_directory_contents(const fs::path& dir_path) { void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
current_path = dir_path; current_path = dir_path;
entry_list.clear(); entry_list.clear();
@ -180,6 +185,7 @@ fs::path FileManBaseView::get_selected_full_path() const {
} }
const fileman_entry& FileManBaseView::get_selected_entry() 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()]; return entry_list[menu_view.highlighted_index()];
} }
@ -212,8 +218,7 @@ FileManBaseView::FileManBaseView(
text_current.set("EMPTY SD CARD!"); text_current.set("EMPTY SD CARD!");
} else { } else {
menu_view.on_left = [this]() { menu_view.on_left = [this]() {
current_path = current_path.parent_path(); pop_dir();
reload_current();
}; };
} }
} }
@ -226,10 +231,32 @@ void FileManBaseView::focus() {
} }
} }
void FileManBaseView::push_dir(const fs::path& path) {
if (path == parent_dir_path) {
pop_dir();
} else {
current_path /= path;
saved_index_stack.push_back(menu_view.highlighted_index());
menu_view.set_highlighted(0);
reload_current();
}
}
void FileManBaseView::pop_dir() {
if (saved_index_stack.empty())
return;
current_path = current_path.parent_path();
reload_current();
menu_view.set_highlighted(saved_index_stack.back());
saved_index_stack.pop_back();
}
void FileManBaseView::refresh_list() { void FileManBaseView::refresh_list() {
if (on_refresh_widgets) if (on_refresh_widgets)
on_refresh_widgets(false); on_refresh_widgets(false);
auto prev_highlight = menu_view.highlighted_index();
menu_view.clear(); menu_view.clear();
for (const auto& entry : entry_list) { for (const auto& entry : entry_list) {
@ -262,7 +289,7 @@ void FileManBaseView::refresh_list() {
} }
} }
menu_view.set_highlighted(0); // Refresh menu_view.set_highlighted(prev_highlight);
} }
void FileManBaseView::reload_current() { void FileManBaseView::reload_current() {
@ -305,6 +332,8 @@ FileSaveView::FileSaveView(
}; };
}*/ }*/
/* FileLoadView **************************************************************/
void FileLoadView::refresh_widgets(const bool) { void FileLoadView::refresh_widgets(const bool) {
set_dirty(); set_dirty();
} }
@ -329,35 +358,32 @@ FileLoadView::FileLoadView(
on_select_entry = [this](KeyEvent) { on_select_entry = [this](KeyEvent) {
if (get_selected_entry().is_directory) { if (get_selected_entry().is_directory) {
current_path = get_selected_full_path(); push_dir(get_selected_entry().path);
reload_current();
} else { } else {
nav_.pop();
if (on_changed) if (on_changed)
on_changed(get_selected_full_path()); on_changed(get_selected_full_path());
nav_.pop();
} }
}; };
} }
/* FileManagerView ***********************************************************/
void FileManagerView::on_rename() { void FileManagerView::on_rename() {
auto& entry = get_selected_entry(); auto& entry = get_selected_entry();
// Don't rename ".."
if (entry.path == parent_dir_path)
return;
name_buffer = entry.path.filename().string(); name_buffer = entry.path.filename().string();
uint32_t cursor_pos = (uint32_t)name_buffer.length(); uint32_t cursor_pos = (uint32_t)name_buffer.length();
if (auto pos = name_buffer.find_last_of("."); pos != name_buffer.npos)
if (auto pos = name_buffer.find_last_of(".");
pos != name_buffer.npos && !entry.is_directory)
cursor_pos = pos; cursor_pos = pos;
text_prompt(nav_, name_buffer, cursor_pos, max_filename_length, text_prompt(nav_, name_buffer, cursor_pos, max_filename_length,
[this, &entry](std::string& renamed) { [this](std::string& renamed) {
auto renamed_path = fs::path{ renamed }; auto renamed_path = fs::path{ renamed };
rename_file(get_selected_full_path(), current_path / renamed_path); rename_file(get_selected_full_path(), current_path / renamed_path);
auto has_partner = partner_file_prompt(nav_, entry.path, "Rename", auto has_partner = partner_file_prompt(nav_, get_selected_full_path(), "Rename",
[this, renamed_path](const fs::path& partner, bool should_rename) mutable { [this, renamed_path](const fs::path& partner, bool should_rename) mutable {
if (should_rename) { if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension()); auto new_name = renamed_path.replace_extension(partner.extension());
@ -373,20 +399,14 @@ void FileManagerView::on_rename() {
} }
void FileManagerView::on_delete() { void FileManagerView::on_delete() {
auto& entry = get_selected_entry(); auto name = get_selected_entry().path.filename().string();
// Don't delete ".."
if (entry.path == parent_dir_path)
return;
auto name = entry.path.filename().string();
nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO, nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this, &entry](bool choice) { [this](bool choice) {
if (choice) { if (choice) {
delete_file(get_selected_full_path()); delete_file(get_selected_full_path());
auto has_partner = partner_file_prompt( auto has_partner = partner_file_prompt(
nav_, entry.path, "Delete", nav_, get_selected_full_path(), "Delete",
[this](const fs::path& partner, bool should_delete) { [this](const fs::path& partner, bool should_delete) {
if (should_delete) if (should_delete)
delete_file(current_path / partner); delete_file(current_path / partner);
@ -409,6 +429,11 @@ void FileManagerView::on_new_dir() {
}); });
} }
bool FileManagerView::selected_is_valid() const {
return !entry_list.empty() &&
get_selected_entry().path != parent_dir_path;
}
void FileManagerView::refresh_widgets(const bool v) { void FileManagerView::refresh_widgets(const bool v) {
button_rename.hidden(v); button_rename.hidden(v);
button_delete.hidden(v); button_delete.hidden(v);
@ -438,26 +463,31 @@ FileManagerView::FileManagerView(
}); });
menu_view.on_highlight = [this]() { menu_view.on_highlight = [this]() {
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path()))); // TODO: enable/disable buttons.
if (selected_is_valid())
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
else
text_date.set("");
}; };
refresh_list(); refresh_list();
on_select_entry = [this](KeyEvent key) { on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) { if (key == KeyEvent::Select && get_selected_entry().is_directory) {
load_directory_contents(get_selected_full_path()); push_dir(get_selected_entry().path);
refresh_list();
} else { } else {
button_rename.focus(); button_rename.focus();
} }
}; };
button_rename.on_select = [this](Button&) { button_rename.on_select = [this](Button&) {
on_rename(); if (selected_is_valid())
on_rename();
}; };
button_delete.on_select = [this](Button&) { button_delete.on_select = [this](Button&) {
on_delete(); if (selected_is_valid())
on_delete();
}; };
button_new_dir.on_select = [this](Button&) { button_new_dir.on_select = [this](Button&) {

View File

@ -69,6 +69,8 @@ protected:
std::filesystem::path get_selected_full_path() const; std::filesystem::path get_selected_full_path() const;
const fileman_entry& get_selected_entry() const; const fileman_entry& get_selected_entry() const;
void push_dir(const std::filesystem::path& path);
void pop_dir();
void refresh_list(); void refresh_list();
void reload_current(); void reload_current();
void load_directory_contents(const std::filesystem::path& dir_path); void load_directory_contents(const std::filesystem::path& dir_path);
@ -85,6 +87,7 @@ protected:
std::filesystem::path extension_filter { u"" }; std::filesystem::path extension_filter { u"" };
std::vector<fileman_entry> entry_list { }; std::vector<fileman_entry> entry_list { };
std::vector<uint32_t> saved_index_stack { };
Labels labels { Labels labels {
{ { 0, 0 }, "Path:", Color::light_grey() } { { 0, 0 }, "Path:", Color::light_grey() }
@ -152,6 +155,9 @@ private:
void on_rename(); void on_rename();
void on_delete(); void on_delete();
void on_new_dir(); void on_new_dir();
// True if the selected entry is a real file item.
bool selected_is_valid() const;
Labels labels { Labels labels {
{ { 0, 26 * 8 }, "Created ", Color::light_grey() } { { 0, 26 * 8 }, "Created ", Color::light_grey() }

View File

@ -197,13 +197,6 @@ std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::
return directory_list; return directory_list;
} }
bool file_exists(const std::filesystem::path& file_path) {
FILINFO filinfo;
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
return fr == FR_OK;
}
uint32_t delete_file(const std::filesystem::path& file_path) { uint32_t delete_file(const std::filesystem::path& file_path) {
return f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str())); return f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str()));
} }
@ -316,6 +309,10 @@ bool operator==(const path& lhs, const path& rhs) {
return lhs.native() == rhs.native(); return lhs.native() == rhs.native();
} }
bool operator!=(const path& lhs, const path& rhs) {
return !(lhs == rhs);
}
bool operator<(const path& lhs, const path& rhs) { bool operator<(const path& lhs, const path& rhs) {
return lhs.native() < rhs.native(); return lhs.native() < rhs.native();
} }
@ -343,7 +340,7 @@ directory_iterator::directory_iterator(
{ {
impl = std::make_shared<Impl>(); impl = std::make_shared<Impl>();
const auto result = f_findfirst(&impl->dir, &impl->filinfo, reinterpret_cast<const TCHAR*>(path.c_str()), reinterpret_cast<const TCHAR*>(pattern.c_str())); const auto result = f_findfirst(&impl->dir, &impl->filinfo, reinterpret_cast<const TCHAR*>(path.c_str()), reinterpret_cast<const TCHAR*>(pattern.c_str()));
if( result != FR_OK ) { if( result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
impl.reset(); impl.reset();
// TODO: Throw exception if/when I enable exceptions... // TODO: Throw exception if/when I enable exceptions...
} }
@ -365,6 +362,20 @@ bool is_regular_file(const file_status s) {
return !(s & AM_DIR); return !(s & AM_DIR);
} }
bool file_exists(const path& file_path) {
FILINFO filinfo;
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
return fr == FR_OK;
}
bool is_directory(const path& file_path) {
FILINFO filinfo;
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
return fr == FR_OK && is_directory(static_cast<file_status>(filinfo.fattrib));
}
space_info space(const path& p) { space_info space(const path& p) {
DWORD free_clusters { 0 }; DWORD free_clusters { 0 };
FATFS* fs; FATFS* fs;

View File

@ -166,6 +166,7 @@ private:
}; };
bool operator==(const path& lhs, const path& rhs); bool operator==(const path& lhs, const path& rhs);
bool operator!=(const path& lhs, const path& rhs);
bool operator<(const path& lhs, const path& rhs); bool operator<(const path& lhs, const path& rhs);
bool operator>(const path& lhs, const path& rhs); bool operator>(const path& lhs, const path& rhs);
path operator+(const path& lhs, const path& rhs); path operator+(const path& lhs, const path& rhs);
@ -238,6 +239,8 @@ inline bool operator!=(const directory_iterator& lhs, const directory_iterator&
bool is_directory(const file_status s); bool is_directory(const file_status s);
bool is_regular_file(const file_status s); bool is_regular_file(const file_status s);
bool file_exists(const path& file_path);
bool is_directory(const path& file_path);
space_info space(const path& p); space_info space(const path& p);
@ -249,7 +252,6 @@ struct FATTimestamp {
uint16_t FAT_time; uint16_t FAT_time;
}; };
bool file_exists(const std::filesystem::path& file_path);
uint32_t delete_file(const std::filesystem::path& file_path); 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); uint32_t rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
FATTimestamp file_created_date(const std::filesystem::path& file_path); FATTimestamp file_created_date(const std::filesystem::path& file_path);

View File

@ -163,6 +163,8 @@ void MenuView::clear() {
} }
menu_items.clear(); menu_items.clear();
highlighted_item = 0;
offset = 0;
} }
void MenuView::add_item(MenuItem new_item) { void MenuView::add_item(MenuItem new_item) {
@ -209,7 +211,7 @@ MenuItemView* MenuView::item_view(size_t index) const {
bool MenuView::set_highlighted(int32_t new_value) { bool MenuView::set_highlighted(int32_t new_value) {
int32_t item_count = (int32_t)menu_items.size(); int32_t item_count = (int32_t)menu_items.size();
if (new_value < 0) if (new_value < 0 || item_count == 0)
return false; return false;
if (new_value >= item_count) if (new_value >= item_count)