public release
This commit is contained in:
23
sphaira/include/ui/error_box.hpp
Normal file
23
sphaira/include/ui/error_box.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class ErrorBox final : public Widget {
|
||||
public:
|
||||
ErrorBox(Result code, const std::string& message);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto OnLayoutChange() -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
private:
|
||||
Result m_code;
|
||||
std::string m_message;
|
||||
std::string m_module_str;
|
||||
std::string m_description_str;
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
220
sphaira/include/ui/menus/appstore.hpp
Normal file
220
sphaira/include/ui/menus/appstore.hpp
Normal file
@@ -0,0 +1,220 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrollable_text.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::appstore {
|
||||
|
||||
struct ManifestEntry {
|
||||
char command;
|
||||
fs::FsPath path;
|
||||
};
|
||||
|
||||
using ManifestEntries = std::vector<ManifestEntry>;
|
||||
|
||||
enum class ImageDownloadState {
|
||||
None, // not started
|
||||
Progress, // Download started
|
||||
Done, // finished downloading
|
||||
Failed, // attempted to download but failed
|
||||
};
|
||||
|
||||
struct LazyImage {
|
||||
LazyImage() = default;
|
||||
~LazyImage();
|
||||
int image{};
|
||||
int w{}, h{};
|
||||
ImageDownloadState state{ImageDownloadState::None};
|
||||
u8 first_pixel[4]{};
|
||||
};
|
||||
|
||||
enum class EntryStatus {
|
||||
Get,
|
||||
Installed,
|
||||
Local,
|
||||
Update,
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
std::string category; // todo: lable
|
||||
std::string binary; // optional, only valid for .nro
|
||||
std::string updated; // date of update
|
||||
std::string name;
|
||||
std::string license; // optional
|
||||
std::string title; // same as name but with spaces
|
||||
std::string url; // url of repo (optional?)
|
||||
std::string description;
|
||||
std::string author;
|
||||
std::string changelog; // optional
|
||||
u64 screens; // number of screenshots
|
||||
u64 extracted; // extracted size in KiB
|
||||
std::string version;
|
||||
u64 filesize; // compressed size in KiB
|
||||
std::string details;
|
||||
u64 app_dls;
|
||||
std::string md5; // md5 of the zip
|
||||
|
||||
LazyImage image;
|
||||
u32 updated_num;
|
||||
EntryStatus status{EntryStatus::Get};
|
||||
};
|
||||
|
||||
// number to index m_entries to get entry
|
||||
using EntryMini = u32;
|
||||
struct Menu; // fwd
|
||||
|
||||
struct EntryMenu final : MenuBase {
|
||||
EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu);
|
||||
~EntryMenu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
// void OnFocusGained() override;
|
||||
|
||||
void ShowChangelogAction();
|
||||
void SetIndex(std::size_t index);
|
||||
|
||||
void UpdateOptions();
|
||||
|
||||
private:
|
||||
struct Option {
|
||||
Option(const std::string& dt, const std::string& ct, std::function<void(void)> f)
|
||||
: display_text{dt}, confirm_text{ct}, func{f} {}
|
||||
Option(const std::string& dt, std::function<void(void)> f)
|
||||
: display_text{dt}, func{f} {}
|
||||
|
||||
std::string display_text{};
|
||||
std::string confirm_text{};
|
||||
std::function<void(void)> func{};
|
||||
};
|
||||
|
||||
Entry& m_entry;
|
||||
const LazyImage& m_default_icon;
|
||||
Menu& m_menu;
|
||||
|
||||
std::size_t m_index{}; // where i am in the array
|
||||
std::vector<Option> m_options;
|
||||
LazyImage m_banner;
|
||||
std::vector<LazyImage> m_screens;
|
||||
|
||||
std::shared_ptr<ScrollableText> m_details;
|
||||
std::shared_ptr<ScrollableText> m_changelog;
|
||||
std::shared_ptr<ScrollableText> m_detail_changelog;
|
||||
|
||||
bool m_show_changlog{};
|
||||
};
|
||||
|
||||
enum Filter {
|
||||
Filter_All,
|
||||
Filter_Games,
|
||||
Filter_Emulators,
|
||||
Filter_Tools,
|
||||
Filter_Advanced,
|
||||
Filter_Themes,
|
||||
Filter_Legacy,
|
||||
Filter_Misc,
|
||||
Filter_MAX,
|
||||
};
|
||||
|
||||
enum SortType {
|
||||
SortType_Updated,
|
||||
SortType_Downloads,
|
||||
SortType_Size,
|
||||
SortType_Alphabetical,
|
||||
};
|
||||
|
||||
enum OrderType {
|
||||
OrderType_Decending,
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
struct FeedbackEntry {
|
||||
u32 id;
|
||||
u64 time;
|
||||
std::string package; // name of package
|
||||
std::string content; // the feedback message that was sent
|
||||
std::string reply; // the reply, "" if no reply yet :)
|
||||
};
|
||||
|
||||
struct FeedbackMenu final : MenuBase {
|
||||
FeedbackMenu(const std::vector<Entry>& package_entries, LazyImage& default_image);
|
||||
~FeedbackMenu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(std::size_t index);
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
|
||||
private:
|
||||
const std::vector<Entry>& m_package_entries;
|
||||
LazyImage& m_default_image;
|
||||
std::vector<FeedbackEntry> m_entries;
|
||||
std::size_t m_start{};
|
||||
std::size_t m_index{}; // where i am in the array
|
||||
ImageDownloadState m_repo_download_state{ImageDownloadState::None};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(const std::vector<NroEntry>& nro_entries);
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(std::size_t index);
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
|
||||
void SetFilter(Filter filter);
|
||||
void SetSort(SortType sort);
|
||||
void SetOrder(OrderType order);
|
||||
|
||||
void SetSearch(const std::string& term);
|
||||
void SetAuthor();
|
||||
|
||||
auto GetEntry() -> Entry& {
|
||||
return m_entries[m_entries_current[m_index]];
|
||||
}
|
||||
|
||||
auto SetDirty() {
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<NroEntry>& m_nro_entries;
|
||||
std::vector<Entry> m_entries;
|
||||
std::vector<EntryMini> m_entries_index[Filter_MAX];
|
||||
std::vector<EntryMini> m_entries_index_author;
|
||||
std::vector<EntryMini> m_entries_index_search;
|
||||
std::span<EntryMini> m_entries_current;
|
||||
|
||||
Filter m_filter{Filter::Filter_All};
|
||||
SortType m_sort{SortType::SortType_Updated};
|
||||
OrderType m_order{OrderType::OrderType_Decending};
|
||||
|
||||
std::size_t m_start{};
|
||||
std::size_t m_index{}; // where i am in the array
|
||||
LazyImage m_default_image;
|
||||
LazyImage m_update;
|
||||
LazyImage m_get;
|
||||
LazyImage m_local;
|
||||
LazyImage m_installed;
|
||||
ImageDownloadState m_repo_download_state{ImageDownloadState::None};
|
||||
|
||||
std::string m_search_term;
|
||||
std::string m_author_term;
|
||||
u64 m_entry_search_jump_back{};
|
||||
u64 m_entry_author_jump_back{};
|
||||
bool m_is_search{};
|
||||
bool m_is_author{};
|
||||
bool m_dirty{}; // if set, does a sort
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::appstore
|
||||
30
sphaira/include/ui/menus/file_viewer.hpp
Normal file
30
sphaira/include/ui/menus/file_viewer.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrollable_text.hpp"
|
||||
#include "fs.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::fileview {
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(const fs::FsPath& path);
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
private:
|
||||
const fs::FsPath m_path;
|
||||
fs::FsNativeSd m_fs;
|
||||
FsFile m_file;
|
||||
s64 m_file_size{};
|
||||
s64 m_file_offset{};
|
||||
|
||||
std::unique_ptr<ScrollableText> m_scroll_text;
|
||||
|
||||
std::size_t m_start{};
|
||||
std::size_t m_index{}; // where i am in the array
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::fileview
|
||||
252
sphaira/include/ui/menus/filebrowser.hpp
Normal file
252
sphaira/include/ui/menus/filebrowser.hpp
Normal file
@@ -0,0 +1,252 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
// #include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::filebrowser {
|
||||
|
||||
enum class SelectedType {
|
||||
None,
|
||||
Copy,
|
||||
Cut,
|
||||
Delete,
|
||||
};
|
||||
|
||||
enum SortType {
|
||||
SortType_Size,
|
||||
SortType_Alphabetical,
|
||||
};
|
||||
|
||||
enum OrderType {
|
||||
OrderType_Decending,
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
// 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?
|
||||
bool selected{}; // is this file selected?
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
auto IsSelected() const -> bool {
|
||||
return selected;
|
||||
}
|
||||
};
|
||||
|
||||
struct FileAssocEntry {
|
||||
fs::FsPath path{}; // ini name
|
||||
std::string name; // ini name
|
||||
std::vector<std::string> ext; // list of ext
|
||||
std::vector<std::string> database; // list of systems
|
||||
};
|
||||
struct LastFile {
|
||||
fs::FsPath name;
|
||||
u64 index;
|
||||
u64 offset;
|
||||
u64 entries_count;
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(const std::vector<NroEntry>& nro_entries);
|
||||
~Menu();
|
||||
|
||||
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:
|
||||
void SetIndex(std::size_t index);
|
||||
void InstallForwarder();
|
||||
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
|
||||
|
||||
void LoadAssocEntriesPath(const fs::FsPath& path);
|
||||
void LoadAssocEntries();
|
||||
auto FindFileAssocFor() -> std::vector<FileAssocEntry>;
|
||||
void OnIndexChange();
|
||||
|
||||
auto GetNewPath(const FileEntry& entry) const -> fs::FsPath {
|
||||
return GetNewPath(m_path, entry.name);
|
||||
};
|
||||
|
||||
auto GetNewPath(u64 index) const -> fs::FsPath {
|
||||
return GetNewPath(m_path, GetEntry(index).name);
|
||||
};
|
||||
|
||||
auto GetNewPathCurrent() const -> fs::FsPath {
|
||||
return GetNewPath(m_index);
|
||||
};
|
||||
|
||||
auto GetSelectedEntries() const -> std::vector<FileEntry> {
|
||||
if (!m_selected_count) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<FileEntry> out;
|
||||
|
||||
for (auto&e : m_entries) {
|
||||
if (e.IsSelected()) {
|
||||
out.emplace_back(e);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void AddSelectedEntries(SelectedType type) {
|
||||
auto entries = GetSelectedEntries();
|
||||
if (entries.empty()) {
|
||||
// log_write("%s with no selected files\n", __PRETTY_FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
m_selected_type = type;
|
||||
m_selected_files = entries;
|
||||
m_selected_path = m_path;
|
||||
}
|
||||
|
||||
void AddCurrentFileToSelection(SelectedType type) {
|
||||
m_selected_files.emplace_back(GetEntry());
|
||||
m_selected_count++;
|
||||
m_selected_type = type;
|
||||
m_selected_path = m_path;
|
||||
}
|
||||
|
||||
void ResetSelection() {
|
||||
m_selected_files.clear();
|
||||
m_selected_count = 0;
|
||||
m_selected_type = SelectedType::None;
|
||||
m_selected_path = {};
|
||||
}
|
||||
|
||||
auto HasTypeInSelectedEntries(FsDirEntryType type) const -> bool {
|
||||
if (!m_selected_count) {
|
||||
return GetEntry().type == type;
|
||||
} else {
|
||||
for (auto&p : m_selected_files) {
|
||||
if (p.type == type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void Sort();
|
||||
void SortAndFindLastFile();
|
||||
void SetIndexFromLastFile(const LastFile& last_file);
|
||||
void UpdateSubheading();
|
||||
|
||||
void OnDeleteCallback();
|
||||
void OnPasteCallback();
|
||||
void OnRenameCallback();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "filebrowser";
|
||||
|
||||
const std::vector<NroEntry>& m_nro_entries;
|
||||
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::vector<u32> m_entries_index_search; // files found via search
|
||||
std::span<u32> m_entries_current;
|
||||
|
||||
// search options
|
||||
// show files [X]
|
||||
// show folders [X]
|
||||
// recursive (slow) [ ]
|
||||
|
||||
std::vector<FileAssocEntry> m_assoc_entries;
|
||||
std::vector<FileEntry> m_selected_files;
|
||||
|
||||
// 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;
|
||||
fs::FsPath m_selected_path;
|
||||
std::size_t m_index{};
|
||||
std::size_t m_index_offset{};
|
||||
std::size_t m_selected_count{};
|
||||
SelectedType m_selected_type{SelectedType::None};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending};
|
||||
option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false};
|
||||
option::OptionBool m_folders_first{INI_SECTION, "folders_first", true};
|
||||
option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false};
|
||||
|
||||
option::OptionBool m_search_show_files{INI_SECTION, "search_show_files", true};
|
||||
option::OptionBool m_search_show_folders{INI_SECTION, "search_show_folders", true};
|
||||
option::OptionBool m_search_recursive{INI_SECTION, "search_recursive", false};
|
||||
|
||||
bool m_loaded_assoc_entries{};
|
||||
bool m_is_update_folder{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::filebrowser
|
||||
51
sphaira/include/ui/menus/homebrew.hpp
Normal file
51
sphaira/include/ui/menus/homebrew.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::homebrew {
|
||||
|
||||
enum SortType {
|
||||
SortType_Updated,
|
||||
SortType_Size,
|
||||
SortType_Alphabetical,
|
||||
};
|
||||
|
||||
enum OrderType {
|
||||
OrderType_Decending,
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu();
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(std::size_t index);
|
||||
void InstallHomebrew();
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
void SortAndFindLastFile();
|
||||
|
||||
auto GetHomebrewList() const -> const std::vector<NroEntry>& {
|
||||
return m_entries;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "homebrew";
|
||||
|
||||
std::vector<NroEntry> m_entries;
|
||||
std::size_t m_start{};
|
||||
std::size_t m_index{}; // where i am in the array
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending};
|
||||
option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};}
|
||||
;
|
||||
|
||||
} // namespace sphaira::ui::menu::homebrew
|
||||
67
sphaira/include/ui/menus/irs_menu.hpp
Normal file
67
sphaira/include/ui/menus/irs_menu.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::irs {
|
||||
|
||||
enum Rotation {
|
||||
Rotation_0,
|
||||
Rotation_90,
|
||||
Rotation_180,
|
||||
Rotation_270,
|
||||
};
|
||||
|
||||
enum Colour {
|
||||
Colour_Grey,
|
||||
Colour_Ironbow,
|
||||
Colour_Green,
|
||||
Colour_Red,
|
||||
Colour_Blue,
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
IrsIrCameraHandle m_handle{};
|
||||
IrsIrCameraStatus status{};
|
||||
bool m_update_needed{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu();
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void PollCameraStatus(bool statup = false);
|
||||
void LoadDefaultConfig();
|
||||
void UpdateConfig(const IrsImageTransferProcessorExConfig* config);
|
||||
void ResetImage();
|
||||
void UpdateImage();
|
||||
void updateColourArray();
|
||||
|
||||
private:
|
||||
Result m_init_rc{};
|
||||
|
||||
IrsImageTransferProcessorExConfig m_config{};
|
||||
IrsMomentProcessorConfig m_moment_config{};
|
||||
IrsClusteringProcessorConfig m_clustering_config{};
|
||||
IrsTeraPluginProcessorConfig m_tera_config{};
|
||||
IrsIrLedProcessorConfig m_led_config{};
|
||||
IrsAdaptiveClusteringProcessorConfig m_adaptive_config{};
|
||||
IrsHandAnalysisConfig m_hand_config{};
|
||||
|
||||
Entry m_entries[IRS_MAX_CAMERAS]{};
|
||||
u32 m_irs_width{};
|
||||
u32 m_irs_height{};
|
||||
std::vector<u32> m_rgba{};
|
||||
std::vector<u8> m_irs_buffer{};
|
||||
IrsImageTransferProcessorState m_prev_state{};
|
||||
Rotation m_rotation{Rotation_90};
|
||||
Colour m_colour{Colour_Grey};
|
||||
int m_image{};
|
||||
std::size_t m_index{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::irs
|
||||
37
sphaira/include/ui/menus/main_menu.hpp
Normal file
37
sphaira/include/ui/menus/main_menu.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/menus/homebrew.hpp"
|
||||
#include "ui/menus/filebrowser.hpp"
|
||||
#include "ui/menus/appstore.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::main {
|
||||
|
||||
// this holds 2 menus and allows for switching between them
|
||||
struct MainMenu final : Widget {
|
||||
MainMenu();
|
||||
~MainMenu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
void OnFocusLost() override;
|
||||
|
||||
private:
|
||||
void OnLRPress(std::shared_ptr<MenuBase> menu, Button b);
|
||||
void AddOnLPress();
|
||||
void AddOnRPress();
|
||||
|
||||
private:
|
||||
std::shared_ptr<homebrew::Menu> m_homebrew_menu{};
|
||||
std::shared_ptr<filebrowser::Menu> m_filebrowser_menu{};
|
||||
std::shared_ptr<appstore::Menu> m_app_store_menu{};
|
||||
std::shared_ptr<MenuBase> m_current_menu{};
|
||||
|
||||
std::string m_update_url{};
|
||||
std::string m_update_version{};
|
||||
std::string m_update_description{};
|
||||
bool m_update_avaliable{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::main
|
||||
26
sphaira/include/ui/menus/menu_base.hpp
Normal file
26
sphaira/include/ui/menus/menu_base.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "nro.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace sphaira::ui::menu {
|
||||
|
||||
struct MenuBase : Widget {
|
||||
MenuBase(std::string title);
|
||||
virtual ~MenuBase();
|
||||
|
||||
virtual void Update(Controller* controller, TouchInfo* touch);
|
||||
virtual void Draw(NVGcontext* vg, Theme* theme);
|
||||
void SetTitle(std::string title);
|
||||
void SetTitleSubHeading(std::string sub_heading);
|
||||
void SetSubHeading(std::string sub_heading);
|
||||
|
||||
private:
|
||||
std::string m_title;
|
||||
std::string m_title_sub_heading;
|
||||
std::string m_sub_heading;
|
||||
AppletType m_applet_type;
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu
|
||||
206
sphaira/include/ui/menus/themezer.hpp
Normal file
206
sphaira/include/ui/menus/themezer.hpp
Normal file
@@ -0,0 +1,206 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrollable_text.hpp"
|
||||
#include "option.hpp"
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::themezer {
|
||||
|
||||
enum class ImageDownloadState {
|
||||
None, // not started
|
||||
Progress, // Download started
|
||||
Done, // finished downloading
|
||||
Failed, // attempted to download but failed
|
||||
};
|
||||
|
||||
struct LazyImage {
|
||||
LazyImage() = default;
|
||||
~LazyImage();
|
||||
int image{};
|
||||
int w{}, h{};
|
||||
ImageDownloadState state{ImageDownloadState::None};
|
||||
u8 first_pixel[4]{};
|
||||
};
|
||||
|
||||
// "mutation setLike($type: String!, $id: String!, $value: Boolean!) {\n setLike(type: $type, id: $id, value: $value)\n}\n"
|
||||
|
||||
// https://api.themezer.net/?query=query($nsfw:Boolean,$target:String,$page:Int,$limit:Int,$sort:String,$order:String,$query:String){themeList(nsfw:$nsfw,target:$target,page:$page,limit:$limit,sort:$sort,order:$order,query:$query){id,creator{id,display_name},details{name,description},last_updated,dl_count,like_count,target,preview{original,thumb}}}&variables={"nsfw":false,"target":null,"page":1,"limit":10,"sort":"updated","order":"desc","query":null}
|
||||
// https://api.themezer.net/?query=query($nsfw:Boolean,$page:Int,$limit:Int,$sort:String,$order:String,$query:String){packList(nsfw:$nsfw,page:$page,limit:$limit,sort:$sort,order:$order,query:$query){id,creator{id,display_name},details{name,description},last_updated,dl_count,like_count,themes{id,creator{display_name},details{name,description},last_updated,dl_count,like_count,target,preview{original,thumb}}}}&variables={"nsfw":false,"page":1,"limit":10,"sort":"updated","order":"desc","query":null}
|
||||
// https://api.themezer.net/?query=query($id:String!){pack(id:$id){id,creator{display_name},details{name,description},last_updated,categories,dl_count,like_count,themes{id,details{name},layout{id,details{name}},categories,target,preview{original,thumb},last_updated,dl_count,like_count}}}&variables={"id":"16d"}
|
||||
|
||||
// https://api.themezer.net/?query=query{nxinstaller(id:"t9a6"){themes{filename,url,mimetype}}}
|
||||
// https://api.themezer.net/?query=query{downloadTheme(id:"t9a6"){filename,url,mimetype}}
|
||||
// https://api.themezer.net/?query=query{downloadPack(id:"t9a6"){filename,url,mimetype}}
|
||||
|
||||
// {"data":{"setLike":true}}
|
||||
// https://api.themezer.net/?query=mutation{setLike(type:"packs",id:"5",value:true){data{setLike}}}
|
||||
// https://api.themezer.net/?query=mutation($type:String!,$id:String!,$value:Boolean!){setLike(type:$type,id:$id,value:$value){data{setLike}}}&variables={"type":"packs","id":"5","value":true}
|
||||
|
||||
enum MenuState {
|
||||
MenuState_Normal,
|
||||
MenuState_Search,
|
||||
MenuState_Creator,
|
||||
};
|
||||
|
||||
enum ListType {
|
||||
ListType_Pack, // list complete packs
|
||||
ListType_Target, // list types
|
||||
};
|
||||
|
||||
enum class PageLoadState {
|
||||
None,
|
||||
Loading,
|
||||
Done,
|
||||
Error,
|
||||
};
|
||||
|
||||
struct Creator {
|
||||
std::string id;
|
||||
std::string display_name;
|
||||
};
|
||||
|
||||
struct Details {
|
||||
std::string name;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
struct Preview {
|
||||
std::string original;
|
||||
std::string thumb;
|
||||
LazyImage lazy_image;
|
||||
};
|
||||
|
||||
struct DownloadPack {
|
||||
std::string filename;
|
||||
std::string url;
|
||||
std::string mimetype;
|
||||
};
|
||||
|
||||
using DownloadTheme = DownloadPack;
|
||||
|
||||
struct ThemeEntry {
|
||||
std::string id;
|
||||
Creator creator;
|
||||
Details details;
|
||||
std::string last_updated;
|
||||
u64 dl_count;
|
||||
u64 like_count;
|
||||
std::vector<std::string> categories;
|
||||
std::string target;
|
||||
Preview preview;
|
||||
};
|
||||
|
||||
// struct Pack {
|
||||
// std::string id;
|
||||
// Creator creator;
|
||||
// Details details;
|
||||
// std::string last_updated;
|
||||
// std::vector<std::string> categories;
|
||||
// u64 dl_count;
|
||||
// u64 like_count;
|
||||
// std::vector<ThemeEntry> themes;
|
||||
// };
|
||||
|
||||
struct PackListEntry {
|
||||
std::string id;
|
||||
Creator creator;
|
||||
Details details;
|
||||
std::string last_updated;
|
||||
std::vector<std::string> categories;
|
||||
u64 dl_count;
|
||||
u64 like_count;
|
||||
std::vector<ThemeEntry> themes;
|
||||
};
|
||||
|
||||
struct Pagination {
|
||||
u64 page;
|
||||
u64 limit;
|
||||
u64 page_count;
|
||||
u64 item_count;
|
||||
};
|
||||
|
||||
struct PackList {
|
||||
std::vector<PackListEntry> packList;
|
||||
Pagination pagination;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
// these index into a string array
|
||||
u32 target_index{};
|
||||
u32 sort_index{};
|
||||
u32 order_index{};
|
||||
// search query, if empty, its not used
|
||||
std::string query;
|
||||
// this is actually an array of creator ids, but we don't support that feature
|
||||
// if empty, its not used
|
||||
std::string creator;
|
||||
// defaults
|
||||
u32 page{1};
|
||||
u32 limit{18};
|
||||
bool nsfw{false};
|
||||
|
||||
void SetQuery(std::string new_query) {
|
||||
query = new_query;
|
||||
}
|
||||
|
||||
void RemoveQuery() {
|
||||
query.clear();
|
||||
}
|
||||
|
||||
void SetCreator(Creator new_creator) {
|
||||
creator = new_creator.id;
|
||||
}
|
||||
|
||||
void RemoveCreator() {
|
||||
creator.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct Menu; // fwd
|
||||
|
||||
struct PageEntry {
|
||||
std::vector<PackListEntry> m_packList;
|
||||
Pagination m_pagination{};
|
||||
PageLoadState m_ready{PageLoadState::None};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu();
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(std::size_t index) {
|
||||
m_index = index;
|
||||
}
|
||||
|
||||
// void SetSearch(const std::string& term);
|
||||
// void SetAuthor();
|
||||
|
||||
void InvalidateAllPages();
|
||||
void PackListDownload();
|
||||
void OnPackListDownload();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "themezer";
|
||||
static constexpr inline u32 MAX_ON_PAGE = 16; // same as website
|
||||
|
||||
std::vector<PageEntry> m_pages;
|
||||
std::size_t m_page_index{};
|
||||
std::size_t m_page_index_max{1};
|
||||
|
||||
std::string m_search{};
|
||||
|
||||
std::size_t m_start{};
|
||||
std::size_t m_index{}; // where i am in the array
|
||||
|
||||
// options
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
||||
option::OptionLong m_order{INI_SECTION, "order", 0};
|
||||
option::OptionBool m_nsfw{INI_SECTION, "nsfw", false};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::themezer
|
||||
57
sphaira/include/ui/notification.hpp
Normal file
57
sphaira/include/ui/notification.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "object.hpp"
|
||||
|
||||
#include <deque>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class NotifEntry final : public Object {
|
||||
public:
|
||||
enum class Side { LEFT, RIGHT };
|
||||
|
||||
public:
|
||||
NotifEntry(std::string text, Side side);
|
||||
~NotifEntry() = default;
|
||||
|
||||
auto Draw(NVGcontext* vg, Theme* theme, float y) -> bool;
|
||||
auto GetSide() const noexcept { return m_side; }
|
||||
auto IsDone() const noexcept { return m_count == 0; }
|
||||
|
||||
private:
|
||||
void OnLayoutChange() override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
std::string m_text;
|
||||
std::size_t m_count{180}; // count down to zero
|
||||
Side m_side;
|
||||
bool m_bounds_measured{};
|
||||
};
|
||||
|
||||
class NotifMananger final : public Object {
|
||||
public:
|
||||
NotifMananger() = default;
|
||||
~NotifMananger() = default;
|
||||
|
||||
void OnLayoutChange() override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
void Push(const NotifEntry& entry);
|
||||
void Pop(NotifEntry::Side side);
|
||||
void Clear(NotifEntry::Side side);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
using Entries = std::deque<NotifEntry>;
|
||||
|
||||
private:
|
||||
void Draw(NVGcontext* vg, Theme* theme, Entries& entries);
|
||||
|
||||
private:
|
||||
Entries m_entries_left;
|
||||
Entries m_entries_right;
|
||||
Mutex m_mutex{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
92
sphaira/include/ui/nvg_util.hpp
Normal file
92
sphaira/include/ui/nvg_util.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include "nanovg.h"
|
||||
#include "ui/widget.hpp"
|
||||
|
||||
namespace sphaira::ui::gfx {
|
||||
|
||||
enum class Colour {
|
||||
BLACK,
|
||||
LIGHT_BLACK,
|
||||
SILVER,
|
||||
DARK_GREY,
|
||||
GREY,
|
||||
WHITE,
|
||||
CYAN,
|
||||
TEAL,
|
||||
BLUE,
|
||||
LIGHT_BLUE,
|
||||
YELLOW,
|
||||
RED,
|
||||
};
|
||||
|
||||
void drawImage(NVGcontext*, float x, float y, float w, float h, int texture);
|
||||
void drawImage(NVGcontext*, Vec4 v, int texture);
|
||||
void drawImageRounded(NVGcontext*, float x, float y, float w, float h, int texture);
|
||||
void drawImageRounded(NVGcontext*, Vec4 v, int texture);
|
||||
|
||||
auto getColour(Colour c) -> NVGcolor;
|
||||
|
||||
void dimBackground(NVGcontext*);
|
||||
|
||||
void drawRect(NVGcontext*, float x, float y, float w, float h, Colour c, bool rounded = false);
|
||||
void drawRect(NVGcontext*, Vec4 vec, Colour c, bool rounded = false);
|
||||
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor& c, bool rounded = false);
|
||||
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor&& c, bool rounded = false);
|
||||
void drawRect(NVGcontext*, Vec4 vec, const NVGcolor& c, bool rounded = false);
|
||||
void drawRect(NVGcontext*, Vec4 vec, const NVGcolor&& c, bool rounded = false);
|
||||
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint& p, bool rounded = false);
|
||||
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint&& p, bool rounded = false);
|
||||
void drawRect(NVGcontext*, Vec4 vec, const NVGpaint& p, bool rounded = false);
|
||||
void drawRect(NVGcontext*, Vec4 vec, const NVGpaint&& p, bool rounded = false);
|
||||
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, Colour c);
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, Colour c);
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGcolor& c);
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGcolor&& c);
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGcolor& c);
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGcolor&& c);
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGpaint& p);
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGpaint&& p);
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint& p);
|
||||
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint&& p);
|
||||
|
||||
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, Colour c);
|
||||
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c);
|
||||
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor&& c);
|
||||
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p);
|
||||
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint&& p);
|
||||
|
||||
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, Colour c);
|
||||
void drawText(NVGcontext*, float x, float y, float size, Colour c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
void drawText(NVGcontext*, Vec2 vec, float size, const char* str, const char* end, int align, Colour c);
|
||||
void drawText(NVGcontext*, Vec2 vec, float size, Colour c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c);
|
||||
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor&& c);
|
||||
void drawText(NVGcontext*, float x, float y, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
void drawText(NVGcontext*, float x, float y, float size, const NVGcolor&& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
void drawText(NVGcontext*, Vec2 vec, float size, const char* str, const char* end, int align, const NVGcolor& c);
|
||||
void drawText(NVGcontext*, Vec2 vec, float size, const char* str, const char* end, int align, const NVGcolor&& c);
|
||||
void drawText(NVGcontext*, Vec2 vec, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
void drawText(NVGcontext*, Vec2 vec, float size, const NVGcolor&& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
void drawTextArgs(NVGcontext*, float x, float y, float size, int align, Colour c, const char* str, ...) __attribute__ ((format (printf, 7, 8)));
|
||||
void drawTextArgs(NVGcontext*, float x, float y, float size, int align, const NVGcolor& c, const char* str, ...) __attribute__ ((format (printf, 7, 8)));
|
||||
|
||||
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, NVGcolor&& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, Colour c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
|
||||
void textBounds(NVGcontext*, float x, float y, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6)));
|
||||
// void textBounds(NVGcontext*, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6)));
|
||||
// void textBounds(NVGcontext*, float *bounds, const char* str);
|
||||
|
||||
auto getButton(Button button) -> const char*;
|
||||
void drawButton(NVGcontext* vg, float x, float y, float size, Button button);
|
||||
void drawButtons(NVGcontext* vg, const Widget::Actions& actions, const NVGcolor& c, float start_x = 1220.f);
|
||||
|
||||
void drawDimBackground(NVGcontext* vg);
|
||||
|
||||
void updateHighlightAnimation();
|
||||
void getHighlightAnimation(float* gradientX, float* gradientY, float* color);
|
||||
|
||||
} // namespace sphaira::ui::gfx
|
||||
81
sphaira/include/ui/object.hpp
Normal file
81
sphaira/include/ui/object.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class Object {
|
||||
public:
|
||||
Object() = default;
|
||||
virtual ~Object() = default;
|
||||
|
||||
// virtual auto OnLayoutChange() -> void = 0;
|
||||
virtual auto OnLayoutChange() -> void {};
|
||||
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void = 0;
|
||||
|
||||
auto GetPos() const noexcept {
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
auto GetX() const noexcept {
|
||||
return m_pos.x;
|
||||
}
|
||||
|
||||
auto GetY() const noexcept {
|
||||
return m_pos.y;
|
||||
}
|
||||
|
||||
auto GetW() const noexcept {
|
||||
return m_pos.w;
|
||||
}
|
||||
|
||||
auto GetH() const noexcept {
|
||||
return m_pos.h;
|
||||
}
|
||||
|
||||
auto SetX(float a) noexcept {
|
||||
return m_pos.x = a;
|
||||
}
|
||||
|
||||
auto SetY(float a) noexcept {
|
||||
return m_pos.y = a;
|
||||
}
|
||||
|
||||
auto SetW(float a) noexcept {
|
||||
return m_pos.w = a;
|
||||
}
|
||||
|
||||
auto SetH(float a) noexcept {
|
||||
return m_pos.h = a;
|
||||
}
|
||||
|
||||
auto SetPos(float x, float y, float w, float h) noexcept -> void {
|
||||
m_pos = { x, y, w, h };
|
||||
}
|
||||
|
||||
auto SetPos(Vec4 v) noexcept -> void {
|
||||
m_pos = v;
|
||||
}
|
||||
|
||||
auto InXBounds(float x) const -> bool {
|
||||
return x >= m_pos.x && x <= m_pos.x + m_pos.w;
|
||||
}
|
||||
|
||||
auto InYBounds(float y) const -> bool {
|
||||
return y >= m_pos.y && y <= m_pos.y + m_pos.h;
|
||||
}
|
||||
|
||||
auto IsHidden() const noexcept -> bool {
|
||||
return m_hidden;
|
||||
}
|
||||
|
||||
auto SetHidden(bool value = true) noexcept -> void {
|
||||
m_hidden = value;
|
||||
}
|
||||
|
||||
protected:
|
||||
Vec4 m_pos{};
|
||||
bool m_hidden{false};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
59
sphaira/include/ui/option_box.hpp
Normal file
59
sphaira/include/ui/option_box.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class OptionBoxEntry final : public Widget {
|
||||
public:
|
||||
|
||||
public:
|
||||
OptionBoxEntry(const std::string& text, Vec4 pos);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override {}
|
||||
auto OnLayoutChange() -> void override {}
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
auto Selected(bool enable) -> void;
|
||||
private:
|
||||
|
||||
private:
|
||||
std::string m_text;
|
||||
Vec2 m_text_pos{};
|
||||
bool m_selected{false};
|
||||
};
|
||||
|
||||
// todo: support multiline messages
|
||||
// todo: support upto 4 options.
|
||||
class OptionBox final : public Widget {
|
||||
public:
|
||||
using Callback = std::function<void(std::optional<std::size_t> index)>;
|
||||
using Option = std::string;
|
||||
using Options = std::vector<Option>;
|
||||
|
||||
public:
|
||||
OptionBox(const std::string& message, const Option& a, Callback cb); // confirm
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, Callback cb); // yesno
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, std::size_t index, Callback cb); // yesno
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, Callback cb); // tri
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, std::size_t index, Callback cb); // tri
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto OnLayoutChange() -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
private:
|
||||
auto Setup(std::size_t index) -> void; // common setup values
|
||||
|
||||
private:
|
||||
std::string m_message;
|
||||
Callback m_callback;
|
||||
|
||||
Vec4 m_spacer_line{};
|
||||
|
||||
std::size_t m_index{};
|
||||
std::vector<OptionBoxEntry> m_entries;
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
27
sphaira/include/ui/option_list.hpp
Normal file
27
sphaira/include/ui/option_list.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class OptionList final : public Widget {
|
||||
public:
|
||||
using Options = std::vector<std::pair<std::string, std::function<void()>>>;
|
||||
|
||||
public:
|
||||
OptionList(Options _options);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto OnLayoutChange() -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
protected:
|
||||
Options m_options;
|
||||
std::size_t m_index{};
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
47
sphaira/include/ui/popup_list.hpp
Normal file
47
sphaira/include/ui/popup_list.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/scrollbar.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class PopupList final : public Widget {
|
||||
public:
|
||||
using Items = std::vector<std::string>;
|
||||
using Callback = std::function<void(std::optional<std::size_t>)>;
|
||||
|
||||
public:
|
||||
explicit PopupList(std::string title, Items items, Callback cb, std::size_t index = 0);
|
||||
PopupList(std::string title, Items items, Callback cb, std::string index);
|
||||
PopupList(std::string title, Items items, std::string& index_str_ref, std::size_t& index);
|
||||
PopupList(std::string title, Items items, std::string& index_ref);
|
||||
PopupList(std::string title, Items items, std::size_t& index_ref);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto OnLayoutChange() -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
private:
|
||||
static constexpr Vec2 m_title_pos{70.f, 28.f};
|
||||
static constexpr Vec4 m_block{280.f, 110.f, 720.f, 60.f};
|
||||
static constexpr float m_text_xoffset{15.f};
|
||||
static constexpr float m_line_width{1220.f};
|
||||
|
||||
std::string m_title;
|
||||
Items m_items;
|
||||
Callback m_callback;
|
||||
std::size_t m_index; // index in list array
|
||||
std::size_t m_index_offset{}; // drawing from array start
|
||||
|
||||
// std::size_t& index_ref;
|
||||
// std::string& index_str_ref;
|
||||
|
||||
float m_selected_y{};
|
||||
float m_yoff{};
|
||||
float m_line_top{};
|
||||
float m_line_bottom{};
|
||||
ScrollBar m_scrollbar;
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
62
sphaira/include/ui/progress_box.hpp
Normal file
62
sphaira/include/ui/progress_box.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "widget.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <functional>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
struct ProgressBox;
|
||||
using ProgressBoxCallback = std::function<bool(ProgressBox*)>;
|
||||
using ProgressBoxDoneCallback = std::function<void(bool success)>;
|
||||
|
||||
struct ProgressBox final : Widget {
|
||||
ProgressBox(
|
||||
const std::string& title,
|
||||
ProgressBoxCallback callback, ProgressBoxDoneCallback done = [](bool success){},
|
||||
int cpuid = 1, int prio = 0x2C, int stack_size = 1024*1024
|
||||
);
|
||||
~ProgressBox();
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
auto NewTransfer(const std::string& transfer) -> ProgressBox&;
|
||||
auto UpdateTransfer(u64 offset, u64 size) -> ProgressBox&;
|
||||
void RequestExit();
|
||||
auto ShouldExit() -> bool;
|
||||
|
||||
// helper functions
|
||||
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||
void Yield();
|
||||
|
||||
public:
|
||||
struct ThreadData {
|
||||
ProgressBox* pbox;
|
||||
ProgressBoxCallback callback;
|
||||
bool result;
|
||||
};
|
||||
|
||||
private:
|
||||
Mutex m_mutex{};
|
||||
Thread m_thread{};
|
||||
ThreadData m_thread_data{};
|
||||
|
||||
ProgressBoxDoneCallback m_done{};
|
||||
std::string m_title{};
|
||||
std::string m_transfer{};
|
||||
u64 m_size{};
|
||||
u64 m_offset{};
|
||||
bool m_exit_requested{};
|
||||
};
|
||||
|
||||
// this is a helper function that does many things.
|
||||
// 1. creates a progress box, pushes that box to app
|
||||
// 2. creates a thread and passes the pbox and callback to that thread
|
||||
// 3. that thread calls the callback.
|
||||
|
||||
// this allows for blocking processes to run on a seperate thread whilst
|
||||
// updating the ui with the progress of the operation.
|
||||
// the callback should poll ShouldExit() whether to keep running
|
||||
|
||||
} // namespace sphaira::ui {
|
||||
28
sphaira/include/ui/scrollable_text.hpp
Normal file
28
sphaira/include/ui/scrollable_text.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
// todo: remove fixed values from appstore
|
||||
struct ScrollableText final : Widget {
|
||||
ScrollableText(const std::string& text, float x, float y, float y_clip, float w, float font_size);
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
std::string m_text;
|
||||
// static constexpr float m_y_off_base = 374;
|
||||
// float m_y_off = m_y_off_base;
|
||||
// static constexpr float m_clip_y = 250.0F;
|
||||
|
||||
const float m_y_off_base;
|
||||
float m_y_off;
|
||||
const float m_clip_y;
|
||||
const float m_end_w;
|
||||
static constexpr float m_step = 30;
|
||||
|
||||
int m_index = 0;
|
||||
const float m_font_size;
|
||||
float m_bounds[4];
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
34
sphaira/include/ui/scrollbar.hpp
Normal file
34
sphaira/include/ui/scrollbar.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class ScrollBar final : public Widget {
|
||||
public:
|
||||
enum class Direction { DOWN, UP };
|
||||
|
||||
public:
|
||||
ScrollBar() = default;
|
||||
ScrollBar(Vec4 bounds, float entry_height, std::size_t entries);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override {}
|
||||
auto OnLayoutChange() -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
auto Setup(Vec4 bounds, float entry_height, std::size_t entries) -> void;
|
||||
auto Move(Direction direction) -> void;
|
||||
|
||||
private:
|
||||
auto Setup() -> void;
|
||||
|
||||
private:
|
||||
Vec4 m_bounds{};
|
||||
std::size_t m_entries{};
|
||||
std::size_t m_index{};
|
||||
float m_entry_height{};
|
||||
float m_step_size{};
|
||||
bool m_should_draw{false};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
136
sphaira/include/ui/sidebar.hpp
Normal file
136
sphaira/include/ui/sidebar.hpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class SidebarEntryBase : public Widget {
|
||||
public:
|
||||
SidebarEntryBase(std::string&& title);
|
||||
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
virtual auto OnLayoutChange() -> void override {}
|
||||
|
||||
protected:
|
||||
std::string m_title;
|
||||
Vec2 m_offset{};
|
||||
};
|
||||
|
||||
class SidebarEntryBool final : public SidebarEntryBase {
|
||||
public:
|
||||
using Callback = std::function<void(bool&)>;
|
||||
|
||||
public:
|
||||
SidebarEntryBool(std::string title, bool option, Callback cb, std::string true_str = "On", std::string false_str = "Off");
|
||||
SidebarEntryBool(std::string title, bool& option, std::string true_str = "On", std::string false_str = "Off");
|
||||
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
private:
|
||||
bool m_option;
|
||||
Callback m_callback;
|
||||
std::string m_true_str;
|
||||
std::string m_false_str;
|
||||
};
|
||||
|
||||
class SidebarEntryCallback final : public SidebarEntryBase {
|
||||
public:
|
||||
using Callback = std::function<void()>;
|
||||
|
||||
public:
|
||||
SidebarEntryCallback(std::string title, Callback cb, bool pop_on_click = false);
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
private:
|
||||
Callback m_callback;
|
||||
bool m_pop_on_click;
|
||||
};
|
||||
|
||||
class SidebarEntryArray final : public SidebarEntryBase {
|
||||
public:
|
||||
using Items = std::vector<std::string>;
|
||||
using ListCallback = std::function<void()>;
|
||||
using Callback = std::function<void(std::size_t& index)>;
|
||||
|
||||
public:
|
||||
explicit SidebarEntryArray(std::string title, Items items, Callback cb, std::size_t index = 0);
|
||||
SidebarEntryArray(std::string title, Items items, Callback cb, std::string index);
|
||||
SidebarEntryArray(std::string title, Items items, std::string& index);
|
||||
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
private:
|
||||
Items m_items;
|
||||
ListCallback m_list_callback;
|
||||
Callback m_callback;
|
||||
std::size_t m_index;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class SidebarEntrySlider final : public SidebarEntryBase {
|
||||
public:
|
||||
SidebarEntrySlider(std::string title, T& value, T min, T max)
|
||||
: SidebarEntryBase{title}
|
||||
, m_value{value}
|
||||
, m_min{min}
|
||||
, m_max{max} {
|
||||
|
||||
}
|
||||
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
|
||||
private:
|
||||
T& m_value;
|
||||
T m_min;
|
||||
T m_max;
|
||||
T m_step{};
|
||||
Vec4 m_bar{};
|
||||
Vec4 m_bar_fill{};
|
||||
};
|
||||
|
||||
class Sidebar final : public Widget {
|
||||
public:
|
||||
enum class Side { LEFT, RIGHT };
|
||||
using Items = std::vector<std::shared_ptr<SidebarEntryBase>>;
|
||||
|
||||
public:
|
||||
Sidebar(std::string title, Side side, Items&& items);
|
||||
Sidebar(std::string title, Side side);
|
||||
Sidebar(std::string title, std::string sub, Side side, Items&& items);
|
||||
Sidebar(std::string title, std::string sub, Side side);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto OnLayoutChange() -> void override {}
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
auto OnFocusGained() noexcept -> void override;
|
||||
auto OnFocusLost() noexcept -> void override;
|
||||
|
||||
void Add(std::shared_ptr<SidebarEntryBase> entry);
|
||||
void AddSpacer();
|
||||
void AddHeader(std::string name);
|
||||
|
||||
private:
|
||||
void SetIndex(std::size_t index);
|
||||
|
||||
private:
|
||||
std::string m_title;
|
||||
std::string m_sub;
|
||||
Side m_side;
|
||||
Items m_items;
|
||||
std::size_t m_index{};
|
||||
std::size_t m_index_offset{};
|
||||
|
||||
Vec4 m_top_bar{};
|
||||
Vec4 m_bottom_bar{};
|
||||
Vec2 m_title_pos{};
|
||||
Vec4 m_base_pos{};
|
||||
|
||||
float m_selected_y{};
|
||||
|
||||
static constexpr float m_title_size{28.f};
|
||||
// static constexpr Vec2 box_size{380.f, 70.f};
|
||||
static constexpr Vec2 m_box_size{400.f, 70.f};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
367
sphaira/include/ui/types.hpp
Normal file
367
sphaira/include/ui/types.hpp
Normal file
@@ -0,0 +1,367 @@
|
||||
#pragma once
|
||||
|
||||
#include "nanovg.h"
|
||||
#include "pulsar.h"
|
||||
#include "fs.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <variant>
|
||||
|
||||
namespace sphaira {
|
||||
|
||||
#define SCREEN_WIDTH 1280.f
|
||||
#define SCREEN_HEIGHT 720.f
|
||||
|
||||
struct [[nodiscard]] Vec2 {
|
||||
constexpr Vec2() = default;
|
||||
constexpr Vec2(float _x, float _y) : x{_x}, y{_y} {}
|
||||
|
||||
float& operator[](std::size_t idx) {
|
||||
switch (idx) {
|
||||
case 0: return x;
|
||||
case 1: return y;
|
||||
}
|
||||
__builtin_unreachable();
|
||||
// throw;
|
||||
}
|
||||
|
||||
constexpr const float& operator[](std::size_t idx) const {
|
||||
switch (idx) {
|
||||
case 0: return x;
|
||||
case 1: return y;
|
||||
}
|
||||
__builtin_unreachable();
|
||||
// throw;
|
||||
}
|
||||
|
||||
constexpr Vec2 operator+(const Vec2& v) const noexcept {
|
||||
return {x + v.x, y + v.y};
|
||||
}
|
||||
|
||||
constexpr Vec2& operator+=(const Vec2& v) noexcept {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool operator==(const Vec2& v) const noexcept {
|
||||
return x == v.x && y == v.y;
|
||||
}
|
||||
|
||||
float x{}, y{};
|
||||
};
|
||||
|
||||
struct [[nodiscard]] Vec4 {
|
||||
constexpr Vec4() = default;
|
||||
constexpr Vec4(float _x, float _y, float _w, float _h) : x{_x}, y{_y}, w{_w}, h{_h} {}
|
||||
constexpr Vec4(Vec2 vec0, Vec2 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.x}, h{vec1.y} {}
|
||||
constexpr Vec4(Vec4 vec0, Vec4 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.w}, h{vec1.h} {}
|
||||
|
||||
float& operator[](std::size_t idx) {
|
||||
switch (idx) {
|
||||
case 0: return x;
|
||||
case 1: return y;
|
||||
case 2: return w;
|
||||
case 3: return h;
|
||||
}
|
||||
__builtin_unreachable();
|
||||
// throw;
|
||||
}
|
||||
|
||||
constexpr const float& operator[](std::size_t idx) const {
|
||||
switch (idx) {
|
||||
case 0: return x;
|
||||
case 1: return y;
|
||||
case 2: return w;
|
||||
case 3: return h;
|
||||
}
|
||||
__builtin_unreachable();
|
||||
// throw;
|
||||
}
|
||||
|
||||
constexpr Vec2 operator+(const Vec2& v) const noexcept {
|
||||
return {x + v.x, y + v.y};
|
||||
}
|
||||
|
||||
constexpr Vec4 operator+(const Vec4& v) const noexcept {
|
||||
return {x + v.x, y + v.y, w + v.w, h + v.h};
|
||||
}
|
||||
|
||||
constexpr Vec4& operator+=(const Vec2& v) noexcept {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Vec4& operator+=(const Vec4& v) noexcept {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool operator==(const Vec2& v) const noexcept {
|
||||
return x == v.x && y == v.y;
|
||||
}
|
||||
|
||||
constexpr bool operator==(const Vec4& v) const noexcept {
|
||||
return x == v.x && y == v.y && w == v.w && h == v.h;
|
||||
}
|
||||
|
||||
float x{}, y{}, w{}, h{};
|
||||
};
|
||||
|
||||
struct TimeStamp {
|
||||
TimeStamp() {
|
||||
start = armGetSystemTick();
|
||||
}
|
||||
|
||||
auto GetNs() -> u64 {
|
||||
const auto end_ticks = armGetSystemTick();
|
||||
return armTicksToNs(end_ticks) - armTicksToNs(start);
|
||||
}
|
||||
|
||||
auto GetSeconds() -> double {
|
||||
const double ns = GetNs();
|
||||
return ns/1000.0/1000.0/1000.0;
|
||||
}
|
||||
|
||||
u64 start;
|
||||
};
|
||||
|
||||
enum class ElementType {
|
||||
None,
|
||||
Texture,
|
||||
Colour,
|
||||
};
|
||||
|
||||
struct ElementEntry {
|
||||
ElementType type;
|
||||
int texture;
|
||||
NVGcolor colour;
|
||||
};
|
||||
|
||||
enum ThemeEntryID {
|
||||
ThemeEntryID_BACKGROUND,
|
||||
ThemeEntryID_LOGO,
|
||||
|
||||
ThemeEntryID_GRID,
|
||||
ThemeEntryID_GRID_HOVER,
|
||||
ThemeEntryID_SELECTED,
|
||||
ThemeEntryID_SELECTED_OVERLAY,
|
||||
ThemeEntryID_TEXT,
|
||||
ThemeEntryID_TEXT_SELECTED,
|
||||
|
||||
ThemeEntryID_ICON_AUDIO,
|
||||
ThemeEntryID_ICON_VIDEO,
|
||||
ThemeEntryID_ICON_IMAGE,
|
||||
ThemeEntryID_ICON_FILE,
|
||||
ThemeEntryID_ICON_FOLDER,
|
||||
ThemeEntryID_ICON_ZIP,
|
||||
ThemeEntryID_ICON_GAME,
|
||||
ThemeEntryID_ICON_NRO,
|
||||
|
||||
ThemeEntryID_MAX,
|
||||
};
|
||||
|
||||
struct ThemeMeta {
|
||||
std::string name;
|
||||
std::string author;
|
||||
std::string version;
|
||||
std::string ini_path;
|
||||
};
|
||||
|
||||
struct Theme {
|
||||
std::string name;
|
||||
std::string author;
|
||||
std::string version;
|
||||
fs::FsPath path;
|
||||
PLSR_BFSTM music;
|
||||
ElementEntry elements[ThemeEntryID_MAX];
|
||||
|
||||
// NVGcolor background; // bg
|
||||
// NVGcolor lines; // grid lines
|
||||
// NVGcolor spacer; // lines in popup box
|
||||
// NVGcolor text; // text colour
|
||||
// NVGcolor text_info; // description text
|
||||
NVGcolor selected; // selected colours
|
||||
// NVGcolor overlay; // popup overlay colour
|
||||
|
||||
// void DrawElement(float x, float y, float w, float h, ThemeEntryID id);
|
||||
};
|
||||
|
||||
enum class TouchState {
|
||||
Start, // set when touch has started
|
||||
Touching, // set when touch is held longer than 1 frame
|
||||
Stop, // set after touch is released
|
||||
None, // set when there is no touch
|
||||
};
|
||||
|
||||
struct TouchInfo {
|
||||
s32 initial_x;
|
||||
s32 initial_y;
|
||||
|
||||
s32 cur_x;
|
||||
s32 cur_y;
|
||||
|
||||
s32 prev_x;
|
||||
s32 prev_y;
|
||||
|
||||
u32 finger_id;
|
||||
|
||||
bool is_touching;
|
||||
bool is_tap;
|
||||
};
|
||||
|
||||
enum class Button : u64 {
|
||||
A = static_cast<u64>(HidNpadButton_A),
|
||||
B = static_cast<u64>(HidNpadButton_B),
|
||||
X = static_cast<u64>(HidNpadButton_X),
|
||||
Y = static_cast<u64>(HidNpadButton_Y),
|
||||
L = static_cast<u64>(HidNpadButton_L),
|
||||
R = static_cast<u64>(HidNpadButton_R),
|
||||
L2 = static_cast<u64>(HidNpadButton_ZL),
|
||||
R2 = static_cast<u64>(HidNpadButton_ZR),
|
||||
L3 = static_cast<u64>(HidNpadButton_StickL),
|
||||
R3 = static_cast<u64>(HidNpadButton_StickR),
|
||||
START = static_cast<u64>(HidNpadButton_Plus),
|
||||
SELECT = static_cast<u64>(HidNpadButton_Minus),
|
||||
|
||||
// todo:
|
||||
DPAD_LEFT = static_cast<u64>(HidNpadButton_Left),
|
||||
DPAD_RIGHT = static_cast<u64>(HidNpadButton_Right),
|
||||
DPAD_UP = static_cast<u64>(HidNpadButton_Up),
|
||||
DPAD_DOWN = static_cast<u64>(HidNpadButton_Down),
|
||||
|
||||
LS_LEFT = static_cast<u64>(HidNpadButton_StickLLeft),
|
||||
LS_RIGHT = static_cast<u64>(HidNpadButton_StickLRight),
|
||||
LS_UP = static_cast<u64>(HidNpadButton_StickLUp),
|
||||
LS_DOWN = static_cast<u64>(HidNpadButton_StickLDown),
|
||||
|
||||
RS_LEFT = static_cast<u64>(HidNpadButton_StickRLeft),
|
||||
RS_RIGHT = static_cast<u64>(HidNpadButton_StickRRight),
|
||||
RS_UP = static_cast<u64>(HidNpadButton_StickRUp),
|
||||
RS_DOWN = static_cast<u64>(HidNpadButton_StickRDown),
|
||||
|
||||
ANY_LEFT = static_cast<u64>(HidNpadButton_AnyLeft),
|
||||
ANY_RIGHT = static_cast<u64>(HidNpadButton_AnyRight),
|
||||
ANY_UP = static_cast<u64>(HidNpadButton_AnyUp),
|
||||
ANY_DOWN = static_cast<u64>(HidNpadButton_AnyDown),
|
||||
|
||||
// todo: remove these old buttons
|
||||
LEFT = static_cast<u64>(HidNpadButton_AnyLeft),
|
||||
RIGHT = static_cast<u64>(HidNpadButton_AnyRight),
|
||||
UP = static_cast<u64>(HidNpadButton_AnyUp),
|
||||
DOWN = static_cast<u64>(HidNpadButton_AnyDown),
|
||||
|
||||
ANY_BUTTON = A | B | X | Y | L | R | L2 | R2 | L3 | R3 | START | SELECT,
|
||||
ANY_HORIZONTAL = LEFT | RIGHT,
|
||||
ANY_VERTICAL = UP | DOWN,
|
||||
ANY_DIRECTION = ANY_HORIZONTAL | ANY_VERTICAL,
|
||||
ANY = ANY_BUTTON | ANY_DIRECTION
|
||||
};
|
||||
|
||||
inline Button operator|(Button a, Button b) {
|
||||
return static_cast<Button>(static_cast<u64>(a) | static_cast<u64>(b));
|
||||
}
|
||||
|
||||
// when the callback we be called, can be xord
|
||||
enum ActionType : u8 {
|
||||
DOWN = 1 << 0,
|
||||
UP = 1 << 1,
|
||||
HELD = 1 << 2,
|
||||
};
|
||||
|
||||
inline ActionType operator|(ActionType a, ActionType b) {
|
||||
return static_cast<ActionType>(static_cast<u64>(a) | static_cast<u64>(b));
|
||||
}
|
||||
|
||||
struct Action final {
|
||||
using Callback = std::variant<
|
||||
std::function<void()>,
|
||||
std::function<void(bool)>
|
||||
>;
|
||||
|
||||
Action(Callback cb) : m_type{ActionType::DOWN}, m_hint{""}, m_callback{cb}, m_hidden{true} {}
|
||||
Action(std::string hint, Callback cb) : m_type{ActionType::DOWN}, m_hint{hint}, m_callback{cb} {}
|
||||
Action(u8 type, Callback cb) : m_type{type}, m_hint{""}, m_callback{cb}, m_hidden{true} {}
|
||||
Action(u8 type, std::string hint, Callback cb) : m_type{type}, m_hint{hint}, m_callback{cb} {}
|
||||
|
||||
auto IsHidden() const noexcept { return m_hidden; }
|
||||
|
||||
auto Invoke(bool down) const {
|
||||
// todo: make this a visit
|
||||
switch (m_callback.index()) {
|
||||
case 0:
|
||||
std::get<0>(m_callback)();
|
||||
break;
|
||||
case 1:
|
||||
std::get<1>(m_callback)(down);
|
||||
break;
|
||||
}
|
||||
// std::visit([down, this](auto& cb){
|
||||
// cb(down);
|
||||
// }), m_callback;
|
||||
}
|
||||
|
||||
u8 m_type;
|
||||
std::string m_hint; // todo: make optional
|
||||
Callback m_callback;
|
||||
bool m_hidden{false}; // replace this optional text
|
||||
};
|
||||
|
||||
struct Controller {
|
||||
u64 m_kdown{};
|
||||
u64 m_kheld{};
|
||||
u64 m_kup{};
|
||||
|
||||
constexpr auto Set(Button button, bool down) noexcept -> void {
|
||||
m_kdown = static_cast<u64>(down ? m_kdown | static_cast<u64>(button) : m_kdown & ~static_cast<u64>(button));
|
||||
}
|
||||
|
||||
constexpr auto Got(u64 k, Button button) const noexcept -> bool {
|
||||
return (k & static_cast<u64>(button)) > 0;
|
||||
// return (k & static_cast<u64>(button)) == static_cast<u64>(button);
|
||||
}
|
||||
|
||||
constexpr auto GotDown(Button button) const noexcept -> bool {
|
||||
return Got(m_kdown, button);
|
||||
}
|
||||
|
||||
constexpr auto GotHeld(Button button) const noexcept -> bool {
|
||||
return Got(m_kheld, button);
|
||||
}
|
||||
|
||||
constexpr auto GotUp(Button button) const noexcept -> bool {
|
||||
return (m_kup & static_cast<u64>(button)) > 0;
|
||||
}
|
||||
|
||||
constexpr auto Reset() noexcept -> void {
|
||||
m_kdown = 0;
|
||||
m_kup = 0;
|
||||
}
|
||||
|
||||
void UpdateButtonHeld(HidNpadButton buttons) {
|
||||
if (m_kdown & buttons) {
|
||||
m_step = 50;
|
||||
m_counter = 0;
|
||||
} else if (m_kheld & buttons) {
|
||||
m_counter += m_step;
|
||||
|
||||
if (m_counter >= m_MAX) {
|
||||
m_kdown |= buttons;
|
||||
m_counter = 0;
|
||||
m_step = std::min(m_step + 50, m_MAX_STEP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int m_MAX = 1000;
|
||||
static constexpr int m_MAX_STEP = 250;
|
||||
int m_step = 50;
|
||||
int m_counter = 0;
|
||||
};
|
||||
|
||||
} // namespace sphaira
|
||||
75
sphaira/include/ui/widget.hpp
Normal file
75
sphaira/include/ui/widget.hpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/object.hpp"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
struct Widget : public Object {
|
||||
virtual ~Widget() = default;
|
||||
|
||||
virtual void Update(Controller* controller, TouchInfo* touch);
|
||||
virtual void Draw(NVGcontext* vg, Theme* theme);
|
||||
|
||||
virtual void OnFocusGained() {
|
||||
m_focus = true;
|
||||
}
|
||||
|
||||
virtual void OnFocusLost() {
|
||||
m_focus = false;
|
||||
}
|
||||
|
||||
virtual auto HasFocus() const -> bool {
|
||||
return m_focus;
|
||||
}
|
||||
|
||||
// void PushWidget(std::shared_ptr<Widget> widget);
|
||||
// void PopWidget();
|
||||
|
||||
void SetParent(Widget* parent) {
|
||||
m_parent = parent;
|
||||
}
|
||||
auto GetParent() -> Widget* {
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
auto HasAction(Button button) const -> bool;
|
||||
void SetAction(Button button, Action action);
|
||||
void SetActions(std::same_as<std::pair<Button, Action>> auto ...args) {
|
||||
const std::array list = {args...};
|
||||
for (const auto& [button, action] : list) {
|
||||
SetAction(button, action);
|
||||
}
|
||||
}
|
||||
|
||||
auto GetActions() const {
|
||||
return m_actions;
|
||||
}
|
||||
|
||||
void RemoveAction(Button button);
|
||||
|
||||
void RemoveActions() {
|
||||
m_actions.clear();
|
||||
}
|
||||
|
||||
void SetPop(bool pop = true) {
|
||||
m_pop = pop;
|
||||
}
|
||||
|
||||
auto ShouldPop() const -> bool {
|
||||
return m_pop;
|
||||
}
|
||||
|
||||
using Actions = std::map<Button, Action>;
|
||||
// using Actions = std::unordered_map<Button, Action>;
|
||||
Actions m_actions;
|
||||
Widget* m_parent{};
|
||||
// std::vector<std::shared_ptr<Widget>> widgets;
|
||||
bool m_focus{false};
|
||||
bool m_pop{false};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
Reference in New Issue
Block a user