#include "ui/nvg_util.hpp" #include "log.hpp" #include #include #include #include #include #include #include namespace sphaira::ui::gfx { namespace { constexpr auto ALIGN_HOR = NVG_ALIGN_LEFT|NVG_ALIGN_CENTER|NVG_ALIGN_RIGHT; constexpr auto ALIGN_VER = NVG_ALIGN_TOP|NVG_ALIGN_MIDDLE|NVG_ALIGN_BOTTOM|NVG_ALIGN_BASELINE; constexpr std::array buttons = { std::pair{Button::A, "\uE0E0"}, std::pair{Button::B, "\uE0E1"}, std::pair{Button::X, "\uE0E2"}, std::pair{Button::Y, "\uE0E3"}, std::pair{Button::L, "\uE0E4"}, std::pair{Button::R, "\uE0E5"}, std::pair{Button::L2, "\uE0E6"}, std::pair{Button::R2, "\uE0E7"}, std::pair{Button::UP, "\uE0EB"}, std::pair{Button::DOWN, "\uE0EC"}, std::pair{Button::LEFT, "\uE0ED"}, std::pair{Button::RIGHT, "\uE0EE"}, std::pair{Button::START, "\uE0EF"}, std::pair{Button::SELECT, "\uE0F0"}, // std::pair{Button::LS, "\uE101"}, // std::pair{Button::RS, "\uE102"}, std::pair{Button::L3, "\uE104"}, std::pair{Button::R3, "\uE105"}, }; // software based clipping, saves a few cpu cycles. bool ClipRect(float x, float y) { return x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT; } bool ClipText(float x, float y, int align) { if ((!(align & ALIGN_HOR) || (align & NVG_ALIGN_LEFT)) && x >= SCREEN_WIDTH) { return true; } if ((!(align & ALIGN_VER) || (align & NVG_ALIGN_TOP)) && y >= SCREEN_HEIGHT) { return true; } return false; } // NEW --------------------- void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGcolor& c, float rounded) { if (ClipRect(v.x, v.y)) { return; } nvgBeginPath(vg); nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded); nvgFillColor(vg, c); nvgFill(vg); } void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGpaint& p, float rounded) { if (ClipRect(v.x, v.y)) { return; } nvgBeginPath(vg); nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded); nvgFillPaint(vg, p); nvgFill(vg); } void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, const Vec4& v) { float gradientX, gradientY, color; getHighlightAnimation(&gradientX, &gradientY, &color); const auto strokeWidth = 5.F; auto v2 = v; v2.x -= strokeWidth / 2.F; v2.y -= strokeWidth / 2.F; v2.w += strokeWidth; v2.h += strokeWidth; const auto corner_radius = 0.5F; const auto shadow_width = 2.F; const auto shadow_offset = 10.F; const auto shadow_feather = 10.F; const auto shadow_opacity = 128.F; // Shadow NVGpaint shadowPaint = nvgBoxGradient(vg, v2.x, v2.y + shadow_width, v2.w, v2.h, corner_radius * 2, shadow_feather, nvgRGBA(0, 0, 0, shadow_opacity * 1.f), nvgRGBA(0, 0, 0, 0)); nvgBeginPath(vg); nvgRect(vg, v2.x - shadow_offset, v2.y - shadow_offset, v2.w + shadow_offset * 2, v2.h + shadow_offset * 3); nvgRoundedRect(vg, v2.x, v2.y, v2.w, v2.h, corner_radius); nvgPathWinding(vg, NVG_HOLE); nvgFillPaint(vg, shadowPaint); nvgFill(vg); const auto color1 = theme->GetColour(ThemeEntryID_HIGHLIGHT_1); const auto color2 = theme->GetColour(ThemeEntryID_HIGHLIGHT_2); const auto borderColor = nvgRGBAf(color2.r, color2.g, color2.b, 0.5); const auto transparent = nvgRGBA(0, 0, 0, 0); const auto pulsationColor = nvgRGBAf((color * color1.r) + (1 - color) * color2.r, (color * color1.g) + (1 - color) * color2.g, (color * color1.b) + (1 - color) * color2.b, 1.f); const auto border1Paint = nvgRadialGradient(vg, v2.x + gradientX * v2.w, v2.y + gradientY * v2.h, strokeWidth * 10, strokeWidth * 40, borderColor, transparent); const auto border2Paint = nvgRadialGradient(vg, v2.x + (1 - gradientX) * v2.w, v2.y + (1 - gradientY) * v2.h, strokeWidth * 10, strokeWidth * 40, borderColor, transparent); nvgBeginPath(vg); nvgStrokeColor(vg, pulsationColor); nvgStrokeWidth(vg, strokeWidth); nvgRoundedRect(vg, v2.x, v2.y, v2.w, v2.h, corner_radius); nvgStroke(vg); nvgBeginPath(vg); nvgStrokePaint(vg, border1Paint); nvgStrokeWidth(vg, strokeWidth); nvgRoundedRect(vg, v2.x, v2.y, v2.w, v2.h, corner_radius); nvgStroke(vg); nvgBeginPath(vg); nvgStrokePaint(vg, border2Paint); nvgStrokeWidth(vg, strokeWidth); nvgRoundedRect(vg, v2.x, v2.y, v2.w, v2.h, corner_radius); nvgStroke(vg); } void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, const Vec4& v, const NVGcolor& c) { if (ClipRect(v.x, v.y)) { return; } const auto corner_radius = 0.5; drawRectOutlineInternal(vg, theme, size, v); nvgBeginPath(vg); nvgRoundedRect(vg, v.x, v.y, v.w, v.h, corner_radius); nvgFillColor(vg, c); nvgFill(vg); } void drawTextIntenal(NVGcontext* vg, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c) { if (ClipText(v.x, v.y, align)) { return; } nvgBeginPath(vg); nvgFontSize(vg, size); nvgTextAlign(vg, align); nvgFillColor(vg, c); 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) { for (auto& [key, val] : buttons) { if (key == want) { return val; } } std::unreachable(); } void drawTextArgs(NVGcontext* vg, float x, float y, float size, int align, const NVGcolor& c, const char* str, ...) { std::va_list v; va_start(v, str); char buffer[0x100]; std::vsnprintf(buffer, sizeof(buffer), str, v); va_end(v); drawText(vg, x, y, size, buffer, nullptr, align, c); } void drawImage(NVGcontext* vg, const Vec4& v, int texture, float rounded) { const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f); drawRect(vg, v, paint, rounded); } void drawImage(NVGcontext* vg, float x, float y, float w, float h, int texture, float rounded) { drawImage(vg, Vec4(x, y, w, h), texture, rounded); } void drawTextBox(NVGcontext* vg, float x, float y, float size, float bound, const NVGcolor& c, const char* str, int align, const char* end) { if (ClipText(x, y, align)) { return; } nvgBeginPath(vg); nvgFontSize(vg, size); nvgTextAlign(vg, align); nvgFillColor(vg, c); nvgTextBox(vg, x, y, bound, str, end); } void textBounds(NVGcontext* vg, float x, float y, float *bounds, const char* str, ...) { char buf[0x100]; va_list v; va_start(v, str); std::vsnprintf(buf, sizeof(buf), str, v); va_end(v); nvgTextBounds(vg, x, y, buf, nullptr, bounds); } // NEW----------- void dimBackground(NVGcontext* vg) { drawRectIntenal(vg, {0.f,0.f,SCREEN_WIDTH,SCREEN_HEIGHT}, nvgRGBA(0, 0, 0, 180), false); } void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGcolor& c, float rounded) { drawRectIntenal(vg, {x,y,w,h}, c, rounded); } void drawRect(NVGcontext* vg, const Vec4& v, const NVGcolor& c, float rounded) { drawRectIntenal(vg, v, c, rounded); } void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGpaint& p, float rounded) { drawRectIntenal(vg, {x,y,w,h}, p, rounded); } void drawRect(NVGcontext* vg, const Vec4& v, const NVGpaint& p, float rounded) { drawRectIntenal(vg, v, p, rounded); } void drawRectOutline(NVGcontext* vg, const Theme* theme, float size, float x, float y, float w, float h) { drawRectOutlineInternal(vg, theme, size, {x,y,w,h}, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND)); } void drawRectOutline(NVGcontext* vg, const Theme* theme, float size, const Vec4& v) { drawRectOutlineInternal(vg, theme, size, v, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND)); } void drawText(NVGcontext* vg, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c) { drawTextIntenal(vg, {x,y}, size, str, end, align, c); } void drawText(NVGcontext* vg, float x, float y, float size, const NVGcolor& c, const char* str, int align, const char* end) { drawTextIntenal(vg, {x,y}, size, str, end, align, c); } void drawText(NVGcontext* vg, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c) { drawTextIntenal(vg, v, size, str, end, align, c); } void drawText(NVGcontext* vg, const Vec2& v, float size, const NVGcolor& c, const char* str, int align, const char* end) { drawTextIntenal(vg, v, size, str, end, align, c); } void drawScrollbar(NVGcontext* vg, const Theme* theme, float x, float y, float h, u32 index_off, u32 count, u32 max_per_page) { const s64 SCROLL = index_off; const s64 max_entry_display = max_per_page; const s64 entry_total = count; const float scc2 = 8.0; const float scw = 2.0; // only draw scrollbar if needed if (entry_total > max_entry_display) { const float sb_h = 1.f / (float)entry_total * h; const float sb_y = SCROLL; gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), 5); gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(max_entry_display) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), 5); } } void drawScrollbar(NVGcontext* vg, const Theme* theme, u32 index_off, u32 count, u32 max_per_page) { drawScrollbar(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, max_per_page); } void drawScrollbar2(NVGcontext* vg, const Theme* theme, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page) { // round up if (count % row) { count = count + (row - count % row); } const float scc2 = 6.0; const float scw = 2.0; // only draw scrollbar if needed if (count > page) { const float sb_h = 1.f / (float)count * h; const float sb_y = index_off; gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), 5); gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(page) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), 5); } } void drawScrollbar2(NVGcontext* vg, const Theme* theme, s64 index_off, s64 count, s64 row, s64 page) { drawScrollbar2(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, row, page); } 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); 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; static double highlightGradientY = 0; static double highlightColor = 0; void updateHighlightAnimation() { const auto currentTime = svcGetSystemTick() * 10 / 192 / 1000; // Update variables highlightGradientX = (std::cos((double)currentTime / HIGHLIGHT_SPEED / 3.0) + 1.0) / 2.0; highlightGradientY = (std::sin((double)currentTime / HIGHLIGHT_SPEED / 3.0) + 1.0) / 2.0; highlightColor = (std::sin((double)currentTime / HIGHLIGHT_SPEED * 2.0) + 1.0) / 2.0; } void getHighlightAnimation(float* gradientX, float* gradientY, float* color) { if (gradientX) *gradientX = (float)highlightGradientX; if (gradientY) *gradientY = (float)highlightGradientY; if (color) *color = (float)highlightColor; } } // namespace sphaira::ui::gfx