306 lines
8.4 KiB
C++
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
|