Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70d2e9873c | ||
|
|
705947fefb | ||
|
|
f48f9a527f | ||
|
|
f824187248 |
@@ -51,13 +51,12 @@ add_executable(sphaira
|
||||
source/ui/notification.cpp
|
||||
source/ui/nvg_util.cpp
|
||||
source/ui/option_box.cpp
|
||||
source/ui/option_list.cpp
|
||||
source/ui/popup_list.cpp
|
||||
source/ui/progress_box.cpp
|
||||
source/ui/scrollable_text.cpp
|
||||
source/ui/scrollbar.cpp
|
||||
source/ui/sidebar.cpp
|
||||
source/ui/widget.cpp
|
||||
source/ui/list.cpp
|
||||
|
||||
source/app.cpp
|
||||
source/download.cpp
|
||||
|
||||
@@ -33,7 +33,9 @@ enum class LaunchType {
|
||||
Forwader_Sphaira,
|
||||
};
|
||||
|
||||
// todo: why is this global???
|
||||
void DrawElement(float x, float y, float w, float h, ThemeEntryID id);
|
||||
void DrawElement(const Vec4&, ThemeEntryID id);
|
||||
|
||||
class App {
|
||||
public:
|
||||
@@ -56,8 +58,8 @@ public:
|
||||
static void NotifyFlashLed();
|
||||
|
||||
static auto GetThemeMetaList() -> std::span<ThemeMeta>;
|
||||
static void SetTheme(u64 theme_index);
|
||||
static auto GetThemeIndex() -> u64;
|
||||
static void SetTheme(s64 theme_index);
|
||||
static auto GetThemeIndex() -> s64;
|
||||
|
||||
static auto GetDefaultImage(int* w = nullptr, int* h = nullptr) -> int;
|
||||
|
||||
@@ -143,7 +145,7 @@ public:
|
||||
|
||||
Theme m_theme{};
|
||||
fs::FsPath theme_path{};
|
||||
std::size_t m_theme_index{};
|
||||
s64 m_theme_index{};
|
||||
|
||||
bool m_quit{};
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ 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:
|
||||
|
||||
57
sphaira/include/ui/list.hpp
Normal file
57
sphaira/include/ui/list.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/object.hpp"
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
struct List final : Object {
|
||||
using Callback = std::function<void(NVGcontext* vg, Theme* theme, Vec4 v, s64 index)>;
|
||||
using TouchCallback = std::function<void(s64 index)>;
|
||||
|
||||
List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad = {});
|
||||
|
||||
void OnUpdate(Controller* controller, TouchInfo* touch, s64 count, TouchCallback callback);
|
||||
|
||||
void Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
|
||||
|
||||
auto SetScrollBarPos(float x, float y, float h) {
|
||||
m_scrollbar.x = x;
|
||||
m_scrollbar.y = y;
|
||||
m_scrollbar.h = h;
|
||||
}
|
||||
|
||||
auto ScrollDown(s64& index, s64 step, s64 count) -> bool;
|
||||
auto ScrollUp(s64& index, s64 step, s64 count) -> bool;
|
||||
|
||||
auto GetYoff() const {
|
||||
return m_yoff;
|
||||
}
|
||||
|
||||
void SetYoff(float y = 0) {
|
||||
m_yoff = y;
|
||||
}
|
||||
|
||||
auto GetMaxY() const {
|
||||
return m_v.h + m_pad.y;
|
||||
}
|
||||
|
||||
private:
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override {}
|
||||
auto ClampY(float y, s64 count) const -> float;
|
||||
|
||||
private:
|
||||
const s64 m_row;
|
||||
const s64 m_page;
|
||||
|
||||
Vec4 m_v;
|
||||
Vec2 m_pad;
|
||||
|
||||
Vec4 m_scrollbar{};
|
||||
|
||||
// current y offset.
|
||||
float m_yoff{};
|
||||
// in progress y offset, used when scrolling.
|
||||
float m_y_prog{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrollable_text.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <span>
|
||||
@@ -77,7 +78,7 @@ struct EntryMenu final : MenuBase {
|
||||
// void OnFocusGained() override;
|
||||
|
||||
void ShowChangelogAction();
|
||||
void SetIndex(std::size_t index);
|
||||
void SetIndex(s64 index);
|
||||
|
||||
void UpdateOptions();
|
||||
|
||||
@@ -97,10 +98,10 @@ private:
|
||||
const LazyImage& m_default_icon;
|
||||
Menu& m_menu;
|
||||
|
||||
std::size_t m_index{}; // where i am in the array
|
||||
s64 m_index{}; // where i am in the array
|
||||
std::vector<Option> m_options;
|
||||
LazyImage m_banner;
|
||||
std::vector<LazyImage> m_screens;
|
||||
std::unique_ptr<List> m_list;
|
||||
|
||||
std::shared_ptr<ScrollableText> m_details;
|
||||
std::shared_ptr<ScrollableText> m_changelog;
|
||||
@@ -149,7 +150,7 @@ struct FeedbackMenu final : MenuBase {
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(std::size_t index);
|
||||
void SetIndex(s64 index);
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
|
||||
@@ -157,8 +158,7 @@ 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
|
||||
s64 m_index{}; // where i am in the array
|
||||
ImageDownloadState m_repo_download_state{ImageDownloadState::None};
|
||||
};
|
||||
|
||||
@@ -170,7 +170,7 @@ struct Menu final : MenuBase {
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(std::size_t index);
|
||||
void SetIndex(s64 index);
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
|
||||
@@ -201,19 +201,19 @@ private:
|
||||
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
|
||||
s64 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::unique_ptr<List> m_list;
|
||||
|
||||
std::string m_search_term;
|
||||
std::string m_author_term;
|
||||
u64 m_entry_search_jump_back{};
|
||||
u64 m_entry_author_jump_back{};
|
||||
s64 m_entry_search_jump_back{};
|
||||
s64 m_entry_author_jump_back{};
|
||||
bool m_is_search{};
|
||||
bool m_is_author{};
|
||||
bool m_dirty{}; // if set, does a sort
|
||||
|
||||
@@ -23,8 +23,8 @@ private:
|
||||
|
||||
std::unique_ptr<ScrollableText> m_scroll_text;
|
||||
|
||||
std::size_t m_start{};
|
||||
std::size_t m_index{}; // where i am in the array
|
||||
s64 m_start{};
|
||||
s64 m_index{}; // where i am in the array
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::fileview
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
@@ -92,9 +93,9 @@ struct FileAssocEntry {
|
||||
|
||||
struct LastFile {
|
||||
fs::FsPath name;
|
||||
u64 index;
|
||||
u64 offset;
|
||||
u64 entries_count;
|
||||
s64 index;
|
||||
float offset;
|
||||
s64 entries_count;
|
||||
};
|
||||
|
||||
struct FsDirCollection {
|
||||
@@ -119,7 +120,7 @@ struct Menu final : MenuBase {
|
||||
}
|
||||
|
||||
private:
|
||||
void SetIndex(std::size_t index);
|
||||
void SetIndex(s64 index);
|
||||
void InstallForwarder();
|
||||
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
|
||||
|
||||
@@ -131,7 +132,7 @@ private:
|
||||
return GetNewPath(m_path, entry.name);
|
||||
}
|
||||
|
||||
auto GetNewPath(u64 index) const -> fs::FsPath {
|
||||
auto GetNewPath(s64 index) const -> fs::FsPath {
|
||||
return GetNewPath(m_path, GetEntry(index).name);
|
||||
}
|
||||
|
||||
@@ -239,6 +240,7 @@ private:
|
||||
std::vector<u32> m_entries_index_search; // files found via search
|
||||
std::span<u32> m_entries_current;
|
||||
|
||||
std::unique_ptr<List> m_list;
|
||||
std::optional<fs::FsPath> m_daybreak_path;
|
||||
|
||||
// search options
|
||||
@@ -255,9 +257,8 @@ private:
|
||||
// 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{};
|
||||
s64 m_index{};
|
||||
s64 m_selected_count{};
|
||||
SelectedType m_selected_type{SelectedType::None};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
#include <vector>
|
||||
@@ -49,7 +50,7 @@ struct Menu final : MenuBase {
|
||||
void OnFocusGained() override;
|
||||
|
||||
private:
|
||||
void SetIndex(std::size_t index);
|
||||
void SetIndex(s64 index);
|
||||
void Scan();
|
||||
void LoadEntriesFromPath(const fs::FsPath& path);
|
||||
|
||||
@@ -66,8 +67,9 @@ private:
|
||||
|
||||
private:
|
||||
std::vector<Entry> m_entries;
|
||||
std::size_t m_index{};
|
||||
std::size_t m_index_offset{};
|
||||
s64 m_index{};
|
||||
s64 m_index_offset{};
|
||||
std::unique_ptr<List> m_list;
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::gh
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
@@ -29,7 +30,7 @@ struct Menu final : MenuBase {
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(std::size_t index);
|
||||
void SetIndex(s64 index);
|
||||
void InstallHomebrew();
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
@@ -50,8 +51,8 @@ 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
|
||||
s64 m_index{}; // where i am in the array
|
||||
std::unique_ptr<List> m_list;
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending};
|
||||
|
||||
@@ -61,7 +61,7 @@ private:
|
||||
Rotation m_rotation{Rotation_90};
|
||||
Colour m_colour{Colour_Grey};
|
||||
int m_image{};
|
||||
std::size_t m_index{};
|
||||
s64 m_index{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::irs
|
||||
|
||||
@@ -34,8 +34,7 @@ struct MainMenu final : Widget {
|
||||
|
||||
private:
|
||||
void OnLRPress(std::shared_ptr<MenuBase> menu, Button b);
|
||||
void AddOnLPress();
|
||||
void AddOnRPress();
|
||||
void AddOnLRPress();
|
||||
|
||||
private:
|
||||
std::shared_ptr<homebrew::Menu> m_homebrew_menu{};
|
||||
|
||||
@@ -21,9 +21,6 @@ struct MenuBase : Widget {
|
||||
void SetTitleSubHeading(std::string sub_heading);
|
||||
void SetSubHeading(std::string sub_heading);
|
||||
|
||||
static auto ScrollHelperDown(u64& index, u64& start, u64 step, s64 row, s64 page, u64 size) -> bool;
|
||||
static auto ScrollHelperUp(u64& index, u64& start, s64 step, s64 row, s64 page, s64 size) -> bool;
|
||||
|
||||
private:
|
||||
void UpdateVars();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrollable_text.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "option.hpp"
|
||||
#include <span>
|
||||
|
||||
@@ -164,8 +165,11 @@ struct Menu final : MenuBase {
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(std::size_t index) {
|
||||
void SetIndex(s64 index) {
|
||||
m_index = index;
|
||||
if (!m_index) {
|
||||
m_list->SetYoff(0);
|
||||
}
|
||||
}
|
||||
|
||||
// void SetSearch(const std::string& term);
|
||||
@@ -180,13 +184,13 @@ private:
|
||||
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};
|
||||
s64 m_page_index{};
|
||||
s64 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
|
||||
s64 m_index{}; // where i am in the array
|
||||
std::unique_ptr<List> m_list;
|
||||
|
||||
// options
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
||||
|
||||
@@ -19,7 +19,6 @@ public:
|
||||
auto IsDone() const noexcept { return m_count == 0; }
|
||||
|
||||
private:
|
||||
void OnLayoutChange() override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
@@ -34,7 +33,6 @@ public:
|
||||
NotifMananger() = default;
|
||||
~NotifMananger() = default;
|
||||
|
||||
void OnLayoutChange() override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
void Push(const NotifEntry& entry);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "nanovg.h"
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/types.hpp"
|
||||
|
||||
namespace sphaira::ui::gfx {
|
||||
|
||||
@@ -81,10 +81,11 @@ void textBounds(NVGcontext*, float x, float y, float *bounds, const char* str, .
|
||||
// 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 drawScrollbar(NVGcontext* vg, Theme* theme, u32 index_off, u32 count, u32 max_per_page);
|
||||
void drawScrollbar(NVGcontext* vg, Theme* theme, float x, float y, float h, u32 index_off, u32 count, u32 max_per_page);
|
||||
|
||||
void drawDimBackground(NVGcontext* vg);
|
||||
void drawScrollbar2(NVGcontext* vg, Theme* theme, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page);
|
||||
void drawScrollbar2(NVGcontext* vg, Theme* theme, s64 index_off, s64 count, s64 row, s64 page);
|
||||
|
||||
void updateHighlightAnimation();
|
||||
void getHighlightAnimation(float* gradientX, float* gradientY, float* color);
|
||||
|
||||
@@ -9,8 +9,6 @@ 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 {
|
||||
|
||||
@@ -12,7 +12,6 @@ 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;
|
||||
@@ -28,25 +27,25 @@ private:
|
||||
// todo: support upto 4 options.
|
||||
class OptionBox final : public Widget {
|
||||
public:
|
||||
using Callback = std::function<void(std::optional<std::size_t> index)>;
|
||||
using Callback = std::function<void(std::optional<s64> index)>;
|
||||
using Option = std::string;
|
||||
using Options = std::vector<Option>;
|
||||
|
||||
public:
|
||||
OptionBox(const std::string& message, const Option& a, Callback cb = [](auto){}); // 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, s64 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
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, s64 index, Callback cb); // tri
|
||||
|
||||
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;
|
||||
|
||||
private:
|
||||
auto Setup(std::size_t index) -> void; // common setup values
|
||||
auto Setup(s64 index) -> void; // common setup values
|
||||
void SetIndex(s64 index);
|
||||
|
||||
private:
|
||||
std::string m_message;
|
||||
@@ -54,7 +53,7 @@ private:
|
||||
|
||||
Vec4 m_spacer_line{};
|
||||
|
||||
std::size_t m_index{};
|
||||
s64 m_index{};
|
||||
std::vector<OptionBoxEntry> m_entries;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#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
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/scrollbar.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace sphaira::ui {
|
||||
@@ -9,21 +9,23 @@ 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>)>;
|
||||
using Callback = std::function<void(std::optional<s64>)>;
|
||||
|
||||
public:
|
||||
explicit PopupList(std::string title, Items items, Callback cb, std::size_t index = 0);
|
||||
explicit PopupList(std::string title, Items items, Callback cb, s64 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_str_ref, s64& index);
|
||||
PopupList(std::string title, Items items, std::string& index_ref);
|
||||
PopupList(std::string title, Items items, std::size_t& index_ref);
|
||||
PopupList(std::string title, Items items, s64& index_ref);
|
||||
|
||||
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;
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
|
||||
private:
|
||||
static constexpr Vec2 m_title_pos{70.f, 28.f};
|
||||
static constexpr Vec4 m_block{280.f, 110.f, 720.f, 60.f};
|
||||
@@ -33,17 +35,14 @@ private:
|
||||
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
|
||||
s64 m_index; // index in list array
|
||||
s64 m_index_offset{}; // drawing from array start
|
||||
|
||||
// std::size_t& index_ref;
|
||||
// std::string& index_str_ref;
|
||||
std::unique_ptr<List> m_list;
|
||||
|
||||
float m_selected_y{};
|
||||
float m_yoff{};
|
||||
float m_line_top{};
|
||||
float m_line_bottom{};
|
||||
ScrollBar m_scrollbar;
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -22,7 +22,7 @@ struct ProgressBox final : Widget {
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
auto NewTransfer(const std::string& transfer) -> ProgressBox&;
|
||||
auto UpdateTransfer(u64 offset, u64 size) -> ProgressBox&;
|
||||
auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;
|
||||
void RequestExit();
|
||||
auto ShouldExit() -> bool;
|
||||
|
||||
@@ -55,8 +55,8 @@ private:
|
||||
ProgressBoxDoneCallback m_done{};
|
||||
std::string m_title{};
|
||||
std::string m_transfer{};
|
||||
u64 m_size{};
|
||||
u64 m_offset{};
|
||||
s64 m_size{};
|
||||
s64 m_offset{};
|
||||
bool m_exit_requested{};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#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
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui {
|
||||
@@ -9,7 +10,6 @@ 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;
|
||||
@@ -24,9 +24,9 @@ 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");
|
||||
|
||||
private:
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
private:
|
||||
bool m_option;
|
||||
Callback m_callback;
|
||||
std::string m_true_str;
|
||||
@@ -50,10 +50,10 @@ 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)>;
|
||||
using Callback = std::function<void(s64& index)>;
|
||||
|
||||
public:
|
||||
explicit SidebarEntryArray(std::string title, Items items, Callback cb, std::size_t index = 0);
|
||||
explicit SidebarEntryArray(std::string title, Items items, Callback cb, s64 index = 0);
|
||||
SidebarEntryArray(std::string title, Items items, Callback cb, std::string index);
|
||||
SidebarEntryArray(std::string title, Items items, std::string& index);
|
||||
|
||||
@@ -63,7 +63,7 @@ private:
|
||||
Items m_items;
|
||||
ListCallback m_list_callback;
|
||||
Callback m_callback;
|
||||
std::size_t m_index;
|
||||
s64 m_index;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -101,33 +101,31 @@ public:
|
||||
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);
|
||||
void SetIndex(s64 index);
|
||||
void SetupButtons();
|
||||
|
||||
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{};
|
||||
s64 m_index{};
|
||||
s64 m_index_offset{};
|
||||
|
||||
std::unique_ptr<List> m_list;
|
||||
|
||||
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};
|
||||
|
||||
@@ -196,39 +196,31 @@ struct Theme {
|
||||
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
|
||||
};
|
||||
// enum class TouchGesture {
|
||||
// None,
|
||||
// Tap,
|
||||
// Scroll,
|
||||
// };
|
||||
|
||||
struct TouchInfo {
|
||||
s32 initial_x;
|
||||
s32 initial_y;
|
||||
HidTouchState initial;
|
||||
HidTouchState cur;
|
||||
|
||||
s32 cur_x;
|
||||
s32 cur_y;
|
||||
auto in_range(const Vec4& v) const -> bool {
|
||||
return cur.x >= v.x && cur.x <= v.x + v.w && cur.y >= v.y && cur.y <= v.y + v.h;
|
||||
}
|
||||
|
||||
s32 prev_x;
|
||||
s32 prev_y;
|
||||
|
||||
u32 finger_id;
|
||||
auto in_range(s32 x, s32 y, s32 w, s32 h) const -> bool {
|
||||
return in_range(Vec4(x, y, w, h));
|
||||
}
|
||||
|
||||
bool is_touching;
|
||||
bool is_tap;
|
||||
bool is_scroll;
|
||||
bool is_clicked;
|
||||
bool is_end;
|
||||
};
|
||||
|
||||
enum class Button : u64 {
|
||||
|
||||
@@ -8,7 +8,20 @@
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
struct uiButton final : Object {
|
||||
uiButton(Button button, Action action) : m_button{button}, m_action{action} {}
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
Button m_button;
|
||||
Action m_action;
|
||||
Vec4 m_button_pos{};
|
||||
Vec4 m_hint_pos{};
|
||||
};
|
||||
|
||||
struct Widget : public Object {
|
||||
using Actions = std::map<Button, Action>;
|
||||
using uiButtons = std::vector<uiButton>;
|
||||
|
||||
virtual ~Widget() = default;
|
||||
|
||||
virtual void Update(Controller* controller, TouchInfo* touch);
|
||||
@@ -49,6 +62,8 @@ struct Widget : public Object {
|
||||
m_actions.clear();
|
||||
}
|
||||
|
||||
auto FireAction(Button button, u8 type = ActionType::DOWN) -> bool;
|
||||
|
||||
void SetPop(bool pop = true) {
|
||||
m_pop = pop;
|
||||
}
|
||||
@@ -57,9 +72,14 @@ struct Widget : public Object {
|
||||
return m_pop;
|
||||
}
|
||||
|
||||
using Actions = std::map<Button, Action>;
|
||||
// using Actions = std::unordered_map<Button, Action>;
|
||||
auto SetUiButtonPos(Vec2 pos) {
|
||||
m_button_pos = pos;
|
||||
}
|
||||
|
||||
auto GetUiButtons() const -> uiButtons;
|
||||
|
||||
Actions m_actions;
|
||||
Vec2 m_button_pos{1220, 675};
|
||||
bool m_focus{false};
|
||||
bool m_pop{false};
|
||||
};
|
||||
|
||||
@@ -391,12 +391,12 @@ auto App::GetThemeMetaList() -> std::span<ThemeMeta> {
|
||||
return g_app->m_theme_meta_entries;
|
||||
}
|
||||
|
||||
void App::SetTheme(u64 theme_index) {
|
||||
void App::SetTheme(s64 theme_index) {
|
||||
g_app->LoadTheme(g_app->m_theme_meta_entries[theme_index].ini_path.c_str());
|
||||
g_app->m_theme_index = theme_index;
|
||||
}
|
||||
|
||||
auto App::GetThemeIndex() -> u64 {
|
||||
auto App::GetThemeIndex() -> s64 {
|
||||
return g_app->m_theme_index;
|
||||
}
|
||||
|
||||
@@ -701,41 +701,40 @@ void App::ExitRestart() {
|
||||
void App::Poll() {
|
||||
m_controller.Reset();
|
||||
|
||||
HidTouchScreenState state{};
|
||||
hidGetTouchScreenStates(&state, 1);
|
||||
m_touch_info.is_clicked = false;
|
||||
|
||||
if (state.count == 1 && !m_touch_info.is_touching) {
|
||||
m_touch_info.initial = m_touch_info.cur = state.touches[0];
|
||||
m_touch_info.is_touching = true;
|
||||
m_touch_info.is_tap = true;
|
||||
} else if (state.count >= 1 && m_touch_info.is_touching) {
|
||||
m_touch_info.cur = state.touches[0];
|
||||
|
||||
if (m_touch_info.is_tap &&
|
||||
(std::abs((s32)m_touch_info.initial.x - (s32)m_touch_info.cur.x) > 20 ||
|
||||
std::abs((s32)m_touch_info.initial.y - (s32)m_touch_info.cur.y) > 20)) {
|
||||
m_touch_info.is_tap = false;
|
||||
m_touch_info.is_scroll = true;
|
||||
}
|
||||
} else if (m_touch_info.is_touching) {
|
||||
m_touch_info.is_touching = false;
|
||||
m_touch_info.is_scroll = false;
|
||||
if (m_touch_info.is_tap) {
|
||||
m_touch_info.is_clicked = true;
|
||||
} else {
|
||||
m_touch_info.is_end = true;
|
||||
}
|
||||
}
|
||||
|
||||
// todo: better implement this to match hos
|
||||
if (!m_touch_info.is_touching && !m_touch_info.is_clicked) {
|
||||
padUpdate(&m_pad);
|
||||
m_controller.m_kdown = padGetButtonsDown(&m_pad);
|
||||
m_controller.m_kheld = padGetButtons(&m_pad);
|
||||
m_controller.m_kup = padGetButtonsUp(&m_pad);
|
||||
m_controller.UpdateButtonHeld(static_cast<u64>(Button::ANY_DIRECTION));
|
||||
|
||||
HidTouchScreenState touch_state{};
|
||||
hidGetTouchScreenStates(&touch_state, 1);
|
||||
|
||||
if (touch_state.count == 1 && !m_touch_info.is_touching) {
|
||||
m_touch_info.initial_x = m_touch_info.prev_x = m_touch_info.cur_x = touch_state.touches[0].x;
|
||||
m_touch_info.initial_y = m_touch_info.prev_y = m_touch_info.cur_y = touch_state.touches[0].y;
|
||||
m_touch_info.finger_id = touch_state.touches[0].finger_id;
|
||||
m_touch_info.is_touching = true;
|
||||
m_touch_info.is_tap = true;
|
||||
// PlaySoundEffect(SoundEffect_Limit);
|
||||
} else if (touch_state.count >= 1 && m_touch_info.is_touching && m_touch_info.finger_id == touch_state.touches[0].finger_id) {
|
||||
m_touch_info.prev_x = m_touch_info.cur_x;
|
||||
m_touch_info.prev_y = m_touch_info.cur_y;
|
||||
|
||||
m_touch_info.cur_x = touch_state.touches[0].x;
|
||||
m_touch_info.cur_y = touch_state.touches[0].y;
|
||||
|
||||
if (m_touch_info.is_tap &&
|
||||
(std::abs(m_touch_info.initial_x - m_touch_info.cur_x) > 20 ||
|
||||
std::abs(m_touch_info.initial_y - m_touch_info.cur_y) > 20)) {
|
||||
m_touch_info.is_tap = false;
|
||||
}
|
||||
} else if (m_touch_info.is_touching) {
|
||||
m_touch_info.is_touching = false;
|
||||
|
||||
// check if we clicked on anything, if so, handle it
|
||||
if (m_touch_info.is_tap) {
|
||||
// todo:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,17 +808,21 @@ auto App::GetVg() -> NVGcontext* {
|
||||
}
|
||||
|
||||
void DrawElement(float x, float y, float w, float h, ThemeEntryID id) {
|
||||
DrawElement({x, y, w, h}, id);
|
||||
}
|
||||
|
||||
void DrawElement(const Vec4& v, ThemeEntryID id) {
|
||||
const auto& e = g_app->m_theme.elements[id];
|
||||
|
||||
switch (e.type) {
|
||||
case ElementType::None: {
|
||||
} break;
|
||||
case ElementType::Texture: {
|
||||
const auto paint = nvgImagePattern(g_app->vg, x, y, w, h, 0, e.texture, 1.f);
|
||||
ui::gfx::drawRect(g_app->vg, x, y, w, h, paint);
|
||||
const auto paint = nvgImagePattern(g_app->vg, v.x, v.y, v.w, v.h, 0, e.texture, 1.f);
|
||||
ui::gfx::drawRect(g_app->vg, v, paint);
|
||||
} break;
|
||||
case ElementType::Colour: {
|
||||
ui::gfx::drawRect(g_app->vg, x, y, w, h, e.colour);
|
||||
ui::gfx::drawRect(g_app->vg, v, e.colour);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ Mutex g_mutex_share[CURL_LOCK_DATA_LAST]{};
|
||||
|
||||
struct DataStruct {
|
||||
std::vector<u8> data;
|
||||
u64 offset{};
|
||||
s64 offset{};
|
||||
FsFile f{};
|
||||
s64 file_offset{};
|
||||
};
|
||||
|
||||
@@ -1134,10 +1134,6 @@ auto ErrorBox::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
Widget::Update(controller, touch);
|
||||
}
|
||||
|
||||
auto ErrorBox::OnLayoutChange() -> void {
|
||||
|
||||
}
|
||||
|
||||
auto ErrorBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
gfx::dimBackground(vg);
|
||||
gfx::drawRect(vg, m_pos, theme->elements[ThemeEntryID_SELECTED].colour);
|
||||
|
||||
189
sphaira/source/ui/list.cpp
Normal file
189
sphaira/source/ui/list.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
#include "ui/list.hpp"
|
||||
#include "ui/nvg_util.hpp"
|
||||
#include "app.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
List::List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad)
|
||||
: m_row{row}
|
||||
, m_page{page}
|
||||
, m_v{v}
|
||||
, m_pad{pad} {
|
||||
m_pos = pos;
|
||||
SetScrollBarPos(SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200);
|
||||
}
|
||||
|
||||
auto List::ClampY(float y, s64 count) const -> float {
|
||||
float y_max = 0;
|
||||
|
||||
if (count >= m_page) {
|
||||
// round up
|
||||
if (count % m_row) {
|
||||
count = count + (m_row - count % m_row);
|
||||
}
|
||||
y_max = (count - m_page) / m_row * GetMaxY();
|
||||
}
|
||||
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
} else if (y > y_max) {
|
||||
y = y_max;
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
void List::OnUpdate(Controller* controller, TouchInfo* touch, s64 count, TouchCallback callback) {
|
||||
if (touch->is_clicked && touch->in_range(GetPos())) {
|
||||
auto v = m_v;
|
||||
v.y -= ClampY(m_yoff + m_y_prog, count);
|
||||
|
||||
for (s64 i = 0; i < count; v.y += v.h + m_pad.y) {
|
||||
if (v.y > GetY() + GetH()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto x = v.x;
|
||||
|
||||
for (; i < count; i++, v.x += v.w + m_pad.x) {
|
||||
// only draw if full x is in bounds
|
||||
if (v.x + v.w > GetX() + GetW()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// skip anything not visible
|
||||
if (v.y + v.h < GetY()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vec4 vv = v;
|
||||
// if not drawing, only return clipped v as its used for touch
|
||||
vv.w = std::min(v.x + v.w, m_pos.x + m_pos.w) - v.x;
|
||||
vv.h = std::min(v.y + v.h, m_pos.y + m_pos.h) - v.y;
|
||||
|
||||
if (touch->in_range(vv)) {
|
||||
callback(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
v.x = x;
|
||||
}
|
||||
} else if (touch->is_scroll && touch->in_range(GetPos())) {
|
||||
m_y_prog = (float)touch->initial.y - (float)touch->cur.y;
|
||||
} else if (touch->is_end) {
|
||||
m_yoff = ClampY(m_yoff + m_y_prog, count);
|
||||
m_y_prog = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void List::Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const {
|
||||
const auto yoff = ClampY(m_yoff + m_y_prog, count);
|
||||
const s64 start = yoff / GetMaxY() * m_row;
|
||||
gfx::drawScrollbar2(vg, theme, m_scrollbar.x, m_scrollbar.y, m_scrollbar.h, start, count, m_row, m_page);
|
||||
|
||||
auto v = m_v;
|
||||
v.y -= yoff;
|
||||
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, GetX(), GetY(), GetW(), GetH());
|
||||
|
||||
for (s64 i = 0; i < count; v.y += v.h + m_pad.y) {
|
||||
if (v.y > GetY() + GetH()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto x = v.x;
|
||||
|
||||
for (; i < count; i++, v.x += v.w + m_pad.x) {
|
||||
// only draw if full x is in bounds
|
||||
if (v.x + v.w > GetX() + GetW()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// skip anything not visible
|
||||
if (v.y + v.h < GetY()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
callback(vg, theme, v, i);
|
||||
}
|
||||
|
||||
v.x = x;
|
||||
}
|
||||
|
||||
nvgRestore(vg);
|
||||
}
|
||||
|
||||
auto List::ScrollDown(s64& index, s64 step, s64 count) -> bool {
|
||||
const auto old_index = index;
|
||||
|
||||
if (!count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index + step < count) {
|
||||
index += step;
|
||||
} else {
|
||||
index = count - 1;
|
||||
}
|
||||
|
||||
if (index != old_index) {
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
s64 delta = index - old_index;
|
||||
s64 start = m_yoff / GetMaxY() * m_row;
|
||||
|
||||
while (index < start) {
|
||||
start -= m_row;
|
||||
m_yoff -= GetMaxY();
|
||||
}
|
||||
|
||||
if (index - start >= m_page) {
|
||||
do {
|
||||
start += m_row;
|
||||
delta -= m_row;
|
||||
m_yoff += GetMaxY();
|
||||
} while (delta > 0 && start + m_page < count);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto List::ScrollUp(s64& index, s64 step, s64 count) -> bool {
|
||||
const auto old_index = index;
|
||||
|
||||
if (!count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index >= step) {
|
||||
index -= step;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (index != old_index) {
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
s64 start = m_yoff / GetMaxY() * m_row;
|
||||
|
||||
while (index < start) {
|
||||
start -= m_row;
|
||||
m_yoff -= GetMaxY();
|
||||
}
|
||||
|
||||
while (index - start >= m_page && start + m_page < count) {
|
||||
start += m_row;
|
||||
m_yoff += GetMaxY();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui
|
||||
@@ -271,19 +271,11 @@ void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, float x, f
|
||||
rounded_image = false;
|
||||
gfx::drawRect(vg, x, y, w, h, nvgRGB(i.first_pixel[0], i.first_pixel[1], i.first_pixel[2]), rounded);
|
||||
}
|
||||
if (iw > w || ih > h) {
|
||||
crop = true;
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, x, y, w, h);
|
||||
}
|
||||
if (rounded_image) {
|
||||
gfx::drawImageRounded(vg, ix, iy, iw, ih, i.image);
|
||||
} else {
|
||||
gfx::drawImage(vg, ix, iy, iw, ih, i.image);
|
||||
}
|
||||
if (crop) {
|
||||
nvgRestore(vg);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, Vec4 vec, bool rounded = true, float scale = 1.0) {
|
||||
@@ -711,6 +703,11 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
|
||||
SetSubHeading(m_entry.binary);
|
||||
SetSubHeading(m_entry.description);
|
||||
UpdateOptions();
|
||||
|
||||
// todo: see Draw()
|
||||
// const Vec4 v{75, 110, 370, 155};
|
||||
// const Vec2 pad{10, 10};
|
||||
// m_list = std::make_unique<List>(3, 3, v, pad);
|
||||
}
|
||||
|
||||
EntryMenu::~EntryMenu() {
|
||||
@@ -755,6 +752,7 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
// for (const auto& option : m_options) {
|
||||
const auto& text_col = theme->elements[ThemeEntryID_TEXT].colour;
|
||||
|
||||
// todo: rewrite this mess and use list
|
||||
constexpr float mm = 0;//20;
|
||||
constexpr Vec4 block{968.f + mm, 110.f, 256.f - mm*2, 60.f};
|
||||
constexpr float text_xoffset{15.f};
|
||||
@@ -858,7 +856,7 @@ void EntryMenu::UpdateOptions() {
|
||||
SetIndex(0);
|
||||
}
|
||||
|
||||
void EntryMenu::SetIndex(std::size_t index) {
|
||||
void EntryMenu::SetIndex(s64 index) {
|
||||
m_index = index;
|
||||
const auto option = m_options[m_index];
|
||||
if (option.confirm_text.empty()) {
|
||||
@@ -886,8 +884,6 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
|
||||
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/banners");
|
||||
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/screens");
|
||||
|
||||
// m_span = m_entries;
|
||||
|
||||
this->SetActions(
|
||||
std::make_pair(Button::RIGHT, Action{[this](){
|
||||
if (m_entries_current.empty()) {
|
||||
@@ -912,22 +908,22 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (ScrollHelperDown(m_index, m_start, 3, 3, 9, m_entries_current.size())) {
|
||||
if (m_list->ScrollDown(m_index, 3, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (ScrollHelperUp(m_index, m_start, 3, 3, 9, m_entries_current.size())) {
|
||||
if (m_list->ScrollUp(m_index, 3, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::R2, Action{[this](){
|
||||
if (ScrollHelperDown(m_index, m_start, 9, 3, 9, m_entries_current.size())) {
|
||||
if (m_list->ScrollDown(m_index, 9, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::L2, Action{[this](){
|
||||
if (ScrollHelperUp(m_index, m_start, 9, 3, 9, m_entries_current.size())) {
|
||||
if (m_list->ScrollUp(m_index, 9, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
@@ -962,17 +958,17 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
|
||||
order_items.push_back("Decending"_i18n);
|
||||
order_items.push_back("Ascending"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this, filter_items](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this, filter_items](s64& index_out){
|
||||
SetFilter((Filter)index_out);
|
||||
}, (std::size_t)m_filter));
|
||||
}, (s64)m_filter));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
|
||||
SetSort((SortType)index_out);
|
||||
}, (std::size_t)m_sort));
|
||||
}, (s64)m_sort));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
|
||||
SetOrder((OrderType)index_out);
|
||||
}, (std::size_t)m_order));
|
||||
}, (s64)m_order));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Search"_i18n, [this](){
|
||||
std::string out;
|
||||
@@ -1005,6 +1001,9 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
|
||||
m_sort = (SortType)ini_getl(INI_SECTION, "sort", m_sort, App::CONFIG_PATH);
|
||||
m_order = (OrderType)ini_getl(INI_SECTION, "order", m_order, App::CONFIG_PATH);
|
||||
|
||||
const Vec4 v{75, 110, 370, 155};
|
||||
const Vec2 pad{10, 10};
|
||||
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
|
||||
Sort();
|
||||
}
|
||||
|
||||
@@ -1014,6 +1013,14 @@ Menu::~Menu() {
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_entries_current.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
SetIndex(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
@@ -1029,26 +1036,12 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 SCROLL = m_start;
|
||||
const u64 max_entry_display = 9;
|
||||
const u64 nro_total = m_entries_current.size();
|
||||
const u64 cursor_pos = m_index;
|
||||
|
||||
// only draw scrollbar if needed
|
||||
if (nro_total > max_entry_display) {
|
||||
const auto scrollbar_size = 500.f;
|
||||
const auto sb_h = 3.f / (float)nro_total * scrollbar_size;
|
||||
const auto sb_y = SCROLL / 3.f;
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, theme->elements[ThemeEntryID_GRID].colour);
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * 2) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour);
|
||||
}
|
||||
|
||||
// max images per frame, in order to not hit io / gpu too hard.
|
||||
const int image_load_max = 2;
|
||||
int image_load_count = 0;
|
||||
|
||||
for (u64 i = 0, pos = SCROLL, y = 110, w = 370, h = 155; pos < nro_total && i < max_entry_display; y += h + 10) {
|
||||
for (u64 j = 0, x = 75; j < 3 && pos < nro_total && i < max_entry_display; j++, i++, pos++, x += w + 10) {
|
||||
m_list->Draw(vg, theme, m_entries_current.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
|
||||
const auto& [x, y, w, h] = v;
|
||||
const auto index = m_entries_current[pos];
|
||||
auto& e = m_entries[index];
|
||||
auto& image = e.image;
|
||||
@@ -1106,7 +1099,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
}
|
||||
|
||||
auto text_id = ThemeEntryID_TEXT;
|
||||
if (pos == cursor_pos) {
|
||||
if (pos == m_index) {
|
||||
text_id = ThemeEntryID_TEXT_SELECTED;
|
||||
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour);
|
||||
} else {
|
||||
@@ -1120,7 +1113,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
// gfx::drawImage(vg, x + 20, y + 20, image_size, image_size_h, image.image ? image.image : m_default_image);
|
||||
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, x, y, w - 30.f, h); // clip
|
||||
nvgIntersectScissor(vg, v.x, v.y, w - 30.f, h); // clip
|
||||
{
|
||||
const float font_size = 18;
|
||||
gfx::drawTextArgs(vg, x + 148, y + 45, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, e.title.c_str());
|
||||
@@ -1144,8 +1137,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
gfx::drawImageRounded(vg, x + w - 30.f, y + 110, i_size, i_size, m_update.image);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::OnFocusGained() {
|
||||
@@ -1174,21 +1166,16 @@ void Menu::OnFocusGained() {
|
||||
if (m_dirty) {
|
||||
m_dirty = false;
|
||||
const auto& current_entry = m_entries[m_entries_current[m_index]];
|
||||
// m_start = 0;
|
||||
// m_index = 0;
|
||||
log_write("\nold index: %zu start: %zu\n", m_index, m_start);
|
||||
// old index: 19 start: 12
|
||||
Sort();
|
||||
|
||||
for (u32 i = 0; i < m_entries_current.size(); i++) {
|
||||
if (current_entry.name == m_entries[m_entries_current[i]].name) {
|
||||
SetIndex(i);
|
||||
if (i >= 9) {
|
||||
m_start = (i - 9) / 3 * 3 + 3;
|
||||
m_list->SetYoff((((i - 9) + 3) / 3) * m_list->GetMaxY());
|
||||
} else {
|
||||
m_start = 0;
|
||||
m_list->SetYoff(0);
|
||||
}
|
||||
log_write("\nnew index: %zu start: %zu\n", m_index, m_start);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1196,10 +1183,10 @@ void Menu::OnFocusGained() {
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::SetIndex(std::size_t index) {
|
||||
void Menu::SetIndex(s64 index) {
|
||||
m_index = index;
|
||||
if (!m_index) {
|
||||
m_start = 0;
|
||||
m_list->SetYoff(0);
|
||||
}
|
||||
|
||||
this->SetSubHeading(std::to_string(m_index + 1) + " / " + std::to_string(m_entries_current.size()));
|
||||
@@ -1394,9 +1381,10 @@ void Menu::SetSearch(const std::string& term) {
|
||||
SetFilter(m_filter);
|
||||
SetIndex(m_entry_search_jump_back);
|
||||
if (m_entry_search_jump_back >= 9) {
|
||||
m_start = (m_entry_search_jump_back - 9) / 3 * 3 + 3;
|
||||
m_list->SetYoff(0);
|
||||
m_list->SetYoff((((m_entry_search_jump_back - 9) + 3) / 3) * m_list->GetMaxY());
|
||||
} else {
|
||||
m_start = 0;
|
||||
m_list->SetYoff(0);
|
||||
}
|
||||
}});
|
||||
|
||||
@@ -1427,11 +1415,12 @@ void Menu::SetAuthor() {
|
||||
} else {
|
||||
SetFilter(m_filter);
|
||||
}
|
||||
|
||||
SetIndex(m_entry_author_jump_back);
|
||||
if (m_entry_author_jump_back >= 9) {
|
||||
m_start = (m_entry_author_jump_back - 9) / 3 * 3 + 3;
|
||||
m_list->SetYoff((((m_entry_author_jump_back - 9) + 3) / 3) * m_list->GetMaxY());
|
||||
} else {
|
||||
m_start = 0;
|
||||
m_list->SetYoff(0);
|
||||
}
|
||||
}});
|
||||
|
||||
|
||||
@@ -246,22 +246,22 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (ScrollHelperDown(m_index, m_index_offset, 1, 1, 8, m_entries_current.size())) {
|
||||
if (m_list->ScrollDown(m_index, 1, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (ScrollHelperUp(m_index, m_index_offset, 1, 1, 8, m_entries_current.size())) {
|
||||
if (m_list->ScrollUp(m_index, 1, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DPAD_RIGHT, Action{[this](){
|
||||
if (ScrollHelperDown(m_index, m_index_offset, 8, 1, 8, m_entries_current.size())) {
|
||||
if (m_list->ScrollDown(m_index, 8, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DPAD_LEFT, Action{[this](){
|
||||
if (ScrollHelperUp(m_index, m_index_offset, 8, 1, 8, m_entries_current.size())) {
|
||||
if (m_list->ScrollUp(m_index, 8, m_entries_current.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
@@ -355,12 +355,12 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
order_items.push_back("Decending"_i18n);
|
||||
order_items.push_back("Ascending"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](s64& index_out){
|
||||
m_sort.Set(index_out);
|
||||
SortAndFindLastFile();
|
||||
}, m_sort.Get()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this](s64& index_out){
|
||||
m_order.Set(index_out);
|
||||
SortAndFindLastFile();
|
||||
}, m_order.Get()));
|
||||
@@ -542,7 +542,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
mount_items.push_back("Image System memory"_i18n);
|
||||
mount_items.push_back("Image microSD card"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Mount"_i18n, mount_items, [this](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Mount"_i18n, mount_items, [this](s64& index_out){
|
||||
App::PopToMenu();
|
||||
m_mount.Set(index_out);
|
||||
SetFs("/", index_out);
|
||||
@@ -551,6 +551,9 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}})
|
||||
);
|
||||
|
||||
const Vec4 v{75, GetY() + 1.f + 42.f, 1220.f-45.f*2, 60};
|
||||
m_list = std::make_unique<List>(1, 8, m_pos, v);
|
||||
|
||||
fs::FsPath buf;
|
||||
ini_gets("paths", "last_path", "/", buf, sizeof(buf), App::CONFIG_PATH);
|
||||
SetFs(buf, m_mount.Get());
|
||||
@@ -562,6 +565,14 @@ Menu::~Menu() {
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_entries_current.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
SetIndex(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
@@ -574,35 +585,10 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 SCROLL = m_index_offset;
|
||||
constexpr u64 max_entry_display = 8;
|
||||
const u64 entry_total = m_entries_current.size();
|
||||
|
||||
// only draw scrollbar if needed
|
||||
if (entry_total > max_entry_display) {
|
||||
const auto scrollbar_size = 500.f;
|
||||
const auto sb_h = 1.f / (float)entry_total * scrollbar_size;
|
||||
const auto sb_y = SCROLL;
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, gfx::getColour(gfx::Colour::BLACK));
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * (max_entry_display - 1)) - 4, gfx::getColour(gfx::Colour::SILVER));
|
||||
}
|
||||
|
||||
// constexpr Vec4 line_top{30.f, 86.f, 1220.f, 1.f};
|
||||
// constexpr Vec4 line_bottom{30.f, 646.f, 1220.f, 1.f};
|
||||
// constexpr Vec4 block{280.f, 110.f, 720.f, 60.f};
|
||||
constexpr Vec4 block{75.f, 110.f, 1220.f-45.f*2, 60.f};
|
||||
constexpr float text_xoffset{15.f};
|
||||
|
||||
// todo: cleanup
|
||||
const float x = block.x;
|
||||
float y = GetY() + 1.f + 42.f;
|
||||
const float h = block.h;
|
||||
const float w = block.w;
|
||||
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, GetX(), GetY(), GetW(), GetH());
|
||||
|
||||
for (std::size_t i = m_index_offset; i < m_entries_current.size(); i++) {
|
||||
m_list->Draw(vg, theme, m_entries_current.size(), [this, text_col](auto* vg, auto* theme, auto v, auto i) {
|
||||
const auto& [x, y, w, h] = v;
|
||||
auto& e = GetEntry(i);
|
||||
|
||||
if (e.IsDir()) {
|
||||
@@ -628,7 +614,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
text_id = ThemeEntryID_TEXT_SELECTED;
|
||||
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour);
|
||||
} else {
|
||||
if (i == m_index_offset) {
|
||||
if (i == m_index) {
|
||||
gfx::drawRect(vg, x, y, w, 1.f, text_col);
|
||||
}
|
||||
gfx::drawRect(vg, x, y + h, w, 1.f, text_col);
|
||||
@@ -655,8 +641,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
}
|
||||
|
||||
nvgSave(vg);
|
||||
const auto txt_clip = std::min(GetY() + GetH(), y + h) - y;
|
||||
nvgScissor(vg, x + text_xoffset+65, y, w-(x+text_xoffset+65+50), txt_clip);
|
||||
nvgIntersectScissor(vg, x + text_xoffset+65, y, w-(x+text_xoffset+65+50), h);
|
||||
gfx::drawText(vg, x + text_xoffset+65, y + (h / 2.f), 20.f, e.name, NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->elements[text_id].colour);
|
||||
nvgRestore(vg);
|
||||
|
||||
@@ -678,14 +663,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->elements[text_id].colour, "%.2f MiB", (double)e.file_size / 1024.0 / 1024.0);
|
||||
}
|
||||
}
|
||||
|
||||
y += h;
|
||||
if (!InYBounds(y)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nvgRestore(vg);
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::OnFocusGained() {
|
||||
@@ -705,10 +683,10 @@ void Menu::OnFocusGained() {
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::SetIndex(std::size_t index) {
|
||||
void Menu::SetIndex(s64 index) {
|
||||
m_index = index;
|
||||
if (!m_index) {
|
||||
m_index_offset = 0;
|
||||
m_list->SetYoff();
|
||||
}
|
||||
|
||||
if (!m_entries_current.empty() && !GetEntry().checked_internal_extension && GetEntry().extension == "zip") {
|
||||
@@ -803,16 +781,14 @@ void Menu::InstallForwarder() {
|
||||
auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result {
|
||||
log_write("new scan path: %s\n", new_path);
|
||||
if (!is_walk_up && !m_path.empty() && !m_entries_current.empty()) {
|
||||
const LastFile f{GetEntry().name, m_index, m_index_offset, m_entries_current.size()};
|
||||
const LastFile f(GetEntry().name, m_index, m_list->GetYoff(), m_entries_current.size());
|
||||
m_previous_highlighted_file.emplace_back(f);
|
||||
}
|
||||
|
||||
log_write("\nold index: %zu start: %zu\n", m_index, m_index_offset);
|
||||
|
||||
m_path = new_path;
|
||||
m_entries.clear();
|
||||
m_index = 0;
|
||||
m_index_offset = 0;
|
||||
m_list->SetYoff(0);
|
||||
SetTitleSubHeading(m_path);
|
||||
|
||||
if (m_selected_type == SelectedType::None) {
|
||||
@@ -1090,7 +1066,7 @@ void Menu::Sort() {
|
||||
void Menu::SortAndFindLastFile() {
|
||||
std::optional<LastFile> last_file;
|
||||
if (!m_path.empty() && !m_entries_current.empty()) {
|
||||
last_file = LastFile{GetEntry().name, m_index, m_index_offset, m_entries_current.size()};
|
||||
last_file = LastFile(GetEntry().name, m_index, m_list->GetYoff(), m_entries_current.size());
|
||||
}
|
||||
|
||||
Sort();
|
||||
@@ -1111,21 +1087,21 @@ void Menu::SetIndexFromLastFile(const LastFile& last_file) {
|
||||
}
|
||||
}
|
||||
if (index >= 0) {
|
||||
if ((u64)index == last_file.index && m_entries_current.size() == last_file.entries_count) {
|
||||
m_index_offset = last_file.offset;
|
||||
if (index == last_file.index && m_entries_current.size() == last_file.entries_count) {
|
||||
m_list->SetYoff(last_file.offset);
|
||||
log_write("index is the same as last time\n");
|
||||
} else {
|
||||
// file position changed!
|
||||
log_write("file position changed\n");
|
||||
// guesstimate where the position is
|
||||
if (index >= 8) {
|
||||
m_index_offset = (index - 8) + 1;
|
||||
m_list->SetYoff(((index - 8) + 1) * m_list->GetMaxY());
|
||||
} else {
|
||||
m_index_offset = 0;
|
||||
m_list->SetYoff(0);
|
||||
}
|
||||
}
|
||||
SetIndex(index);
|
||||
log_write("\nnew index: %zu start: %zu mod: %zu\n", m_index, m_index_offset, index % 8);
|
||||
log_write("\nnew index: %zu %zu mod: %zu\n", m_index, index % 8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry
|
||||
}
|
||||
|
||||
std::vector<char> buf(chunk_size);
|
||||
u64 offset{};
|
||||
s64 offset{};
|
||||
while (offset < info.uncompressed_size) {
|
||||
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
|
||||
if (bytes_read <= 0) {
|
||||
@@ -248,22 +248,22 @@ Menu::Menu() : MenuBase{"GitHub"_i18n} {
|
||||
|
||||
this->SetActions(
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (ScrollHelperDown(m_index, m_index_offset, 1, 1, 8, m_entries.size())) {
|
||||
if (m_list->ScrollDown(m_index, 1, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (ScrollHelperUp(m_index, m_index_offset, 1, 1, 8, m_entries.size())) {
|
||||
if (m_list->ScrollUp(m_index, 1, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DPAD_RIGHT, Action{[this](){
|
||||
if (ScrollHelperDown(m_index, m_index_offset, 8, 1, 8, m_entries.size())) {
|
||||
if (m_list->ScrollDown(m_index, 8, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DPAD_LEFT, Action{[this](){
|
||||
if (ScrollHelperUp(m_index, m_index_offset, 8, 1, 8, m_entries.size())) {
|
||||
if (m_list->ScrollUp(m_index, 8, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
@@ -363,6 +363,9 @@ Menu::Menu() : MenuBase{"GitHub"_i18n} {
|
||||
SetPop();
|
||||
}})
|
||||
);
|
||||
|
||||
const Vec4 v{75, GetY() + 1.f + 42.f, 1220.f-45.f*2, 60};
|
||||
m_list = std::make_unique<List>(1, 8, m_pos, v);
|
||||
}
|
||||
|
||||
Menu::~Menu() {
|
||||
@@ -370,6 +373,14 @@ Menu::~Menu() {
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_entries.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
SetIndex(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
@@ -382,35 +393,10 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 SCROLL = m_index_offset;
|
||||
constexpr u64 max_entry_display = 8;
|
||||
const u64 entry_total = m_entries.size();
|
||||
|
||||
// only draw scrollbar if needed
|
||||
if (entry_total > max_entry_display) {
|
||||
const auto scrollbar_size = 500.f;
|
||||
const auto sb_h = 1.f / (float)entry_total * scrollbar_size;
|
||||
const auto sb_y = SCROLL;
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, gfx::getColour(gfx::Colour::BLACK));
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * (max_entry_display - 1)) - 4, gfx::getColour(gfx::Colour::SILVER));
|
||||
}
|
||||
|
||||
// constexpr Vec4 line_top{30.f, 86.f, 1220.f, 1.f};
|
||||
// constexpr Vec4 line_bottom{30.f, 646.f, 1220.f, 1.f};
|
||||
// constexpr Vec4 block{280.f, 110.f, 720.f, 60.f};
|
||||
constexpr Vec4 block{75.f, 110.f, 1220.f-45.f*2, 60.f};
|
||||
constexpr float text_xoffset{15.f};
|
||||
|
||||
// todo: cleanup
|
||||
const float x = block.x;
|
||||
float y = GetY() + 1.f + 42.f;
|
||||
const float h = block.h;
|
||||
const float w = block.w;
|
||||
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, GetX(), GetY(), GetW(), GetH());
|
||||
|
||||
for (std::size_t i = m_index_offset; i < m_entries.size(); i++) {
|
||||
m_list->Draw(vg, theme, m_entries.size(), [this, text_col](auto* vg, auto* theme, auto v, auto i) {
|
||||
const auto& [x, y, w, h] = v;
|
||||
auto& e = m_entries[i];
|
||||
|
||||
auto text_id = ThemeEntryID_TEXT;
|
||||
@@ -425,20 +411,12 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
}
|
||||
|
||||
nvgSave(vg);
|
||||
const auto txt_clip = std::min(GetY() + GetH(), y + h) - y;
|
||||
nvgScissor(vg, x + text_xoffset, y, w-(x+text_xoffset+50), txt_clip);
|
||||
nvgIntersectScissor(vg, x + text_xoffset, y, w-(x+text_xoffset+50), h);
|
||||
gfx::drawTextArgs(vg, x + text_xoffset, y + (h / 2.f), 20.f, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->elements[text_id].colour, "%s By %s", e.repo.c_str(), e.owner.c_str());
|
||||
nvgRestore(vg);
|
||||
|
||||
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f), 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE, theme->elements[text_id].colour, "version: %s", e.tag.c_str());
|
||||
|
||||
y += h;
|
||||
if (!InYBounds(y)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nvgRestore(vg);
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::OnFocusGained() {
|
||||
@@ -448,12 +426,16 @@ void Menu::OnFocusGained() {
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::SetIndex(std::size_t index) {
|
||||
void Menu::SetIndex(s64 index) {
|
||||
m_index = index;
|
||||
if (!m_index) {
|
||||
m_index_offset = 0;
|
||||
}
|
||||
|
||||
if (m_index > m_index_offset && m_index - m_index_offset >= 7) {
|
||||
m_index_offset = m_index - 7;
|
||||
}
|
||||
|
||||
SetTitleSubHeading(m_entries[m_index].json_path);
|
||||
UpdateSubheading();
|
||||
}
|
||||
|
||||
@@ -43,22 +43,22 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (ScrollHelperDown(m_index, m_start, 3, 3, 9, m_entries.size())) {
|
||||
if (m_list->ScrollDown(m_index, 3, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (ScrollHelperUp(m_index, m_start, 3, 3, 9, m_entries.size())) {
|
||||
if (m_list->ScrollUp(m_index, 3, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::R2, Action{[this](){
|
||||
if (ScrollHelperDown(m_index, m_start, 9, 3, 9, m_entries.size())) {
|
||||
if (m_list->ScrollDown(m_index, 9, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::L2, Action{[this](){
|
||||
if (ScrollHelperUp(m_index, m_start, 9, 3, 9, m_entries.size())) {
|
||||
if (m_list->ScrollUp(m_index, 9, m_entries.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
@@ -86,12 +86,12 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
||||
order_items.push_back("Decending"_i18n);
|
||||
order_items.push_back("Ascending"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
|
||||
m_sort.Set(index_out);
|
||||
SortAndFindLastFile();
|
||||
}, m_sort.Get()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
|
||||
m_order.Set(index_out);
|
||||
SortAndFindLastFile();
|
||||
}, m_order.Get()));
|
||||
@@ -141,6 +141,10 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
||||
}
|
||||
}})
|
||||
);
|
||||
|
||||
const Vec4 v{75, 110, 370, 155};
|
||||
const Vec2 pad{10, 10};
|
||||
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
|
||||
}
|
||||
|
||||
Menu::~Menu() {
|
||||
@@ -153,32 +157,25 @@ Menu::~Menu() {
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
m_list->OnUpdate(controller, touch, m_entries.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
SetIndex(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
MenuBase::Draw(vg, theme);
|
||||
|
||||
const u64 SCROLL = m_start;
|
||||
const u64 max_entry_display = 9;
|
||||
const u64 nro_total = m_entries.size();
|
||||
const u64 cursor_pos = m_index;
|
||||
fs::FsNativeSd fs;
|
||||
|
||||
// only draw scrollbar if needed
|
||||
if (nro_total > max_entry_display) {
|
||||
const auto scrollbar_size = 500.f;
|
||||
const auto sb_h = 3.f / (float)nro_total * scrollbar_size;
|
||||
const auto sb_y = SCROLL / 3.f;
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, theme->elements[ThemeEntryID_GRID].colour);
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * 2) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour);
|
||||
}
|
||||
|
||||
// max images per frame, in order to not hit io / gpu too hard.
|
||||
const int image_load_max = 2;
|
||||
int image_load_count = 0;
|
||||
|
||||
for (u64 i = 0, pos = SCROLL, y = 110, w = 370, h = 155; pos < nro_total && i < max_entry_display; y += h + 10) {
|
||||
for (u64 j = 0, x = 75; j < 3 && pos < nro_total && i < max_entry_display; j++, i++, pos++, x += w + 10) {
|
||||
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
|
||||
const auto& [x, y, w, h] = v;
|
||||
auto& e = m_entries[pos];
|
||||
|
||||
// lazy load image
|
||||
@@ -197,23 +194,23 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
}
|
||||
|
||||
auto text_id = ThemeEntryID_TEXT;
|
||||
if (pos == cursor_pos) {
|
||||
if (pos == m_index) {
|
||||
text_id = ThemeEntryID_TEXT_SELECTED;
|
||||
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour);
|
||||
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, v, theme->elements[ThemeEntryID_SELECTED].colour);
|
||||
} else {
|
||||
DrawElement(x, y, w, h, ThemeEntryID_GRID);
|
||||
DrawElement(v, ThemeEntryID_GRID);
|
||||
}
|
||||
|
||||
const float image_size = 115;
|
||||
gfx::drawImageRounded(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage());
|
||||
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, x, y, w - 30.f, h); // clip
|
||||
nvgIntersectScissor(vg, x, y, w - 30.f, h); // clip
|
||||
{
|
||||
bool has_star = false;
|
||||
if (IsStarEnabled()) {
|
||||
if (!e.has_star.has_value()) {
|
||||
e.has_star = fs.FileExists(GenerateStarPath(e.path));
|
||||
e.has_star = fs::FsNativeSd().FileExists(GenerateStarPath(e.path));
|
||||
}
|
||||
has_star = e.has_star.value();
|
||||
}
|
||||
@@ -224,8 +221,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
gfx::drawTextArgs(vg, x + 148, y + 115, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, e.GetDisplayVersion());
|
||||
}
|
||||
nvgRestore(vg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::OnFocusGained() {
|
||||
@@ -235,10 +231,10 @@ void Menu::OnFocusGained() {
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::SetIndex(std::size_t index) {
|
||||
void Menu::SetIndex(s64 index) {
|
||||
m_index = index;
|
||||
if (!m_index) {
|
||||
m_start = 0;
|
||||
m_list->SetYoff(0);
|
||||
}
|
||||
|
||||
const auto& e = m_entries[m_index];
|
||||
@@ -428,9 +424,9 @@ void Menu::SortAndFindLastFile() {
|
||||
if (index >= 0) {
|
||||
// guesstimate where the position is
|
||||
if (index >= 9) {
|
||||
m_start = (index - 9) / 3 * 3 + 3;
|
||||
m_list->SetYoff((((index - 9) + 3) / 3) * m_list->GetMaxY());
|
||||
} else {
|
||||
m_start = 0;
|
||||
m_list->SetYoff(0);
|
||||
}
|
||||
SetIndex(index);
|
||||
}
|
||||
|
||||
@@ -139,44 +139,44 @@ Menu::Menu() : MenuBase{"Irs"_i18n} {
|
||||
format_str.emplace_back("20x15"_i18n);
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Controller"_i18n, controller_str, [this](std::size_t& index){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Controller"_i18n, controller_str, [this](s64& index){
|
||||
irsStopImageProcessor(m_entries[m_index].m_handle);
|
||||
m_index = index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_index));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Rotation"_i18n, rotation_str, [this](std::size_t& index){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Rotation"_i18n, rotation_str, [this](s64& index){
|
||||
m_rotation = (Rotation)index;
|
||||
}, m_rotation));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Colour"_i18n, colour_str, [this](std::size_t& index){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Colour"_i18n, colour_str, [this](s64& index){
|
||||
m_colour = (Colour)index;
|
||||
updateColourArray();
|
||||
}, m_colour));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Light Target"_i18n, light_target_str, [this](std::size_t& index){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Light Target"_i18n, light_target_str, [this](s64& index){
|
||||
m_config.light_target = index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_config.light_target));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Gain"_i18n, gain_str, [this](std::size_t& index){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Gain"_i18n, gain_str, [this](s64& index){
|
||||
m_config.gain = GAIN_MIN + index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_config.gain - GAIN_MIN));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Negative Image"_i18n, is_negative_image_used_str, [this](std::size_t& index){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Negative Image"_i18n, is_negative_image_used_str, [this](s64& index){
|
||||
m_config.is_negative_image_used = index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_config.is_negative_image_used));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Format"_i18n, format_str, [this](std::size_t& index){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Format"_i18n, format_str, [this](s64& index){
|
||||
m_config.orig_format = index;
|
||||
m_config.trimming_format = index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_config.orig_format));
|
||||
|
||||
if (hosversionAtLeast(4,0,0)) {
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Trimming Format"_i18n, format_str, [this](std::size_t& index){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Trimming Format"_i18n, format_str, [this](s64& index){
|
||||
// you cannot set trim a larger region than the source
|
||||
if (index < m_config.orig_format) {
|
||||
index = m_config.orig_format;
|
||||
|
||||
@@ -115,7 +115,7 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
|
||||
}
|
||||
|
||||
std::vector<char> buf(chunk_size);
|
||||
u64 offset{};
|
||||
s64 offset{};
|
||||
while (offset < info.uncompressed_size) {
|
||||
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
|
||||
if (bytes_read <= 0) {
|
||||
@@ -205,9 +205,6 @@ MainMenu::MainMenu() {
|
||||
}
|
||||
});
|
||||
|
||||
AddOnLPress();
|
||||
AddOnRPress();
|
||||
|
||||
this->SetActions(
|
||||
std::make_pair(Button::START, Action{App::Exit}),
|
||||
std::make_pair(Button::Y, Action{"Menu"_i18n, [this](){
|
||||
@@ -229,8 +226,6 @@ MainMenu::MainMenu() {
|
||||
language_items.push_back("Russian"_i18n);
|
||||
language_items.push_back("Swedish"_i18n);
|
||||
|
||||
options->AddHeader("Header"_i18n);
|
||||
options->AddSpacer();
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Theme"_i18n, [this](){
|
||||
SidebarEntryArray::Items theme_items{};
|
||||
const auto theme_meta = App::GetThemeMetaList();
|
||||
@@ -241,7 +236,7 @@ MainMenu::MainMenu() {
|
||||
auto options = std::make_shared<Sidebar>("Theme Options"_i18n, Sidebar::Side::LEFT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Select Theme"_i18n, theme_items, [this, theme_items](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Select Theme"_i18n, theme_items, [this, theme_items](s64& index_out){
|
||||
App::SetTheme(index_out);
|
||||
}, App::GetThemeIndex()));
|
||||
|
||||
@@ -294,9 +289,9 @@ MainMenu::MainMenu() {
|
||||
}
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Language"_i18n, language_items, [this](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Language"_i18n, language_items, [this](s64& index_out){
|
||||
App::SetLanguage(index_out);
|
||||
}, (std::size_t)App::GetLanguage()));
|
||||
}, (s64)App::GetLanguage()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Misc"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Misc Options"_i18n, Sidebar::Side::LEFT);
|
||||
@@ -341,9 +336,9 @@ MainMenu::MainMenu() {
|
||||
App::SetInstallEnable(enable);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Install location"_i18n, install_items, [this](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Install location"_i18n, install_items, [this](s64& index_out){
|
||||
App::SetInstallSdEnable(index_out);
|
||||
}, (std::size_t)App::GetInstallSdEnable()));
|
||||
}, (s64)App::GetInstallSdEnable()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Show install warning"_i18n, App::GetInstallPrompt(), [this](bool& enable){
|
||||
App::SetInstallPrompt(enable);
|
||||
@@ -357,6 +352,8 @@ MainMenu::MainMenu() {
|
||||
m_app_store_menu = std::make_shared<appstore::Menu>(m_homebrew_menu->GetHomebrewList());
|
||||
m_current_menu = m_homebrew_menu;
|
||||
|
||||
AddOnLRPress();
|
||||
|
||||
for (auto [button, action] : m_actions) {
|
||||
m_current_menu->SetAction(button, action);
|
||||
}
|
||||
@@ -389,17 +386,11 @@ void MainMenu::OnLRPress(std::shared_ptr<MenuBase> menu, Button b) {
|
||||
if (m_current_menu == m_homebrew_menu) {
|
||||
m_current_menu = menu;
|
||||
RemoveAction(b);
|
||||
if (b == Button::L) {
|
||||
AddOnRPress();
|
||||
} else {
|
||||
AddOnLPress();
|
||||
}
|
||||
} else {
|
||||
m_current_menu = m_homebrew_menu;
|
||||
AddOnRPress();
|
||||
AddOnLPress();
|
||||
}
|
||||
|
||||
AddOnLRPress();
|
||||
m_current_menu->OnFocusGained();
|
||||
|
||||
for (auto [button, action] : m_actions) {
|
||||
@@ -407,18 +398,20 @@ void MainMenu::OnLRPress(std::shared_ptr<MenuBase> menu, Button b) {
|
||||
}
|
||||
}
|
||||
|
||||
void MainMenu::AddOnLPress() {
|
||||
void MainMenu::AddOnLRPress() {
|
||||
if (m_current_menu != m_filebrowser_menu) {
|
||||
const auto label = m_current_menu == m_homebrew_menu ? "Files" : "Apps";
|
||||
SetAction(Button::L, Action{i18n::get(label), [this]{
|
||||
OnLRPress(m_filebrowser_menu, Button::L);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
void MainMenu::AddOnRPress() {
|
||||
if (m_current_menu != m_app_store_menu) {
|
||||
const auto label = m_current_menu == m_homebrew_menu ? "Store" : "Apps";
|
||||
SetAction(Button::R, Action{i18n::get(label), [this]{
|
||||
OnLRPress(m_app_store_menu, Button::R);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::main
|
||||
|
||||
@@ -103,61 +103,4 @@ void MenuBase::UpdateVars() {
|
||||
m_poll_timestamp.Update();
|
||||
}
|
||||
|
||||
auto MenuBase::ScrollHelperDown(u64& index, u64& start, u64 step, s64 row, s64 page, u64 size) -> bool {
|
||||
const auto old_index = index;
|
||||
|
||||
if (!size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index + step < size) {
|
||||
index += step;
|
||||
} else {
|
||||
index = size - 1;
|
||||
}
|
||||
|
||||
if (index != old_index) {
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
s64 delta = index - old_index;
|
||||
|
||||
if (index - start >= page) {
|
||||
do {
|
||||
start += row;
|
||||
delta -= row;
|
||||
} while (delta > 0 && start + page < size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto MenuBase::ScrollHelperUp(u64& index, u64& start, s64 step, s64 row, s64 page, s64 size) -> bool {
|
||||
const auto old_index = index;
|
||||
|
||||
if (!size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index >= step) {
|
||||
index -= step;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (index != old_index) {
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
// if ()
|
||||
while (index < start) {
|
||||
// log_write("moved up\n");
|
||||
start -= row;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu
|
||||
|
||||
@@ -369,7 +369,7 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
|
||||
}
|
||||
|
||||
std::vector<char> buf(chunk_size);
|
||||
u64 offset{};
|
||||
s64 offset{};
|
||||
while (offset < info.uncompressed_size) {
|
||||
if (pbox->ShouldExit()) {
|
||||
return false;
|
||||
@@ -429,25 +429,25 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
|
||||
}}),
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (ScrollHelperDown(m_index, m_start, 3, 3, 6, page.m_packList.size())) {
|
||||
if (m_list->ScrollDown(m_index, 3, page.m_packList.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (ScrollHelperUp(m_index, m_start, 3, 3, 6, page.m_packList.size())) {
|
||||
if (m_list->ScrollUp(m_index, 3, page.m_packList.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::R2, Action{[this](){
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (ScrollHelperDown(m_index, m_start, 6, 3, 6, page.m_packList.size())) {
|
||||
if (m_list->ScrollDown(m_index, 6, page.m_packList.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::L2, Action{[this](){
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (ScrollHelperUp(m_index, m_start, 6, 3, 6, page.m_packList.size())) {
|
||||
if (m_list->ScrollUp(m_index, 6, page.m_packList.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
@@ -492,14 +492,14 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
|
||||
InvalidateAllPages();
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
|
||||
if (m_sort.Get() != index_out) {
|
||||
m_sort.Set(index_out);
|
||||
InvalidateAllPages();
|
||||
}
|
||||
}, m_sort.Get()));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](std::size_t& index_out){
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
|
||||
if (m_order.Get() != index_out) {
|
||||
m_order.Set(index_out);
|
||||
InvalidateAllPages();
|
||||
@@ -545,6 +545,10 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
|
||||
m_page_index = 0;
|
||||
m_pages.resize(1);
|
||||
PackListDownload();
|
||||
|
||||
const Vec4 v{75, 110, 350, 250};
|
||||
const Vec2 pad{10, 10};
|
||||
m_list = std::make_unique<List>(3, 6, m_pos, v, pad);
|
||||
}
|
||||
|
||||
Menu::~Menu() {
|
||||
@@ -553,6 +557,24 @@ Menu::~Menu() {
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
|
||||
if (m_pages.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& page = m_pages[m_page_index];
|
||||
if (page.m_ready != PageLoadState::Done) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_list->OnUpdate(controller, touch, page.m_packList.size(), [this](auto i) {
|
||||
if (m_index == i) {
|
||||
FireAction(Button::A);
|
||||
} else {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
SetIndex(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
@@ -579,34 +601,16 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 SCROLL = m_start;
|
||||
const u64 max_entry_display = 9;
|
||||
const u64 nro_total = page.m_packList.size();// m_entries_current.size();
|
||||
const u64 cursor_pos = m_index;
|
||||
|
||||
// only draw scrollbar if needed
|
||||
if (nro_total > max_entry_display) {
|
||||
const auto scrollbar_size = 500.f;
|
||||
const auto sb_h = 3.f / (float)(nro_total + 3) * scrollbar_size;
|
||||
const auto sb_y = SCROLL / 3.f;
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, theme->elements[ThemeEntryID_GRID].colour);
|
||||
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * 2) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour);
|
||||
}
|
||||
|
||||
// max images per frame, in order to not hit io / gpu too hard.
|
||||
const int image_load_max = 2;
|
||||
int image_load_count = 0;
|
||||
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, 30, 87, 1220 - 30, 646 - 87); // clip
|
||||
|
||||
for (u64 i = 0, pos = SCROLL, y = 110, w = 350, h = 250; pos < nro_total && i < max_entry_display; y += h + 10) {
|
||||
for (u64 j = 0, x = 75; j < 3 && pos < nro_total && i < max_entry_display; j++, i++, pos++, x += w + 10) {
|
||||
const auto index = pos;
|
||||
auto& e = page.m_packList[index];
|
||||
m_list->Draw(vg, theme, page.m_packList.size(), [this, &page, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
|
||||
const auto& [x, y, w, h] = v;
|
||||
auto& e = page.m_packList[pos];
|
||||
|
||||
auto text_id = ThemeEntryID_TEXT;
|
||||
if (pos == cursor_pos) {
|
||||
if (pos == m_index) {
|
||||
text_id = ThemeEntryID_TEXT_SELECTED;
|
||||
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour);
|
||||
} else {
|
||||
@@ -676,12 +680,14 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
gfx::drawImageRounded(vg, x + xoff, y, 320, 180, image.image ? image.image : App::GetDefaultImage());
|
||||
}
|
||||
|
||||
nvgSave(vg);
|
||||
nvgIntersectScissor(vg, x, y, w - 30.f, h); // clip
|
||||
{
|
||||
gfx::drawTextArgs(vg, x + xoff, y + 180 + 20, 18, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s", e.details.name.c_str());
|
||||
gfx::drawTextArgs(vg, x + xoff, y + 180 + 55, 18, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s", e.creator.display_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
nvgRestore(vg);
|
||||
});
|
||||
}
|
||||
|
||||
void Menu::OnFocusGained() {
|
||||
@@ -704,7 +710,7 @@ void Menu::PackListDownload() {
|
||||
SetSubHeading(subheading);
|
||||
|
||||
m_index = 0;
|
||||
m_start = 0;
|
||||
m_list->SetYoff(0);
|
||||
|
||||
// already downloaded
|
||||
if (m_pages[m_page_index].m_ready != PageLoadState::None) {
|
||||
|
||||
@@ -11,10 +11,6 @@ NotifEntry::NotifEntry(std::string text, Side side)
|
||||
, m_side{side} {
|
||||
}
|
||||
|
||||
auto NotifEntry::OnLayoutChange() -> void {
|
||||
|
||||
}
|
||||
|
||||
auto NotifEntry::Draw(NVGcontext* vg, Theme* theme, float y) -> bool {
|
||||
m_pos.y = y;
|
||||
Draw(vg, theme);
|
||||
@@ -57,11 +53,6 @@ auto NotifEntry::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
gfx::drawText(vg, Vec2{m_pos.x + (m_pos.w / 2.f), m_pos.y + (m_pos.h / 2.f)}, font_size, text_col, m_text.c_str(), NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER);
|
||||
}
|
||||
|
||||
auto NotifMananger::OnLayoutChange() -> void {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
}
|
||||
|
||||
auto NotifMananger::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
@@ -77,6 +77,19 @@ inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor&
|
||||
float gradientX, gradientY, color;
|
||||
getHighlightAnimation(&gradientX, &gradientY, &color);
|
||||
|
||||
#if 1
|
||||
// NVGcolor pulsationColor = nvgRGBAf((color * out_col.r) + (1 - color) * out_col.r,
|
||||
// (color * out_col.g) + (1 - color) * out_col.g,
|
||||
// (color * out_col.b) + (1 - color) * out_col.b,
|
||||
// out_col.a);
|
||||
NVGcolor pulsationColor = nvgRGBAf((color * out_col.r) + (1 - color) * out_col.r,
|
||||
(color * out_col.g) + (1 - color) * out_col.g,
|
||||
(color * out_col.b) + (1 - color) * out_col.b,
|
||||
out_col.a);
|
||||
|
||||
drawRectIntenal(vg, {vec.x-size,vec.y-size,vec.w+(size*2.f),vec.h+(size * 2.f)}, pulsationColor, false);
|
||||
drawRectIntenal(vg, vec, c, false);
|
||||
#else
|
||||
const auto strokeWidth = 5.0;
|
||||
auto v2 = vec;
|
||||
v2.x -= strokeWidth / 2.0;
|
||||
@@ -85,8 +98,8 @@ inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor&
|
||||
v2.h += strokeWidth;
|
||||
const auto corner_radius = 0.5;
|
||||
|
||||
nvgSave(vg);
|
||||
nvgResetScissor(vg);
|
||||
// nvgSave(vg);
|
||||
// nvgResetScissor(vg);
|
||||
|
||||
// const auto stroke_width = 5.0f;
|
||||
// const auto shadow_corner_radius = 6.0f;
|
||||
@@ -155,7 +168,8 @@ inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor&
|
||||
nvgFillColor(vg, c);
|
||||
nvgFill(vg);
|
||||
|
||||
nvgRestore(vg);
|
||||
// nvgRestore(vg);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint& p) {
|
||||
@@ -291,7 +305,7 @@ void textBounds(NVGcontext* vg, float x, float y, float *bounds, const char* str
|
||||
void dimBackground(NVGcontext* vg) {
|
||||
// drawRectIntenal(vg, {0.f,0.f,1280.f,720.f}, nvgRGBA(30,30,30,180));
|
||||
// drawRectIntenal(vg, {0.f,0.f,1920.f,1080.f}, nvgRGBA(20, 20, 20, 225), false);
|
||||
drawRectIntenal(vg, {0.f,0.f,1920.f,1080.f}, nvgRGBA(0, 0, 0, 220), false);
|
||||
drawRectIntenal(vg, {0.f,0.f,1920.f,1080.f}, nvgRGBA(0, 0, 0, 230), false);
|
||||
}
|
||||
|
||||
void drawRect(NVGcontext* vg, float x, float y, float w, float h, Colour c, bool rounded) {
|
||||
@@ -453,58 +467,47 @@ void drawTextArgs(NVGcontext* vg, float x, float y, float size, int align, Colou
|
||||
drawTextIntenal(vg, {x, y}, size, buffer, nullptr, align, getColour(c));
|
||||
}
|
||||
|
||||
void drawButton(NVGcontext* vg, float x, float y, float size, Button button) {
|
||||
drawText(vg, x, y, size, getButton(button), nullptr, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, getColour(Colour::WHITE));
|
||||
}
|
||||
void drawScrollbar(NVGcontext* vg, Theme* theme, float x, float y, float h, u32 index_off, u32 count, u32 max_per_page) {
|
||||
const s64 SCROLL = index_off;
|
||||
const s64 max_entry_display = max_per_page;
|
||||
const s64 entry_total = count;
|
||||
const float scc2 = 8.0;
|
||||
const float scw = 2.0;
|
||||
|
||||
void drawButtons(NVGcontext* vg, const Widget::Actions& _actions, const NVGcolor& c, float start_x) {
|
||||
nvgBeginPath(vg);
|
||||
nvgFontSize(vg, 24.f);
|
||||
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
|
||||
nvgFillColor(vg, c);
|
||||
|
||||
float x = start_x;
|
||||
const float y = 675.f;
|
||||
float bounds[4]{};
|
||||
|
||||
// swaps L/R position, idc how shit this is, it's called once per frame.
|
||||
std::vector<std::pair<Button, Action>> actions;
|
||||
actions.reserve(_actions.size());
|
||||
|
||||
for (const auto a: _actions) {
|
||||
// swap
|
||||
if (a.first == Button::R && actions.size() && actions.back().first == Button::L) {
|
||||
const auto s = actions.back();
|
||||
actions.back() = a;
|
||||
actions.emplace_back(s);
|
||||
|
||||
} else {
|
||||
actions.emplace_back(a);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [button, action] : actions) {
|
||||
if (action.IsHidden() || action.m_hint.empty()) {
|
||||
continue;
|
||||
}
|
||||
nvgFontSize(vg, 20.f);
|
||||
nvgTextBounds(vg, x, y, action.m_hint.c_str(), nullptr, bounds);
|
||||
auto len = bounds[2] - bounds[0];
|
||||
nvgText(vg, x, y, action.m_hint.c_str(), nullptr);
|
||||
|
||||
x -= len + 8.f;
|
||||
nvgFontSize(vg, 26.f);
|
||||
nvgTextBounds(vg, x, y - 7.f, getButton(button), nullptr, bounds);
|
||||
len = bounds[2] - bounds[0];
|
||||
nvgText(vg, x, y - 4.f, getButton(button), nullptr);
|
||||
x -= len + 34.f;
|
||||
// only draw scrollbar if needed
|
||||
if (entry_total > max_entry_display) {
|
||||
const float sb_h = 1.f / (float)entry_total * h;
|
||||
const float sb_y = SCROLL;
|
||||
gfx::drawRect(vg, x, y, scc2, h, theme->elements[ThemeEntryID_GRID].colour, false);
|
||||
gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(max_entry_display) - scw * 2, theme->elements[ThemeEntryID_TEXT_SELECTED].colour, false);
|
||||
}
|
||||
}
|
||||
|
||||
// from gc installer
|
||||
void drawDimBackground(NVGcontext* vg) {
|
||||
// drawRect(vg, 0, 0, 1920, 1080, nvgRGBA(20, 20, 20, 225));
|
||||
drawRect(vg, 0, 0, 1920, 1080, nvgRGBA(0, 0, 0, 220));
|
||||
void drawScrollbar(NVGcontext* vg, Theme* theme, u32 index_off, u32 count, u32 max_per_page) {
|
||||
// drawScrollbar(vg, SCREEN_WIDTH - 50, 100, 500, index_off, count, max_per_page);
|
||||
drawScrollbar(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, max_per_page);
|
||||
}
|
||||
|
||||
void drawScrollbar2(NVGcontext* vg, Theme* theme, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page) {
|
||||
// round up
|
||||
if (count % row) {
|
||||
count = count + (row - count % row);
|
||||
}
|
||||
|
||||
const float scc2 = 8.0;
|
||||
const float scw = 2.0;
|
||||
|
||||
// only draw scrollbar if needed
|
||||
if (count > page) {
|
||||
const float sb_h = 1.f / (float)count * h;
|
||||
const float sb_y = index_off;
|
||||
gfx::drawRect(vg, x, y, scc2, h, theme->elements[ThemeEntryID_GRID].colour, false);
|
||||
gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(page) - scw * 2, theme->elements[ThemeEntryID_TEXT_SELECTED].colour, false);
|
||||
}
|
||||
}
|
||||
|
||||
void drawScrollbar2(NVGcontext* vg, Theme* theme, s64 index_off, s64 count, s64 row, s64 page) {
|
||||
drawScrollbar2(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, row, page);
|
||||
}
|
||||
|
||||
#define HIGHLIGHT_SPEED 350.0
|
||||
|
||||
@@ -45,7 +45,7 @@ OptionBox::OptionBox(const std::string& message, const Option& a, const Option&
|
||||
|
||||
}
|
||||
|
||||
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, std::size_t index, Callback cb)
|
||||
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb)
|
||||
: m_message{message}
|
||||
, m_callback{cb} {
|
||||
|
||||
@@ -70,7 +70,7 @@ OptionBox::OptionBox(const std::string& message, const Option& a, const Option&
|
||||
|
||||
}
|
||||
|
||||
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, std::size_t index, Callback cb)
|
||||
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, s64 index, Callback cb)
|
||||
: m_message{message}
|
||||
, m_callback{cb} {
|
||||
|
||||
@@ -79,26 +79,16 @@ OptionBox::OptionBox(const std::string& message, const Option& a, const Option&
|
||||
auto OptionBox::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
Widget::Update(controller, touch);
|
||||
|
||||
// if (!controller->GotDown(Button::ANY_HORIZONTAL)) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const auto old_index = m_index;
|
||||
|
||||
// if (controller->GotDown(Button::LEFT) && m_index) {
|
||||
// m_index--;
|
||||
// } else if (controller->GotDown(Button::RIGHT) && m_index < (m_entries.size() - 1)) {
|
||||
// m_index++;
|
||||
// }
|
||||
|
||||
// if (old_index != m_index) {
|
||||
// m_entries[old_index].Selected(false);
|
||||
// m_entries[m_index].Selected(true);
|
||||
// }
|
||||
}
|
||||
|
||||
auto OptionBox::OnLayoutChange() -> void {
|
||||
|
||||
if (touch->is_clicked) {
|
||||
for (s64 i = 0; i < m_entries.size(); i++) {
|
||||
auto& e = m_entries[i];
|
||||
if (touch->in_range(e.GetPos())) {
|
||||
SetIndex(i);
|
||||
FireAction(Button::A);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto OptionBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
@@ -128,26 +118,20 @@ auto OptionBox::OnFocusLost() noexcept -> void {
|
||||
SetHidden(true);
|
||||
}
|
||||
|
||||
auto OptionBox::Setup(std::size_t index) -> void {
|
||||
m_index = std::min(m_entries.size() - 1, index);
|
||||
auto OptionBox::Setup(s64 index) -> void {
|
||||
m_index = std::min<s64>(m_entries.size() - 1, index);
|
||||
m_entries[m_index].Selected(true);
|
||||
m_spacer_line = Vec4{m_pos.x, m_pos.y + 220.f - 2.f, m_pos.w, 2.f};
|
||||
|
||||
SetActions(
|
||||
std::make_pair(Button::LEFT, Action{[this](){
|
||||
if (m_index) {
|
||||
m_entries[m_index].Selected(false);
|
||||
m_index--;
|
||||
m_entries[m_index].Selected(true);
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
SetIndex(m_index - 1);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::RIGHT, Action{[this](){
|
||||
if (m_index < (m_entries.size() - 1)) {
|
||||
m_entries[m_index].Selected(false);
|
||||
m_index++;
|
||||
m_entries[m_index].Selected(true);
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
SetIndex(m_index + 1);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::A, Action{[this](){
|
||||
@@ -161,4 +145,12 @@ auto OptionBox::Setup(std::size_t index) -> void {
|
||||
);
|
||||
}
|
||||
|
||||
void OptionBox::SetIndex(s64 index) {
|
||||
if (m_index != index) {
|
||||
m_entries[m_index].Selected(false);
|
||||
m_index = index;
|
||||
m_entries[m_index].Selected(true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#include "ui/option_list.hpp"
|
||||
#include "app.hpp"
|
||||
#include "ui/nvg_util.hpp"
|
||||
#include "i18n.hpp"
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
OptionList::OptionList(Options options)
|
||||
: m_options{std::move(options)} {
|
||||
SetAction(Button::A, Action{"Select"_i18n, [this](){
|
||||
const auto& [_, func] = m_options[m_index];
|
||||
func();
|
||||
SetPop();
|
||||
}});
|
||||
|
||||
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
||||
SetPop();
|
||||
}});
|
||||
}
|
||||
|
||||
auto OptionList::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
|
||||
}
|
||||
|
||||
auto OptionList::OnLayoutChange() -> void {
|
||||
|
||||
}
|
||||
|
||||
auto OptionList::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
PopupList::PopupList(std::string title, Items items, std::string& index_str_ref, std::size_t& index_ref)
|
||||
PopupList::PopupList(std::string title, Items items, std::string& index_str_ref, s64& index_ref)
|
||||
: PopupList{std::move(title), std::move(items), Callback{}, index_ref} {
|
||||
|
||||
m_callback = [&index_str_ref, &index_ref, this](auto op_idx) {
|
||||
@@ -22,7 +22,9 @@ PopupList::PopupList(std::string title, Items items, std::string& index_ref)
|
||||
const auto it = std::find(m_items.cbegin(), m_items.cend(), index_ref);
|
||||
if (it != m_items.cend()) {
|
||||
m_index = std::distance(m_items.cbegin(), it);
|
||||
m_selected_y = m_line_top + 1.f + 42.f + (static_cast<float>(m_index) * m_block.h);
|
||||
if (m_index >= 7) {
|
||||
m_index_offset = m_index - 6;
|
||||
}
|
||||
}
|
||||
|
||||
m_callback = [&index_ref, this](auto op_idx) {
|
||||
@@ -32,7 +34,7 @@ PopupList::PopupList(std::string title, Items items, std::string& index_ref)
|
||||
};
|
||||
}
|
||||
|
||||
PopupList::PopupList(std::string title, Items items, std::size_t& index_ref)
|
||||
PopupList::PopupList(std::string title, Items items, s64& index_ref)
|
||||
: PopupList{std::move(title), std::move(items), Callback{}, index_ref} {
|
||||
|
||||
m_callback = [&index_ref, this](auto op_idx) {
|
||||
@@ -47,28 +49,29 @@ PopupList::PopupList(std::string title, Items items, Callback cb, std::string in
|
||||
|
||||
const auto it = std::find(m_items.cbegin(), m_items.cend(), index);
|
||||
if (it != m_items.cend()) {
|
||||
m_index = std::distance(m_items.cbegin(), it);
|
||||
m_selected_y = m_line_top + 1.f + 42.f + (static_cast<float>(m_index) * m_block.h);
|
||||
SetIndex(std::distance(m_items.cbegin(), it));
|
||||
if (m_index >= 7) {
|
||||
m_index_offset = m_index - 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PopupList::PopupList(std::string title, Items items, Callback cb, std::size_t index)
|
||||
PopupList::PopupList(std::string title, Items items, Callback cb, s64 index)
|
||||
: m_title{std::move(title)}
|
||||
, m_items{std::move(items)}
|
||||
, m_callback{cb}
|
||||
, m_index{index} {
|
||||
|
||||
m_pos.w = 1280.f;
|
||||
const float a = std::min(405.f, (60.f * static_cast<float>(m_items.size())));
|
||||
m_pos.h = 80.f + 140.f + a;
|
||||
m_pos.y = 720.f - m_pos.h;
|
||||
m_line_top = m_pos.y + 70.f;
|
||||
m_line_bottom = 720.f - 73.f;
|
||||
m_selected_y = m_line_top + 1.f + 42.f + (static_cast<float>(m_index) * m_block.h);
|
||||
|
||||
m_scrollbar.Setup(Vec4{1220.f, m_line_top, 1.f, m_line_bottom - m_line_top}, m_block.h, m_items.size());
|
||||
|
||||
SetActions(
|
||||
this->SetActions(
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (m_list->ScrollDown(m_index, 1, m_items.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (m_list->ScrollUp(m_index, 1, m_items.size())) {
|
||||
SetIndex(m_index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::A, Action{"Select"_i18n, [this](){
|
||||
if (m_callback) {
|
||||
m_callback(m_index);
|
||||
@@ -76,48 +79,33 @@ PopupList::PopupList(std::string title, Items items, Callback cb, std::size_t in
|
||||
SetPop();
|
||||
}}),
|
||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||
if (m_callback) {
|
||||
m_callback(std::nullopt);
|
||||
}
|
||||
SetPop();
|
||||
}})
|
||||
);
|
||||
|
||||
m_pos.w = 1280.f;
|
||||
const float a = std::min(405.f, (60.f * static_cast<float>(m_items.size())));
|
||||
m_pos.h = 80.f + 140.f + a;
|
||||
m_pos.y = 720.f - m_pos.h;
|
||||
m_line_top = m_pos.y + 70.f;
|
||||
m_line_bottom = 720.f - 73.f;
|
||||
if (m_index >= 7) {
|
||||
m_index_offset = m_index - 6;
|
||||
}
|
||||
|
||||
Vec4 v{m_block};
|
||||
v.y = m_line_top + 1.f + 42.f;
|
||||
const Vec4 pos{0, m_line_top, 1280.f, m_line_bottom - m_line_top};
|
||||
m_list = std::make_unique<List>(1, 7, pos, v);
|
||||
m_list->SetScrollBarPos(1250, m_line_top + 20, m_line_bottom - m_line_top - 40);
|
||||
}
|
||||
|
||||
auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
Widget::Update(controller, touch);
|
||||
|
||||
if (!controller->GotDown(Button::ANY_VERTICAL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old_index = m_index;
|
||||
|
||||
if (controller->GotDown(Button::DOWN) && m_index < (m_items.size() - 1)) {
|
||||
m_index++;
|
||||
m_selected_y += m_block.h;
|
||||
} else if (controller->GotDown(Button::UP) && m_index != 0) {
|
||||
m_index--;
|
||||
m_selected_y -= m_block.h;
|
||||
}
|
||||
|
||||
if (old_index != m_index) {
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
OnLayoutChange();
|
||||
}
|
||||
}
|
||||
|
||||
auto PopupList::OnLayoutChange() -> void {
|
||||
if ((m_selected_y + m_block.h) > m_line_bottom) {
|
||||
m_selected_y -= m_block.h;
|
||||
m_index_offset++;
|
||||
m_scrollbar.Move(ScrollBar::Direction::DOWN);
|
||||
} else if (m_selected_y <= m_line_top) {
|
||||
m_selected_y += m_block.h;
|
||||
m_index_offset--;
|
||||
m_scrollbar.Move(ScrollBar::Direction::UP);
|
||||
}
|
||||
// LOG("sely: %.2f, index_off: %lu\n", m_selected_y, m_index_offset);
|
||||
m_list->OnUpdate(controller, touch, m_items.size(), [this](auto i) {
|
||||
SetIndex(i);
|
||||
FireAction(Button::A);
|
||||
});
|
||||
}
|
||||
|
||||
auto PopupList::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
@@ -127,16 +115,8 @@ auto PopupList::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
gfx::drawRect(vg, 30.f, m_line_top, m_line_width, 1.f, theme->elements[ThemeEntryID_TEXT].colour);
|
||||
gfx::drawRect(vg, 30.f, m_line_bottom, m_line_width, 1.f, theme->elements[ThemeEntryID_TEXT].colour);
|
||||
|
||||
// todo: cleanup
|
||||
const float x = m_block.x;
|
||||
float y = m_line_top + 1.f + 42.f;
|
||||
const float h = m_block.h;
|
||||
const float w = m_block.w;
|
||||
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, 0, m_line_top, 1280.f, m_line_bottom - m_line_top);
|
||||
|
||||
for (std::size_t i = m_index_offset; i < m_items.size(); ++i) {
|
||||
m_list->Draw(vg, theme, m_items.size(), [this](auto* vg, auto* theme, auto v, auto i) {
|
||||
const auto& [x, y, w, h] = v;
|
||||
if (m_index == i) {
|
||||
gfx::drawRect(vg, x - 4.f, y - 4.f, w + 8.f, h + 8.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour);
|
||||
gfx::drawRect(vg, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour);
|
||||
@@ -146,14 +126,8 @@ auto PopupList::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
gfx::drawRect(vg, x, y + h, w, 1.f, theme->elements[ThemeEntryID_TEXT].colour);
|
||||
gfx::drawText(vg, x + m_text_xoffset, y + (h / 2.f), 20.f, m_items[i].c_str(), NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->elements[ThemeEntryID_TEXT].colour);
|
||||
}
|
||||
y += h;
|
||||
if (y > m_line_bottom) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
nvgRestore(vg);
|
||||
});
|
||||
|
||||
m_scrollbar.Draw(vg, theme);
|
||||
Widget::Draw(vg, theme);
|
||||
}
|
||||
|
||||
@@ -167,4 +141,12 @@ auto PopupList::OnFocusLost() noexcept -> void {
|
||||
SetHidden(true);
|
||||
}
|
||||
|
||||
void PopupList::SetIndex(s64 index) {
|
||||
m_index = index;
|
||||
|
||||
if (m_index > m_index_offset && m_index - m_index_offset >= 6) {
|
||||
m_index_offset = m_index - 6;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -119,7 +119,7 @@ auto ProgressBox::NewTransfer(const std::string& transfer) -> ProgressBox& {
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto ProgressBox::UpdateTransfer(u64 offset, u64 size) -> ProgressBox& {
|
||||
auto ProgressBox::UpdateTransfer(s64 offset, s64 size) -> ProgressBox& {
|
||||
mutexLock(&m_mutex);
|
||||
m_size = size;
|
||||
m_offset = offset;
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
#include "ui/scrollbar.hpp"
|
||||
#include "ui/nvg_util.hpp"
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
ScrollBar::ScrollBar(Vec4 bounds, float entry_height, std::size_t entries)
|
||||
: m_bounds{bounds}
|
||||
, m_entries{entries}
|
||||
, m_entry_height{entry_height} {
|
||||
Setup();
|
||||
}
|
||||
|
||||
auto ScrollBar::OnLayoutChange() -> void {
|
||||
|
||||
}
|
||||
|
||||
auto ScrollBar::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
if (m_should_draw) {
|
||||
gfx::drawRect(vg, m_pos, gfx::Colour::RED);
|
||||
}
|
||||
}
|
||||
|
||||
auto ScrollBar::Setup(Vec4 bounds, float entry_height, std::size_t entries) -> void {
|
||||
m_bounds = bounds;
|
||||
m_entry_height = entry_height;
|
||||
m_entries = entries;
|
||||
Setup();
|
||||
}
|
||||
|
||||
auto ScrollBar::Setup() -> void {
|
||||
m_bounds.y += 5.f;
|
||||
m_bounds.h -= 10.f;
|
||||
|
||||
const float total_size = (m_entry_height) * static_cast<float>(m_entries);
|
||||
if (total_size > m_bounds.h) {
|
||||
m_step_size = total_size / m_entries;
|
||||
m_pos.x = m_bounds.x;
|
||||
m_pos.y = m_bounds.y;
|
||||
m_pos.w = 2.f;
|
||||
m_pos.h = total_size - m_bounds.h;
|
||||
m_should_draw = true;
|
||||
// LOG("total size: %.2f\n", total_size);
|
||||
// LOG("step size: %.2f\n", m_step_size);
|
||||
// LOG("pos y: %.2f\n", m_pos.y);
|
||||
// LOG("pos h: %.2f\n", m_pos.h);
|
||||
} else {
|
||||
// LOG("not big enough for scroll total: %.2f bounds: %.2f\n", total_size, bounds.h);
|
||||
}
|
||||
}
|
||||
|
||||
auto ScrollBar::Move(Direction direction) -> void {
|
||||
switch (direction) {
|
||||
case Direction::DOWN:
|
||||
if (m_index < (m_entries - 1)) {
|
||||
m_index++;
|
||||
m_pos.y += m_step_size;
|
||||
}
|
||||
break;
|
||||
case Direction::UP:
|
||||
if (m_index != 0) {
|
||||
m_index--;
|
||||
m_pos.y -= m_step_size;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui
|
||||
@@ -7,13 +7,12 @@
|
||||
namespace sphaira::ui {
|
||||
namespace {
|
||||
|
||||
struct SidebarSpacer : SidebarEntryBase {
|
||||
|
||||
};
|
||||
|
||||
struct SidebarHeader : SidebarEntryBase {
|
||||
|
||||
};
|
||||
auto DistanceBetweenY(Vec4 va, Vec4 vb) -> Vec4 {
|
||||
return Vec4{
|
||||
va.x, va.y,
|
||||
va.w, vb.y - va.y
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -127,7 +126,7 @@ SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb
|
||||
}
|
||||
}
|
||||
|
||||
SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb, std::size_t index)
|
||||
SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb, s64 index)
|
||||
: SidebarEntryBase{std::forward<std::string>(title)}
|
||||
, m_items{std::move(items)}
|
||||
, m_callback{cb}
|
||||
@@ -191,24 +190,12 @@ Sidebar::Sidebar(std::string title, std::string sub, Side side, Items&& items)
|
||||
m_title_pos = Vec2{m_pos.x + 30.f, m_pos.y + 40.f};
|
||||
m_base_pos = Vec4{GetX() + 30.f, GetY() + 170.f, m_pos.w - (30.f * 2.f), 70.f};
|
||||
|
||||
// each item has it's own Action, but we take over B
|
||||
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
||||
SetPop();
|
||||
}});
|
||||
// set button positions
|
||||
SetUiButtonPos({m_pos.x + m_pos.w - 60.f, 675});
|
||||
|
||||
m_selected_y = m_base_pos.y;
|
||||
|
||||
if (!m_items.empty()) {
|
||||
// setup positions
|
||||
m_selected_y = m_base_pos.y;
|
||||
// for (auto&p : m_items) {
|
||||
// p->SetPos(m_base_pos);
|
||||
// m_base_pos.y += m_base_pos.h;
|
||||
// }
|
||||
|
||||
// // give focus to first entry.
|
||||
// m_items[m_index]->OnFocusGained();
|
||||
}
|
||||
const Vec4 pos = DistanceBetweenY(m_top_bar, m_bottom_bar);
|
||||
m_list = std::make_unique<List>(1, 6, pos, m_base_pos);
|
||||
m_list->SetScrollBarPos(GetX() + GetW() - 20, m_base_pos.y - 10, pos.h - m_base_pos.y + 48);
|
||||
}
|
||||
|
||||
Sidebar::Sidebar(std::string title, std::string sub, Side side)
|
||||
@@ -217,46 +204,21 @@ Sidebar::Sidebar(std::string title, std::string sub, Side side)
|
||||
|
||||
|
||||
auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
m_items[m_index]->Update(controller, touch);
|
||||
Widget::Update(controller, touch);
|
||||
|
||||
// if touched out of bounds, pop the sidebar and all widgets below it.
|
||||
if (touch->is_clicked && !touch->in_range(GetPos())) {
|
||||
App::PopToMenu();
|
||||
} else {
|
||||
m_list->OnUpdate(controller, touch, m_items.size(), [this](auto i) {
|
||||
SetIndex(i);
|
||||
FireAction(Button::A);
|
||||
});
|
||||
}
|
||||
|
||||
if (m_items[m_index]->ShouldPop()) {
|
||||
SetPop();
|
||||
}
|
||||
|
||||
const auto old_index = m_index;
|
||||
if (controller->GotDown(Button::ANY_DOWN) && m_index < (m_items.size() - 1)) {
|
||||
m_index++;
|
||||
m_selected_y += m_box_size.y;
|
||||
} else if (controller->GotDown(Button::ANY_UP) && m_index != 0) {
|
||||
m_index--;
|
||||
m_selected_y -= m_box_size.y;
|
||||
}
|
||||
|
||||
// if we moved
|
||||
if (m_index != old_index) {
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
m_items[old_index]->OnFocusLost();
|
||||
m_items[m_index]->OnFocusGained();
|
||||
|
||||
// move offset
|
||||
if ((m_selected_y + m_box_size.y) >= m_bottom_bar.y) {
|
||||
m_selected_y -= m_box_size.y;
|
||||
m_index_offset++;
|
||||
// LOG("move down\n");
|
||||
} else if (m_selected_y <= m_top_bar.y) {
|
||||
// LOG("move up sely %.2f top %.2f\n", m_selected_y, m_top_bar.y);
|
||||
m_selected_y += m_box_size.y;
|
||||
m_index_offset--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto DistanceBetweenY(Vec4 va, Vec4 vb) -> Vec4 {
|
||||
return Vec4{
|
||||
va.x, va.y,
|
||||
va.w, vb.y - va.y
|
||||
};
|
||||
}
|
||||
|
||||
auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
@@ -268,26 +230,13 @@ auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
gfx::drawRect(vg, m_top_bar, theme->elements[ThemeEntryID_TEXT].colour);
|
||||
gfx::drawRect(vg, m_bottom_bar, theme->elements[ThemeEntryID_TEXT].colour);
|
||||
|
||||
const auto dist = DistanceBetweenY(m_top_bar, m_bottom_bar);
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, dist.x, dist.y, dist.w, dist.h);
|
||||
Widget::Draw(vg, theme);
|
||||
|
||||
// for (std::size_t i = m_index_offset; i < m_items.size(); ++i) {
|
||||
// m_items[i]->Draw(vg, theme);
|
||||
// }
|
||||
|
||||
for (auto&p : m_items) {
|
||||
p->Draw(vg, theme);
|
||||
}
|
||||
|
||||
nvgRestore(vg);
|
||||
|
||||
// draw the buttons. fetch the actions from current item and insert into array.
|
||||
Actions draw_actions{m_actions};
|
||||
const auto& actions_ref = m_items[m_index]->GetActions();
|
||||
draw_actions.insert(actions_ref.cbegin(), actions_ref.cend());
|
||||
|
||||
gfx::drawButtons(vg, draw_actions, theme->elements[ThemeEntryID_TEXT].colour, m_pos.x + m_pos.w - 60.f);
|
||||
m_list->Draw(vg, theme, m_items.size(), [this](auto* vg, auto* theme, auto v, auto i) {
|
||||
const auto& [x, y, w, h] = v;
|
||||
m_items[i]->SetY(y);
|
||||
m_items[i]->Draw(vg, theme);
|
||||
});
|
||||
}
|
||||
|
||||
auto Sidebar::OnFocusGained() noexcept -> void {
|
||||
@@ -303,23 +252,56 @@ auto Sidebar::OnFocusLost() noexcept -> void {
|
||||
void Sidebar::Add(std::shared_ptr<SidebarEntryBase> entry) {
|
||||
m_items.emplace_back(entry);
|
||||
m_items.back()->SetPos(m_base_pos);
|
||||
m_base_pos.y += m_base_pos.h;
|
||||
|
||||
// for (auto&p : m_items) {
|
||||
// p->SetPos(base_pos);
|
||||
// m_base_pos.y += m_base_pos.h;
|
||||
// }
|
||||
|
||||
// give focus to first entry.
|
||||
if (m_items.size() == 1) {
|
||||
m_items[m_index]->OnFocusGained();
|
||||
SetupButtons();
|
||||
}
|
||||
}
|
||||
|
||||
void Sidebar::AddSpacer() {
|
||||
void Sidebar::SetIndex(s64 index) {
|
||||
// if we moved
|
||||
if (m_index != index) {
|
||||
m_items[m_index]->OnFocusLost();
|
||||
m_index = index;
|
||||
m_items[m_index]->OnFocusGained();
|
||||
|
||||
if (m_index > m_index_offset && m_index - m_index_offset >= 5) {
|
||||
m_index_offset = m_index - 5;
|
||||
}
|
||||
|
||||
SetupButtons();
|
||||
}
|
||||
}
|
||||
|
||||
void Sidebar::AddHeader(std::string name) {
|
||||
void Sidebar::SetupButtons() {
|
||||
RemoveActions();
|
||||
|
||||
// add entry actions
|
||||
for (const auto& [button, action] : m_items[m_index]->GetActions()) {
|
||||
SetAction(button, action);
|
||||
}
|
||||
|
||||
// add default actions, overriding if needed.
|
||||
this->SetActions(
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
auto index = m_index;
|
||||
if (m_list->ScrollDown(index, 1, m_items.size())) {
|
||||
SetIndex(index);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
auto index = m_index;
|
||||
if (m_list->ScrollUp(index, 1, m_items.size())) {
|
||||
SetIndex(index);
|
||||
}
|
||||
}}),
|
||||
// each item has it's own Action, but we take over B
|
||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||
SetPop();
|
||||
}})
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/nvg_util.hpp"
|
||||
#include "app.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
auto uiButton::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
// enable to see button region
|
||||
// gfx::drawRect(vg, m_pos, gfx::Colour::RED);
|
||||
|
||||
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
|
||||
nvgFillColor(vg, theme->elements[ThemeEntryID_TEXT].colour);
|
||||
nvgFontSize(vg, 20);
|
||||
nvgText(vg, m_hint_pos.x, m_hint_pos.y, m_action.m_hint.c_str(), nullptr);
|
||||
nvgFontSize(vg, 26);
|
||||
nvgText(vg, m_button_pos.x, m_button_pos.y, gfx::getButton(m_button), nullptr);
|
||||
}
|
||||
|
||||
void Widget::Update(Controller* controller, TouchInfo* touch) {
|
||||
for (const auto& [button, action] : m_actions) {
|
||||
if ((action.m_type & ActionType::DOWN) && controller->GotDown(button)) {
|
||||
@@ -11,26 +24,34 @@ void Widget::Update(Controller* controller, TouchInfo* touch) {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
}
|
||||
action.Invoke(true);
|
||||
break;
|
||||
}
|
||||
else if ((action.m_type & ActionType::UP) && controller->GotUp(button)) {
|
||||
action.Invoke(false);
|
||||
break;
|
||||
}
|
||||
else if ((action.m_type & ActionType::HELD) && controller->GotHeld(button)) {
|
||||
action.Invoke(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_actions = GetUiButtons();
|
||||
for (auto& e : draw_actions) {
|
||||
if (touch->is_clicked && touch->in_range(e.GetPos())) {
|
||||
log_write("got click: %s\n", e.m_action.m_hint.c_str());
|
||||
FireAction(e.m_button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::Draw(NVGcontext* vg, Theme* theme) {
|
||||
Actions draw_actions;
|
||||
auto draw_actions = GetUiButtons();
|
||||
|
||||
for (const auto& [button, action] : m_actions) {
|
||||
if (!action.IsHidden()) {
|
||||
draw_actions.emplace(button, action);
|
||||
for (auto& e : draw_actions) {
|
||||
e.Draw(vg, theme);
|
||||
}
|
||||
}
|
||||
|
||||
gfx::drawButtons(vg, draw_actions, theme->elements[ThemeEntryID_TEXT].colour);
|
||||
}
|
||||
|
||||
auto Widget::HasAction(Button button) const -> bool {
|
||||
@@ -47,4 +68,67 @@ void Widget::RemoveAction(Button button) {
|
||||
}
|
||||
}
|
||||
|
||||
auto Widget::FireAction(Button b, u8 type) -> bool {
|
||||
for (const auto& [button, action] : m_actions) {
|
||||
if (button == b && (action.m_type & type)) {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
action.Invoke(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Widget::GetUiButtons() const -> uiButtons {
|
||||
auto vg = App::GetVg();
|
||||
auto [x, y] = m_button_pos;
|
||||
|
||||
uiButtons draw_actions;
|
||||
draw_actions.reserve(m_actions.size());
|
||||
|
||||
// build array
|
||||
for (const auto& [button, action] : m_actions) {
|
||||
if (action.IsHidden() || action.m_hint.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uiButton ui_button{button, action};
|
||||
|
||||
// swap
|
||||
if (button == Button::R && draw_actions.size() && draw_actions.back().m_button == Button::L) {
|
||||
const auto s = draw_actions.back();
|
||||
draw_actions.back().m_button = button;
|
||||
draw_actions.back().m_action = action;
|
||||
draw_actions.emplace_back(s);
|
||||
} else {
|
||||
draw_actions.emplace_back(ui_button);
|
||||
}
|
||||
}
|
||||
|
||||
float bounds[4]{};
|
||||
for (auto& e : draw_actions) {
|
||||
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
|
||||
|
||||
nvgFontSize(vg, 20.f);
|
||||
nvgTextBounds(vg, x, y, e.m_action.m_hint.c_str(), nullptr, bounds);
|
||||
auto len = bounds[2] - bounds[0];
|
||||
e.m_hint_pos = {x, 675, len, 20};
|
||||
|
||||
x -= len + 8.f;
|
||||
nvgFontSize(vg, 26.f);
|
||||
nvgTextBounds(vg, x, y - 7.f, gfx::getButton(e.m_button), nullptr, bounds);
|
||||
len = bounds[2] - bounds[0];
|
||||
e.m_button_pos = {x, 675 - 4.f, len, 26};
|
||||
x -= len + 34.f;
|
||||
|
||||
e.SetPos(e.m_button_pos);
|
||||
e.SetX(e.GetX() - 40);
|
||||
e.SetW(e.m_hint_pos.x - e.m_button_pos.x + len + 25);
|
||||
e.SetY(e.GetY() - 18);
|
||||
e.SetH(26 + 18 * 2);
|
||||
}
|
||||
|
||||
return draw_actions;
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
Reference in New Issue
Block a user