public release

This commit is contained in:
ITotalJustice
2024-12-16 21:13:05 +00:00
commit 0370e47f7f
248 changed files with 20513 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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 {

View 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

View 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

View 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

View 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

View 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