Add RTC time adjustment menu

This commit is contained in:
spicyjpeg 2024-04-20 11:55:32 +02:00
parent 3f259377ce
commit 88528c1e72
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
26 changed files with 794 additions and 389 deletions

View File

@ -94,6 +94,7 @@ addExecutable(
src/main/main.cpp
src/main/uibase.cpp
src/main/uicommon.cpp
src/main/uimodals.cpp
src/main/zs01.cpp
src/main/app/app.cpp
src/main/app/cartactions.cpp

View File

@ -323,6 +323,15 @@
"name": "View IDE device and filesystem information",
"prompt": "Display information about the connected IDE/ATAPI devices and mounted FAT filesystem (if any)."
},
"runExecutable": {
"name": "Run executable from hard drive",
"prompt": "Load and launch a System 573 executable file from the IDE hard drive or CF card connected as secondary drive (if any).",
"filePrompt": "Note that PlayStation executables built without proper System 573 support will not run unless the watchdog is manually disabled."
},
"setRTCTime": {
"name": "Set RTC date and time",
"prompt": "Adjust the current date and time. Note that the time will not persist after a power cycle if the RTC's internal battery is empty."
},
"setResolution": {
"name": "Change screen resolution",
"prompt": "Switch to a different screen resolution and aspect ratio. Some monitors and upscalers may not support all resolutions."
@ -331,11 +340,6 @@
"name": "About this tool",
"prompt": "View information about this tool, including open source licenses."
},
"runExecutable": {
"name": "Run executable from hard drive",
"prompt": "Load and launch a System 573 executable file from the IDE hard drive or CF card connected as secondary drive (if any).",
"filePrompt": "Note that PlayStation executables built without proper System 573 support will not run unless the watchdog is manually disabled."
},
"ejectCD": {
"name": "Eject CD-ROM",
"prompt": "Open the CD-ROM drive's tray. You may use this option if the drive's eject button is not easily accessible on your 573."
@ -383,6 +387,13 @@
"640x480i": "640x480 (4:3), interlaced"
},
"RTCTimeScreen": {
"title": "Set RTC date and time",
"body": "Enter the current date and time. Note that System 573 games only accept years in 1970-2069 range.\n\nUse {LEFT_BUTTON}{RIGHT_BUTTON} to move the cursor, hold {START_BUTTON} and use {LEFT_BUTTON}{RIGHT_BUTTON} to edit the highlighted field.",
"cancel": "Cancel",
"ok": "Confirm"
},
"StorageActionsScreen": {
"title": "{CART_ICON} Storage device options",
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",

View File

@ -3,6 +3,7 @@
#include <stdint.h>
#include "common/ide.hpp"
#include "common/io.hpp"
#include "common/util.hpp"
#include "vendor/diskio.h"
/* FatFs library API glue */
@ -87,5 +88,8 @@ extern "C" DRESULT disk_ioctl(uint8_t drive, uint8_t cmd, void *data) {
}
extern "C" uint32_t get_fattime(void) {
return io::getRTCTime();
util::Date date;
io::getRTCTime(date);
return date.toDOSTime();
}

View File

@ -2,6 +2,7 @@
#include <stddef.h>
#include <stdint.h>
#include "common/io.hpp"
#include "common/util.hpp"
#include "ps1/registers.h"
#include "ps1/registers573.h"
#include "ps1/system.h"
@ -63,32 +64,53 @@ uint32_t getJAMMAInputs(void) {
return inputs ^ 0x1fffffff;
}
uint32_t getRTCTime(void) {
void getRTCTime(util::Date &output) {
SYS573_RTC_CTRL |= SYS573_RTC_CTRL_READ;
int year = SYS573_RTC_YEAR, month = SYS573_RTC_MONTH, day = SYS573_RTC_DAY;
int hour = SYS573_RTC_HOUR, min = SYS573_RTC_MINUTE, sec = SYS573_RTC_SECOND;
auto second = SYS573_RTC_SECOND, minute = SYS573_RTC_MINUTE;
auto hour = SYS573_RTC_HOUR, day = SYS573_RTC_DAY;
auto month = SYS573_RTC_MONTH, year = SYS573_RTC_YEAR;
SYS573_RTC_CTRL &= ~SYS573_RTC_CTRL_READ;
year = (year & 15) + 10 * ((year >> 4) & 15); // 0-99
month = (month & 15) + 10 * ((month >> 4) & 1); // 1-12
day = (day & 15) + 10 * ((day >> 4) & 3); // 1-31
hour = (hour & 15) + 10 * ((hour >> 4) & 3); // 0-23
min = (min & 15) + 10 * ((min >> 4) & 7); // 0-59
sec = (sec & 15) + 10 * ((sec >> 4) & 7); // 0-59
output.year = (year & 15) + 10 * ((year >> 4) & 15); // 0-99
output.month = (month & 15) + 10 * ((month >> 4) & 1); // 1-12
output.day = (day & 15) + 10 * ((day >> 4) & 3); // 1-31
output.hour = (hour & 15) + 10 * ((hour >> 4) & 3); // 0-23
output.minute = (minute & 15) + 10 * ((minute >> 4) & 7); // 0-59
output.second = (second & 15) + 10 * ((second >> 4) & 7); // 0-59
// Return all values packed into a FAT/MS-DOS-style bitfield. Assume the
// year is always in 1995-2094 range.
int _year = (year >= 95) ? (year + 1900 - 1980) : (year + 2000 - 1980);
output.year += (output.year < 70) ? 2000 : 1900;
}
return 0
| (_year << 25)
| (month << 21)
| (day << 16)
| (hour << 11)
| (min << 5)
| (sec >> 1);
void setRTCTime(const util::Date &value, bool stop) {
//assert((value.year >= 1970) && (value.year <= 2069));
int _year = value.year % 100;
int weekday = value.getDayOfWeek();
int year = (_year % 10) | (((_year / 10) & 15) << 4);
int month = (value.month % 10) | (((value.month / 10) & 1) << 4);
int day = (value.day % 10) | (((value.day / 10) & 3) << 4);
int hour = (value.hour % 10) | (((value.hour / 10) & 3) << 4);
int minute = (value.minute % 10) | (((value.minute / 10) & 7) << 4);
int second = (value.second % 10) | (((value.second / 10) & 7) << 4);
SYS573_RTC_CTRL |= SYS573_RTC_CTRL_WRITE;
SYS573_RTC_SECOND = second
| (stop ? SYS573_RTC_SECOND_STOP : 0);
SYS573_RTC_MINUTE = minute;
SYS573_RTC_HOUR = hour;
SYS573_RTC_WEEKDAY = weekday
| SYS573_RTC_WEEKDAY_CENTURY
| SYS573_RTC_WEEKDAY_CENTURY_ENABLE;
SYS573_RTC_DAY = day
| SYS573_RTC_DAY_BATTERY_MONITOR;
SYS573_RTC_MONTH = month;
SYS573_RTC_YEAR = year;
SYS573_RTC_CTRL &= ~SYS573_RTC_CTRL_WRITE;
}
bool isRTCBatteryLow(void) {

View File

@ -3,6 +3,8 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "common/util.hpp"
#include "ps1/registers.h"
#include "ps1/registers573.h"
@ -159,7 +161,8 @@ static inline void setDIO1Wire(bool value) {
void init(void);
uint32_t getJAMMAInputs(void);
uint32_t getRTCTime(void);
void getRTCTime(util::Date &output);
void setRTCTime(const util::Date &value, bool stop = false);
bool isRTCBatteryLow(void);
bool loadBitstream(const uint8_t *data, size_t length);

View File

@ -31,6 +31,69 @@ Hash hash(const uint8_t *data, size_t length) {
return value;
}
/* Date and time class */
int Date::getDayOfWeek(void) const {
// See https://datatracker.ietf.org/doc/html/rfc3339#appendix-B
int _year = year, _month = month - 2;
if (_month <= 0) {
_month += 12;
_year--;
}
int century = _year / 100;
_year %= 100;
int weekday = 0
+ day
+ (_month * 26 - 2) / 10
+ _year
+ _year / 4
+ century / 4
+ century * 5;
return weekday % 7;
}
int Date::getMonthDayCount(void) const {
switch (month) {
case 2:
return isLeapYear() ? 29 : 28;
case 4:
case 6:
case 9:
case 11:
return 30;
default:
return 31;
}
}
uint32_t Date::toDOSTime(void) const {
int _year = year + 2000 - 1980;
if ((_year < 0) || (_year > 127))
return 0;
return 0
| (_year << 25)
| (month << 21)
| (day << 16)
| (hour << 11)
| (minute << 5)
| (second >> 1);
}
size_t Date::toString(char *output) const {
return sprintf(
output, "%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute,
second
);
}
/* Tween/animation classes */
template<typename T, typename E> void Tween<T, E>::setValue(

View File

@ -158,6 +158,29 @@ public:
}
};
/* Date and time class */
class Date {
public:
uint16_t year;
uint8_t month, day;
uint8_t hour, minute, second;
inline bool isLeapYear(void) const {
if (year % 4)
return false;
if (!(year % 100) && (year % 400))
return false;
return true;
}
int getDayOfWeek(void) const;
int getMonthDayCount(void) const;
uint32_t toDOSTime(void) const;
size_t toString(char *output) const;
};
/* Tween/animation classes */
static constexpr int TWEEN_UNIT = 1 << 12;

View File

@ -187,7 +187,11 @@ void App::_interruptHandler(void) {
_setupInterrupts();
_loadResources();
_backgroundLayer.text = "v" VERSION_STRING;
char dateString[24];
_backgroundLayer.leftText = dateString;
_backgroundLayer.rightText = "v" VERSION_STRING;
_ctx.background = &_backgroundLayer;
#ifdef ENABLE_LOG_BUFFER
_ctx.overlay = &_overlayLayer;
@ -195,6 +199,11 @@ void App::_interruptHandler(void) {
_ctx.show(_workerStatusScreen);
for (;;) {
util::Date date;
io::getRTCTime(date);
date.toString(dateString);
_ctx.update();
_ctx.draw();

View File

@ -60,6 +60,7 @@ class App {
friend class StorageInfoScreen;
friend class StorageActionsScreen;
friend class IDEInfoScreen;
friend class RTCTimeScreen;
friend class ResolutionScreen;
friend class AboutScreen;
friend class CartInfoScreen;
@ -84,6 +85,7 @@ private:
StorageInfoScreen _storageInfoScreen;
StorageActionsScreen _storageActionsScreen;
IDEInfoScreen _ideInfoScreen;
RTCTimeScreen _rtcTimeScreen;
ResolutionScreen _resolutionScreen;
AboutScreen _aboutScreen;
CartInfoScreen _cartInfoScreen;

View File

@ -306,8 +306,6 @@ void SystemIDEntryScreen::show(ui::Context &ctx, bool goBack) {
_buttons[1] = STR("SystemIDEntryScreen.ok");
_numButtons = 2;
_locked = false;
_bufferLength = 8;
_separator = '-';

View File

@ -3,10 +3,10 @@
#include <stddef.h>
#include "common/gpu.hpp"
#include "common/util.hpp"
#include "main/cartdata.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
#include "main/uimodals.hpp"
/* Unlocked cartridge screens */

View File

@ -343,8 +343,6 @@ void KeyEntryScreen::show(ui::Context &ctx, bool goBack) {
_buttons[1] = STR("KeyEntryScreen.ok");
_numButtons = 2;
_locked = false;
_bufferLength = 8;
_separator = '-';

View File

@ -3,6 +3,7 @@
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
#include "main/uimodals.hpp"
/* Pre-unlock cartridge screens */

View File

@ -99,6 +99,14 @@ static const MenuEntry _MENU_ENTRIES[]{
.name = "MainMenuScreen.ideInfo.name"_h,
.prompt = "MainMenuScreen.ideInfo.prompt"_h,
.target = &MainMenuScreen::ideInfo
}, {
.name = "MainMenuScreen.runExecutable.name"_h,
.prompt = "MainMenuScreen.runExecutable.prompt"_h,
.target = &MainMenuScreen::runExecutable
}, {
.name = "MainMenuScreen.setRTCTime.name"_h,
.prompt = "MainMenuScreen.setRTCTime.prompt"_h,
.target = &MainMenuScreen::setRTCTime
}, {
.name = "MainMenuScreen.setResolution.name"_h,
.prompt = "MainMenuScreen.setResolution.prompt"_h,
@ -107,10 +115,6 @@ static const MenuEntry _MENU_ENTRIES[]{
.name = "MainMenuScreen.about.name"_h,
.prompt = "MainMenuScreen.about.prompt"_h,
.target = &MainMenuScreen::about
}, {
.name = "MainMenuScreen.runExecutable.name"_h,
.prompt = "MainMenuScreen.runExecutable.prompt"_h,
.target = &MainMenuScreen::runExecutable
}, {
.name = "MainMenuScreen.ejectCD.name"_h,
.prompt = "MainMenuScreen.ejectCD.prompt"_h,
@ -143,14 +147,6 @@ void MainMenuScreen::ideInfo(ui::Context &ctx) {
ctx.show(APP->_ideInfoScreen, false, true);
}
void MainMenuScreen::setResolution(ui::Context &ctx) {
ctx.show(APP->_resolutionScreen, false, true);
}
void MainMenuScreen::about(ui::Context &ctx) {
ctx.show(APP->_aboutScreen, false, true);
}
void MainMenuScreen::runExecutable(ui::Context &ctx) {
APP->_filePickerScreen.setMessage(
*this,
@ -164,6 +160,18 @@ void MainMenuScreen::runExecutable(ui::Context &ctx) {
APP->_filePickerScreen.loadRootAndShow(ctx);
}
void MainMenuScreen::setRTCTime(ui::Context &ctx) {
ctx.show(APP->_rtcTimeScreen, false, true);
}
void MainMenuScreen::setResolution(ui::Context &ctx) {
ctx.show(APP->_resolutionScreen, false, true);
}
void MainMenuScreen::about(ui::Context &ctx) {
ctx.show(APP->_aboutScreen, false, true);
}
void MainMenuScreen::ejectCD(ui::Context &ctx) {
APP->_setupWorker(&App::_atapiEjectWorker);
ctx.show(APP->_workerStatusScreen, false, true);

View File

@ -3,6 +3,7 @@
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
#include "main/uimodals.hpp"
/* Main menu screens */
@ -33,9 +34,10 @@ public:
void cartInfo(ui::Context &ctx);
void storageInfo(ui::Context &ctx);
void ideInfo(ui::Context &ctx);
void runExecutable(ui::Context &ctx);
void setRTCTime(ui::Context &ctx);
void setResolution(ui::Context &ctx);
void about(ui::Context &ctx);
void runExecutable(ui::Context &ctx);
void ejectCD(ui::Context &ctx);
void reboot(ui::Context &ctx);

View File

@ -4,6 +4,7 @@
#include <stdio.h>
#include "common/file.hpp"
#include "common/ide.hpp"
#include "common/io.hpp"
#include "common/spu.hpp"
#include "common/util.hpp"
#include "main/app/app.hpp"
@ -107,6 +108,32 @@ void IDEInfoScreen::update(ui::Context &ctx) {
/* Misc. screens */
void RTCTimeScreen::show(ui::Context &ctx, bool goBack) {
_title = STR("RTCTimeScreen.title");
_body = STR("RTCTimeScreen.body");
_buttons[0] = STR("RTCTimeScreen.cancel");
_buttons[1] = STR("RTCTimeScreen.ok");
_numButtons = 2;
io::getRTCTime(_date);
DateEntryScreen::show(ctx, goBack);
}
void RTCTimeScreen::update(ui::Context &ctx) {
DateEntryScreen::update(ctx);
if (
ctx.buttons.pressed(ui::BTN_START) &&
(_activeButton >= _buttonIndexOffset)
) {
if (_activeButton == (_buttonIndexOffset + 1))
io::setRTCTime(_date);
ctx.show(APP->_mainMenuScreen, true, true);
}
}
struct Resolution {
public:
util::Hash name;

View File

@ -6,6 +6,7 @@
#include "common/util.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
#include "main/uimodals.hpp"
/* System information screens */
@ -20,6 +21,12 @@ public:
/* Misc. screens */
class RTCTimeScreen : public ui::DateEntryScreen {
public:
void show(ui::Context &ctx, bool goBack = false);
void update(ui::Context &ctx);
};
class ResolutionScreen : public ui::ListScreen {
protected:
const char *_getItemName(ui::Context &ctx, int index) const;

View File

@ -5,6 +5,7 @@
#include "common/util.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
#include "main/uimodals.hpp"
/* Modal screens */

View File

@ -6,6 +6,7 @@
#include "common/rom.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
#include "main/uimodals.hpp"
/* Storage device submenu */

View File

@ -216,17 +216,23 @@ void TiledBackground::draw(Context &ctx, bool active) const {
tile.draw(ctx.gpuCtx, x, y);
}
if (!text)
return;
gpu::RectWH rect;
int width = ctx.font.getStringWidth(text);
rect.x = ctx.gpuCtx.width - (8 + width);
rect.y = ctx.gpuCtx.height - (8 + ctx.font.metrics.lineHeight);
rect.w = width;
rect.h = ctx.font.metrics.lineHeight;
ctx.font.draw(ctx.gpuCtx, text, rect, ctx.colors[COLOR_TEXT2]);
if (leftText) {
rect.x = 8;
rect.w = ctx.gpuCtx.width - 16;
ctx.font.draw(ctx.gpuCtx, leftText, rect, ctx.colors[COLOR_TEXT2]);
}
if (rightText) {
int width = ctx.font.getStringWidth(rightText);
rect.x = ctx.gpuCtx.width - (8 + width);
rect.w = width;
ctx.font.draw(ctx.gpuCtx, rightText, rect, ctx.colors[COLOR_TEXT2]);
}
}
LogOverlay::LogOverlay(util::LogBuffer &buffer)

View File

@ -186,10 +186,10 @@ public:
class TiledBackground : public Layer {
public:
gpu::Image tile;
const char *text;
const char *leftText, *rightText;
inline TiledBackground(void)
: text(nullptr) {}
: leftText(nullptr), rightText(nullptr) {}
void draw(Context &ctx, bool active = true) const;
};

View File

@ -1,14 +1,13 @@
#include "common/defs.hpp"
#include "common/gpu.hpp"
#include "common/gpufont.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
#include "ps1/gpucmd.h"
namespace ui {
/* Common higher-level screens */
/* Common screens */
void PlaceholderScreen::draw(Context &ctx, bool active) const {
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
@ -17,268 +16,6 @@ void PlaceholderScreen::draw(Context &ctx, bool active) const {
);
}
MessageBoxScreen::MessageBoxScreen(void)
: ModalScreen(MODAL_WIDTH, MODAL_HEIGHT_FULL), _numButtons(0),
_buttonIndexOffset(0), _locked(false) {}
void MessageBoxScreen::show(Context &ctx, bool goBack) {
ModalScreen::show(ctx, goBack);
_activeButton = 0;
_buttonAnim.setValue(_getButtonWidth());
}
void MessageBoxScreen::draw(Context &ctx, bool active) const {
ModalScreen::draw(ctx, active);
if (!active || !_numButtons)
return;
int activeButton = _activeButton - _buttonIndexOffset;
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();
rect.h = rect.y + ctx.font.metrics.lineHeight;
//rect.h = BUTTON_HEIGHT - BUTTON_PADDING * 2;
for (int i = 0; i < _numButtons; i++) {
rect.x = buttonX +
(rect.w - ctx.font.getStringWidth(_buttons[i])) / 2;
if (_locked) {
ctx.gpuCtx.drawRect(
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
ctx.colors[COLOR_SHADOW], true
);
ctx.font.draw(
ctx.gpuCtx, _buttons[i], rect, ctx.colors[COLOR_TEXT2]
);
} else {
if (i == activeButton) {
ctx.gpuCtx.drawRect(
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
ctx.colors[COLOR_HIGHLIGHT2]
);
ctx.gpuCtx.drawRect(
buttonX, buttonY, _buttonAnim.getValue(ctx.time),
BUTTON_HEIGHT, ctx.colors[COLOR_HIGHLIGHT1]
);
} else {
ctx.gpuCtx.drawRect(
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
ctx.colors[COLOR_WINDOW3]
);
}
ctx.font.draw(
ctx.gpuCtx, _buttons[i], rect, ctx.colors[COLOR_TITLE]
);
}
buttonX += rect.w + BUTTON_SPACING;
}
}
void MessageBoxScreen::update(Context &ctx) {
if (_locked)
return;
int numButtons = _buttonIndexOffset + _numButtons;
if (
ctx.buttons.pressed(ui::BTN_LEFT) ||
(ctx.buttons.repeating(ui::BTN_LEFT) && (_activeButton > 0))
) {
_activeButton--;
if (_activeButton < 0) {
_activeButton += numButtons;
ctx.sounds[SOUND_CLICK].play();
} else {
ctx.sounds[SOUND_MOVE].play();
}
_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;
ctx.sounds[SOUND_CLICK].play();
} else {
ctx.sounds[SOUND_MOVE].play();
}
_buttonAnim.setValue(ctx.time, 0, _getButtonWidth(), SPEED_FASTEST);
}
}
HexEntryScreen::HexEntryScreen(void)
: _bufferLength(0) {
__builtin_memset(_buffer, 0, sizeof(_buffer));
}
void HexEntryScreen::show(Context &ctx, bool goBack) {
MessageBoxScreen::show(ctx, goBack);
_buttonIndexOffset = _bufferLength * 2;
//__builtin_memset(_buffer, 0, _bufferLength);
_charIndex = 0;
_cursorAnim.setValue(0);
}
void HexEntryScreen::draw(Context &ctx, bool active) const {
MessageBoxScreen::draw(ctx, active);
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(
MODAL_PADDING, boxY, boxWidth, BUTTON_HEIGHT, ctx.colors[COLOR_BOX1]
);
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(
textOffset + _cursorAnim.getValue(ctx.time),
boxY + BUTTON_HEIGHT / 2, digitWidth, BUTTON_HEIGHT / 2,
ctx.colors[COLOR_BOX1], ctx.colors[COLOR_HIGHLIGHT1]
);
// Text
rect.x1 = textOffset;
rect.y1 = boxY + BUTTON_PADDING;
rect.x2 = _width - MODAL_PADDING;
rect.y2 = boxY + BUTTON_PADDING + ctx.font.metrics.lineHeight;
ctx.font.draw(ctx.gpuCtx, text, rect, ctx.colors[COLOR_TITLE]);
// Highlighted digit
if (_activeButton < _buttonIndexOffset) {
text[0] = text[_charIndex];
text[1] = 0;
rect.x1 = textOffset + _cursorAnim.getTargetValue();
ctx.font.draw(ctx.gpuCtx, text, rect, ctx.colors[COLOR_SUBTITLE]);
}
}
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;
MessageBoxScreen::update(ctx);
// 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);
}
}
}
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(
barX, barY, fullBarWidth, PROGRESS_BAR_HEIGHT, ctx.colors[COLOR_WINDOW3]
);
ctx.gpuCtx.drawGradientRectH(
barX, barY, _progressBarAnim.getValue(ctx.time), PROGRESS_BAR_HEIGHT,
ctx.colors[COLOR_PROGRESS2], ctx.colors[COLOR_PROGRESS1]
);
}
TextScreen::TextScreen(void)
: _title(nullptr), _body(nullptr), _prompt(nullptr) {}

View File

@ -2,81 +2,19 @@
#pragma once
#include <stdint.h>
#include "common/gpufont.hpp"
#include "common/gpu.hpp"
#include "common/util.hpp"
#include "main/uibase.hpp"
namespace ui {
/* Common higher-level screens */
/* Common screens */
class PlaceholderScreen : public AnimatedScreen {
public:
void draw(Context &ctx, bool active) const;
};
class MessageBoxScreen : public ModalScreen {
private:
util::Tween<int, util::QuadOutEasing> _buttonAnim;
inline int _getButtonWidth(void) const {
return ((_width / 5) * 4) / _numButtons - BUTTON_SPACING;
}
protected:
int _numButtons, _activeButton, _buttonIndexOffset;
bool _locked;
const char *_buttons[4];
public:
MessageBoxScreen(void);
virtual void show(Context &ctx, bool goBack = false);
virtual void draw(Context &ctx, bool active = true) const;
virtual void update(Context &ctx);
};
class HexEntryScreen : public MessageBoxScreen {
private:
int _charIndex;
util::Tween<int, util::QuadOutEasing> _cursorAnim;
protected:
uint8_t _buffer[32];
char _separator;
int _bufferLength;
public:
HexEntryScreen(void);
virtual void show(Context &ctx, bool goBack = false);
virtual void draw(Context &ctx, bool active = true) const;
virtual void update(Context &ctx);
};
class ProgressScreen : public ModalScreen {
private:
util::Tween<int, util::QuadOutEasing> _progressBarAnim;
protected:
inline void _setProgress(Context &ctx, int part, int total) {
if (!total)
total = 1;
int totalWidth = _width - MODAL_PADDING * 2;
int partWidth = (totalWidth * part) / total;
if (_progressBarAnim.getTargetValue() != partWidth)
_progressBarAnim.setValue(ctx.time, partWidth, SPEED_FASTEST);
}
public:
ProgressScreen(void);
virtual void show(Context &ctx, bool goBack = false);
virtual void draw(Context &ctx, bool active = true) const;
};
class TextScreen : public AnimatedScreen {
private:
util::Tween<int, util::QuadOutEasing> _scrollAnim;

446
src/main/uimodals.cpp Normal file
View File

@ -0,0 +1,446 @@
#include <stddef.h>
#include <stdint.h>
#include "common/gpu.hpp"
#include "main/uibase.hpp"
#include "main/uimodals.hpp"
namespace ui {
MessageBoxScreen::MessageBoxScreen(void)
: ModalScreen(MODAL_WIDTH, MODAL_HEIGHT_FULL), _numButtons(0),
_buttonIndexOffset(0), _locked(false) {}
void MessageBoxScreen::show(Context &ctx, bool goBack) {
ModalScreen::show(ctx, goBack);
_activeButton = 0;
_buttonAnim.setValue(_getButtonWidth());
}
void MessageBoxScreen::draw(Context &ctx, bool active) const {
ModalScreen::draw(ctx, active);
if (!active || !_numButtons)
return;
int activeButton = _activeButton - _buttonIndexOffset;
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();
rect.h = rect.y + ctx.font.metrics.lineHeight;
//rect.h = BUTTON_HEIGHT - BUTTON_PADDING * 2;
for (int i = 0; i < _numButtons; i++) {
rect.x = buttonX +
(rect.w - ctx.font.getStringWidth(_buttons[i])) / 2;
if (_locked) {
ctx.gpuCtx.drawRect(
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
ctx.colors[COLOR_SHADOW], true
);
ctx.font.draw(
ctx.gpuCtx, _buttons[i], rect, ctx.colors[COLOR_TEXT2]
);
} else {
if (i == activeButton) {
ctx.gpuCtx.drawRect(
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
ctx.colors[COLOR_HIGHLIGHT2]
);
ctx.gpuCtx.drawRect(
buttonX, buttonY, _buttonAnim.getValue(ctx.time),
BUTTON_HEIGHT, ctx.colors[COLOR_HIGHLIGHT1]
);
} else {
ctx.gpuCtx.drawRect(
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
ctx.colors[COLOR_WINDOW3]
);
}
ctx.font.draw(
ctx.gpuCtx, _buttons[i], rect, ctx.colors[COLOR_TITLE]
);
}
buttonX += rect.w + BUTTON_SPACING;
}
}
void MessageBoxScreen::update(Context &ctx) {
if (_locked)
return;
int numButtons = _buttonIndexOffset + _numButtons;
if (
ctx.buttons.pressed(ui::BTN_LEFT) ||
(ctx.buttons.repeating(ui::BTN_LEFT) && (_activeButton > 0))
) {
_activeButton--;
if (_activeButton < 0) {
_activeButton += numButtons;
ctx.sounds[SOUND_CLICK].play();
} else {
ctx.sounds[SOUND_MOVE].play();
}
_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;
ctx.sounds[SOUND_CLICK].play();
} else {
ctx.sounds[SOUND_MOVE].play();
}
_buttonAnim.setValue(ctx.time, 0, _getButtonWidth(), SPEED_FASTEST);
}
}
HexEntryScreen::HexEntryScreen(void)
: _bufferLength(0) {
__builtin_memset(_buffer, 0, sizeof(_buffer));
}
void HexEntryScreen::show(Context &ctx, bool goBack) {
MessageBoxScreen::show(ctx, goBack);
//__builtin_memset(_buffer, 0, _bufferLength);
_buttonIndexOffset = _bufferLength * 2;
_charWidth = ctx.font.getCharacterWidth('0');
_separatorWidth = ctx.font.getCharacterWidth(_separator);
_stringWidth = 0
+ _charWidth * (_bufferLength * 2)
+ _separatorWidth * (_bufferLength - 1);
_cursorAnim.setValue(0);
}
void HexEntryScreen::draw(Context &ctx, bool active) const {
MessageBoxScreen::draw(ctx, active);
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(
MODAL_PADDING, boxY, boxWidth, BUTTON_HEIGHT, ctx.colors[COLOR_BOX1]
);
char string[128];
gpu::Rect rect;
util::hexToString(string, _buffer, _bufferLength, _separator);
int stringOffset = MODAL_PADDING + (boxWidth - _stringWidth) / 2;
int charIndex = _activeButton + _activeButton / 2;
// Cursor
if (_activeButton < _buttonIndexOffset)
ctx.gpuCtx.drawGradientRectV(
stringOffset + _cursorAnim.getValue(ctx.time),
boxY + BUTTON_HEIGHT / 2, _charWidth, BUTTON_HEIGHT / 2,
ctx.colors[COLOR_BOX1], ctx.colors[COLOR_HIGHLIGHT1]
);
// Current string
rect.x1 = stringOffset;
rect.y1 = boxY + BUTTON_PADDING;
rect.x2 = _width - MODAL_PADDING;
rect.y2 = boxY + BUTTON_PADDING + ctx.font.metrics.lineHeight;
ctx.font.draw(ctx.gpuCtx, string, rect, ctx.colors[COLOR_TITLE]);
// Highlighted field
if (_activeButton < _buttonIndexOffset) {
auto ptr = &string[charIndex];
ptr[1] = 0;
rect.x1 = stringOffset + _cursorAnim.getTargetValue();
ctx.font.draw(ctx.gpuCtx, ptr, rect, ctx.colors[COLOR_SUBTITLE]);
}
}
void HexEntryScreen::update(Context &ctx) {
if (
ctx.buttons.held(ui::BTN_START) && (_activeButton < _buttonIndexOffset)
) {
auto ptr = &_buffer[_activeButton / 2];
int value = (_activeButton % 2) ? (*ptr & 0x0f) : (*ptr >> 4);
if (
ctx.buttons.pressed(ui::BTN_LEFT) ||
(ctx.buttons.repeating(ui::BTN_LEFT) && (value > 0))
) {
if (--value < 0) {
value = 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) && (value < 0xf))
) {
if (++value > 0xf) {
value = 0;
ctx.sounds[SOUND_CLICK].play();
} else {
ctx.sounds[SOUND_MOVE].play();
}
}
if (_activeButton % 2)
*ptr = (*ptr & 0xf0) | value;
else
*ptr = (*ptr & 0x0f) | (value << 4);
} else {
int oldActive = _activeButton;
MessageBoxScreen::update(ctx);
// Update the cursor's position if necessary.
if (oldActive != _activeButton) {
int cursorX = 0
+ _charWidth * _activeButton
+ _separatorWidth * (_activeButton / 2);
_cursorAnim.setValue(ctx.time, cursorX, SPEED_FASTEST);
}
}
}
struct DateField {
public:
size_t offset;
uint16_t minValue, maxValue;
};
static const DateField _DATE_FIELDS[]{
{
.offset = offsetof(util::Date, year),
.minValue = 1970,
.maxValue = 2069
}, {
.offset = offsetof(util::Date, month),
.minValue = 1,
.maxValue = 12
}, {
.offset = offsetof(util::Date, day),
.minValue = 1,
.maxValue = 31
}, {
.offset = offsetof(util::Date, hour),
.minValue = 0,
.maxValue = 23
}, {
.offset = offsetof(util::Date, minute),
.minValue = 0,
.maxValue = 59
}, {
.offset = offsetof(util::Date, second),
.minValue = 0,
.maxValue = 59
}
};
DateEntryScreen::DateEntryScreen(void) {
_date.year = 2000;
_date.month = 1;
_date.day = 1;
_date.hour = 0;
_date.minute = 0;
_date.second = 0;
}
void DateEntryScreen::show(Context &ctx, bool goBack) {
MessageBoxScreen::show(ctx, goBack);
_buttonIndexOffset = 6;
_charWidth = ctx.font.getCharacterWidth('0');
int dateSepWidth = ctx.font.getCharacterWidth('-');
int spaceWidth = ctx.font.metrics.spaceWidth;
int timeSepWidth = ctx.font.getCharacterWidth(':');
_fieldOffsets[0] = 0;
_fieldOffsets[1] = _fieldOffsets[0] + _charWidth * 4 + dateSepWidth;
_fieldOffsets[2] = _fieldOffsets[1] + _charWidth * 2 + dateSepWidth;
_fieldOffsets[3] = _fieldOffsets[2] + _charWidth * 2 + spaceWidth;
_fieldOffsets[4] = _fieldOffsets[3] + _charWidth * 2 + timeSepWidth;
_fieldOffsets[5] = _fieldOffsets[4] + _charWidth * 2 + timeSepWidth;
_stringWidth = _fieldOffsets[5] + _charWidth * 2;
_cursorAnim.setValue(0);
}
void DateEntryScreen::draw(Context &ctx, bool active) const {
MessageBoxScreen::draw(ctx, active);
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(
MODAL_PADDING, boxY, boxWidth, BUTTON_HEIGHT, ctx.colors[COLOR_BOX1]
);
char string[24];
gpu::Rect rect;
_date.toString(string);
int stringOffset = MODAL_PADDING + (boxWidth - _stringWidth) / 2;
int charIndex = _activeButton * 3;
int fieldLength = 2;
// The first field (year) has 4 digits, while all others have 2.
if (_activeButton)
charIndex += 2;
else
fieldLength += 2;
// Cursor
if (_activeButton < _buttonIndexOffset)
ctx.gpuCtx.drawGradientRectV(
stringOffset + _cursorAnim.getValue(ctx.time),
boxY + BUTTON_HEIGHT / 2, _charWidth * fieldLength,
BUTTON_HEIGHT / 2,ctx.colors[COLOR_BOX1],
ctx.colors[COLOR_HIGHLIGHT1]
);
// Current string
rect.x1 = stringOffset;
rect.y1 = boxY + BUTTON_PADDING;
rect.x2 = _width - MODAL_PADDING;
rect.y2 = boxY + BUTTON_PADDING + ctx.font.metrics.lineHeight;
ctx.font.draw(ctx.gpuCtx, string, rect, ctx.colors[COLOR_TITLE]);
// Highlighted field
if (_activeButton < _buttonIndexOffset) {
auto ptr = &string[charIndex];
ptr[fieldLength] = 0;
rect.x1 = stringOffset + _cursorAnim.getTargetValue();
ctx.font.draw(ctx.gpuCtx, ptr, rect, ctx.colors[COLOR_SUBTITLE]);
}
}
void DateEntryScreen::update(Context &ctx) {
if (
ctx.buttons.held(ui::BTN_START) && (_activeButton < _buttonIndexOffset)
) {
auto &field = _DATE_FIELDS[_activeButton];
int value;
// The year is the only 16-bit field.
if (!_activeButton)
value = _date.year;
else
value = *reinterpret_cast<uint8_t *>(
reinterpret_cast<uintptr_t>(&_date) + field.offset
);
if (
ctx.buttons.pressed(ui::BTN_LEFT) ||
(ctx.buttons.repeating(ui::BTN_LEFT) && (value > field.minValue))
) {
if (--value < field.minValue) {
value = field.maxValue;
ctx.sounds[SOUND_CLICK].play();
} else {
ctx.sounds[SOUND_MOVE].play();
}
}
if (
ctx.buttons.pressed(ui::BTN_RIGHT) ||
(ctx.buttons.repeating(ui::BTN_RIGHT) && (value < field.maxValue))
) {
if (++value > field.maxValue) {
value = field.minValue;
ctx.sounds[SOUND_CLICK].play();
} else {
ctx.sounds[SOUND_MOVE].play();
}
}
if (!_activeButton)
_date.year = value;
else
*reinterpret_cast<uint8_t *>(
reinterpret_cast<uintptr_t>(&_date) + field.offset
) = value;
// The day field must be fixed up after any date change.
int maxDayValue = _date.getMonthDayCount();
if (_date.day > maxDayValue)
_date.day = maxDayValue;
} else {
int oldActive = _activeButton;
MessageBoxScreen::update(ctx);
// Update the cursor's position if necessary.
if (oldActive != _activeButton)
_cursorAnim.setValue(
ctx.time, _fieldOffsets[_activeButton], SPEED_FASTEST
);
}
}
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(
barX, barY, fullBarWidth, PROGRESS_BAR_HEIGHT, ctx.colors[COLOR_WINDOW3]
);
ctx.gpuCtx.drawGradientRectH(
barX, barY, _progressBarAnim.getValue(ctx.time), PROGRESS_BAR_HEIGHT,
ctx.colors[COLOR_PROGRESS2], ctx.colors[COLOR_PROGRESS1]
);
}
}

90
src/main/uimodals.hpp Normal file
View File

@ -0,0 +1,90 @@
#pragma once
#include <stdint.h>
#include "common/util.hpp"
#include "main/uibase.hpp"
namespace ui {
/* Common modal screens */
class MessageBoxScreen : public ModalScreen {
private:
util::Tween<int, util::QuadOutEasing> _buttonAnim;
inline int _getButtonWidth(void) const {
return ((_width / 5) * 4) / _numButtons - BUTTON_SPACING;
}
protected:
int _numButtons, _activeButton, _buttonIndexOffset;
bool _locked;
const char *_buttons[4];
public:
MessageBoxScreen(void);
virtual void show(Context &ctx, bool goBack = false);
virtual void draw(Context &ctx, bool active = true) const;
virtual void update(Context &ctx);
};
class HexEntryScreen : public MessageBoxScreen {
private:
uint8_t _charWidth, _separatorWidth, _stringWidth;
util::Tween<int, util::QuadOutEasing> _cursorAnim;
protected:
uint8_t _buffer[32];
char _separator;
int _bufferLength;
public:
HexEntryScreen(void);
virtual void show(Context &ctx, bool goBack = false);
virtual void draw(Context &ctx, bool active = true) const;
virtual void update(Context &ctx);
};
class DateEntryScreen : public MessageBoxScreen {
private:
uint8_t _charWidth, _stringWidth, _fieldOffsets[6];
util::Tween<int, util::QuadOutEasing> _cursorAnim;
protected:
util::Date _date;
public:
DateEntryScreen(void);
virtual void show(Context &ctx, bool goBack = false);
virtual void draw(Context &ctx, bool active = true) const;
virtual void update(Context &ctx);
};
class ProgressScreen : public ModalScreen {
private:
util::Tween<int, util::QuadOutEasing> _progressBarAnim;
protected:
inline void _setProgress(Context &ctx, int part, int total) {
if (!total)
total = 1;
int totalWidth = _width - MODAL_PADDING * 2;
int partWidth = (totalWidth * part) / total;
if (_progressBarAnim.getTargetValue() != partWidth)
_progressBarAnim.setValue(ctx.time, partWidth, SPEED_FASTEST);
}
public:
ProgressScreen(void);
virtual void show(Context &ctx, bool goBack = false);
virtual void draw(Context &ctx, bool active = true) const;
};
}

View File

@ -91,6 +91,13 @@ typedef enum {
SYS573_RTC_SECOND_STOP = 1 << 7
} Sys573RTCSecondFlag;
typedef enum {
SYS573_RTC_WEEKDAY_UNITS_BITMASK = 7 << 0,
SYS573_RTC_WEEKDAY_CENTURY = 1 << 4,
SYS573_RTC_WEEKDAY_CENTURY_ENABLE = 1 << 5,
SYS573_RTC_WEEKDAY_FREQUENCY_TEST = 1 << 6
} Sys573RTCWeekdayFlag;
typedef enum {
SYS573_RTC_DAY_UNITS_BITMASK = 15 << 0,
SYS573_RTC_DAY_TENS_BITMASK = 3 << 4,