add layout options to grid based menues.

This commit is contained in:
ITotalJustice
2025-05-11 02:39:03 +01:00
parent 5d9e24af31
commit e279a70606
18 changed files with 579 additions and 244 deletions

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ old_code
created_ncas created_ncas
assets/romfs/shaders assets/romfs/shaders
.vscode/settings.json .vscode/settings.json
.idea
info/ info/
romfs/shaders romfs/shaders
assets/unused assets/unused

View File

@@ -50,6 +50,7 @@ add_executable(sphaira
source/ui/menus/ftp_menu.cpp source/ui/menus/ftp_menu.cpp
source/ui/menus/gc_menu.cpp source/ui/menus/gc_menu.cpp
source/ui/menus/game_menu.cpp source/ui/menus/game_menu.cpp
source/ui/menus/grid_menu_base.cpp
source/ui/error_box.cpp source/ui/error_box.cpp
source/ui/notification.cpp source/ui/notification.cpp

View File

@@ -5,6 +5,11 @@
namespace sphaira::ui { namespace sphaira::ui {
struct List final : Object { struct List final : Object {
enum class Layout {
HOME,
GRID,
};
using Callback = std::function<void(NVGcontext* vg, Theme* theme, Vec4 v, s64 index)>; using Callback = std::function<void(NVGcontext* vg, Theme* theme, Vec4 v, s64 index)>;
using TouchCallback = std::function<void(bool touch, s64 index)>; using TouchCallback = std::function<void(bool touch, s64 index)>;
@@ -35,10 +40,36 @@ struct List final : Object {
return m_v.h + m_pad.y; return m_v.h + m_pad.y;
} }
auto GetMaxX() const {
return m_v.w + m_pad.x;
}
auto GetLayout() const {
return m_layout;
}
void SetLayout(Layout layout) {
m_layout = layout;
}
auto GetRow() const {
return m_row;
}
auto GetPage() const {
return m_page;
}
private: private:
auto Draw(NVGcontext* vg, Theme* theme) -> void override {} auto Draw(NVGcontext* vg, Theme* theme) -> void override {}
auto ClampX(float x, s64 count) const -> float;
auto ClampY(float y, s64 count) const -> float; auto ClampY(float y, s64 count) const -> float;
void OnUpdateHome(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
void OnUpdateGrid(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
void DrawHome(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
void DrawGrid(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
private: private:
const s64 m_row; const s64 m_row;
const s64 m_page; const s64 m_page;
@@ -52,6 +83,8 @@ private:
float m_yoff{}; float m_yoff{};
// in progress y offset, used when scrolling. // in progress y offset, used when scrolling.
float m_y_prog{}; float m_y_prog{};
Layout m_layout{Layout::GRID};
}; };
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -1,10 +1,11 @@
#pragma once #pragma once
#include "ui/menus/menu_base.hpp" #include "ui/menus/grid_menu_base.hpp"
#include "ui/scrollable_text.hpp" #include "ui/scrollable_text.hpp"
#include "ui/scrolling_text.hpp" #include "ui/scrolling_text.hpp"
#include "ui/list.hpp" #include "ui/list.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "option.hpp"
#include <span> #include <span>
namespace sphaira::ui::menu::appstore { namespace sphaira::ui::menu::appstore {
@@ -135,7 +136,9 @@ enum OrderType {
OrderType_Ascending, OrderType_Ascending,
}; };
struct Menu final : MenuBase { using LayoutType = grid::LayoutType;
struct Menu final : grid::Menu {
Menu(); Menu();
~Menu(); ~Menu();
@@ -144,19 +147,14 @@ struct Menu final : MenuBase {
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override; void OnFocusGained() override;
void SetIndex(s64 index);
void ScanHomebrew();
void Sort();
void SetFilter(Filter filter);
void SetSort(SortType sort);
void SetOrder(OrderType order);
void SetSearch(const std::string& term);
void SetAuthor(); void SetAuthor();
auto GetEntry(s64 i) -> Entry& {
return m_entries[m_entries_current[i]];
}
auto GetEntry() -> Entry& { auto GetEntry() -> Entry& {
return m_entries[m_entries_current[m_index]]; return GetEntry(m_index);
} }
auto SetDirty() { auto SetDirty() {
@@ -164,19 +162,27 @@ struct Menu final : MenuBase {
} }
private: private:
void SetIndex(s64 index);
void ScanHomebrew();
void Sort();
void SortAndFindLastFile();
void SetFilter();
void SetSearch(const std::string& term);
void OnLayoutChange();
private:
static constexpr inline const char* INI_SECTION = "appstore";
std::vector<Entry> m_entries{}; std::vector<Entry> m_entries{};
std::vector<EntryMini> m_entries_index[Filter_MAX]{}; std::vector<EntryMini> m_entries_index[Filter_MAX]{};
std::vector<EntryMini> m_entries_index_author{}; std::vector<EntryMini> m_entries_index_author{};
std::vector<EntryMini> m_entries_index_search{}; std::vector<EntryMini> m_entries_index_search{};
std::span<EntryMini> m_entries_current{}; std::span<EntryMini> m_entries_current{};
ScrollingText m_scroll_name{}; option::OptionLong m_filter{INI_SECTION, "filter", Filter::Filter_All};
ScrollingText m_scroll_author{}; option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
ScrollingText m_scroll_version{}; option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
Filter m_filter{Filter::Filter_All};
SortType m_sort{SortType::SortType_Updated};
OrderType m_order{OrderType::OrderType_Descending};
s64 m_index{}; // where i am in the array s64 m_index{}; // where i am in the array
LazyImage m_default_image{}; LazyImage m_default_image{};

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "ui/menus/menu_base.hpp" #include "ui/menus/grid_menu_base.hpp"
#include "ui/scrolling_text.hpp"
#include "ui/list.hpp" #include "ui/list.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "option.hpp" #include "option.hpp"
@@ -51,7 +50,9 @@ enum OrderType {
OrderType_Ascending, OrderType_Ascending,
}; };
struct Menu final : MenuBase { using LayoutType = grid::LayoutType;
struct Menu final : grid::Menu {
Menu(); Menu();
~Menu(); ~Menu();
@@ -66,6 +67,7 @@ private:
void Sort(); void Sort();
void SortAndFindLastFile(bool scan); void SortAndFindLastFile(bool scan);
void FreeEntries(); void FreeEntries();
void OnLayoutChange();
private: private:
static constexpr inline const char* INI_SECTION = "games"; static constexpr inline const char* INI_SECTION = "games";
@@ -77,12 +79,9 @@ private:
bool m_is_reversed{}; bool m_is_reversed{};
bool m_dirty{}; bool m_dirty{};
ScrollingText m_scroll_name{};
ScrollingText m_scroll_author{};
ScrollingText m_scroll_version{};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated}; option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending}; option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
option::OptionBool m_hide_forwarders{INI_SECTION, "hide_forwarders", false}; option::OptionBool m_hide_forwarders{INI_SECTION, "hide_forwarders", false};
}; };

View File

@@ -0,0 +1,35 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "ui/scrolling_text.hpp"
#include "ui/list.hpp"
#include <string>
#include <memory>
namespace sphaira::ui::menu::grid {
enum LayoutType {
LayoutType_List,
LayoutType_Grid,
LayoutType_GridDetail,
};
struct Menu : MenuBase {
using MenuBase::MenuBase;
protected:
void OnLayoutChange(std::unique_ptr<List>& list, int layout);
void DrawEntry(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version);
// same as above but doesn't draw image and returns image dimension.
Vec4 DrawEntryNoImage(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, const char* name, const char* author, const char* version);
private:
Vec4 DrawEntry(NVGcontext* vg, Theme* theme, bool draw_image, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version);
private:
ScrollingText m_scroll_name{};
ScrollingText m_scroll_author{};
ScrollingText m_scroll_version{};
};
} // namespace sphaira::ui::menu::grid

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "ui/menus/menu_base.hpp" #include "ui/menus/grid_menu_base.hpp"
#include "ui/scrolling_text.hpp"
#include "ui/list.hpp" #include "ui/list.hpp"
#include "nro.hpp" #include "nro.hpp"
#include "fs.hpp" #include "fs.hpp"
@@ -23,7 +22,9 @@ enum OrderType {
OrderType_Ascending, OrderType_Ascending,
}; };
struct Menu final : MenuBase { using LayoutType = grid::LayoutType;
struct Menu final : grid::Menu {
Menu(); Menu();
~Menu(); ~Menu();
@@ -46,6 +47,7 @@ private:
void Sort(); void Sort();
void SortAndFindLastFile(); void SortAndFindLastFile();
void FreeEntries(); void FreeEntries();
void OnLayoutChange();
auto IsStarEnabled() -> bool { auto IsStarEnabled() -> bool {
return m_sort.Get() >= SortType_UpdatedStar; return m_sort.Get() >= SortType_UpdatedStar;
@@ -58,12 +60,9 @@ private:
s64 m_index{}; // where i am in the array s64 m_index{}; // where i am in the array
std::unique_ptr<List> m_list{}; std::unique_ptr<List> m_list{};
ScrollingText m_scroll_name{};
ScrollingText m_scroll_author{};
ScrollingText m_scroll_version{};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar}; option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending}; option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false}; option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};
}; };

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "ui/widget.hpp" #include "ui/widget.hpp"
#include "nro.hpp"
#include <string> #include <string>
namespace sphaira::ui::menu { namespace sphaira::ui::menu {

View File

@@ -2,6 +2,7 @@
#include "nanovg.h" #include "nanovg.h"
#include "ui/types.hpp" #include "ui/types.hpp"
#include "ui/scrolling_text.hpp"
namespace sphaira::ui::gfx { namespace sphaira::ui::gfx {
@@ -18,6 +19,9 @@ void drawRect(NVGcontext*, const Vec4& v, const NVGpaint& p, float rounding = 0.
void drawRectOutline(NVGcontext*, const Theme*, float size, float x, float y, float w, float h); void drawRectOutline(NVGcontext*, const Theme*, float size, float x, float y, float w, float h);
void drawRectOutline(NVGcontext*, const Theme*, float size, const Vec4& v); void drawRectOutline(NVGcontext*, const Theme*, float size, const Vec4& v);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p);
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c); void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c);
void drawText(NVGcontext*, float x, float y, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr); void drawText(NVGcontext*, float x, float y, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c); void drawText(NVGcontext*, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c);
@@ -35,6 +39,8 @@ void drawScrollbar(NVGcontext*, const Theme*, float x, float y, float h, u32 ind
void drawScrollbar2(NVGcontext*, const Theme*, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page); void drawScrollbar2(NVGcontext*, const Theme*, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page);
void drawScrollbar2(NVGcontext*, const Theme*, s64 index_off, s64 count, s64 row, s64 page); void drawScrollbar2(NVGcontext*, const Theme*, s64 index_off, s64 count, s64 row, s64 page);
void drawAppLable(NVGcontext* vg, const Theme*, ScrollingText& st, float x, float y, float w, const char* name);
void updateHighlightAnimation(); void updateHighlightAnimation();
void getHighlightAnimation(float* gradientX, float* gradientY, float* color); void getHighlightAnimation(float* gradientX, float* gradientY, float* color);

View File

@@ -9,6 +9,7 @@ struct ScrollingText final {
public: public:
void Draw(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const std::string& text_entry); void Draw(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const std::string& text_entry);
void DrawArgs(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const char* s, ...) __attribute__ ((format (printf, 10, 11))); void DrawArgs(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const char* s, ...) __attribute__ ((format (printf, 10, 11)));
void Reset(const std::string& text_entry = "");
private: private:
std::string m_str; std::string m_str;

View File

@@ -14,7 +14,7 @@ namespace sphaira {
#define SCREEN_WIDTH 1280.f #define SCREEN_WIDTH 1280.f
#define SCREEN_HEIGHT 720.f #define SCREEN_HEIGHT 720.f
struct [[nodiscard]] Vec2 { struct Vec2 {
constexpr Vec2() = default; constexpr Vec2() = default;
constexpr Vec2(float _x, float _y) : x{_x}, y{_y} {} constexpr Vec2(float _x, float _y) : x{_x}, y{_y} {}
@@ -53,7 +53,7 @@ struct [[nodiscard]] Vec2 {
float x{}, y{}; float x{}, y{};
}; };
struct [[nodiscard]] Vec4 { struct Vec4 {
constexpr Vec4() = default; constexpr Vec4() = default;
constexpr Vec4(float _x, float _y, float _w, float _h) : x{_x}, y{_y}, w{_w}, h{_h} {} constexpr Vec4(float _x, float _y, float _w, float _h) : x{_x}, y{_y}, w{_w}, h{_h} {}
constexpr Vec4(Vec2 vec0, Vec2 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.x}, h{vec1.y} {} constexpr Vec4(Vec2 vec0, Vec2 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.x}, h{vec1.y} {}

View File

@@ -14,6 +14,11 @@ List::List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad)
SetScrollBarPos(SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200); SetScrollBarPos(SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200);
} }
auto List::ClampX(float x, s64 count) const -> float {
const float x_max = count * GetMaxX();
return std::clamp(x, 0.F, x_max);
}
auto List::ClampY(float y, s64 count) const -> float { auto List::ClampY(float y, s64 count) const -> float {
float y_max = 0; float y_max = 0;
@@ -25,16 +30,140 @@ auto List::ClampY(float y, s64 count) const -> float {
y_max = (count - m_page) / m_row * GetMaxY(); y_max = (count - m_page) / m_row * GetMaxY();
} }
if (y < 0) { return std::clamp(y, 0.F, y_max);
y = 0;
} else if (y > y_max) {
y = y_max;
}
return y;
} }
void List::OnUpdate(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback) { void List::OnUpdate(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback) {
switch (m_layout) {
case Layout::HOME:
OnUpdateHome(controller, touch, index, count, callback);
break;
case Layout::GRID:
OnUpdateGrid(controller, touch, index, count, callback);
break;
}
}
void List::Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const {
switch (m_layout) {
case Layout::HOME:
DrawHome(vg, theme, count, callback);
break;
case Layout::GRID:
DrawGrid(vg, theme, count, callback);
break;
}
}
auto List::ScrollDown(s64& index, s64 step, s64 count) -> bool {
const auto old_index = index;
const auto max = m_layout == Layout::GRID ? GetMaxY() : GetMaxX();
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 / max * m_row;
while (index < start) {
start -= m_row;
m_yoff -= max;
}
if (index - start >= m_page) {
do {
start += m_row;
delta -= m_row;
m_yoff += max;
} 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;
const auto max = m_layout == Layout::GRID ? GetMaxY() : GetMaxX();
if (!count) {
return false;
}
if (index >= step) {
index -= step;
} else {
index = 0;
}
if (index != old_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
s64 start = m_yoff / max * m_row;
while (index < start) {
start -= m_row;
m_yoff -= max;
}
while (index - start >= m_page && start + m_page < count) {
start += m_row;
m_yoff += max;
}
return true;
}
return false;
}
void List::OnUpdateHome(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback) {
if (controller->GotDown(Button::RIGHT)) {
if (ScrollDown(index, m_row, count)) {
callback(false, index);
}
} else if (controller->GotDown(Button::LEFT)) {
if (ScrollUp(index, m_row, count)) {
callback(false, index);
}
} else if (touch->is_clicked && touch->in_range(GetPos())) {
auto v = m_v;
v.x -= ClampX(m_yoff + m_y_prog, count);
for (s64 i = 0; i < count; i++, v.x += v.w + m_pad.x) {
if (v.x > GetX() + GetW()) {
break;
}
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(true, i);
return;
}
}
} else if (touch->is_scroll && touch->in_range(GetPos())) {
m_y_prog = (float)touch->initial.x - (float)touch->cur.x;
} else if (touch->is_end) {
m_yoff = ClampX(m_yoff + m_y_prog, count);
m_y_prog = 0;
}
}
void List::OnUpdateGrid(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback) {
const auto page_up_button = m_row == 1 ? Button::DPAD_LEFT : Button::L2; const auto page_up_button = m_row == 1 ? Button::DPAD_LEFT : Button::L2;
const auto page_down_button = m_row == 1 ? Button::DPAD_RIGHT : Button::R2; const auto page_down_button = m_row == 1 ? Button::DPAD_RIGHT : Button::R2;
@@ -105,7 +234,26 @@ void List::OnUpdate(Controller* controller, TouchInfo* touch, s64 index, s64 cou
} }
} }
void List::Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const { void List::DrawHome(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const {
const auto yoff = ClampX(m_yoff + m_y_prog, count);
auto v = m_v;
v.x -= yoff;
nvgSave(vg);
nvgIntersectScissor(vg, GetX(), GetY(), GetW(), GetH());
for (s64 i = 0; i < count; i++, v.x += v.w + m_pad.x) {
if (v.x > GetX() + GetW()) {
break;
}
callback(vg, theme, v, i);
}
nvgRestore(vg);
}
void List::DrawGrid(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const {
const auto yoff = ClampY(m_yoff + m_y_prog, count); const auto yoff = ClampY(m_yoff + m_y_prog, count);
const s64 start = yoff / GetMaxY() * m_row; 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); gfx::drawScrollbar2(vg, theme, m_scrollbar.x, m_scrollbar.y, m_scrollbar.h, start, count, m_row, m_page);
@@ -114,7 +262,7 @@ void List::Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) cons
v.y -= yoff; v.y -= yoff;
nvgSave(vg); nvgSave(vg);
nvgScissor(vg, GetX(), GetY(), GetW(), GetH()); nvgIntersectScissor(vg, GetX(), GetY(), GetW(), GetH());
for (s64 i = 0; i < count; v.y += v.h + m_pad.y) { for (s64 i = 0; i < count; v.y += v.h + m_pad.y) {
if (v.y > GetY() + GetH()) { if (v.y > GetY() + GetH()) {
@@ -143,74 +291,4 @@ void List::Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) cons
nvgRestore(vg); 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 } // namespace sphaira::ui

View File

@@ -13,6 +13,7 @@
#include "yyjson_helper.hpp" #include "yyjson_helper.hpp"
#include "swkbd.hpp" #include "swkbd.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "nro.hpp"
#include <minIni.h> #include <minIni.h>
#include <string> #include <string>
@@ -34,8 +35,6 @@ constexpr auto URL_JSON = "https://switch.cdn.fortheusers.org/repo.json";
constexpr auto URL_POST_FEEDBACK = "http://switchbru.com/appstore/feedback"; constexpr auto URL_POST_FEEDBACK = "http://switchbru.com/appstore/feedback";
constexpr auto URL_GET_FEEDACK = "http://switchbru.com/appstore/feedback"; constexpr auto URL_GET_FEEDACK = "http://switchbru.com/appstore/feedback";
constexpr const char* INI_SECTION = "appstore";
constexpr const char* FILTER_STR[] = { constexpr const char* FILTER_STR[] = {
"All", "All",
"Games", "Games",
@@ -846,7 +845,7 @@ void EntryMenu::SetIndex(s64 index) {
} }
} }
Menu::Menu() : MenuBase{"AppStore"_i18n} { Menu::Menu() : grid::Menu{"AppStore"_i18n} {
fs::FsNativeSd fs; fs::FsNativeSd fs;
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/icons"); fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/icons");
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/banners"); fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/banners");
@@ -859,7 +858,7 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} {
if (m_is_search) { if (m_is_search) {
SetSearch(m_search_term); SetSearch(m_search_term);
} else { } else {
SetFilter(m_filter); SetFilter();
} }
SetIndex(m_entry_author_jump_back); SetIndex(m_entry_author_jump_back);
@@ -870,7 +869,7 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} {
} }
} else if (m_is_search) { } else if (m_is_search) {
m_is_search = false; m_is_search = false;
SetFilter(m_filter); SetFilter();
SetIndex(m_entry_search_jump_back); SetIndex(m_entry_search_jump_back);
if (m_entry_search_jump_back >= 9) { if (m_entry_search_jump_back >= 9) {
m_list->SetYoff(0); m_list->SetYoff(0);
@@ -913,17 +912,30 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} {
order_items.push_back("Descending"_i18n); order_items.push_back("Descending"_i18n);
order_items.push_back("Ascending"_i18n); order_items.push_back("Ascending"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this, filter_items](s64& index_out){ SidebarEntryArray::Items layout_items;
SetFilter((Filter)index_out); layout_items.push_back("List"_i18n);
}, (s64)m_filter)); layout_items.push_back("Icon"_i18n);
layout_items.push_back("Grid"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){ options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this](s64& index_out){
SetSort((SortType)index_out); m_filter.Set(index_out);
}, (s64)m_sort)); SetFilter();
}, m_filter.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){ options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](s64& index_out){
SetOrder((OrderType)index_out); m_sort.Set(index_out);
}, (s64)m_order)); SortAndFindLastFile();
}, m_sort.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this](s64& index_out){
m_order.Set(index_out);
SortAndFindLastFile();
}, m_order.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Layout"_i18n, layout_items, [this](s64& index_out){
m_layout.Set(index_out);
OnLayoutChange();
}, m_layout.Get()));
options->Add(std::make_shared<SidebarEntryCallback>("Search"_i18n, [this](){ options->Add(std::make_shared<SidebarEntryCallback>("Search"_i18n, [this](){
std::string out; std::string out;
@@ -953,14 +965,7 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} {
} }
}); });
m_filter = (Filter)ini_getl(INI_SECTION, "filter", m_filter, App::CONFIG_PATH); OnLayoutChange();
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();
} }
Menu::~Menu() { Menu::~Menu() {
@@ -1055,42 +1060,27 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} }
} }
auto text_id = ThemeEntryID_TEXT;
const auto selected = pos == m_index; const auto selected = pos == m_index;
if (selected) { const auto image_vec = DrawEntryNoImage(vg, theme, m_layout.Get(), v, selected, e.title.c_str(), e.author.c_str(), e.version.c_str());
text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, theme, 4.f, v);
} else {
DrawElement(x, y, w, h, ThemeEntryID_GRID);
}
constexpr double image_scale = 256.0 / 115.0; const auto image_scale = 256.0 / image_vec.w;
// const float image_size = 256 / image_scale; DrawIcon(vg, e.image, m_default_image, image_vec.x, image_vec.y, image_vec.w, image_vec.h, true, image_scale);
// const float image_size_h = 150 / image_scale;
DrawIcon(vg, e.image, m_default_image, x + 20, y + 20, 115, 115, true, image_scale);
// gfx::drawImage(vg, x + 20, y + 20, image_size, image_size_h, image.image ? image.image : m_default_image); // gfx::drawImage(vg, x + 20, y + 20, image_size, image_size_h, image.image ? image.image : m_default_image);
const auto text_off = 148; // todo: fix position on non-grid layout.
const auto text_x = x + text_off;
const auto text_clip_w = w - 30.f - text_off;
const float font_size = 18;
m_scroll_name.Draw(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.title.c_str());
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.author.c_str());
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.version.c_str());
float i_size = 22; float i_size = 22;
switch (e.status) { switch (e.status) {
case EntryStatus::Get: case EntryStatus::Get:
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_get.image, 15); gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_get.image, 0);
break; break;
case EntryStatus::Installed: case EntryStatus::Installed:
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_installed.image, 15); gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_installed.image, 0);
break; break;
case EntryStatus::Local: case EntryStatus::Local:
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_local.image, 15); gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_local.image, 0);
break; break;
case EntryStatus::Update: case EntryStatus::Update:
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_update.image, 15); gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_update.image, 0);
break; break;
} }
}); });
@@ -1126,12 +1116,16 @@ void Menu::OnFocusGained() {
for (u32 i = 0; i < m_entries_current.size(); i++) { for (u32 i = 0; i < m_entries_current.size(); i++) {
if (current_entry.name == m_entries[m_entries_current[i]].name) { if (current_entry.name == m_entries[m_entries_current[i]].name) {
SetIndex(i); const auto index = i;
if (i >= 9) { const auto row = m_list->GetRow();
m_list->SetYoff((((i - 9) + 3) / 3) * m_list->GetMaxY()); const auto page = m_list->GetPage();
// guesstimate where the position is
if (index >= page) {
m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY());
} else { } else {
m_list->SetYoff(0); m_list->SetYoff(0);
} }
SetIndex(i);
break; break;
} }
} }
@@ -1215,15 +1209,20 @@ void Menu::ScanHomebrew() {
index.shrink_to_fit(); index.shrink_to_fit();
} }
SetFilter(Filter_All); SetFilter();
SetIndex(0); SetIndex(0);
Sort();
} }
void Menu::Sort() { void Menu::Sort() {
// log_write("doing sort: size: %zu count: %zu\n", repo_json.size(), m_entries.size()); // log_write("doing sort: size: %zu count: %zu\n", repo_json.size(), m_entries.size());
const auto sort = m_sort.Get();
const auto order = m_order.Get();
const auto filter = m_filter.Get();
// returns true if lhs should be before rhs // returns true if lhs should be before rhs
const auto sorter = [this](EntryMini _lhs, EntryMini _rhs) -> bool { const auto sorter = [this, sort, order](EntryMini _lhs, EntryMini _rhs) -> bool {
const auto& lhs = m_entries[_lhs]; const auto& lhs = m_entries[_lhs];
const auto& rhs = m_entries[_rhs]; const auto& rhs = m_entries[_rhs];
@@ -1241,11 +1240,11 @@ void Menu::Sort() {
} else if (!(lhs.status == EntryStatus::Local) && rhs.status == EntryStatus::Local) { } else if (!(lhs.status == EntryStatus::Local) && rhs.status == EntryStatus::Local) {
return false; return false;
} else { } else {
switch (m_sort) { switch (sort) {
case SortType_Updated: { case SortType_Updated: {
if (lhs.updated_num == rhs.updated_num) { if (lhs.updated_num == rhs.updated_num) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else if (m_order == OrderType_Descending) { } else if (order == OrderType_Descending) {
return lhs.updated_num > rhs.updated_num; return lhs.updated_num > rhs.updated_num;
} else { } else {
return lhs.updated_num < rhs.updated_num; return lhs.updated_num < rhs.updated_num;
@@ -1254,7 +1253,7 @@ void Menu::Sort() {
case SortType_Downloads: { case SortType_Downloads: {
if (lhs.app_dls == rhs.app_dls) { if (lhs.app_dls == rhs.app_dls) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else if (m_order == OrderType_Descending) { } else if (order == OrderType_Descending) {
return lhs.app_dls > rhs.app_dls; return lhs.app_dls > rhs.app_dls;
} else { } else {
return lhs.app_dls < rhs.app_dls; return lhs.app_dls < rhs.app_dls;
@@ -1263,14 +1262,14 @@ void Menu::Sort() {
case SortType_Size: { case SortType_Size: {
if (lhs.extracted == rhs.extracted) { if (lhs.extracted == rhs.extracted) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else if (m_order == OrderType_Descending) { } else if (order == OrderType_Descending) {
return lhs.extracted > rhs.extracted; return lhs.extracted > rhs.extracted;
} else { } else {
return lhs.extracted < rhs.extracted; return lhs.extracted < rhs.extracted;
} }
} break; } break;
case SortType_Alphabetical: { case SortType_Alphabetical: {
if (m_order == OrderType_Descending) { if (order == OrderType_Descending) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else { } else {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) > 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) > 0;
@@ -1284,33 +1283,43 @@ void Menu::Sort() {
char subheader[128]{}; char subheader[128]{};
std::snprintf(subheader, sizeof(subheader), "Filter: %s | Sort: %s | Order: %s"_i18n.c_str(), i18n::get(FILTER_STR[m_filter]).c_str(), i18n::get(SORT_STR[m_sort]).c_str(), i18n::get(ORDER_STR[m_order]).c_str()); std::snprintf(subheader, sizeof(subheader), "Filter: %s | Sort: %s | Order: %s"_i18n.c_str(), i18n::get(FILTER_STR[filter]).c_str(), i18n::get(SORT_STR[sort]).c_str(), i18n::get(ORDER_STR[order]).c_str());
SetTitleSubHeading(subheader); SetTitleSubHeading(subheader);
std::sort(m_entries_current.begin(), m_entries_current.end(), sorter); std::sort(m_entries_current.begin(), m_entries_current.end(), sorter);
} }
void Menu::SetFilter(Filter filter) { void Menu::SortAndFindLastFile() {
const auto name = GetEntry().name;
Sort();
SetIndex(0);
s64 index = -1;
for (u64 i = 0; i < m_entries_current.size(); i++) {
if (name == GetEntry(i).name) {
index = i;
break;
}
}
if (index >= 0) {
const auto row = m_list->GetRow();
const auto page = m_list->GetPage();
// guesstimate where the position is
if (index >= page) {
m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY());
} else {
m_list->SetYoff(0);
}
SetIndex(index);
}
}
void Menu::SetFilter() {
m_is_search = false; m_is_search = false;
m_is_author = false; m_is_author = false;
m_filter = filter; m_entries_current = m_entries_index[m_filter.Get()];
m_entries_current = m_entries_index[m_filter];
ini_putl(INI_SECTION, "filter", m_filter, App::CONFIG_PATH);
SetIndex(0);
Sort();
}
void Menu::SetSort(SortType sort) {
m_sort = sort;
ini_putl(INI_SECTION, "sort", m_sort, App::CONFIG_PATH);
SetIndex(0);
Sort();
}
void Menu::SetOrder(OrderType order) {
m_order = order;
ini_putl(INI_SECTION, "order", m_order, App::CONFIG_PATH);
SetIndex(0); SetIndex(0);
Sort(); Sort();
} }
@@ -1359,6 +1368,11 @@ void Menu::SetAuthor() {
Sort(); Sort();
} }
void Menu::OnLayoutChange() {
m_index = 0;
grid::Menu::OnLayoutChange(m_list, m_layout.Get());
}
LazyImage::~LazyImage() { LazyImage::~LazyImage() {
if (image) { if (image) {
nvgDeleteImage(App::GetVg(), image); nvgDeleteImage(App::GetVg(), image);

View File

@@ -113,7 +113,7 @@ void LaunchEntry(const Entry& e) {
} // namespace } // namespace
Menu::Menu() : MenuBase{"Games"_i18n} { Menu::Menu() : grid::Menu{"Games"_i18n} {
this->SetActions( this->SetActions(
std::make_pair(Button::B, Action{"Back"_i18n, [this](){ std::make_pair(Button::B, Action{"Back"_i18n, [this](){
SetPop(); SetPop();
@@ -140,16 +140,26 @@ Menu::Menu() : MenuBase{"Games"_i18n} {
order_items.push_back("Descending"_i18n); order_items.push_back("Descending"_i18n);
order_items.push_back("Ascending"_i18n); order_items.push_back("Ascending"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){ SidebarEntryArray::Items layout_items;
layout_items.push_back("List"_i18n);
layout_items.push_back("Icon"_i18n);
layout_items.push_back("Grid"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](s64& index_out){
m_sort.Set(index_out); m_sort.Set(index_out);
SortAndFindLastFile(false); SortAndFindLastFile(false);
}, m_sort.Get())); }, m_sort.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){ options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this](s64& index_out){
m_order.Set(index_out); m_order.Set(index_out);
SortAndFindLastFile(false); SortAndFindLastFile(false);
}, m_order.Get())); }, m_order.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Layout"_i18n, layout_items, [this](s64& index_out){
m_layout.Set(index_out);
OnLayoutChange();
}, m_layout.Get()));
options->Add(std::make_shared<SidebarEntryBool>("Hide forwarders"_i18n, m_hide_forwarders.Get(), [this](bool& v_out){ options->Add(std::make_shared<SidebarEntryBool>("Hide forwarders"_i18n, m_hide_forwarders.Get(), [this](bool& v_out){
m_hide_forwarders.Set(v_out); m_hide_forwarders.Set(v_out);
m_dirty = true; m_dirty = true;
@@ -241,9 +251,7 @@ Menu::Menu() : MenuBase{"Games"_i18n} {
}}) }})
); );
const Vec4 v{75, 110, 370, 155}; OnLayoutChange();
const Vec2 pad{10, 10};
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
nsInitialize(); nsInitialize();
nsGetApplicationRecordUpdateSystemEvent(&m_event); nsGetApplicationRecordUpdateSystemEvent(&m_event);
@@ -285,7 +293,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
int image_load_count = 0; int image_load_count = 0;
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) { 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; // const auto& [x, y, w, h] = v;
auto& e = m_entries[pos]; auto& e = m_entries[pos];
if (e.status == NacpLoadStatus::None) { if (e.status == NacpLoadStatus::None) {
@@ -299,25 +307,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} }
} }
auto text_id = ThemeEntryID_TEXT;
const auto selected = pos == m_index; const auto selected = pos == m_index;
if (selected) { DrawEntry(vg, theme, m_layout.Get(), v, selected, e.image, e.GetName(), e.GetAuthor(), e.GetDisplayVersion());
text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, theme, 4.f, v);
} else {
DrawElement(v, ThemeEntryID_GRID);
}
const float image_size = 115;
gfx::drawImage(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage(), 5);
const auto text_off = 148;
const auto text_x = x + text_off;
const auto text_clip_w = w - 30.f - text_off;
const float font_size = 18;
m_scroll_name.Draw(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetName());
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetAuthor());
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetDisplayVersion());
}); });
} }
@@ -446,9 +437,11 @@ void Menu::SortAndFindLastFile(bool scan) {
} }
if (index >= 0) { if (index >= 0) {
const auto row = m_list->GetRow();
const auto page = m_list->GetPage();
// guesstimate where the position is // guesstimate where the position is
if (index >= 9) { if (index >= page) {
m_list->SetYoff((((index - 9) + 3) / 3) * m_list->GetMaxY()); m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY());
} else { } else {
m_list->SetYoff(0); m_list->SetYoff(0);
} }
@@ -466,4 +459,9 @@ void Menu::FreeEntries() {
m_entries.clear(); m_entries.clear();
} }
void Menu::OnLayoutChange() {
m_index = 0;
grid::Menu::OnLayoutChange(m_list, m_layout.Get());
}
} // namespace sphaira::ui::menu::game } // namespace sphaira::ui::menu::game

View File

@@ -0,0 +1,81 @@
#include "app.hpp"
#include "ui/menus/grid_menu_base.hpp"
#include "ui/nvg_util.hpp"
namespace sphaira::ui::menu::grid {
void Menu::DrawEntry(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version) {
DrawEntry(vg, theme, true, layout, v, selected, image, name, author, version);
}
Vec4 Menu::DrawEntryNoImage(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, const char* name, const char* author, const char* version) {
return DrawEntry(vg, theme, false, layout, v, selected, 0, name, author, version);
}
Vec4 Menu::DrawEntry(NVGcontext* vg, Theme* theme, bool draw_image, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version) {
const auto& [x, y, w, h] = v;
auto text_id = ThemeEntryID_TEXT;
if (selected) {
text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, theme, 4.f, v);
} else {
DrawElement(v, ThemeEntryID_GRID);
}
Vec4 image_v = v;
if (layout == LayoutType_GridDetail) {
image_v.x += 20;
image_v.y += 20;
image_v.w = 115;
image_v.h = 115;
const auto text_off = 148;
const auto text_x = x + text_off;
const auto text_clip_w = w - 30.f - text_off;
const float font_size = 18;
m_scroll_name.Draw(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), name);
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), author);
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), version);
} else {
if (selected) {
gfx::drawAppLable(vg, theme, m_scroll_name, x, y, w, name);
}
}
if (draw_image) {
gfx::drawImage(vg, image_v, image ?: App::GetDefaultImage(), 5);
}
return image_v;
}
void Menu::OnLayoutChange(std::unique_ptr<List>& list, int layout) {
m_scroll_name.Reset();
m_scroll_author.Reset();
m_scroll_version.Reset();
switch (layout) {
case LayoutType_List: {
const Vec2 pad{14, 14};
const Vec4 v{106, 194, 256, 256};
list = std::make_unique<List>(1, 4, m_pos, v, pad);
list->SetLayout(List::Layout::HOME);
} break;
case LayoutType_Grid: {
const Vec2 pad{10, 10};
const Vec4 v{93, 186, 174, 174};
list = std::make_unique<List>(6, 6*2, m_pos, v, pad);
} break;
case LayoutType_GridDetail: {
const Vec2 pad{10, 10};
const Vec4 v{75, 110, 370, 155};
list = std::make_unique<List>(3, 3*3, m_pos, v, pad);
} break;
}
}
} // namespace sphaira::ui::menu::grid

View File

@@ -31,7 +31,7 @@ void FreeEntry(NVGcontext* vg, NroEntry& e) {
} // namespace } // namespace
Menu::Menu() : MenuBase{"Homebrew"_i18n} { Menu::Menu() : grid::Menu{"Homebrew"_i18n} {
this->SetActions( this->SetActions(
std::make_pair(Button::A, Action{"Launch"_i18n, [this](){ std::make_pair(Button::A, Action{"Launch"_i18n, [this](){
nro_launch(m_entries[m_index].path); nro_launch(m_entries[m_index].path);
@@ -57,6 +57,11 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
order_items.push_back("Descending"_i18n); order_items.push_back("Descending"_i18n);
order_items.push_back("Ascending"_i18n); order_items.push_back("Ascending"_i18n);
SidebarEntryArray::Items layout_items;
layout_items.push_back("List"_i18n);
layout_items.push_back("Icon"_i18n);
layout_items.push_back("Grid"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){ options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
m_sort.Set(index_out); m_sort.Set(index_out);
SortAndFindLastFile(); SortAndFindLastFile();
@@ -67,6 +72,11 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
SortAndFindLastFile(); SortAndFindLastFile();
}, m_order.Get())); }, m_order.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Layout"_i18n, layout_items, [this](s64& index_out){
m_layout.Set(index_out);
OnLayoutChange();
}, m_layout.Get()));
options->Add(std::make_shared<SidebarEntryBool>("Hide Sphaira"_i18n, m_hide_sphaira.Get(), [this](bool& enable){ options->Add(std::make_shared<SidebarEntryBool>("Hide Sphaira"_i18n, m_hide_sphaira.Get(), [this](bool& enable){
m_hide_sphaira.Set(enable); m_hide_sphaira.Set(enable);
})); }));
@@ -114,9 +124,7 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
}}) }})
); );
const Vec4 v{75, 110, 370, 155}; OnLayoutChange();
const Vec2 pad{10, 10};
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
} }
Menu::~Menu() { Menu::~Menu() {
@@ -143,7 +151,6 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
int image_load_count = 0; int image_load_count = 0;
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) { 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]; auto& e = m_entries[pos];
// lazy load image // lazy load image
@@ -161,21 +168,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} }
} }
auto text_id = ThemeEntryID_TEXT;
const auto selected = pos == m_index;
if (selected) {
text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, theme, 4.f, v);
} else {
DrawElement(v, ThemeEntryID_GRID);
}
const float image_size = 115;
gfx::drawImage(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage(), 5);
const auto text_off = 148;
const auto text_x = x + text_off;
const auto text_clip_w = w - 30.f - text_off;
bool has_star = false; bool has_star = false;
if (IsStarEnabled()) { if (IsStarEnabled()) {
if (!e.has_star.has_value()) { if (!e.has_star.has_value()) {
@@ -184,10 +177,15 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
has_star = e.has_star.value(); has_star = e.has_star.value();
} }
const float font_size = 18; std::string name;
m_scroll_name.DrawArgs(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), "%s%s", has_star ? "\u2605 " : "", e.GetName()); if (has_star) {
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetAuthor()); name = std::string("\u2605 ") + e.GetName();
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetDisplayVersion()); } else {
name = e.GetName();
}
const auto selected = pos == m_index;
DrawEntry(vg, theme, m_layout.Get(), v, selected, e.image, name.c_str(), e.GetAuthor(), e.GetDisplayVersion());
}); });
} }
@@ -386,9 +384,11 @@ void Menu::SortAndFindLastFile() {
} }
if (index >= 0) { if (index >= 0) {
const auto row = m_list->GetRow();
const auto page = m_list->GetPage();
// guesstimate where the position is // guesstimate where the position is
if (index >= 9) { if (index >= page) {
m_list->SetYoff((((index - 9) + 3) / 3) * m_list->GetMaxY()); m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY());
} else { } else {
m_list->SetYoff(0); m_list->SetYoff(0);
} }
@@ -406,6 +406,11 @@ void Menu::FreeEntries() {
m_entries.clear(); m_entries.clear();
} }
void Menu::OnLayoutChange() {
m_index = 0;
grid::Menu::OnLayoutChange(m_list, m_layout.Get());
}
Result Menu::InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon) { Result Menu::InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon) {
OwoConfig config{}; OwoConfig config{};
config.nro_path = path.toString(); config.nro_path = path.toString();

View File

@@ -171,6 +171,24 @@ void drawTextIntenal(NVGcontext* vg, const Vec2& v, float size, const char* str,
nvgText(vg, v.x, v.y, str, end); nvgText(vg, v.x, v.y, str, end);
} }
void drawTriangleInternal(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c) {
nvgBeginPath(vg);
nvgMoveTo(vg, aX, aY);
nvgLineTo(vg, bX, bY);
nvgLineTo(vg, cX, cY);
nvgFillColor(vg, c);
nvgFill(vg);
}
void drawTriangleInternal(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p) {
nvgBeginPath(vg);
nvgMoveTo(vg, aX, aY);
nvgLineTo(vg, bX, bY);
nvgLineTo(vg, cX, cY);
nvgFillPaint(vg, p);
nvgFill(vg);
}
} // namespace } // namespace
const char* getButton(const Button want) { const char* getButton(const Button want) {
@@ -309,6 +327,63 @@ void drawScrollbar2(NVGcontext* vg, const Theme* theme, s64 index_off, s64 count
drawScrollbar2(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, row, page); drawScrollbar2(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, row, page);
} }
void drawTriangle(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c) {
drawTriangleInternal(vg, aX, aY, bX, bY, cX, cY, c);
}
void drawTriangle(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p) {
drawTriangleInternal(vg, aX, aY, bX, bY, cX, cY, p);
}
void drawAppLable(NVGcontext* vg, const Theme* theme, ScrollingText& st, float x, float y, float w, const char* name) {
// todo: no more 5am code
const float max_box_w = 392.f;
const float box_h = 48.f;
// used for adjusting the position of the box.
const float clip_pad = 25.f;
const float clip_left = clip_pad;
const float clip_right = 1220.f - clip_pad;
const float text_pad = 25.f;
const float font_size = 22.f;
nvgTextAlign(vg, NVG_ALIGN_LEFT);
nvgFontSize(vg, font_size);
float bounds[4]{};
nvgTextBounds(vg, 0, 0, name, NULL, bounds);
const float trinaglex = x + (w / 2.f) - 9.f;
const float trinagley = y - 14.f;
const float center_x = x + (w / 2.f);
const float y_offset = y - 62.f; // top of box
const float text_width = bounds[2];
float box_w = text_width + text_pad * 2;
if (box_w > max_box_w) {
box_w = max_box_w;
}
float box_x = center_x - (box_w / 2.f);
if (box_x < clip_left) {
box_x = clip_left;
}
if ((box_x + box_w) > clip_right) {
// box_x -= ((box_x + box_w) - clip_right) / 2;
box_x = (clip_right - box_w);
}
const float text_x = box_x + text_pad;
const float text_y = y_offset + (box_h / 2.f);
drawRect(vg, {x-4, y-4, w+8, w+8}, theme->GetColour(ThemeEntryID_GRID));
nvgBeginPath(vg);
nvgRoundedRect(vg, box_x, y_offset, box_w, box_h, 3.f);
nvgFillColor(vg, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND));
nvgFill(vg);
drawTriangle(vg, trinaglex, trinagley, trinaglex + 18.f, trinagley, trinaglex + 9.f, trinagley + 12.f, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND));
st.Draw(vg, true, text_x, text_y, box_w - text_pad * 2, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_SELECTED), name);
}
#define HIGHLIGHT_SPEED 350.0 #define HIGHLIGHT_SPEED 350.0
static double highlightGradientX = 0; static double highlightGradientX = 0;

View File

@@ -32,9 +32,7 @@ void ScrollingText::Draw(NVGcontext* vg, bool focus, float x, float y, float w,
} }
if (m_str != text_entry) { if (m_str != text_entry) {
m_str = text_entry; Reset(text_entry);
m_tick = 0;
m_text_xoff = 0;
} }
float bounds[4]; float bounds[4];
@@ -78,4 +76,10 @@ void ScrollingText::DrawArgs(NVGcontext* vg, bool focus, float x, float y, float
Draw(vg, focus, x, y, w, size, align, colour, buffer); Draw(vg, focus, x, y, w, size, align, colour, buffer);
} }
void ScrollingText::Reset(const std::string& text_entry) {
m_str = text_entry;
m_tick = 0;
m_text_xoff = 0;
}
} // namespace sphaira::ui } // namespace sphaira::ui