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:
* - Paging menu items
* - UI with empty SD card
* - 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.
fs::path get_partner_file(fs::path path) {
if (fs::is_directory(path))
return { };
const fs::path txt_path{ u".TXT" };
const fs::path c16_path{ u".C16" };
auto ext = path.extension();
@ -113,12 +115,13 @@ fs::path get_partner_file(fs::path path) {
return { };
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.
// Runs continuation on_partner_action to update the partner file.
// Returns true is a partner is found, otherwise false.
// Path must be the full path to the file.
bool partner_file_prompt(
NavigationView& nav,
const fs::path& path,
@ -147,6 +150,8 @@ bool partner_file_prompt(
namespace ui {
/* FileManBaseView ***********************************************************/
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
current_path = dir_path;
entry_list.clear();
@ -180,6 +185,7 @@ 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()];
}
@ -212,8 +218,7 @@ FileManBaseView::FileManBaseView(
text_current.set("EMPTY SD CARD!");
} else {
menu_view.on_left = [this]() {
current_path = current_path.parent_path();
reload_current();
pop_dir();
};
}
}
@ -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() {
if (on_refresh_widgets)
on_refresh_widgets(false);
auto prev_highlight = menu_view.highlighted_index();
menu_view.clear();
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() {
@ -305,6 +332,8 @@ FileSaveView::FileSaveView(
};
}*/
/* FileLoadView **************************************************************/
void FileLoadView::refresh_widgets(const bool) {
set_dirty();
}
@ -329,35 +358,32 @@ FileLoadView::FileLoadView(
on_select_entry = [this](KeyEvent) {
if (get_selected_entry().is_directory) {
current_path = get_selected_full_path();
reload_current();
push_dir(get_selected_entry().path);
} else {
nav_.pop();
if (on_changed)
on_changed(get_selected_full_path());
nav_.pop();
}
};
}
/* FileManagerView ***********************************************************/
void FileManagerView::on_rename() {
auto& entry = get_selected_entry();
// Don't rename ".."
if (entry.path == parent_dir_path)
return;
name_buffer = entry.path.filename().string();
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;
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 };
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 {
if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension());
@ -373,20 +399,14 @@ void FileManagerView::on_rename() {
}
void FileManagerView::on_delete() {
auto& entry = get_selected_entry();
// Don't delete ".."
if (entry.path == parent_dir_path)
return;
auto name = entry.path.filename().string();
auto name = get_selected_entry().path.filename().string();
nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this, &entry](bool choice) {
[this](bool choice) {
if (choice) {
delete_file(get_selected_full_path());
auto has_partner = partner_file_prompt(
nav_, entry.path, "Delete",
nav_, get_selected_full_path(), "Delete",
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
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) {
button_rename.hidden(v);
button_delete.hidden(v);
@ -438,26 +463,31 @@ FileManagerView::FileManagerView(
});
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();
on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
load_directory_contents(get_selected_full_path());
refresh_list();
push_dir(get_selected_entry().path);
} else {
button_rename.focus();
}
};
button_rename.on_select = [this](Button&) {
on_rename();
if (selected_is_valid())
on_rename();
};
button_delete.on_select = [this](Button&) {
on_delete();
if (selected_is_valid())
on_delete();
};
button_new_dir.on_select = [this](Button&) {

View File

@ -69,6 +69,8 @@ protected:
std::filesystem::path get_selected_full_path() const;
const fileman_entry& get_selected_entry() const;
void push_dir(const std::filesystem::path& path);
void pop_dir();
void refresh_list();
void reload_current();
void load_directory_contents(const std::filesystem::path& dir_path);
@ -85,6 +87,7 @@ protected:
std::filesystem::path extension_filter { u"" };
std::vector<fileman_entry> entry_list { };
std::vector<uint32_t> saved_index_stack { };
Labels labels {
{ { 0, 0 }, "Path:", Color::light_grey() }
@ -152,6 +155,9 @@ private:
void on_rename();
void on_delete();
void on_new_dir();
// True if the selected entry is a real file item.
bool selected_is_valid() const;
Labels labels {
{ { 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;
}
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) {
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();
}
bool operator!=(const path& lhs, const path& rhs) {
return !(lhs == rhs);
}
bool operator<(const path& lhs, const path& rhs) {
return lhs.native() < rhs.native();
}
@ -343,7 +340,7 @@ directory_iterator::directory_iterator(
{
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()));
if( result != FR_OK ) {
if( result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
impl.reset();
// TODO: Throw exception if/when I enable exceptions...
}
@ -365,6 +362,20 @@ bool is_regular_file(const file_status s) {
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) {
DWORD free_clusters { 0 };
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);
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_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);
@ -249,7 +252,6 @@ struct FATTimestamp {
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 rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
FATTimestamp file_created_date(const std::filesystem::path& file_path);

View File

@ -163,6 +163,8 @@ void MenuView::clear() {
}
menu_items.clear();
highlighted_item = 0;
offset = 0;
}
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) {
int32_t item_count = (int32_t)menu_items.size();
if (new_value < 0)
if (new_value < 0 || item_count == 0)
return false;
if (new_value >= item_count)