2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
#include "ps1/gpucmd.h"
|
|
|
|
#include "defs.hpp"
|
|
|
|
#include "gpu.hpp"
|
2023-06-26 15:23:47 +02:00
|
|
|
#include "gpufont.hpp"
|
2023-05-30 18:08:52 +02:00
|
|
|
#include "uibase.hpp"
|
|
|
|
#include "uicommon.hpp"
|
|
|
|
|
|
|
|
namespace ui {
|
|
|
|
|
|
|
|
/* Common higher-level screens */
|
|
|
|
|
|
|
|
void PlaceholderScreen::draw(Context &ctx, bool active) const {
|
|
|
|
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
|
|
|
ctx.gpuCtx.drawRect(
|
2023-09-14 23:54:59 +02:00
|
|
|
0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height, ctx.colors[COLOR_WINDOW2]
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-05 09:18:10 +02:00
|
|
|
MessageBoxScreen::MessageBoxScreen(void)
|
2023-06-24 10:48:35 +02:00
|
|
|
: ModalScreen(MODAL_WIDTH, MODAL_HEIGHT_FULL), _numButtons(0),
|
|
|
|
_buttonIndexOffset(0), _locked(false) {}
|
2023-05-30 18:08:52 +02:00
|
|
|
|
2023-08-05 09:18:10 +02:00
|
|
|
void MessageBoxScreen::show(Context &ctx, bool goBack) {
|
2023-05-30 18:08:52 +02:00
|
|
|
ModalScreen::show(ctx, goBack);
|
|
|
|
|
|
|
|
_activeButton = 0;
|
|
|
|
_buttonAnim.setValue(_getButtonWidth());
|
|
|
|
}
|
|
|
|
|
2023-08-05 09:18:10 +02:00
|
|
|
void MessageBoxScreen::draw(Context &ctx, bool active) const {
|
2023-05-30 18:08:52 +02:00
|
|
|
ModalScreen::draw(ctx, active);
|
|
|
|
|
|
|
|
if (!active || !_numButtons)
|
|
|
|
return;
|
|
|
|
|
2023-06-24 10:48:35 +02:00
|
|
|
int activeButton = _activeButton - _buttonIndexOffset;
|
|
|
|
|
2023-05-30 18:08:52 +02:00
|
|
|
int buttonX = _width / 8;
|
|
|
|
int buttonY = TITLE_BAR_HEIGHT + _height - (BUTTON_HEIGHT + MODAL_PADDING);
|
|
|
|
gpu::RectWH rect;
|
|
|
|
|
|
|
|
rect.y = buttonY + BUTTON_PADDING;
|
|
|
|
rect.w = _getButtonWidth();
|
2023-09-17 13:41:35 +02:00
|
|
|
rect.h = rect.y + ctx.font.metrics.lineHeight;
|
2023-06-27 16:25:52 +02:00
|
|
|
//rect.h = BUTTON_HEIGHT - BUTTON_PADDING * 2;
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
for (int i = 0; i < _numButtons; i++) {
|
|
|
|
rect.x = buttonX +
|
|
|
|
(rect.w - ctx.font.getStringWidth(_buttons[i])) / 2;
|
|
|
|
|
|
|
|
if (_locked) {
|
|
|
|
ctx.gpuCtx.drawRect(
|
2023-09-14 23:54:59 +02:00
|
|
|
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
|
|
|
|
ctx.colors[COLOR_SHADOW], true
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(
|
|
|
|
ctx.gpuCtx, _buttons[i], rect, ctx.colors[COLOR_TEXT2]
|
|
|
|
);
|
2023-05-30 18:08:52 +02:00
|
|
|
} else {
|
2023-06-24 10:48:35 +02:00
|
|
|
if (i == activeButton) {
|
2023-05-30 18:08:52 +02:00
|
|
|
ctx.gpuCtx.drawRect(
|
2023-09-14 23:54:59 +02:00
|
|
|
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
|
|
|
|
ctx.colors[COLOR_HIGHLIGHT2]
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
ctx.gpuCtx.drawRect(
|
2023-09-14 23:54:59 +02:00
|
|
|
buttonX, buttonY, _buttonAnim.getValue(ctx.time),
|
|
|
|
BUTTON_HEIGHT, ctx.colors[COLOR_HIGHLIGHT1]
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
ctx.gpuCtx.drawRect(
|
2023-09-14 23:54:59 +02:00
|
|
|
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
|
|
|
|
ctx.colors[COLOR_WINDOW3]
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(
|
|
|
|
ctx.gpuCtx, _buttons[i], rect, ctx.colors[COLOR_TITLE]
|
|
|
|
);
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
buttonX += rect.w + BUTTON_SPACING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-05 09:18:10 +02:00
|
|
|
void MessageBoxScreen::update(Context &ctx) {
|
2023-05-30 18:08:52 +02:00
|
|
|
if (_locked)
|
|
|
|
return;
|
|
|
|
|
2023-06-25 06:49:39 +02:00
|
|
|
int numButtons = _buttonIndexOffset + _numButtons;
|
2023-05-30 18:08:52 +02:00
|
|
|
|
2023-06-25 06:49:39 +02:00
|
|
|
if (
|
|
|
|
ctx.buttons.pressed(ui::BTN_LEFT) ||
|
|
|
|
(ctx.buttons.repeating(ui::BTN_LEFT) && (_activeButton > 0))
|
|
|
|
) {
|
|
|
|
_activeButton--;
|
|
|
|
if (_activeButton < 0) {
|
|
|
|
_activeButton += numButtons;
|
2023-05-30 18:08:52 +02:00
|
|
|
ctx.sounds[SOUND_CLICK].play();
|
2023-06-25 06:49:39 +02:00
|
|
|
} else {
|
|
|
|
ctx.sounds[SOUND_MOVE].play();
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
2023-06-22 21:42:50 +02:00
|
|
|
|
2023-06-25 06:49:39 +02:00
|
|
|
_buttonAnim.setValue(ctx.time, 0, _getButtonWidth(), SPEED_FASTEST);
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
ctx.buttons.pressed(ui::BTN_RIGHT) ||
|
|
|
|
(ctx.buttons.repeating(ui::BTN_RIGHT) && (_activeButton < (numButtons - 1)))
|
|
|
|
) {
|
|
|
|
_activeButton++;
|
|
|
|
if (_activeButton >= numButtons) {
|
|
|
|
_activeButton -= numButtons;
|
2023-05-30 18:08:52 +02:00
|
|
|
ctx.sounds[SOUND_CLICK].play();
|
2023-06-25 06:49:39 +02:00
|
|
|
} else {
|
|
|
|
ctx.sounds[SOUND_MOVE].play();
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
2023-06-25 06:49:39 +02:00
|
|
|
|
|
|
|
_buttonAnim.setValue(ctx.time, 0, _getButtonWidth(), SPEED_FASTEST);
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-24 10:48:35 +02:00
|
|
|
HexEntryScreen::HexEntryScreen(void)
|
2023-09-16 14:01:26 +02:00
|
|
|
: _bufferLength(0) {
|
2023-09-17 13:41:35 +02:00
|
|
|
__builtin_memset(_buffer, 0, sizeof(_buffer));
|
2023-09-16 14:01:26 +02:00
|
|
|
}
|
2023-06-24 10:48:35 +02:00
|
|
|
|
|
|
|
void HexEntryScreen::show(Context &ctx, bool goBack) {
|
2023-08-05 09:18:10 +02:00
|
|
|
MessageBoxScreen::show(ctx, goBack);
|
2023-06-24 10:48:35 +02:00
|
|
|
|
|
|
|
_buttonIndexOffset = _bufferLength * 2;
|
2023-09-16 14:01:26 +02:00
|
|
|
//__builtin_memset(_buffer, 0, _bufferLength);
|
2023-06-24 10:48:35 +02:00
|
|
|
|
|
|
|
_charIndex = 0;
|
|
|
|
_cursorAnim.setValue(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexEntryScreen::draw(Context &ctx, bool active) const {
|
2023-08-05 09:18:10 +02:00
|
|
|
MessageBoxScreen::draw(ctx, active);
|
2023-06-24 10:48:35 +02:00
|
|
|
|
|
|
|
if (!active)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int boxY = TITLE_BAR_HEIGHT + _height -
|
|
|
|
(BUTTON_HEIGHT + MODAL_PADDING) * 2;
|
|
|
|
int boxWidth = _width - MODAL_PADDING * 2;
|
|
|
|
|
|
|
|
// Text box
|
|
|
|
ctx.gpuCtx.drawRect(
|
2023-09-14 23:54:59 +02:00
|
|
|
MODAL_PADDING, boxY, boxWidth, BUTTON_HEIGHT, ctx.colors[COLOR_BOX1]
|
2023-06-24 10:48:35 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
char text[128];
|
|
|
|
gpu::Rect rect;
|
|
|
|
|
|
|
|
util::hexToString(text, _buffer, _bufferLength, _separator);
|
|
|
|
|
|
|
|
int digitWidth = ctx.font.getCharacterWidth('0');
|
|
|
|
int textOffset = MODAL_PADDING +
|
|
|
|
(boxWidth - ctx.font.getStringWidth(text)) / 2;
|
|
|
|
|
|
|
|
// Cursor
|
|
|
|
if (_activeButton < _buttonIndexOffset)
|
|
|
|
ctx.gpuCtx.drawGradientRectV(
|
2023-06-25 06:49:39 +02:00
|
|
|
textOffset + _cursorAnim.getValue(ctx.time),
|
2023-09-14 23:54:59 +02:00
|
|
|
boxY + BUTTON_HEIGHT / 2, digitWidth, BUTTON_HEIGHT / 2,
|
|
|
|
ctx.colors[COLOR_BOX1], ctx.colors[COLOR_HIGHLIGHT1]
|
2023-06-24 10:48:35 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
// Text
|
|
|
|
rect.x1 = textOffset;
|
|
|
|
rect.y1 = boxY + BUTTON_PADDING;
|
|
|
|
rect.x2 = _width - MODAL_PADDING;
|
2023-09-17 13:41:35 +02:00
|
|
|
rect.y2 = boxY + BUTTON_PADDING + ctx.font.metrics.lineHeight;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(ctx.gpuCtx, text, rect, ctx.colors[COLOR_TITLE]);
|
2023-06-24 10:48:35 +02:00
|
|
|
|
|
|
|
// Highlighted digit
|
|
|
|
if (_activeButton < _buttonIndexOffset) {
|
|
|
|
text[0] = text[_charIndex];
|
|
|
|
text[1] = 0;
|
|
|
|
|
|
|
|
rect.x1 = textOffset + _cursorAnim.getTargetValue();
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(ctx.gpuCtx, text, rect, ctx.colors[COLOR_SUBTITLE]);
|
2023-06-24 10:48:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HexEntryScreen::update(Context &ctx) {
|
|
|
|
if (
|
|
|
|
ctx.buttons.held(ui::BTN_START) && (_activeButton < _buttonIndexOffset)
|
|
|
|
) {
|
|
|
|
uint8_t *ptr = &_buffer[_activeButton / 2];
|
|
|
|
int digit = (_activeButton % 2) ? (*ptr & 0x0f) : (*ptr >> 4);
|
|
|
|
|
|
|
|
if (
|
|
|
|
ctx.buttons.pressed(ui::BTN_LEFT) ||
|
|
|
|
(ctx.buttons.repeating(ui::BTN_LEFT) && (digit > 0))
|
|
|
|
) {
|
|
|
|
digit--;
|
|
|
|
if (digit < 0) {
|
|
|
|
digit = 0xf;
|
|
|
|
ctx.sounds[SOUND_CLICK].play();
|
|
|
|
} else {
|
|
|
|
ctx.sounds[SOUND_MOVE].play();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
ctx.buttons.pressed(ui::BTN_RIGHT) ||
|
|
|
|
(ctx.buttons.repeating(ui::BTN_RIGHT) && (digit < 0xf))
|
|
|
|
) {
|
|
|
|
digit++;
|
|
|
|
if (digit > 0xf) {
|
|
|
|
digit = 0;
|
|
|
|
ctx.sounds[SOUND_CLICK].play();
|
|
|
|
} else {
|
|
|
|
ctx.sounds[SOUND_MOVE].play();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_activeButton % 2)
|
|
|
|
*ptr = (*ptr & 0xf0) | digit;
|
|
|
|
else
|
|
|
|
*ptr = (*ptr & 0x0f) | (digit << 4);
|
|
|
|
} else {
|
|
|
|
int oldActive = _activeButton;
|
|
|
|
|
2023-08-05 09:18:10 +02:00
|
|
|
MessageBoxScreen::update(ctx);
|
2023-06-24 10:48:35 +02:00
|
|
|
|
|
|
|
// Update the cursor's position if necessary.
|
|
|
|
if (oldActive != _activeButton) {
|
|
|
|
int digitWidth = ctx.font.getCharacterWidth('0');
|
|
|
|
int sepWidth = ctx.font.getCharacterWidth(_separator);
|
|
|
|
|
|
|
|
int offset = _activeButton, cursorX = 0;
|
|
|
|
_charIndex = 0;
|
|
|
|
|
|
|
|
for (; offset >= 2; offset -= 2) {
|
|
|
|
cursorX += digitWidth * 2 + sepWidth;
|
|
|
|
_charIndex += 3;
|
|
|
|
}
|
|
|
|
for (; offset; offset--) {
|
|
|
|
cursorX += digitWidth;
|
|
|
|
_charIndex++;
|
|
|
|
}
|
|
|
|
|
|
|
|
_cursorAnim.setValue(ctx.time, cursorX, SPEED_FASTEST);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-30 18:08:52 +02:00
|
|
|
ProgressScreen::ProgressScreen(void)
|
|
|
|
: ModalScreen(MODAL_WIDTH, MODAL_HEIGHT_REDUCED) {}
|
|
|
|
|
|
|
|
void ProgressScreen::show(Context &ctx, bool goBack) {
|
|
|
|
ModalScreen::show(ctx, goBack);
|
|
|
|
|
|
|
|
_progressBarAnim.setValue(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProgressScreen::draw(Context &ctx, bool active) const {
|
|
|
|
ModalScreen::draw(ctx, active);
|
|
|
|
|
|
|
|
if (!active)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int fullBarWidth = _width - MODAL_PADDING * 2;
|
|
|
|
|
|
|
|
int barX = (_width - fullBarWidth) / 2;
|
|
|
|
int barY = TITLE_BAR_HEIGHT + _height -
|
|
|
|
(PROGRESS_BAR_HEIGHT + MODAL_PADDING);
|
|
|
|
|
|
|
|
_setBlendMode(ctx, GP0_BLEND_SEMITRANS, true);
|
|
|
|
|
|
|
|
ctx.gpuCtx.drawRect(
|
2023-09-14 23:54:59 +02:00
|
|
|
barX, barY, fullBarWidth, PROGRESS_BAR_HEIGHT, ctx.colors[COLOR_WINDOW3]
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
ctx.gpuCtx.drawGradientRectH(
|
|
|
|
barX, barY, _progressBarAnim.getValue(ctx.time), PROGRESS_BAR_HEIGHT,
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.colors[COLOR_PROGRESS2], ctx.colors[COLOR_PROGRESS1]
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
TextScreen::TextScreen(void)
|
|
|
|
: _title(nullptr), _body(nullptr), _prompt(nullptr) {}
|
|
|
|
|
2023-06-26 15:23:47 +02:00
|
|
|
void TextScreen::show(Context &ctx, bool goBack) {
|
|
|
|
AnimatedScreen::show(ctx, goBack);
|
|
|
|
|
|
|
|
_scrollAnim.setValue(0);
|
|
|
|
_updateTextHeight(ctx);
|
|
|
|
}
|
|
|
|
|
2023-05-30 18:08:52 +02:00
|
|
|
void TextScreen::draw(Context &ctx, bool active) const {
|
|
|
|
int screenWidth = ctx.gpuCtx.width - SCREEN_MARGIN_X * 2;
|
|
|
|
int screenHeight = ctx.gpuCtx.height - SCREEN_MARGIN_Y * 2;
|
|
|
|
|
2023-06-26 15:23:47 +02:00
|
|
|
// Top/bottom text
|
2023-05-30 18:08:52 +02:00
|
|
|
_newLayer(
|
|
|
|
ctx, SCREEN_MARGIN_X, SCREEN_MARGIN_Y, screenWidth, screenHeight
|
|
|
|
);
|
|
|
|
|
|
|
|
gpu::Rect rect;
|
|
|
|
|
|
|
|
rect.x1 = 0;
|
|
|
|
rect.y1 = 0;
|
|
|
|
rect.x2 = screenWidth;
|
2023-09-17 13:41:35 +02:00
|
|
|
rect.y2 = ctx.font.metrics.lineHeight;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
2023-05-30 18:08:52 +02:00
|
|
|
|
2023-06-26 08:39:50 +02:00
|
|
|
rect.y1 = screenHeight - SCREEN_PROMPT_HEIGHT_MIN;
|
2023-05-30 18:08:52 +02:00
|
|
|
rect.y2 = screenHeight;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(ctx.gpuCtx, _prompt, rect, ctx.colors[COLOR_TEXT1], true);
|
2023-06-26 15:23:47 +02:00
|
|
|
|
2023-09-17 13:41:35 +02:00
|
|
|
int bodyOffset = ctx.font.metrics.lineHeight + SCREEN_BLOCK_MARGIN;
|
2023-06-27 16:25:52 +02:00
|
|
|
int bodyHeight = screenHeight -
|
|
|
|
(bodyOffset + SCREEN_PROMPT_HEIGHT_MIN + SCREEN_BLOCK_MARGIN);
|
2023-06-26 15:23:47 +02:00
|
|
|
|
|
|
|
// Scrollable text
|
|
|
|
_newLayer(
|
|
|
|
ctx, SCREEN_MARGIN_X, SCREEN_MARGIN_Y + bodyOffset, screenWidth,
|
|
|
|
bodyHeight
|
|
|
|
);
|
|
|
|
|
|
|
|
gpu::Rect clip;
|
|
|
|
|
|
|
|
rect.y1 = -_scrollAnim.getValue(ctx.time);
|
|
|
|
rect.y2 = 0x7fff;
|
|
|
|
clip.x1 = 0;
|
|
|
|
clip.y1 = 0;
|
|
|
|
clip.x2 = screenWidth;
|
|
|
|
clip.y2 = bodyHeight;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(ctx.gpuCtx, _body, rect, clip, ctx.colors[COLOR_TEXT1], true);
|
2023-06-26 15:23:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void TextScreen::update(Context &ctx) {
|
|
|
|
if (!ctx.buttons.held(ui::BTN_LEFT) && !ctx.buttons.held(ui::BTN_RIGHT))
|
|
|
|
return;
|
|
|
|
|
|
|
|
int screenHeight = ctx.gpuCtx.height - SCREEN_MARGIN_Y * 2;
|
2023-09-17 13:41:35 +02:00
|
|
|
int bodyOffset = ctx.font.metrics.lineHeight + SCREEN_BLOCK_MARGIN;
|
2023-06-27 16:25:52 +02:00
|
|
|
int bodyHeight = screenHeight -
|
|
|
|
(bodyOffset + SCREEN_PROMPT_HEIGHT_MIN + SCREEN_BLOCK_MARGIN);
|
2023-06-26 15:23:47 +02:00
|
|
|
|
|
|
|
int scrollHeight = _textHeight - util::min(_textHeight, bodyHeight);
|
|
|
|
|
|
|
|
int oldValue = _scrollAnim.getTargetValue();
|
|
|
|
int value = oldValue;
|
|
|
|
|
|
|
|
if (
|
|
|
|
ctx.buttons.pressed(ui::BTN_LEFT) ||
|
|
|
|
(ctx.buttons.repeating(ui::BTN_LEFT) && (value > 0))
|
|
|
|
) {
|
|
|
|
if (value <= 0) {
|
|
|
|
value = scrollHeight;
|
|
|
|
ctx.sounds[SOUND_CLICK].play();
|
|
|
|
} else {
|
|
|
|
value -= util::min(SCROLL_AMOUNT, value);
|
|
|
|
ctx.sounds[SOUND_MOVE].play();
|
|
|
|
}
|
|
|
|
|
|
|
|
_scrollAnim.setValue(ctx.time, oldValue, value, SPEED_FASTEST);
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
ctx.buttons.pressed(ui::BTN_RIGHT) ||
|
|
|
|
(ctx.buttons.repeating(ui::BTN_RIGHT) && (value < scrollHeight))
|
|
|
|
) {
|
|
|
|
if (value >= scrollHeight) {
|
|
|
|
value = 0;
|
|
|
|
ctx.sounds[SOUND_CLICK].play();
|
|
|
|
} else {
|
|
|
|
value += util::min(SCROLL_AMOUNT, scrollHeight - value);
|
|
|
|
ctx.sounds[SOUND_MOVE].play();
|
|
|
|
}
|
|
|
|
|
|
|
|
_scrollAnim.setValue(ctx.time, oldValue, value, SPEED_FASTEST);
|
|
|
|
}
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ImageScreen::ImageScreen(void)
|
|
|
|
: _imageScale(1), _imagePadding(0), _title(nullptr), _prompt(nullptr) {}
|
|
|
|
|
|
|
|
void ImageScreen::draw(Context &ctx, bool active) const {
|
|
|
|
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
|
|
|
|
|
|
|
if (_image.width && _image.height) {
|
|
|
|
int x = ctx.gpuCtx.width / 2;
|
|
|
|
int y = ctx.gpuCtx.height / 2;
|
|
|
|
int width = _image.width * _imageScale / 2;
|
|
|
|
int height = _image.height * _imageScale / 2;
|
|
|
|
|
|
|
|
if (_prompt)
|
2023-09-17 13:41:35 +02:00
|
|
|
y -= (SCREEN_PROMPT_HEIGHT - ctx.font.metrics.lineHeight) / 2;
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
// Backdrop
|
|
|
|
if (_imagePadding) {
|
2023-06-08 00:30:50 +02:00
|
|
|
int _width = width + _imagePadding;
|
|
|
|
int _height = height + _imagePadding;
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
ctx.gpuCtx.drawRect(
|
2023-06-08 00:30:50 +02:00
|
|
|
x - _width, y - _height, _width * 2, _height * 2, _backdropColor
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Image
|
|
|
|
_image.drawScaled(
|
2023-06-22 21:42:50 +02:00
|
|
|
ctx.gpuCtx, x - width - 1, y - height - 1, width * 2, height * 2
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Text
|
|
|
|
gpu::Rect rect;
|
|
|
|
|
|
|
|
rect.x1 = SCREEN_MARGIN_X;
|
|
|
|
rect.y1 = SCREEN_MARGIN_Y;
|
|
|
|
rect.x2 = ctx.gpuCtx.width - SCREEN_MARGIN_X;
|
2023-09-17 13:41:35 +02:00
|
|
|
rect.y2 = SCREEN_MARGIN_Y + ctx.font.metrics.lineHeight;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
rect.y1 = ctx.gpuCtx.height - (SCREEN_MARGIN_Y + SCREEN_PROMPT_HEIGHT);
|
|
|
|
rect.y2 = ctx.gpuCtx.height - SCREEN_MARGIN_Y;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(ctx.gpuCtx, _prompt, rect, ctx.colors[COLOR_TEXT1], true);
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ListScreen::ListScreen(void)
|
|
|
|
: _listLength(0), _prompt(nullptr) {}
|
|
|
|
|
|
|
|
void ListScreen::_drawItems(Context &ctx) const {
|
|
|
|
int itemY = _scrollAnim.getValue(ctx.time);
|
|
|
|
int itemWidth = _getItemWidth(ctx);
|
|
|
|
int listHeight = _getListHeight(ctx);
|
|
|
|
|
|
|
|
gpu::Rect rect;
|
|
|
|
|
|
|
|
rect.x1 = LIST_BOX_PADDING + LIST_ITEM_PADDING;
|
|
|
|
rect.x2 = itemWidth - LIST_ITEM_PADDING;
|
2023-06-27 16:25:52 +02:00
|
|
|
//rect.y2 = listHeight;
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
for (int i = 0; (i < _listLength) && (itemY < listHeight); i++) {
|
2023-09-17 13:41:35 +02:00
|
|
|
int itemHeight = ctx.font.metrics.lineHeight + LIST_ITEM_PADDING * 2;
|
2023-06-27 16:25:52 +02:00
|
|
|
|
2023-05-30 18:08:52 +02:00
|
|
|
if (i == _activeItem)
|
2023-09-17 13:41:35 +02:00
|
|
|
itemHeight += ctx.font.metrics.lineHeight;
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
if ((itemY + itemHeight) >= 0) {
|
|
|
|
if (i == _activeItem) {
|
|
|
|
ctx.gpuCtx.drawRect(
|
|
|
|
LIST_BOX_PADDING, itemY, itemWidth, itemHeight,
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.colors[COLOR_HIGHLIGHT2]
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
ctx.gpuCtx.drawRect(
|
|
|
|
LIST_BOX_PADDING, itemY, _itemAnim.getValue(ctx.time),
|
2023-09-14 23:54:59 +02:00
|
|
|
itemHeight, ctx.colors[COLOR_HIGHLIGHT1]
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
|
2023-09-17 13:41:35 +02:00
|
|
|
rect.y1 = itemY + LIST_ITEM_PADDING + ctx.font.metrics.lineHeight;
|
|
|
|
rect.y2 = rect.y1 + ctx.font.metrics.lineHeight;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(
|
|
|
|
ctx.gpuCtx, _itemPrompt, rect, ctx.colors[COLOR_SUBTITLE]
|
|
|
|
);
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
rect.y1 = itemY + LIST_ITEM_PADDING;
|
2023-09-17 13:41:35 +02:00
|
|
|
rect.y2 = rect.y1 + ctx.font.metrics.lineHeight;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(
|
|
|
|
ctx.gpuCtx, _getItemName(ctx, i), rect, ctx.colors[COLOR_TITLE]
|
|
|
|
);
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
itemY += itemHeight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListScreen::show(Context &ctx, bool goBack) {
|
|
|
|
AnimatedScreen::show(ctx, goBack);
|
|
|
|
|
|
|
|
_activeItem = 0;
|
|
|
|
_scrollAnim.setValue(LIST_BOX_PADDING);
|
|
|
|
_itemAnim.setValue(_getItemWidth(ctx));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListScreen::draw(Context &ctx, bool active) const {
|
|
|
|
int screenWidth = ctx.gpuCtx.width - SCREEN_MARGIN_X * 2;
|
|
|
|
int screenHeight = ctx.gpuCtx.height - SCREEN_MARGIN_Y * 2;
|
|
|
|
int listHeight = _getListHeight(ctx);
|
|
|
|
|
|
|
|
_newLayer(
|
|
|
|
ctx, SCREEN_MARGIN_X, SCREEN_MARGIN_Y, screenWidth, screenHeight
|
|
|
|
);
|
|
|
|
|
|
|
|
// Text
|
|
|
|
gpu::Rect rect;
|
|
|
|
|
|
|
|
rect.x1 = 0;
|
|
|
|
rect.y1 = 0;
|
|
|
|
rect.x2 = screenWidth;
|
2023-09-17 13:41:35 +02:00
|
|
|
rect.y2 = ctx.font.metrics.lineHeight;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
rect.y1 = screenHeight - SCREEN_PROMPT_HEIGHT;
|
|
|
|
rect.y2 = screenHeight;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(ctx.gpuCtx, _prompt, rect, ctx.colors[COLOR_TEXT1], true);
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
_newLayer(
|
|
|
|
ctx, SCREEN_MARGIN_X,
|
2023-09-17 13:41:35 +02:00
|
|
|
SCREEN_MARGIN_Y + ctx.font.metrics.lineHeight + SCREEN_BLOCK_MARGIN,
|
2023-05-30 18:08:52 +02:00
|
|
|
screenWidth, listHeight
|
|
|
|
);
|
|
|
|
_setBlendMode(ctx, GP0_BLEND_SEMITRANS, true);
|
|
|
|
|
|
|
|
// List box
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.gpuCtx.drawRect(
|
|
|
|
0, 0, screenWidth / 2, listHeight, ctx.colors[COLOR_BOX1]
|
|
|
|
);
|
2023-05-30 18:08:52 +02:00
|
|
|
ctx.gpuCtx.drawGradientRectH(
|
2023-09-14 23:54:59 +02:00
|
|
|
screenWidth / 2, 0, screenWidth / 2, listHeight, ctx.colors[COLOR_BOX1],
|
|
|
|
ctx.colors[COLOR_BOX2]
|
2023-05-30 18:08:52 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (_listLength) {
|
|
|
|
_drawItems(ctx);
|
|
|
|
|
|
|
|
// Up/down arrow icons
|
|
|
|
gpu::RectWH iconRect;
|
|
|
|
|
2023-09-17 13:41:35 +02:00
|
|
|
iconRect.x = screenWidth -
|
|
|
|
(ctx.font.metrics.lineHeight + LIST_BOX_PADDING);
|
|
|
|
iconRect.w = ctx.font.metrics.lineHeight;
|
|
|
|
iconRect.h = ctx.font.metrics.lineHeight;
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
if (_activeItem) {
|
|
|
|
iconRect.y = LIST_BOX_PADDING;
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(
|
|
|
|
ctx.gpuCtx, CH_UP_ARROW, iconRect, ctx.colors[COLOR_TEXT1]
|
|
|
|
);
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
|
|
|
if (_activeItem < (_listLength - 1)) {
|
2023-09-17 13:41:35 +02:00
|
|
|
iconRect.y = listHeight -
|
|
|
|
(ctx.font.metrics.lineHeight + LIST_BOX_PADDING);
|
2023-09-14 23:54:59 +02:00
|
|
|
ctx.font.draw(
|
|
|
|
ctx.gpuCtx, CH_DOWN_ARROW, iconRect, ctx.colors[COLOR_TEXT1]
|
|
|
|
);
|
2023-05-30 18:08:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListScreen::update(Context &ctx) {
|
|
|
|
if (
|
|
|
|
ctx.buttons.pressed(ui::BTN_LEFT) ||
|
|
|
|
(ctx.buttons.repeating(ui::BTN_LEFT) && (_activeItem > 0))
|
|
|
|
) {
|
|
|
|
_activeItem--;
|
|
|
|
if (_activeItem < 0) {
|
|
|
|
_activeItem += _listLength;
|
|
|
|
ctx.sounds[SOUND_CLICK].play();
|
|
|
|
} else {
|
|
|
|
ctx.sounds[SOUND_MOVE].play();
|
|
|
|
}
|
|
|
|
|
|
|
|
_itemAnim.setValue(ctx.time, 0, _getItemWidth(ctx), SPEED_FAST);
|
|
|
|
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
ctx.buttons.pressed(ui::BTN_RIGHT) ||
|
|
|
|
(ctx.buttons.repeating(ui::BTN_RIGHT) && (_activeItem < (_listLength - 1)))
|
|
|
|
) {
|
|
|
|
_activeItem++;
|
|
|
|
if (_activeItem >= _listLength) {
|
|
|
|
_activeItem -= _listLength;
|
|
|
|
ctx.sounds[SOUND_CLICK].play();
|
|
|
|
} else {
|
|
|
|
ctx.sounds[SOUND_MOVE].play();
|
|
|
|
}
|
|
|
|
|
|
|
|
_itemAnim.setValue(ctx.time, 0, _getItemWidth(ctx), SPEED_FAST);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scroll the list if the selected item is not fully visible.
|
2023-09-17 13:41:35 +02:00
|
|
|
int itemHeight = ctx.font.metrics.lineHeight + LIST_ITEM_PADDING * 2;
|
|
|
|
int activeItemHeight = itemHeight + ctx.font.metrics.lineHeight;
|
2023-05-30 18:08:52 +02:00
|
|
|
|
|
|
|
int topOffset = _activeItem * itemHeight;
|
|
|
|
int bottomOffset = topOffset + activeItemHeight - _getListHeight(ctx);
|
|
|
|
int currentOffset = -_scrollAnim.getTargetValue();
|
|
|
|
|
|
|
|
if (topOffset < currentOffset)
|
|
|
|
_scrollAnim.setValue(ctx.time, LIST_BOX_PADDING - topOffset, SPEED_FAST);
|
|
|
|
else if (bottomOffset > currentOffset)
|
|
|
|
_scrollAnim.setValue(ctx.time, -(LIST_BOX_PADDING + bottomOffset), SPEED_FAST);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|