add layout options to grid based menues.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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{};
|
||||||
|
|||||||
@@ -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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
35
sphaira/include/ui/menus/grid_menu_base.hpp
Normal file
35
sphaira/include/ui/menus/grid_menu_base.hpp
Normal 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
|
||||||
@@ -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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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} {}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
81
sphaira/source/ui/menus/grid_menu_base.cpp
Normal file
81
sphaira/source/ui/menus/grid_menu_base.cpp
Normal 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
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user