diff --git a/CMakeLists.txt b/CMakeLists.txt index c3f121f..2570ecb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/assets/app.strings.json b/assets/app.strings.json index 56fb1a2..8ccd03a 100644 --- a/assets/app.strings.json +++ b/assets/app.strings.json @@ -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", diff --git a/src/common/ideglue.cpp b/src/common/ideglue.cpp index 38ea74d..91e9310 100644 --- a/src/common/ideglue.cpp +++ b/src/common/ideglue.cpp @@ -3,6 +3,7 @@ #include #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(); } diff --git a/src/common/io.cpp b/src/common/io.cpp index 03da3f7..4f0b145 100644 --- a/src/common/io.cpp +++ b/src/common/io.cpp @@ -2,6 +2,7 @@ #include #include #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) { diff --git a/src/common/io.hpp b/src/common/io.hpp index f75066f..b2e2493 100644 --- a/src/common/io.hpp +++ b/src/common/io.hpp @@ -3,6 +3,8 @@ #include #include +#include +#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); diff --git a/src/common/util.cpp b/src/common/util.cpp index a8ca7c4..3c66eb5 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -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 void Tween::setValue( diff --git a/src/common/util.hpp b/src/common/util.hpp index 2f70ff2..28f1a0d 100644 --- a/src/common/util.hpp +++ b/src/common/util.hpp @@ -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; diff --git a/src/main/app/app.cpp b/src/main/app/app.cpp index e8fb63b..0d87c27 100644 --- a/src/main/app/app.cpp +++ b/src/main/app/app.cpp @@ -187,14 +187,23 @@ void App::_interruptHandler(void) { _setupInterrupts(); _loadResources(); - _backgroundLayer.text = "v" VERSION_STRING; - _ctx.background = &_backgroundLayer; + char dateString[24]; + + _backgroundLayer.leftText = dateString; + _backgroundLayer.rightText = "v" VERSION_STRING; + + _ctx.background = &_backgroundLayer; #ifdef ENABLE_LOG_BUFFER - _ctx.overlay = &_overlayLayer; + _ctx.overlay = &_overlayLayer; #endif _ctx.show(_workerStatusScreen); for (;;) { + util::Date date; + + io::getRTCTime(date); + date.toString(dateString); + _ctx.update(); _ctx.draw(); diff --git a/src/main/app/app.hpp b/src/main/app/app.hpp index 55f9dc4..54c09f2 100644 --- a/src/main/app/app.hpp +++ b/src/main/app/app.hpp @@ -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; diff --git a/src/main/app/cartactions.cpp b/src/main/app/cartactions.cpp index 47d6239..fbe1f21 100644 --- a/src/main/app/cartactions.cpp +++ b/src/main/app/cartactions.cpp @@ -305,9 +305,7 @@ void SystemIDEntryScreen::show(ui::Context &ctx, bool goBack) { _buttons[0] = STR("SystemIDEntryScreen.cancel"); _buttons[1] = STR("SystemIDEntryScreen.ok"); - _numButtons = 2; - _locked = false; - + _numButtons = 2; _bufferLength = 8; _separator = '-'; diff --git a/src/main/app/cartactions.hpp b/src/main/app/cartactions.hpp index 728bff3..3012c71 100644 --- a/src/main/app/cartactions.hpp +++ b/src/main/app/cartactions.hpp @@ -3,10 +3,10 @@ #include #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 */ diff --git a/src/main/app/cartunlock.cpp b/src/main/app/cartunlock.cpp index 4c38865..4e3cd11 100644 --- a/src/main/app/cartunlock.cpp +++ b/src/main/app/cartunlock.cpp @@ -342,9 +342,7 @@ void KeyEntryScreen::show(ui::Context &ctx, bool goBack) { _buttons[0] = STR("KeyEntryScreen.cancel"); _buttons[1] = STR("KeyEntryScreen.ok"); - _numButtons = 2; - _locked = false; - + _numButtons = 2; _bufferLength = 8; _separator = '-'; diff --git a/src/main/app/cartunlock.hpp b/src/main/app/cartunlock.hpp index ee1aa9b..c6b13cc 100644 --- a/src/main/app/cartunlock.hpp +++ b/src/main/app/cartunlock.hpp @@ -3,6 +3,7 @@ #include "main/uibase.hpp" #include "main/uicommon.hpp" +#include "main/uimodals.hpp" /* Pre-unlock cartridge screens */ diff --git a/src/main/app/main.cpp b/src/main/app/main.cpp index f70c8a9..40b24bc 100644 --- a/src/main/app/main.cpp +++ b/src/main/app/main.cpp @@ -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); diff --git a/src/main/app/main.hpp b/src/main/app/main.hpp index 979e901..5bf5fa7 100644 --- a/src/main/app/main.hpp +++ b/src/main/app/main.hpp @@ -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); diff --git a/src/main/app/misc.cpp b/src/main/app/misc.cpp index 65636bf..0a11522 100644 --- a/src/main/app/misc.cpp +++ b/src/main/app/misc.cpp @@ -4,6 +4,7 @@ #include #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; diff --git a/src/main/app/misc.hpp b/src/main/app/misc.hpp index 3d1915d..1afaf11 100644 --- a/src/main/app/misc.hpp +++ b/src/main/app/misc.hpp @@ -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; diff --git a/src/main/app/modals.hpp b/src/main/app/modals.hpp index ebfeac6..e4269cf 100644 --- a/src/main/app/modals.hpp +++ b/src/main/app/modals.hpp @@ -5,6 +5,7 @@ #include "common/util.hpp" #include "main/uibase.hpp" #include "main/uicommon.hpp" +#include "main/uimodals.hpp" /* Modal screens */ diff --git a/src/main/app/romactions.hpp b/src/main/app/romactions.hpp index c53e748..bb3d8c5 100644 --- a/src/main/app/romactions.hpp +++ b/src/main/app/romactions.hpp @@ -6,6 +6,7 @@ #include "common/rom.hpp" #include "main/uibase.hpp" #include "main/uicommon.hpp" +#include "main/uimodals.hpp" /* Storage device submenu */ diff --git a/src/main/uibase.cpp b/src/main/uibase.cpp index 6ede74b..9b7d82b 100644 --- a/src/main/uibase.cpp +++ b/src/main/uibase.cpp @@ -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]); + rect.y = ctx.gpuCtx.height - (8 + ctx.font.metrics.lineHeight); + rect.h = ctx.font.metrics.lineHeight; + + 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) diff --git a/src/main/uibase.hpp b/src/main/uibase.hpp index 56a0ac9..54aa4a7 100644 --- a/src/main/uibase.hpp +++ b/src/main/uibase.hpp @@ -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; }; diff --git a/src/main/uicommon.cpp b/src/main/uicommon.cpp index e4ffcc3..44a4e6e 100644 --- a/src/main/uicommon.cpp +++ b/src/main/uicommon.cpp @@ -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) {} @@ -555,7 +292,7 @@ void ListScreen::update(Context &ctx) { } _itemAnim.setValue(ctx.time, 0, _getItemWidth(ctx), SPEED_FAST); - + } if ( ctx.buttons.pressed(ui::BTN_RIGHT) || diff --git a/src/main/uicommon.hpp b/src/main/uicommon.hpp index bddca17..5c5f0ff 100644 --- a/src/main/uicommon.hpp +++ b/src/main/uicommon.hpp @@ -2,81 +2,19 @@ #pragma once #include -#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 _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 _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 _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 _scrollAnim; diff --git a/src/main/uimodals.cpp b/src/main/uimodals.cpp new file mode 100644 index 0000000..b69dc5b --- /dev/null +++ b/src/main/uimodals.cpp @@ -0,0 +1,446 @@ + +#include +#include +#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( + reinterpret_cast(&_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( + reinterpret_cast(&_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] + ); +} + +} diff --git a/src/main/uimodals.hpp b/src/main/uimodals.hpp new file mode 100644 index 0000000..9425cfe --- /dev/null +++ b/src/main/uimodals.hpp @@ -0,0 +1,90 @@ + +#pragma once + +#include +#include "common/util.hpp" +#include "main/uibase.hpp" + +namespace ui { + +/* Common modal screens */ + +class MessageBoxScreen : public ModalScreen { +private: + util::Tween _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 _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 _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 _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; +}; + +} diff --git a/src/ps1/registers573.h b/src/ps1/registers573.h index e927eeb..4f53fbd 100644 --- a/src/ps1/registers573.h +++ b/src/ps1/registers573.h @@ -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,