Files
sphaira/sphaira/source/ui/list.cpp

306 lines
8.4 KiB
C++

#include "ui/list.hpp"
#include "ui/nvg_util.hpp"
#include "app.hpp"
#include "log.hpp"
#include <algorithm>
namespace sphaira::ui {
List::List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad)
: m_row{row}
, m_page{page}
, m_v{v}
, m_pad{pad} {
m_pos = pos;
// SetScrollBarPos(SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200);
SetScrollBarPos(pos.x + pos.w, 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;
if (count >= m_page) {
// round up
if (count % m_row) {
count = count + (m_row - count % m_row);
}
y_max = (count - m_page) / m_row * GetMaxY();
}
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 = GetPageJump() ? (m_row == 1 ? Button::DPAD_LEFT : Button::L2) : (Button::NONE);
const auto page_down_button = GetPageJump() ? (m_row == 1 ? Button::DPAD_RIGHT : Button::R2) : (Button::NONE);
if (controller->GotDown(Button::DOWN)) {
if (ScrollDown(index, m_row, count)) {
callback(false, index);
}
} else if (controller->GotDown(Button::UP)) {
if (ScrollUp(index, m_row, count)) {
callback(false, index);
}
} else if (controller->GotDown(page_down_button)) {
if (ScrollDown(index, m_page, count)) {
callback(false, index);
}
} else if (controller->GotDown(page_up_button)) {
if (ScrollUp(index, m_page, count)) {
callback(false, index);
}
} else if (m_row > 1 && controller->GotDown(Button::RIGHT)) {
if (count && index < (count - 1) && (index + 1) % m_row != 0) {
callback(false, index + 1);
}
} else if (m_row > 1 && controller->GotDown(Button::LEFT)) {
if (count && index != 0 && (index % m_row) != 0) {
callback(false, index - 1);
}
} else if (touch->is_clicked && touch->in_range(GetPos())) {
auto v = m_v;
v.y -= ClampY(m_yoff + m_y_prog, count);
for (s64 i = 0; i < count; v.y += v.h + m_pad.y) {
if (v.y > GetY() + GetH()) {
break;
}
const auto x = v.x;
for (; i < count; i++, v.x += v.w + m_pad.x) {
// only draw if full x is in bounds
if (v.x + v.w > GetX() + GetW()) {
break;
}
// skip anything not visible
if (v.y + v.h < GetY()) {
continue;
}
Vec4 vv = v;
// if not drawing, only return clipped v as its used for touch
vv.w = std::min(v.x + v.w, m_pos.x + m_pos.w) - v.x;
vv.h = std::min(v.y + v.h, m_pos.y + m_pos.h) - v.y;
if (touch->in_range(vv)) {
callback(true, i);
return;
}
}
v.x = x;
}
} else if (touch->is_scroll && touch->in_range(GetPos())) {
m_y_prog = (float)touch->initial.y - (float)touch->cur.y;
} else if (touch->is_end) {
m_yoff = ClampY(m_yoff + m_y_prog, count);
m_y_prog = 0;
}
}
void List::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) {
// skip anything not visible
if (v.x + v.w < GetX()) {
continue;
}
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);
auto v = m_v;
v.y -= yoff;
nvgSave(vg);
nvgIntersectScissor(vg, GetX(), GetY(), GetW(), GetH());
for (s64 i = 0; i < count; v.y += v.h + m_pad.y) {
if (v.y > GetY() + GetH()) {
break;
}
const auto x = v.x;
for (s64 row = 0; i < count; row++, i++, v.x += v.w + m_pad.x) {
if (row >= m_row) {
break;
}
// only draw if full x is in bounds
if (v.x + v.w > GetX() + GetW()) {
break;
}
// skip anything not visible
if (v.y + v.h < GetY()) {
continue;
}
callback(vg, theme, v, i);
}
v.x = x;
}
nvgRestore(vg);
}
} // namespace sphaira::ui