Add screenshot functionality

This commit is contained in:
spicyjpeg 2024-04-20 16:50:23 +02:00
parent 88528c1e72
commit d85582d9b5
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
25 changed files with 351 additions and 128 deletions

Binary file not shown.

View File

@ -73,6 +73,12 @@
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/click.vag",
"compression": "none"
},
{
"type": "binary",
"name": "assets/sounds/screenshot.vag",
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/screenshot.vag",
"compression": "none"
},
{
"type": "palette",
"name": "assets/app.palette",

View File

@ -4,6 +4,8 @@
#include <stdint.h>
#include <stdio.h>
#include "common/file.hpp"
#include "common/gpu.hpp"
#include "common/util.hpp"
#include "ps1/pcdrv.h"
#include "vendor/ff.h"
#include "vendor/miniz.h"
@ -256,6 +258,8 @@ size_t Provider::saveData(const void *input, size_t length, const char *path) {
#endif
}
// TODO: these *really* belong somewhere else
size_t Provider::loadTIM(gpu::Image &output, const char *path) {
util::Data data;
@ -308,6 +312,89 @@ size_t Provider::loadVAG(spu::Sound &output, const char *path) {
return data.length;
}
struct [[gnu::packed]] BMPHeader {
public:
uint16_t magic;
uint32_t fileLength;
uint8_t _reserved[4];
uint32_t dataOffset;
uint32_t headerLength, width, height;
uint16_t numPlanes, bpp;
uint32_t compType, dataLength, ppmX, ppmY, numColors, numColors2;
inline void init(int _width, int _height, int _bpp) {
util::clear(*this);
size_t length = _width * _height * _bpp / 8;
magic = 0x4d42;
fileLength = sizeof(BMPHeader) + length;
dataOffset = sizeof(BMPHeader);
headerLength = sizeof(BMPHeader) - offsetof(BMPHeader, headerLength);
width = _width;
height = _height;
numPlanes = 1;
bpp = _bpp;
dataLength = length;
}
};
size_t Provider::saveVRAMBMP(gpu::RectWH &rect, const char *path) {
#ifdef ENABLE_FILE_WRITING
auto _file = openFile(path, WRITE | ALLOW_CREATE);
if (!_file)
return 0;
BMPHeader header;
header.init(rect.w, rect.h, 16);
size_t length = _file->write(&header, sizeof(header));
util::Data buffer;
if (buffer.allocate(rect.w * 2 + 32)) {
// Read the image from VRAM one line at a time from the bottom up, as
// the BMP format stores lines in reversed order.
gpu::RectWH slice;
slice.x = rect.x;
slice.w = rect.w;
slice.h = 1;
for (int y = rect.y + rect.h - 1; y >= rect.y; y--) {
slice.y = y;
auto lineLength = gpu::download(slice, buffer.ptr, true);
// BMP stores channels in BGR order as opposed to RGB, so the red
// and blue channels must be swapped.
auto ptr = buffer.as<uint16_t>();
for (int i = lineLength; i > 0; i -= 2) {
uint16_t value = *ptr, newValue;
newValue = (value & (31 << 5));
newValue |= (value & (31 << 10)) >> 10;
newValue |= (value & (31 << 0)) << 10;
*(ptr++) = newValue;
}
length += _file->write(buffer.ptr, lineLength);
}
buffer.destroy();
}
_file->close();
delete _file;
return length;
#else
return 0;
#endif
}
bool HostProvider::init(void) {
int error = pcdrvInit();

View File

@ -148,8 +148,10 @@ public:
virtual size_t loadData(util::Data &output, const char *path);
virtual size_t loadData(void *output, size_t length, const char *path);
virtual size_t saveData(const void *input, size_t length, const char *path);
size_t loadTIM(gpu::Image &output, const char *path);
size_t loadVAG(spu::Sound &output, const char *path);
size_t saveVRAMBMP(gpu::RectWH &rect, const char *path);
};
class HostProvider : public Provider {

View File

@ -54,6 +54,44 @@ size_t upload(const RectWH &rect, const void *data, bool wait) {
return length * _DMA_CHUNK_SIZE * 4;
}
size_t download(const RectWH &rect, void *data, bool wait) {
size_t length = (rect.w * rect.h) / 2;
util::assertAligned<uint32_t>(data);
//assert(!(length % _DMA_CHUNK_SIZE));
length = (length + _DMA_CHUNK_SIZE - 1) / _DMA_CHUNK_SIZE;
if (!waitForDMATransfer(DMA_GPU, _DMA_TIMEOUT))
return 0;
auto enable = disableInterrupts();
GPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_NONE);
while (!(GPU_GP1 & GP1_STAT_CMD_READY))
__asm__ volatile("");
GPU_GP0 = gp0_flushCache();
GPU_GP0 = gp0_vramRead();
GPU_GP0 = gp0_xy(rect.x, rect.y);
GPU_GP0 = gp0_xy(rect.w, rect.h);
GPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_READ);
while (!(GPU_GP1 & GP1_STAT_READ_READY))
__asm__ volatile("");
DMA_MADR(DMA_GPU) = reinterpret_cast<uint32_t>(data);
DMA_BCR (DMA_GPU) = _DMA_CHUNK_SIZE | (length << 16);
DMA_CHCR(DMA_GPU) = DMA_CHCR_READ | DMA_CHCR_MODE_SLICE | DMA_CHCR_ENABLE;
if (enable)
enableInterrupts();
if (wait)
waitForDMATransfer(DMA_GPU, _DMA_TIMEOUT);
return length * _DMA_CHUNK_SIZE * 4;
}
/* Rendering context */
void Context::_applyResolution(

View File

@ -52,6 +52,7 @@ static inline void enableDisplay(bool enable) {
}
size_t upload(const RectWH &rect, const void *data, bool wait);
size_t download(const RectWH &rect, void *data, bool wait);
/* Rendering context */
@ -93,6 +94,15 @@ public:
) : _lastTexpage(0) {
setResolution(mode, width, height, forceInterlace, sideBySide);
}
inline void getVRAMClipRect(RectWH &output) const {
auto &clip = _buffers[_currentBuffer ^ 1].clip;
output.x = clip.x1;
output.y = clip.y1;
output.w = width;
output.h = height;
}
inline void newLayer(int x, int y) {
newLayer(x, y, width, height);
}

View File

@ -3,6 +3,7 @@
#include <stddef.h>
#include <stdint.h>
#include "common/util.hpp"
#include "ps1/registers573.h"
namespace ide {
@ -241,17 +242,14 @@ public:
uint8_t param[11];
uint8_t _reserved[4];
inline void clear(void) {
__builtin_memset(this, 0, sizeof(Packet));
}
inline void setStartStopUnit(ATAPIStartStopMode mode) {
clear();
util::clear(*this);
command = ATAPI_START_STOP_UNIT;
param[3] = mode & 3;
}
inline void setRead(uint32_t lba, size_t count) {
clear();
util::clear(*this);
command = ATAPI_READ12;
param[1] = (lba >> 24) & 0xff;
@ -264,7 +262,7 @@ public:
param[8] = (count >> 0) & 0xff;
}
inline void setSetCDSpeed(uint16_t value) {
clear();
util::clear(*this);
command = ATAPI_SET_CD_SPEED;
param[1] = (value >> 8) & 0xff;

View File

@ -53,6 +53,10 @@ template<typename T, typename X> static inline void assertAligned(X *ptr) {
//assert(!(reinterpret_cast<uintptr_t>(ptr) % alignof(T)));
}
template<typename T> static inline void clear(T &obj, uint8_t value = 0) {
__builtin_memset(&obj, value, sizeof(obj));
}
template<typename T> static constexpr inline size_t countOf(T &array) {
return sizeof(array) / sizeof(array[0]);
}

View File

@ -1,6 +1,7 @@
#include "common/defs.hpp"
#include "common/file.hpp"
#include "common/gpu.hpp"
#include "common/io.hpp"
#include "common/util.hpp"
#include "main/app/app.hpp"
@ -62,7 +63,7 @@ static constexpr size_t _WORKER_STACK_SIZE = 0x20000;
App::App(ui::Context &ctx, file::ZIPProvider &resourceProvider)
#ifdef ENABLE_LOG_BUFFER
: _overlayLayer(_logBuffer),
: _logOverlay(_logBuffer),
#else
:
#endif
@ -94,7 +95,7 @@ void App::_unloadCartData(void) {
_cartDump.chipType = cart::NONE;
_cartDump.flags = 0;
_cartDump.clearIdentifiers();
_cartDump.clearData();
util::clear(_cartDump.data);
_identified = nullptr;
//_selectedEntry = nullptr;
@ -138,11 +139,12 @@ static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{
"assets/sounds/moveright.vag", // ui::SOUND_MOVE_RIGHT
"assets/sounds/enter.vag", // ui::SOUND_ENTER
"assets/sounds/exit.vag", // ui::SOUND_EXIT
"assets/sounds/click.vag" // ui::SOUND_CLICK
"assets/sounds/click.vag", // ui::SOUND_CLICK
"assets/sounds/screenshot.vag" // ui::SOUND_SCREENSHOT
};
void App::_loadResources(void) {
_resourceProvider.loadTIM(_backgroundLayer.tile, "assets/textures/background.tim");
_resourceProvider.loadTIM(_background.tile, "assets/textures/background.tim");
_resourceProvider.loadTIM(_ctx.font.image, "assets/textures/font.tim");
_resourceProvider.loadStruct(_ctx.font.metrics, "assets/textures/font.metrics");
_resourceProvider.loadStruct(_ctx.colors, "assets/app.palette");
@ -152,6 +154,29 @@ void App::_loadResources(void) {
_resourceProvider.loadVAG(_ctx.sounds[i], _UI_SOUND_PATHS[i]);
}
bool App::_takeScreenshot(void) {
file::FileInfo info;
char path[32];
int index = 0;
do {
index++;
snprintf(path, sizeof(path), EXTERNAL_DATA_DIR "/shot%04d.bmp", index);
} while (_fileProvider.getFileInfo(info, path));
gpu::RectWH clip;
_ctx.gpuCtx.getVRAMClipRect(clip);
if (_fileProvider.saveVRAMBMP(clip, path)) {
LOG("%s saved", path);
return true;
} else {
LOG("%s saving failed", path);
return false;
}
}
void App::_worker(void) {
if (_workerFunction) {
(this->*_workerFunction)();
@ -189,13 +214,18 @@ void App::_interruptHandler(void) {
char dateString[24];
_backgroundLayer.leftText = dateString;
_backgroundLayer.rightText = "v" VERSION_STRING;
_textOverlay.leftText = dateString;
_textOverlay.rightText = "v" VERSION_STRING;
_screenshotOverlay.callback = [](ui::Context &ctx) -> bool {
return APP->_takeScreenshot();
};
_ctx.background = &_backgroundLayer;
_ctx.backgrounds[0] = &_background;
_ctx.backgrounds[1] = &_textOverlay;
#ifdef ENABLE_LOG_BUFFER
_ctx.overlay = &_overlayLayer;
_ctx.overlays[0] = &_logOverlay;
#endif
_ctx.overlays[1] = &_screenshotOverlay;
_ctx.show(_workerStatusScreen);
for (;;) {

View File

@ -101,9 +101,11 @@ private:
#ifdef ENABLE_LOG_BUFFER
util::LogBuffer _logBuffer;
ui::LogOverlay _overlayLayer;
ui::LogOverlay _logOverlay;
#endif
ui::TiledBackground _backgroundLayer;
ui::TiledBackground _background;
ui::TextOverlay _textOverlay;
ui::ScreenshotOverlay _screenshotOverlay;
ui::Context &_ctx;
file::ZIPProvider &_resourceProvider;
@ -129,6 +131,7 @@ private:
void _setupWorker(bool (App::*func)(void));
void _setupInterrupts(void);
void _loadResources(void);
bool _takeScreenshot(void);
// cartworkers.cpp
bool _cartDetectWorker(void);

View File

@ -118,7 +118,7 @@ void CartActionsScreen::resetSystemID(ui::Context &ctx) {
APP->_confirmScreen.setMessage(
*this,
[](ui::Context &ctx) {
APP->_cartParser->getIdentifiers()->systemID.clear();
util::clear(APP->_cartParser->getIdentifiers()->systemID);
APP->_cartParser->flush();
APP->_setupWorker(&App::_cartWriteWorker);

View File

@ -278,18 +278,14 @@ void UnlockKeyScreen::useCustomKey(ui::Context &ctx) {
}
void UnlockKeyScreen::use00Key(ui::Context &ctx) {
__builtin_memset(
APP->_cartDump.dataKey, 0x00, sizeof(APP->_cartDump.dataKey)
);
util::clear(APP->_cartDump.dataKey, 0x00);
APP->_selectedEntry = nullptr;
ctx.show(APP->_confirmScreen, false, true);
}
void UnlockKeyScreen::useFFKey(ui::Context &ctx) {
__builtin_memset(
APP->_cartDump.dataKey, 0xff, sizeof(APP->_cartDump.dataKey)
);
util::clear(APP->_cartDump.dataKey, 0xff);
APP->_selectedEntry = nullptr;
ctx.show(APP->_confirmScreen, false, true);

View File

@ -230,7 +230,7 @@ bool App::_cartDumpWorker(void) {
do {
index++;
snprintf(
path, sizeof(path), EXTERNAL_DATA_DIR "/cart%d.573", index
path, sizeof(path), EXTERNAL_DATA_DIR "/cart%04d.573", index
);
} while (_fileProvider.getFileInfo(info, path));
}
@ -389,7 +389,7 @@ bool App::_cartReflashWorker(void) {
auto pri = _cartParser->getIdentifiers();
auto pub = _cartParser->getPublicIdentifiers();
_cartDump.clearData();
util::clear(_cartDump.data);
_cartDump.initConfig(
9, _selectedEntry->flags & cart::DATA_HAS_PUBLIC_SECTION
);

View File

@ -5,7 +5,6 @@
#include "common/util.hpp"
#include "main/app/app.hpp"
#include "main/app/romactions.hpp"
#include "main/cartdata.hpp"
#include "main/uibase.hpp"
#include "main/uicommon.hpp"
@ -264,7 +263,7 @@ void StorageActionsScreen::resetFlashHeader(ui::Context &ctx) {
APP->_confirmScreen.setMessage(
*this,
[](ui::Context &ctx) {
APP->_romHeaderDump.clearData();
util::clear(APP->_romHeaderDump.data);
APP->_setupWorker(&App::_flashHeaderWriteWorker);
ctx.show(APP->_workerStatusScreen, false, true);
},

View File

@ -117,7 +117,7 @@ bool App::_romDumpWorker(void) {
do {
index++;
snprintf(dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%d", index);
snprintf(dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%04d", index);
} while (_fileProvider.getFileInfo(info, dirPath));
LOG("saving dumps to %s", dirPath);

View File

@ -54,7 +54,7 @@ const ChipSize CHIP_SIZES[NUM_CHIP_TYPES]{
};
void CartDump::initConfig(uint8_t maxAttempts, bool hasPublicSection) {
clearConfig();
util::clear(config);
switch (chipType) {
case X76F041:
@ -121,8 +121,8 @@ size_t CartDump::toQRString(char *output) const {
size_t uncompLength = getDumpLength();
size_t compLength = MAX_QR_STRING_LENGTH;
__builtin_memset(compressed, 0, MAX_QR_STRING_LENGTH);
int error = mz_compress2(
util::clear(compressed);
auto error = mz_compress2(
compressed, reinterpret_cast<mz_ulong *>(&compLength),
reinterpret_cast<const uint8_t *>(this), uncompLength,
MZ_BEST_COMPRESSION

View File

@ -43,9 +43,6 @@ public:
inline void copyTo(uint8_t *dest) const {
__builtin_memcpy(dest, data, sizeof(data));
}
inline void clear(void) {
__builtin_memset(data, 0, sizeof(data));
}
inline bool isEmpty(void) const {
return (util::sum(data, sizeof(data)) == 0);
}
@ -102,9 +99,9 @@ public:
return (sizeof(CartDump) - sizeof(data)) + getChipSize().dataLength;
}
inline void clearIdentifiers(void) {
systemID.clear();
cartID.clear();
zsID.clear();
util::clear(systemID);
util::clear(cartID);
util::clear(zsID);
}
inline void copyDataFrom(const uint8_t *source) {
__builtin_memcpy(data, source, getChipSize().dataLength);
@ -112,27 +109,18 @@ public:
inline void copyDataTo(uint8_t *dest) const {
__builtin_memcpy(dest, data, getChipSize().dataLength);
}
inline void clearData(void) {
__builtin_memset(data, 0, sizeof(data));
}
inline void copyKeyFrom(const uint8_t *source) {
__builtin_memcpy(dataKey, source, sizeof(dataKey));
}
inline void copyKeyTo(uint8_t *dest) const {
__builtin_memcpy(dest, dataKey, sizeof(dataKey));
}
inline void clearKey(void) {
__builtin_memset(dataKey, 0, sizeof(dataKey));
}
inline void copyConfigFrom(const uint8_t *source) {
__builtin_memcpy(config, source, sizeof(config));
}
inline void copyConfigTo(uint8_t *dest) const {
__builtin_memcpy(dest, config, sizeof(config));
}
inline void clearConfig(void) {
__builtin_memset(config, 0, sizeof(config));
}
void initConfig(uint8_t maxAttempts, bool hasPublicSection = false);
bool isPublicDataEmpty(void) const;
@ -158,9 +146,6 @@ public:
inline bool validateMagic(void) const {
return (magic == ROM_HEADER_DUMP_HEADER_MAGIC);
}
inline void clearData(void) {
__builtin_memset(data, 0xff, sizeof(data));
}
bool isDataEmpty(void) const;
};

View File

@ -25,7 +25,7 @@ uint8_t IdentifierSet::getFlags(void) const {
}
void IdentifierSet::setInstallID(uint8_t prefix) {
installID.clear();
util::clear(installID);
installID.data[0] = prefix;
installID.updateChecksum();
@ -34,7 +34,7 @@ void IdentifierSet::setInstallID(uint8_t prefix) {
void IdentifierSet::updateTraceID(
TraceIDType type, int param, const Identifier *_cartID
) {
traceID.clear();
util::clear(traceID);
const uint8_t *input = _cartID ? &_cartID->data[1] : &cartID.data[1];
uint16_t checksum = 0;
@ -93,7 +93,7 @@ uint8_t PublicIdentifierSet::getFlags(void) const {
}
void PublicIdentifierSet::setInstallID(uint8_t prefix) {
installID.clear();
util::clear(installID);
installID.data[0] = prefix;
installID.updateChecksum();

View File

@ -57,10 +57,6 @@ class [[gnu::packed]] IdentifierSet {
public:
Identifier traceID, cartID, installID, systemID; // aka TID, SID, MID, XID
inline void clear(void) {
__builtin_memset(this, 0, sizeof(IdentifierSet));
}
uint8_t getFlags(void) const;
void setInstallID(uint8_t prefix);
void updateTraceID(
@ -72,10 +68,6 @@ class [[gnu::packed]] PublicIdentifierSet {
public:
Identifier installID, systemID; // aka MID, XID
inline void clear(void) {
__builtin_memset(this, 0, sizeof(PublicIdentifierSet));
}
uint8_t getFlags(void) const;
void setInstallID(uint8_t prefix);
};

View File

@ -1,6 +1,7 @@
#include <stdint.h>
#include "common/io.hpp"
#include "common/util.hpp"
#include "main/cart.hpp"
#include "main/cartio.hpp"
#include "main/zs01.hpp"
@ -90,11 +91,11 @@ DriverError DummyDriver::erase(void) {
if (!__builtin_memcmp(
_dump.dataKey, dummyDriverDump.dataKey, sizeof(_dump.dataKey)
)) {
dummyDriverDump.clearData();
dummyDriverDump.clearKey();
util::clear(dummyDriverDump.data);
util::clear(dummyDriverDump.dataKey);
// TODO: clear config registers as well
_dump.clearKey();
util::clear(_dump.dataKey);
return NO_ERROR;
}
@ -276,7 +277,7 @@ DriverError X76F041Driver::readPrivateData(void) {
if (error)
return error;
_dump.clearConfig();
util::clear(_dump.config);
io::i2cReadBytes(_dump.config, 5);
io::i2cStopWithCS();
@ -328,7 +329,7 @@ DriverError X76F041Driver::erase(void) {
io::i2cStopWithCS(_X76_WRITE_DELAY);
_dump.clearKey();
util::clear(_dump.dataKey);
return NO_ERROR;
}
@ -607,7 +608,7 @@ DriverError ZS01Driver::erase(void) {
key.unpackFrom(_dump.dataKey);
__builtin_memset(request.data, 0, sizeof(request.data));
util::clear(request.data);
request.address = zs01::ADDR_ERASE;
request.encodeWriteRequest(key, _encoderState);
@ -615,7 +616,7 @@ DriverError ZS01Driver::erase(void) {
if (error)
return error;
_dump.clearKey();
util::clear(_dump.dataKey);
return NO_ERROR;
}

View File

@ -4,6 +4,7 @@
#include "common/gpufont.hpp"
#include "common/io.hpp"
#include "common/pad.hpp"
#include "common/util.hpp"
#include "main/uibase.hpp"
#include "ps1/gpucmd.h"
@ -51,8 +52,9 @@ static const uint32_t _BUTTON_MAPPINGS[NUM_BUTTON_MAPS][NUM_BUTTONS]{
};
ButtonState::ButtonState(void)
: _held(0), _prevHeld(0), _pressed(0), _released(0), _repeating(0),
_repeatTimer(0), buttonMap(MAP_JOYSTICK) {}
: _held(0), _prevHeld(0), _longHeld(0), _prevLongHeld(0), _pressed(0),
_released(0), _longPressed(0), _longReleased(0), _repeatTimer(0),
buttonMap(MAP_JOYSTICK) {}
uint8_t ButtonState::_getHeld(void) const {
uint32_t inputs = io::getJAMMAInputs();
@ -96,15 +98,19 @@ uint8_t ButtonState::_getHeld(void) const {
void ButtonState::reset(void) {
_held = _getHeld();
_prevHeld = _held;
_longHeld = 0;
_prevLongHeld = 0;
_pressed = 0;
_released = 0;
_repeating = 0;
_longPressed = 0;
_longReleased = 0;
_repeatTimer = 0;
}
void ButtonState::update(void) {
_prevHeld = _held;
_prevLongHeld = _longHeld;
_held = _getHeld();
uint32_t changed = _prevHeld ^ _held;
@ -112,7 +118,7 @@ void ButtonState::update(void) {
if (buttonMap == MAP_SINGLE_BUTTON) {
_pressed = 0;
_released = 0;
_repeating = 0;
_longHeld = 0;
// In single-button mode, interpret a short button press as the right
// button and a long press as start.
@ -137,17 +143,22 @@ void ButtonState::update(void) {
_pressed = (changed & _held) & ~_pressed;
_released = (changed & _prevHeld) & ~_released;
_repeating = (_repeatTimer >= REPEAT_DELAY) ? _held : 0;
_longHeld = (_repeatTimer >= REPEAT_DELAY) ? _held : 0;
}
changed = _prevLongHeld ^ _longHeld;
_longPressed = (changed & _longHeld) & ~_longPressed;
_longReleased = (changed & _prevLongHeld) & ~_longReleased;
}
/* UI context */
Context::Context(gpu::Context &gpuCtx, void *screenData)
: _currentScreen(0), gpuCtx(gpuCtx), background(nullptr), overlay(nullptr),
time(0), screenData(screenData) {
_screens[0] = nullptr;
_screens[1] = nullptr;
: _currentScreen(0), gpuCtx(gpuCtx), time(0), screenData(screenData) {
util::clear(_screens);
util::clear(backgrounds);
util::clear(overlays);
}
void Context::show(Screen &screen, bool goBack, bool playSound) {
@ -169,21 +180,30 @@ void Context::draw(void) {
auto oldScreen = _screens[_currentScreen ^ 1];
auto newScreen = _screens[_currentScreen];
if (background)
background->draw(*this);
for (auto layer : backgrounds) {
if (layer)
layer->draw(*this);
}
if (oldScreen)
oldScreen->draw(*this, false);
if (newScreen)
newScreen->draw(*this, true);
if (overlay)
overlay->draw(*this);
for (auto layer : overlays) {
if (layer)
layer->draw(*this);
}
}
void Context::update(void) {
buttons.update();
if (overlay)
overlay->update(*this);
for (auto layer : overlays) {
if (layer)
layer->update(*this);
}
if (_screens[_currentScreen])
_screens[_currentScreen]->update(*this);
}
@ -215,7 +235,9 @@ void TiledBackground::draw(Context &ctx, bool active) const {
for (int y = -offsetY; y < ctx.gpuCtx.height; y += tile.height)
tile.draw(ctx.gpuCtx, x, y);
}
}
void TextOverlay::draw(Context &ctx, bool active) const {
gpu::RectWH rect;
rect.y = ctx.gpuCtx.height - (8 + ctx.font.metrics.lineHeight);
@ -272,7 +294,9 @@ void LogOverlay::draw(Context &ctx, bool active) const {
}
void LogOverlay::update(Context &ctx) {
if (ctx.buttons.pressed(BTN_DEBUG)) {
if (
ctx.buttons.released(BTN_DEBUG) && !ctx.buttons.longReleased(BTN_DEBUG)
) {
bool shown = !_slideAnim.getTargetValue();
_slideAnim.setValue(ctx.time, shown ? ctx.gpuCtx.height : 0, SPEED_SLOW);
@ -280,6 +304,27 @@ void LogOverlay::update(Context &ctx) {
}
}
void ScreenshotOverlay::draw(Context &ctx, bool active) const {
int brightness = _flashAnim.getValue(ctx.time);
if (!brightness)
return;
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
ctx.gpuCtx.drawBackdrop(
gp0_rgb(brightness, brightness, brightness), GP0_BLEND_ADD
);
}
void ScreenshotOverlay::update(Context &ctx) {
if (ctx.buttons.longPressed(BTN_DEBUG)) {
if (callback(ctx)) {
_flashAnim.setValue(ctx.time, 0xff, 0, SPEED_SLOW);
ctx.sounds[ui::SOUND_SCREENSHOT].play();
}
}
}
/* Base screen classes */
void AnimatedScreen::_newLayer(
@ -308,6 +353,7 @@ void BackdropScreen::hide(Context &ctx, bool goBack) {
void BackdropScreen::draw(Context &ctx, bool active) const {
int brightness = _backdropAnim.getValue(ctx.time);
if (!brightness)
return;

View File

@ -12,7 +12,7 @@ namespace ui {
/* Public constants */
static constexpr int NUM_UI_COLORS = 18;
static constexpr int NUM_UI_SOUNDS = 9;
static constexpr int NUM_UI_SOUNDS = 10;
enum Color {
COLOR_DEFAULT = 0,
@ -44,7 +44,8 @@ enum Sound {
SOUND_MOVE_RIGHT = 5,
SOUND_ENTER = 6,
SOUND_EXIT = 7,
SOUND_CLICK = 8
SOUND_CLICK = 8,
SOUND_SCREENSHOT = 9
};
enum AnimationSpeed {
@ -106,7 +107,9 @@ class ButtonState {
private:
uint32_t _mappings[NUM_BUTTONS];
uint8_t _held, _prevHeld;
uint8_t _pressed, _released, _repeating;
uint8_t _longHeld, _prevLongHeld;
uint8_t _pressed, _released;
uint8_t _longPressed, _longReleased;
int _repeatTimer;
@ -115,20 +118,24 @@ private:
public:
ButtonMap buttonMap;
inline bool pressed(Button button) const {
return _pressed & (1 << button);
inline bool held(Button button) const {
return (_held >> button) & 1;
}
inline bool pressedRepeating(Button button) const {
return (_pressed | _repeating) & (1 << button);
inline bool pressed(Button button) const {
return (_pressed >> button) & 1;
}
inline bool released(Button button) const {
return _released & (1 << button);
return (_released >> button) & 1;
}
inline bool repeating(Button button) const {
return _repeating & (1 << button);
inline bool longHeld(Button button) const {
return (_longHeld >> button) & 1;
}
inline bool held(Button button) const {
return _held & (1 << button);
inline bool longPressed(Button button) const {
return (_longPressed >> button) & 1;
}
inline bool longReleased(Button button) const {
return (_longReleased >> button) & 1;
}
ButtonState(void);
@ -148,7 +155,8 @@ private:
public:
gpu::Context &gpuCtx;
Layer *background, *overlay;
Layer *backgrounds[4], *overlays[4];
gpu::Font font;
gpu::Color colors[NUM_UI_COLORS];
@ -186,9 +194,15 @@ public:
class TiledBackground : public Layer {
public:
gpu::Image tile;
void draw(Context &ctx, bool active = true) const;
};
class TextOverlay : public Layer {
public:
const char *leftText, *rightText;
inline TiledBackground(void)
inline TextOverlay(void)
: leftText(nullptr), rightText(nullptr) {}
void draw(Context &ctx, bool active = true) const;
@ -205,6 +219,20 @@ public:
void update(Context &ctx);
};
class ScreenshotOverlay : public Layer {
private:
util::Tween<int, util::QuadOutEasing> _flashAnim;
public:
bool (*callback)(ui::Context &ctx);
inline ScreenshotOverlay(void)
: callback(nullptr) {}
void draw(Context &ctx, bool active = true) const;
void update(Context &ctx);
};
/* Base screen classes */
// This is probably the most stripped-down way to implement something that

View File

@ -84,7 +84,7 @@ void TextScreen::update(Context &ctx) {
if (
ctx.buttons.pressed(ui::BTN_LEFT) ||
(ctx.buttons.repeating(ui::BTN_LEFT) && (value > 0))
(ctx.buttons.longHeld(ui::BTN_LEFT) && (value > 0))
) {
if (value <= 0) {
value = scrollHeight;
@ -98,7 +98,7 @@ void TextScreen::update(Context &ctx) {
}
if (
ctx.buttons.pressed(ui::BTN_RIGHT) ||
(ctx.buttons.repeating(ui::BTN_RIGHT) && (value < scrollHeight))
(ctx.buttons.longHeld(ui::BTN_RIGHT) && (value < scrollHeight))
) {
if (value >= scrollHeight) {
value = 0;
@ -281,7 +281,7 @@ void ListScreen::draw(Context &ctx, bool active) const {
void ListScreen::update(Context &ctx) {
if (
ctx.buttons.pressed(ui::BTN_LEFT) ||
(ctx.buttons.repeating(ui::BTN_LEFT) && (_activeItem > 0))
(ctx.buttons.longHeld(ui::BTN_LEFT) && (_activeItem > 0))
) {
_activeItem--;
if (_activeItem < 0) {
@ -296,7 +296,7 @@ void ListScreen::update(Context &ctx) {
}
if (
ctx.buttons.pressed(ui::BTN_RIGHT) ||
(ctx.buttons.repeating(ui::BTN_RIGHT) && (_activeItem < (_listLength - 1)))
(ctx.buttons.longHeld(ui::BTN_RIGHT) && (_activeItem < (_listLength - 1)))
) {
_activeItem++;
if (_activeItem >= _listLength) {

View File

@ -2,6 +2,7 @@
#include <stddef.h>
#include <stdint.h>
#include "common/gpu.hpp"
#include "common/util.hpp"
#include "main/uibase.hpp"
#include "main/uimodals.hpp"
@ -82,7 +83,7 @@ void MessageBoxScreen::update(Context &ctx) {
if (
ctx.buttons.pressed(ui::BTN_LEFT) ||
(ctx.buttons.repeating(ui::BTN_LEFT) && (_activeButton > 0))
(ctx.buttons.longHeld(ui::BTN_LEFT) && (_activeButton > 0))
) {
_activeButton--;
if (_activeButton < 0) {
@ -96,7 +97,7 @@ void MessageBoxScreen::update(Context &ctx) {
}
if (
ctx.buttons.pressed(ui::BTN_RIGHT) ||
(ctx.buttons.repeating(ui::BTN_RIGHT) && (_activeButton < (numButtons - 1)))
(ctx.buttons.longHeld(ui::BTN_RIGHT) && (_activeButton < (numButtons - 1)))
) {
_activeButton++;
if (_activeButton >= numButtons) {
@ -112,13 +113,13 @@ void MessageBoxScreen::update(Context &ctx) {
HexEntryScreen::HexEntryScreen(void)
: _bufferLength(0) {
__builtin_memset(_buffer, 0, sizeof(_buffer));
util::clear(_buffer);
}
void HexEntryScreen::show(Context &ctx, bool goBack) {
MessageBoxScreen::show(ctx, goBack);
//__builtin_memset(_buffer, 0, _bufferLength);
//util::clear(_buffer);
_buttonIndexOffset = _bufferLength * 2;
_charWidth = ctx.font.getCharacterWidth('0');
@ -187,7 +188,7 @@ void HexEntryScreen::update(Context &ctx) {
if (
ctx.buttons.pressed(ui::BTN_LEFT) ||
(ctx.buttons.repeating(ui::BTN_LEFT) && (value > 0))
(ctx.buttons.longHeld(ui::BTN_LEFT) && (value > 0))
) {
if (--value < 0) {
value = 0xf;
@ -198,7 +199,7 @@ void HexEntryScreen::update(Context &ctx) {
}
if (
ctx.buttons.pressed(ui::BTN_RIGHT) ||
(ctx.buttons.repeating(ui::BTN_RIGHT) && (value < 0xf))
(ctx.buttons.longHeld(ui::BTN_RIGHT) && (value < 0xf))
) {
if (++value > 0xf) {
value = 0;
@ -365,7 +366,7 @@ void DateEntryScreen::update(Context &ctx) {
if (
ctx.buttons.pressed(ui::BTN_LEFT) ||
(ctx.buttons.repeating(ui::BTN_LEFT) && (value > field.minValue))
(ctx.buttons.longHeld(ui::BTN_LEFT) && (value > field.minValue))
) {
if (--value < field.minValue) {
value = field.maxValue;
@ -376,7 +377,7 @@ void DateEntryScreen::update(Context &ctx) {
}
if (
ctx.buttons.pressed(ui::BTN_RIGHT) ||
(ctx.buttons.repeating(ui::BTN_RIGHT) && (value < field.maxValue))
(ctx.buttons.longHeld(ui::BTN_RIGHT) && (value < field.maxValue))
) {
if (++value > field.maxValue) {
value = field.minValue;

View File

@ -64,9 +64,6 @@ public:
inline void copyTo(uint8_t *dest) const {
__builtin_memcpy(dest, data, sizeof(data));
}
inline void clear(void) {
__builtin_memset(data, 0, sizeof(data));
}
void updateCRC(void);
bool validateCRC(void) const;