diff --git a/.gitignore b/.gitignore index 1e73ffe..9dcaad8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ old_code created_ncas assets/romfs/shaders .vscode/settings.json +.idea info/ romfs/shaders assets/unused diff --git a/sphaira/CMakeLists.txt b/sphaira/CMakeLists.txt index b41159c..362a196 100644 --- a/sphaira/CMakeLists.txt +++ b/sphaira/CMakeLists.txt @@ -50,6 +50,7 @@ add_executable(sphaira source/ui/menus/ftp_menu.cpp source/ui/menus/gc_menu.cpp source/ui/menus/game_menu.cpp + source/ui/menus/grid_menu_base.cpp source/ui/error_box.cpp source/ui/notification.cpp diff --git a/sphaira/include/ui/list.hpp b/sphaira/include/ui/list.hpp index 088f9e5..66c11a6 100644 --- a/sphaira/include/ui/list.hpp +++ b/sphaira/include/ui/list.hpp @@ -5,6 +5,11 @@ namespace sphaira::ui { struct List final : Object { + enum class Layout { + HOME, + GRID, + }; + using Callback = std::function; using TouchCallback = std::function; @@ -35,10 +40,36 @@ struct List final : Object { 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: auto Draw(NVGcontext* vg, Theme* theme) -> void override {} + auto ClampX(float x, 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: const s64 m_row; const s64 m_page; @@ -52,6 +83,8 @@ private: float m_yoff{}; // in progress y offset, used when scrolling. float m_y_prog{}; + + Layout m_layout{Layout::GRID}; }; } // namespace sphaira::ui diff --git a/sphaira/include/ui/menus/appstore.hpp b/sphaira/include/ui/menus/appstore.hpp index 7f4085c..d5b6081 100644 --- a/sphaira/include/ui/menus/appstore.hpp +++ b/sphaira/include/ui/menus/appstore.hpp @@ -1,10 +1,11 @@ #pragma once -#include "ui/menus/menu_base.hpp" +#include "ui/menus/grid_menu_base.hpp" #include "ui/scrollable_text.hpp" #include "ui/scrolling_text.hpp" #include "ui/list.hpp" #include "fs.hpp" +#include "option.hpp" #include namespace sphaira::ui::menu::appstore { @@ -135,7 +136,9 @@ enum OrderType { OrderType_Ascending, }; -struct Menu final : MenuBase { +using LayoutType = grid::LayoutType; + +struct Menu final : grid::Menu { Menu(); ~Menu(); @@ -144,19 +147,14 @@ struct Menu final : MenuBase { void Draw(NVGcontext* vg, Theme* theme) 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(); + auto GetEntry(s64 i) -> Entry& { + return m_entries[m_entries_current[i]]; + } + auto GetEntry() -> Entry& { - return m_entries[m_entries_current[m_index]]; + return GetEntry(m_index); } auto SetDirty() { @@ -164,19 +162,27 @@ struct Menu final : MenuBase { } 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 m_entries{}; std::vector m_entries_index[Filter_MAX]{}; std::vector m_entries_index_author{}; std::vector m_entries_index_search{}; std::span m_entries_current{}; - ScrollingText m_scroll_name{}; - ScrollingText m_scroll_author{}; - ScrollingText m_scroll_version{}; - - Filter m_filter{Filter::Filter_All}; - SortType m_sort{SortType::SortType_Updated}; - OrderType m_order{OrderType::OrderType_Descending}; + option::OptionLong m_filter{INI_SECTION, "filter", Filter::Filter_All}; + option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated}; + option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending}; + option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail}; s64 m_index{}; // where i am in the array LazyImage m_default_image{}; diff --git a/sphaira/include/ui/menus/game_menu.hpp b/sphaira/include/ui/menus/game_menu.hpp index 9fd7340..0ed3921 100644 --- a/sphaira/include/ui/menus/game_menu.hpp +++ b/sphaira/include/ui/menus/game_menu.hpp @@ -1,7 +1,6 @@ #pragma once -#include "ui/menus/menu_base.hpp" -#include "ui/scrolling_text.hpp" +#include "ui/menus/grid_menu_base.hpp" #include "ui/list.hpp" #include "fs.hpp" #include "option.hpp" @@ -51,7 +50,9 @@ enum OrderType { OrderType_Ascending, }; -struct Menu final : MenuBase { +using LayoutType = grid::LayoutType; + +struct Menu final : grid::Menu { Menu(); ~Menu(); @@ -66,6 +67,7 @@ private: void Sort(); void SortAndFindLastFile(bool scan); void FreeEntries(); + void OnLayoutChange(); private: static constexpr inline const char* INI_SECTION = "games"; @@ -77,12 +79,9 @@ private: bool m_is_reversed{}; 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_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}; }; diff --git a/sphaira/include/ui/menus/grid_menu_base.hpp b/sphaira/include/ui/menus/grid_menu_base.hpp new file mode 100644 index 0000000..aecf7a0 --- /dev/null +++ b/sphaira/include/ui/menus/grid_menu_base.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "ui/menus/menu_base.hpp" +#include "ui/scrolling_text.hpp" +#include "ui/list.hpp" +#include +#include + +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, 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 diff --git a/sphaira/include/ui/menus/homebrew.hpp b/sphaira/include/ui/menus/homebrew.hpp index 17aecfa..777edf1 100644 --- a/sphaira/include/ui/menus/homebrew.hpp +++ b/sphaira/include/ui/menus/homebrew.hpp @@ -1,7 +1,6 @@ #pragma once -#include "ui/menus/menu_base.hpp" -#include "ui/scrolling_text.hpp" +#include "ui/menus/grid_menu_base.hpp" #include "ui/list.hpp" #include "nro.hpp" #include "fs.hpp" @@ -23,7 +22,9 @@ enum OrderType { OrderType_Ascending, }; -struct Menu final : MenuBase { +using LayoutType = grid::LayoutType; + +struct Menu final : grid::Menu { Menu(); ~Menu(); @@ -46,6 +47,7 @@ private: void Sort(); void SortAndFindLastFile(); void FreeEntries(); + void OnLayoutChange(); auto IsStarEnabled() -> bool { return m_sort.Get() >= SortType_UpdatedStar; @@ -58,12 +60,9 @@ private: s64 m_index{}; // where i am in the array std::unique_ptr 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_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}; }; diff --git a/sphaira/include/ui/menus/menu_base.hpp b/sphaira/include/ui/menus/menu_base.hpp index 20a044f..d56f91e 100644 --- a/sphaira/include/ui/menus/menu_base.hpp +++ b/sphaira/include/ui/menus/menu_base.hpp @@ -1,7 +1,6 @@ #pragma once #include "ui/widget.hpp" -#include "nro.hpp" #include namespace sphaira::ui::menu { diff --git a/sphaira/include/ui/nvg_util.hpp b/sphaira/include/ui/nvg_util.hpp index 0be7260..f15dc95 100644 --- a/sphaira/include/ui/nvg_util.hpp +++ b/sphaira/include/ui/nvg_util.hpp @@ -2,6 +2,7 @@ #include "nanovg.h" #include "ui/types.hpp" +#include "ui/scrolling_text.hpp" 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, 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 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); @@ -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*, 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 getHighlightAnimation(float* gradientX, float* gradientY, float* color); diff --git a/sphaira/include/ui/scrolling_text.hpp b/sphaira/include/ui/scrolling_text.hpp index ec526da..af00f3b 100644 --- a/sphaira/include/ui/scrolling_text.hpp +++ b/sphaira/include/ui/scrolling_text.hpp @@ -9,6 +9,7 @@ struct ScrollingText final { 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 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: std::string m_str; diff --git a/sphaira/include/ui/types.hpp b/sphaira/include/ui/types.hpp index 3e89d09..af6fc0e 100644 --- a/sphaira/include/ui/types.hpp +++ b/sphaira/include/ui/types.hpp @@ -14,7 +14,7 @@ namespace sphaira { #define SCREEN_WIDTH 1280.f #define SCREEN_HEIGHT 720.f -struct [[nodiscard]] Vec2 { +struct Vec2 { constexpr Vec2() = default; constexpr Vec2(float _x, float _y) : x{_x}, y{_y} {} @@ -53,7 +53,7 @@ struct [[nodiscard]] Vec2 { float x{}, y{}; }; -struct [[nodiscard]] Vec4 { +struct Vec4 { constexpr Vec4() = default; constexpr Vec4(float _x, float _y, float _w, float _h) : x{_x}, y{_y}, w{_w}, h{_h} {} constexpr Vec4(Vec2 vec0, Vec2 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.x}, h{vec1.y} {} diff --git a/sphaira/source/ui/list.cpp b/sphaira/source/ui/list.cpp index 3bd480f..1eae425 100644 --- a/sphaira/source/ui/list.cpp +++ b/sphaira/source/ui/list.cpp @@ -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); } +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 { 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(); } - if (y < 0) { - y = 0; - } else if (y > y_max) { - y = y_max; - } - - return y; + return std::clamp(y, 0.F, y_max); } 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_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 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); @@ -114,7 +262,7 @@ void List::Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) cons v.y -= yoff; 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) { if (v.y > GetY() + GetH()) { @@ -143,74 +291,4 @@ void List::Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) cons 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 diff --git a/sphaira/source/ui/menus/appstore.cpp b/sphaira/source/ui/menus/appstore.cpp index e8928dd..d6679cb 100644 --- a/sphaira/source/ui/menus/appstore.cpp +++ b/sphaira/source/ui/menus/appstore.cpp @@ -13,6 +13,7 @@ #include "yyjson_helper.hpp" #include "swkbd.hpp" #include "i18n.hpp" +#include "nro.hpp" #include #include @@ -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_GET_FEEDACK = "http://switchbru.com/appstore/feedback"; -constexpr const char* INI_SECTION = "appstore"; - constexpr const char* FILTER_STR[] = { "All", "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.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/icons"); fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/banners"); @@ -859,7 +858,7 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} { if (m_is_search) { SetSearch(m_search_term); } else { - SetFilter(m_filter); + SetFilter(); } SetIndex(m_entry_author_jump_back); @@ -870,7 +869,7 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} { } } else if (m_is_search) { m_is_search = false; - SetFilter(m_filter); + SetFilter(); SetIndex(m_entry_search_jump_back); if (m_entry_search_jump_back >= 9) { m_list->SetYoff(0); @@ -913,17 +912,30 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} { order_items.push_back("Descending"_i18n); order_items.push_back("Ascending"_i18n); - options->Add(std::make_shared("Filter"_i18n, filter_items, [this, filter_items](s64& index_out){ - SetFilter((Filter)index_out); - }, (s64)m_filter)); + 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("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){ - SetSort((SortType)index_out); - }, (s64)m_sort)); + options->Add(std::make_shared("Filter"_i18n, filter_items, [this](s64& index_out){ + m_filter.Set(index_out); + SetFilter(); + }, m_filter.Get())); - options->Add(std::make_shared("Order"_i18n, order_items, [this, order_items](s64& index_out){ - SetOrder((OrderType)index_out); - }, (s64)m_order)); + options->Add(std::make_shared("Sort"_i18n, sort_items, [this](s64& index_out){ + m_sort.Set(index_out); + SortAndFindLastFile(); + }, m_sort.Get())); + + options->Add(std::make_shared("Order"_i18n, order_items, [this](s64& index_out){ + m_order.Set(index_out); + SortAndFindLastFile(); + }, m_order.Get())); + + options->Add(std::make_shared("Layout"_i18n, layout_items, [this](s64& index_out){ + m_layout.Set(index_out); + OnLayoutChange(); + }, m_layout.Get())); options->Add(std::make_shared("Search"_i18n, [this](){ 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); - 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(3, 9, m_pos, v, pad); - Sort(); + OnLayoutChange(); } Menu::~Menu() { @@ -1055,42 +1060,27 @@ 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(x, y, w, h, ThemeEntryID_GRID); - } + const auto image_vec = DrawEntryNoImage(vg, theme, m_layout.Get(), v, selected, e.title.c_str(), e.author.c_str(), e.version.c_str()); - constexpr double image_scale = 256.0 / 115.0; - // const float image_size = 256 / 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); + const auto image_scale = 256.0 / image_vec.w; + DrawIcon(vg, e.image, m_default_image, image_vec.x, image_vec.y, image_vec.w, image_vec.h, true, image_scale); // gfx::drawImage(vg, x + 20, y + 20, image_size, image_size_h, image.image ? image.image : m_default_image); - 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.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()); - + // todo: fix position on non-grid layout. float i_size = 22; switch (e.status) { 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; 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; 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; 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; } }); @@ -1126,12 +1116,16 @@ void Menu::OnFocusGained() { for (u32 i = 0; i < m_entries_current.size(); i++) { if (current_entry.name == m_entries[m_entries_current[i]].name) { - SetIndex(i); - if (i >= 9) { - m_list->SetYoff((((i - 9) + 3) / 3) * m_list->GetMaxY()); + const auto index = i; + 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(i); break; } } @@ -1215,15 +1209,20 @@ void Menu::ScanHomebrew() { index.shrink_to_fit(); } - SetFilter(Filter_All); + SetFilter(); SetIndex(0); + Sort(); } void Menu::Sort() { // 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 - 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& rhs = m_entries[_rhs]; @@ -1241,11 +1240,11 @@ void Menu::Sort() { } else if (!(lhs.status == EntryStatus::Local) && rhs.status == EntryStatus::Local) { return false; } else { - switch (m_sort) { + switch (sort) { case SortType_Updated: { if (lhs.updated_num == rhs.updated_num) { 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; } else { return lhs.updated_num < rhs.updated_num; @@ -1254,7 +1253,7 @@ void Menu::Sort() { case SortType_Downloads: { if (lhs.app_dls == rhs.app_dls) { 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; } else { return lhs.app_dls < rhs.app_dls; @@ -1263,14 +1262,14 @@ void Menu::Sort() { case SortType_Size: { if (lhs.extracted == rhs.extracted) { 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; } else { return lhs.extracted < rhs.extracted; } } break; case SortType_Alphabetical: { - if (m_order == OrderType_Descending) { + if (order == OrderType_Descending) { return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; } else { return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) > 0; @@ -1284,33 +1283,43 @@ void Menu::Sort() { 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); 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_author = false; - m_filter = filter; - 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); + m_entries_current = m_entries_index[m_filter.Get()]; SetIndex(0); Sort(); } @@ -1359,6 +1368,11 @@ void Menu::SetAuthor() { Sort(); } +void Menu::OnLayoutChange() { + m_index = 0; + grid::Menu::OnLayoutChange(m_list, m_layout.Get()); +} + LazyImage::~LazyImage() { if (image) { nvgDeleteImage(App::GetVg(), image); diff --git a/sphaira/source/ui/menus/game_menu.cpp b/sphaira/source/ui/menus/game_menu.cpp index 0a8243f..0f1ea35 100644 --- a/sphaira/source/ui/menus/game_menu.cpp +++ b/sphaira/source/ui/menus/game_menu.cpp @@ -113,7 +113,7 @@ void LaunchEntry(const Entry& e) { } // namespace -Menu::Menu() : MenuBase{"Games"_i18n} { +Menu::Menu() : grid::Menu{"Games"_i18n} { this->SetActions( std::make_pair(Button::B, Action{"Back"_i18n, [this](){ SetPop(); @@ -140,16 +140,26 @@ Menu::Menu() : MenuBase{"Games"_i18n} { order_items.push_back("Descending"_i18n); order_items.push_back("Ascending"_i18n); - options->Add(std::make_shared("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("Sort"_i18n, sort_items, [this](s64& index_out){ m_sort.Set(index_out); SortAndFindLastFile(false); }, m_sort.Get())); - options->Add(std::make_shared("Order"_i18n, order_items, [this, order_items](s64& index_out){ + options->Add(std::make_shared("Order"_i18n, order_items, [this](s64& index_out){ m_order.Set(index_out); SortAndFindLastFile(false); }, m_order.Get())); + options->Add(std::make_shared("Layout"_i18n, layout_items, [this](s64& index_out){ + m_layout.Set(index_out); + OnLayoutChange(); + }, m_layout.Get())); + options->Add(std::make_shared("Hide forwarders"_i18n, m_hide_forwarders.Get(), [this](bool& v_out){ m_hide_forwarders.Set(v_out); m_dirty = true; @@ -241,9 +251,7 @@ Menu::Menu() : MenuBase{"Games"_i18n} { }}) ); - const Vec4 v{75, 110, 370, 155}; - const Vec2 pad{10, 10}; - m_list = std::make_unique(3, 9, m_pos, v, pad); + OnLayoutChange(); nsInitialize(); nsGetApplicationRecordUpdateSystemEvent(&m_event); @@ -285,7 +293,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) { 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) { - const auto& [x, y, w, h] = v; + // const auto& [x, y, w, h] = v; auto& e = m_entries[pos]; 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; - 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; - 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()); + DrawEntry(vg, theme, m_layout.Get(), v, selected, e.image, e.GetName(), e.GetAuthor(), e.GetDisplayVersion()); }); } @@ -446,9 +437,11 @@ void Menu::SortAndFindLastFile(bool scan) { } if (index >= 0) { + const auto row = m_list->GetRow(); + const auto page = m_list->GetPage(); // guesstimate where the position is - if (index >= 9) { - m_list->SetYoff((((index - 9) + 3) / 3) * m_list->GetMaxY()); + if (index >= page) { + m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY()); } else { m_list->SetYoff(0); } @@ -466,4 +459,9 @@ void Menu::FreeEntries() { m_entries.clear(); } +void Menu::OnLayoutChange() { + m_index = 0; + grid::Menu::OnLayoutChange(m_list, m_layout.Get()); +} + } // namespace sphaira::ui::menu::game diff --git a/sphaira/source/ui/menus/grid_menu_base.cpp b/sphaira/source/ui/menus/grid_menu_base.cpp new file mode 100644 index 0000000..4719d7e --- /dev/null +++ b/sphaira/source/ui/menus/grid_menu_base.cpp @@ -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, 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(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(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(3, 3*3, m_pos, v, pad); + } break; + } +} + +} // namespace sphaira::ui::menu::grid diff --git a/sphaira/source/ui/menus/homebrew.cpp b/sphaira/source/ui/menus/homebrew.cpp index 7de0ef8..cd40c54 100644 --- a/sphaira/source/ui/menus/homebrew.cpp +++ b/sphaira/source/ui/menus/homebrew.cpp @@ -31,7 +31,7 @@ void FreeEntry(NVGcontext* vg, NroEntry& e) { } // namespace -Menu::Menu() : MenuBase{"Homebrew"_i18n} { +Menu::Menu() : grid::Menu{"Homebrew"_i18n} { this->SetActions( std::make_pair(Button::A, Action{"Launch"_i18n, [this](){ 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("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("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){ m_sort.Set(index_out); SortAndFindLastFile(); @@ -67,6 +72,11 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} { SortAndFindLastFile(); }, m_order.Get())); + options->Add(std::make_shared("Layout"_i18n, layout_items, [this](s64& index_out){ + m_layout.Set(index_out); + OnLayoutChange(); + }, m_layout.Get())); + options->Add(std::make_shared("Hide Sphaira"_i18n, m_hide_sphaira.Get(), [this](bool& enable){ m_hide_sphaira.Set(enable); })); @@ -114,9 +124,7 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} { }}) ); - const Vec4 v{75, 110, 370, 155}; - const Vec2 pad{10, 10}; - m_list = std::make_unique(3, 9, m_pos, v, pad); + OnLayoutChange(); } Menu::~Menu() { @@ -143,7 +151,6 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) { 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) { - const auto& [x, y, w, h] = v; auto& e = m_entries[pos]; // 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; if (IsStarEnabled()) { if (!e.has_star.has_value()) { @@ -184,10 +177,15 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) { has_star = e.has_star.value(); } - const float font_size = 18; - 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()); - 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()); + std::string name; + if (has_star) { + name = std::string("\u2605 ") + e.GetName(); + } 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) { + const auto row = m_list->GetRow(); + const auto page = m_list->GetPage(); // guesstimate where the position is - if (index >= 9) { - m_list->SetYoff((((index - 9) + 3) / 3) * m_list->GetMaxY()); + if (index >= page) { + m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY()); } else { m_list->SetYoff(0); } @@ -406,6 +406,11 @@ void Menu::FreeEntries() { 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& icon) { OwoConfig config{}; config.nro_path = path.toString(); diff --git a/sphaira/source/ui/nvg_util.cpp b/sphaira/source/ui/nvg_util.cpp index 49b2a67..3e8a581 100644 --- a/sphaira/source/ui/nvg_util.cpp +++ b/sphaira/source/ui/nvg_util.cpp @@ -171,6 +171,24 @@ void drawTextIntenal(NVGcontext* vg, const Vec2& v, float size, const char* str, 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 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); } +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 static double highlightGradientX = 0; diff --git a/sphaira/source/ui/scrolling_text.cpp b/sphaira/source/ui/scrolling_text.cpp index f51dac1..657daf1 100644 --- a/sphaira/source/ui/scrolling_text.cpp +++ b/sphaira/source/ui/scrolling_text.cpp @@ -32,9 +32,7 @@ void ScrollingText::Draw(NVGcontext* vg, bool focus, float x, float y, float w, } if (m_str != text_entry) { - m_str = text_entry; - m_tick = 0; - m_text_xoff = 0; + Reset(text_entry); } 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); } +void ScrollingText::Reset(const std::string& text_entry) { + m_str = text_entry; + m_tick = 0; + m_text_xoff = 0; +} + } // namespace sphaira::ui