menu: add filepicker, sidebar: add file picker entry.
the filepicker is a stripped down version of the file browser. only file picking is supported atm, no "select folder" yet.
This commit is contained in:
216
sphaira/include/ui/menus/file_picker.hpp
Normal file
216
sphaira/include/ui/menus/file_picker.hpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::filepicker {
|
||||
|
||||
enum FsEntryFlag {
|
||||
FsEntryFlag_None,
|
||||
// write protected.
|
||||
FsEntryFlag_ReadOnly = 1 << 0,
|
||||
// supports file assoc.
|
||||
FsEntryFlag_Assoc = 1 << 1,
|
||||
};
|
||||
|
||||
enum class FsType {
|
||||
Sd,
|
||||
ImageNand,
|
||||
ImageSd,
|
||||
Stdio,
|
||||
};
|
||||
|
||||
enum SortType {
|
||||
SortType_Size,
|
||||
SortType_Alphabetical,
|
||||
};
|
||||
|
||||
enum OrderType {
|
||||
OrderType_Descending,
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
struct FsEntry {
|
||||
fs::FsPath name{};
|
||||
fs::FsPath root{};
|
||||
FsType type{};
|
||||
u32 flags{FsEntryFlag_None};
|
||||
|
||||
auto IsReadOnly() const -> bool {
|
||||
return flags & FsEntryFlag_ReadOnly;
|
||||
}
|
||||
|
||||
auto IsAssoc() const -> bool {
|
||||
return flags & FsEntryFlag_Assoc;
|
||||
}
|
||||
|
||||
auto IsSame(const FsEntry& e) const {
|
||||
return root == e.root && type == e.type;
|
||||
}
|
||||
};
|
||||
|
||||
// roughly 1kib in size per entry
|
||||
struct FileEntry : FsDirectoryEntry {
|
||||
std::string extension{}; // if any
|
||||
std::string internal_name{}; // if any
|
||||
std::string internal_extension{}; // if any
|
||||
s64 file_count{-1}; // number of files in a folder, non-recursive
|
||||
s64 dir_count{-1}; // number folders in a folder, non-recursive
|
||||
FsTimeStampRaw time_stamp{};
|
||||
bool checked_extension{}; // did we already search for an ext?
|
||||
bool checked_internal_extension{}; // did we already search for an ext?
|
||||
|
||||
auto IsFile() const -> bool {
|
||||
return type == FsDirEntryType_File;
|
||||
}
|
||||
|
||||
auto IsDir() const -> bool {
|
||||
return !IsFile();
|
||||
}
|
||||
|
||||
auto IsHidden() const -> bool {
|
||||
return name[0] == '.';
|
||||
}
|
||||
|
||||
auto GetName() const -> std::string {
|
||||
return name;
|
||||
}
|
||||
|
||||
auto GetExtension() const -> std::string {
|
||||
return extension;
|
||||
}
|
||||
|
||||
auto GetInternalName() const -> std::string {
|
||||
if (!internal_name.empty()) {
|
||||
return internal_name;
|
||||
}
|
||||
return GetName();
|
||||
}
|
||||
|
||||
auto GetInternalExtension() const -> std::string {
|
||||
if (!internal_extension.empty()) {
|
||||
return internal_extension;
|
||||
}
|
||||
return GetExtension();
|
||||
}
|
||||
};
|
||||
|
||||
struct LastFile {
|
||||
fs::FsPath name{};
|
||||
s64 index{};
|
||||
float offset{};
|
||||
s64 entries_count{};
|
||||
};
|
||||
|
||||
using Callback = std::function<bool(const fs::FsPath& path)>;
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
explicit Menu(const Callback& cb, const std::vector<std::string>& filter = {}, const fs::FsPath& path = {});
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Picker"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
static auto GetNewPath(const fs::FsPath& root_path, const fs::FsPath& file_path) -> fs::FsPath {
|
||||
return fs::AppendPath(root_path, file_path);
|
||||
}
|
||||
|
||||
private:
|
||||
auto GetFs() {
|
||||
return m_fs.get();
|
||||
}
|
||||
|
||||
auto& GetFsEntry() const {
|
||||
return m_fs_entry;
|
||||
}
|
||||
|
||||
void SetIndex(s64 index);
|
||||
|
||||
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
|
||||
|
||||
auto GetNewPath(const FileEntry& entry) const -> fs::FsPath {
|
||||
return GetNewPath(m_path, entry.name);
|
||||
}
|
||||
|
||||
auto GetNewPath(s64 index) const -> fs::FsPath {
|
||||
return GetNewPath(m_path, GetEntry(index).name);
|
||||
}
|
||||
|
||||
auto GetNewPathCurrent() const -> fs::FsPath {
|
||||
return GetNewPath(m_index);
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) -> FileEntry& {
|
||||
return m_entries[m_entries_current[index]];
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) const -> const FileEntry& {
|
||||
return m_entries[m_entries_current[index]];
|
||||
}
|
||||
|
||||
auto GetEntry() -> FileEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
auto GetEntry() const -> const FileEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
auto IsSd() const -> bool {
|
||||
return m_fs_entry.type == FsType::Sd;
|
||||
}
|
||||
|
||||
void Sort();
|
||||
void SortAndFindLastFile(bool scan = false);
|
||||
void SetIndexFromLastFile(const LastFile& last_file);
|
||||
|
||||
void SetFs(const fs::FsPath& new_path, const FsEntry& new_entry);
|
||||
|
||||
auto GetNative() -> fs::FsNative* {
|
||||
return (fs::FsNative*)m_fs.get();
|
||||
}
|
||||
|
||||
void DisplayOptions();
|
||||
|
||||
void UpdateSubheading();
|
||||
void PromptIfShouldExit();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "filepicker";
|
||||
|
||||
Callback m_callback;
|
||||
std::vector<std::string> m_filter;
|
||||
|
||||
std::unique_ptr<fs::Fs> m_fs{};
|
||||
FsEntry m_fs_entry{};
|
||||
fs::FsPath m_path{};
|
||||
std::vector<FileEntry> m_entries{};
|
||||
std::vector<u32> m_entries_index{}; // files not including hidden
|
||||
std::vector<u32> m_entries_index_hidden{}; // includes hidden files
|
||||
std::span<u32> m_entries_current{};
|
||||
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
// this keeps track of the highlighted file before opening a folder
|
||||
// if the user presses B to go back to the previous dir
|
||||
// this vector is popped, then, that entry is checked if it still exists
|
||||
// if it does, the index becomes that file.
|
||||
std::vector<LastFile> m_previous_highlighted_file{};
|
||||
s64 m_index{};
|
||||
ScrollingText m_scroll_name{};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical, false};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending, false};
|
||||
option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false, false};
|
||||
option::OptionBool m_folders_first{INI_SECTION, "folders_first", true, false};
|
||||
option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false, false};
|
||||
option::OptionBool m_ignore_read_only{INI_SECTION, "ignore_read_only", false, false};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::filepicker
|
||||
@@ -20,6 +20,10 @@ public:
|
||||
|
||||
using Widget::Draw;
|
||||
virtual void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left);
|
||||
auto OnFocusGained() noexcept -> void override;
|
||||
auto OnFocusLost() noexcept -> void override;
|
||||
|
||||
void DrawEntry(NVGcontext* vg, Theme* theme, const std::string& left, const std::string& right, bool use_selected);
|
||||
|
||||
void Depends(const DependsCallback& callback, const std::string& depends_info, const DependsClickCallback& depends_click = {}) {
|
||||
m_depends_callback = callback;
|
||||
@@ -63,6 +67,7 @@ private:
|
||||
DependsCallback m_depends_callback{};
|
||||
DependsClickCallback m_depends_click{};
|
||||
ScrollingText m_scolling_title{};
|
||||
ScrollingText m_scolling_value{};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -110,44 +115,67 @@ public:
|
||||
explicit SidebarEntryArray(const std::string& title, const Items& items, Callback cb, s64 index = 0, const std::string& info = "");
|
||||
explicit SidebarEntryArray(const std::string& title, const Items& items, Callback cb, const std::string& index, const std::string& info = "");
|
||||
explicit SidebarEntryArray(const std::string& title, const Items& items, std::string& index, const std::string& info = "");
|
||||
|
||||
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
|
||||
auto OnFocusGained() noexcept -> void override;
|
||||
auto OnFocusLost() noexcept -> void override;
|
||||
|
||||
private:
|
||||
Items m_items;
|
||||
ListCallback m_list_callback;
|
||||
Callback m_callback;
|
||||
s64 m_index;
|
||||
s64 m_tick{};
|
||||
float m_text_yoff{};
|
||||
};
|
||||
|
||||
class SidebarEntryTextInput final : public SidebarEntryBase {
|
||||
// single text entry.
|
||||
// the callback is called when the entry is clicked.
|
||||
// usually, the within the callback the text will be changed, use SetText().
|
||||
class SidebarEntryTextBase : public SidebarEntryBase {
|
||||
public:
|
||||
using Callback = std::function<void(bool&)>;
|
||||
using Callback = std::function<void(void)>;
|
||||
|
||||
public:
|
||||
explicit SidebarEntryTextInput(const std::string& text, const std::string& guide = {}, const std::string& info = "");
|
||||
explicit SidebarEntryTextBase(const std::string& title, const std::string& value, const Callback& cb, const std::string& info = "");
|
||||
|
||||
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
|
||||
|
||||
auto GetText() const {
|
||||
return m_title;
|
||||
void SetCallback(const Callback& cb) {
|
||||
m_callback = cb;
|
||||
}
|
||||
|
||||
void SetText(const std::string& text) {
|
||||
m_title = text;
|
||||
auto GetValue() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void SetValue(const std::string& value) {
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_value;
|
||||
Callback m_callback;
|
||||
};
|
||||
|
||||
class SidebarEntryTextInput final : public SidebarEntryTextBase {
|
||||
public:
|
||||
explicit SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide = {}, const std::string& info = "");
|
||||
|
||||
void SetGuide(const std::string& guide) {
|
||||
m_guide = guide;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_guide;
|
||||
ScrollingText m_scolling_title{};
|
||||
};
|
||||
|
||||
class SidebarEntryFilePicker final : public SidebarEntryTextBase {
|
||||
public:
|
||||
explicit SidebarEntryFilePicker(const std::string& title, const std::string& value, const std::vector<std::string>& filter, const std::string& info = "");
|
||||
|
||||
// extension filter.
|
||||
void SetFilter(const std::vector<std::string>& filter) {
|
||||
m_filter = filter;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_filter{};
|
||||
};
|
||||
|
||||
class Sidebar final : public Widget {
|
||||
|
||||
Reference in New Issue
Block a user