mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
faster timeout for games
This commit is contained in:
@@ -144,11 +144,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~EventBus() override { vQueueDelete(_queueHandle); }
|
~EventBus() override { vQueueDelete(_queueHandle); }
|
||||||
void post(const sdk::AppEvent& event) override { xQueueSendToBack(_queueHandle, &event, portMAX_DELAY); }
|
void post(const sdk::AppEvent& event) override { xQueueSendToBack(_queueHandle, &event, portMAX_DELAY); }
|
||||||
sdk::AppEvent pop() override {
|
std::optional<sdk::AppEvent> pop(std::optional<std::uint32_t> timeout_ms = std::nullopt) override {
|
||||||
sdk::AppEvent out;
|
sdk::AppEvent out;
|
||||||
xQueueReceive(_queueHandle, &out, portMAX_DELAY);
|
TickType_t ticks = timeout_ms ? pdMS_TO_TICKS(*timeout_ms) : portMAX_DELAY;
|
||||||
return out;
|
if (xQueueReceive(_queueHandle, &out, ticks) == pdTRUE) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ namespace apps {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using cardboy::sdk::AppButtonEvent;
|
using cardboy::sdk::AppButtonEvent;
|
||||||
|
using cardboy::sdk::AppTimeoutEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
using cardboy::sdk::AppTimerEvent;
|
using cardboy::sdk::AppTimerEvent;
|
||||||
|
|
||||||
@@ -55,13 +56,14 @@ public:
|
|||||||
|
|
||||||
void onStop() override { cancelRefreshTimer(); }
|
void onStop() override { cancelRefreshTimer(); }
|
||||||
|
|
||||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
std::optional<std::uint32_t> handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
event.visit(cardboy::sdk::overload(
|
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||||
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
[this](const AppTimerEvent& timer) {
|
||||||
[this](const AppTimerEvent& timer) {
|
if (timer.handle == refreshTimer)
|
||||||
if (timer.handle == refreshTimer)
|
updateDisplay();
|
||||||
updateDisplay();
|
},
|
||||||
}));
|
[](const AppTimeoutEvent&) { /* ignore */ }));
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ class GameboyApp;
|
|||||||
using cardboy::sdk::AppButtonEvent;
|
using cardboy::sdk::AppButtonEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
using cardboy::sdk::AppEvent;
|
using cardboy::sdk::AppEvent;
|
||||||
|
using cardboy::sdk::AppTimeoutEvent;
|
||||||
using cardboy::sdk::AppTimerEvent;
|
using cardboy::sdk::AppTimerEvent;
|
||||||
using cardboy::sdk::AppTimerHandle;
|
using cardboy::sdk::AppTimerHandle;
|
||||||
using cardboy::sdk::InputState;
|
using cardboy::sdk::InputState;
|
||||||
@@ -224,7 +225,6 @@ public:
|
|||||||
::gAudioWriteThunk = &GameboyApp::audioWriteThunk;
|
::gAudioWriteThunk = &GameboyApp::audioWriteThunk;
|
||||||
apu.attach(this);
|
apu.attach(this);
|
||||||
apu.reset();
|
apu.reset();
|
||||||
cancelTick();
|
|
||||||
frameDelayCarryUs = 0;
|
frameDelayCarryUs = 0;
|
||||||
GB_PERF_ONLY(perf.resetAll();)
|
GB_PERF_ONLY(perf.resetAll();)
|
||||||
prevInput = context.input.readState();
|
prevInput = context.input.readState();
|
||||||
@@ -233,13 +233,12 @@ public:
|
|||||||
scaleMode = ScaleMode::FullHeightWide;
|
scaleMode = ScaleMode::FullHeightWide;
|
||||||
ensureFilesystemReady();
|
ensureFilesystemReady();
|
||||||
refreshRomList();
|
refreshRomList();
|
||||||
mode = Mode::Browse;
|
mode = Mode::Browse;
|
||||||
browserDirty = true;
|
browserDirty = true;
|
||||||
scheduleNextTick(0);
|
nextTimeoutMs = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onStop() override {
|
void onStop() override {
|
||||||
cancelTick();
|
|
||||||
frameDelayCarryUs = 0;
|
frameDelayCarryUs = 0;
|
||||||
GB_PERF_ONLY(perf.maybePrintAggregate(true);)
|
GB_PERF_ONLY(perf.maybePrintAggregate(true);)
|
||||||
unloadRom();
|
unloadRom();
|
||||||
@@ -252,14 +251,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleEvent(const AppEvent& event) override {
|
std::optional<std::uint32_t> handleEvent(const AppEvent& event) override {
|
||||||
bool handled = false;
|
|
||||||
event.visit(cardboy::sdk::overload(
|
event.visit(cardboy::sdk::overload(
|
||||||
[this, &handled](const AppTimerEvent& timer) {
|
[this](const AppTimeoutEvent&) {
|
||||||
if (timer.handle != tickTimer)
|
|
||||||
return;
|
|
||||||
handled = true;
|
|
||||||
tickTimer = kInvalidAppTimer;
|
|
||||||
const uint64_t frameStartUs = nowMicros();
|
const uint64_t frameStartUs = nowMicros();
|
||||||
performStep();
|
performStep();
|
||||||
const uint64_t frameEndUs = nowMicros();
|
const uint64_t frameEndUs = nowMicros();
|
||||||
@@ -269,10 +263,10 @@ public:
|
|||||||
},
|
},
|
||||||
[this](const AppButtonEvent&) {
|
[this](const AppButtonEvent&) {
|
||||||
frameDelayCarryUs = 0;
|
frameDelayCarryUs = 0;
|
||||||
scheduleNextTick(0);
|
nextTimeoutMs = 0;
|
||||||
}));
|
},
|
||||||
if (handled)
|
[](const AppTimerEvent&) { /* ignore */ }));
|
||||||
return;
|
return nextTimeoutMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
void performStep() {
|
void performStep() {
|
||||||
@@ -1119,9 +1113,9 @@ public:
|
|||||||
cardboy::sdk::IFilesystem* filesystem = nullptr;
|
cardboy::sdk::IFilesystem* filesystem = nullptr;
|
||||||
cardboy::sdk::IHighResClock* highResClock = nullptr;
|
cardboy::sdk::IHighResClock* highResClock = nullptr;
|
||||||
PerfTracker perf{};
|
PerfTracker perf{};
|
||||||
AppTimerHandle tickTimer = kInvalidAppTimer;
|
|
||||||
int64_t frameDelayCarryUs = 0;
|
int64_t frameDelayCarryUs = 0;
|
||||||
static constexpr uint32_t kTargetFrameUs = 1000000 / 60; // ~16.6 ms
|
std::optional<std::uint32_t> nextTimeoutMs;
|
||||||
|
static constexpr uint32_t kTargetFrameUs = 1000000 / 60; // ~16.6 ms
|
||||||
|
|
||||||
Mode mode = Mode::Browse;
|
Mode mode = Mode::Browse;
|
||||||
ScaleMode scaleMode = ScaleMode::FullHeightWide;
|
ScaleMode scaleMode = ScaleMode::FullHeightWide;
|
||||||
@@ -1155,20 +1149,6 @@ public:
|
|||||||
uint8_t lastLoud = 0;
|
uint8_t lastLoud = 0;
|
||||||
uint32_t stableFrames = 0;
|
uint32_t stableFrames = 0;
|
||||||
|
|
||||||
void cancelTick() {
|
|
||||||
if (tickTimer == kInvalidAppTimer)
|
|
||||||
return;
|
|
||||||
if (auto* timer = context.timer())
|
|
||||||
timer->cancelTimer(tickTimer);
|
|
||||||
tickTimer = kInvalidAppTimer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void scheduleNextTick(uint32_t delayMs) {
|
|
||||||
cancelTick();
|
|
||||||
if (auto* timer = context.timer())
|
|
||||||
tickTimer = timer->scheduleTimer(delayMs, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t idleDelayMs() const { return browserDirty ? 50 : 140; }
|
uint32_t idleDelayMs() const { return browserDirty ? 50 : 140; }
|
||||||
|
|
||||||
void scheduleAfterFrame(uint64_t elapsedUs) {
|
void scheduleAfterFrame(uint64_t elapsedUs) {
|
||||||
@@ -1177,17 +1157,17 @@ public:
|
|||||||
desiredUs += frameDelayCarryUs;
|
desiredUs += frameDelayCarryUs;
|
||||||
if (desiredUs <= 0) {
|
if (desiredUs <= 0) {
|
||||||
frameDelayCarryUs = desiredUs;
|
frameDelayCarryUs = desiredUs;
|
||||||
scheduleNextTick(0);
|
nextTimeoutMs = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
frameDelayCarryUs = desiredUs % 1000;
|
frameDelayCarryUs = desiredUs % 1000;
|
||||||
desiredUs -= frameDelayCarryUs;
|
desiredUs -= frameDelayCarryUs;
|
||||||
uint32_t delayMs = static_cast<uint32_t>(desiredUs / 1000);
|
uint32_t delayMs = static_cast<uint32_t>(desiredUs / 1000);
|
||||||
scheduleNextTick(delayMs);
|
nextTimeoutMs = delayMs;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
frameDelayCarryUs = 0;
|
frameDelayCarryUs = 0;
|
||||||
scheduleNextTick(idleDelayMs());
|
nextTimeoutMs = idleDelayMs();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ensureFilesystemReady() {
|
bool ensureFilesystemReady() {
|
||||||
@@ -1838,7 +1818,7 @@ public:
|
|||||||
promptDirty = true;
|
promptDirty = true;
|
||||||
mode = Mode::Prompt;
|
mode = Mode::Prompt;
|
||||||
gb.direct.joypad = 0xFF;
|
gb.direct.joypad = 0xFF;
|
||||||
scheduleNextTick(0);
|
// scheduleNextTick(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void exitPrompt(Mode nextMode) {
|
void exitPrompt(Mode nextMode) {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
#include "cardboy/sdk/app_framework.hpp"
|
#include "cardboy/sdk/app_framework.hpp"
|
||||||
#include "cardboy/sdk/app_system.hpp"
|
#include "cardboy/sdk/app_system.hpp"
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -18,6 +18,7 @@ namespace apps {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using cardboy::sdk::AppButtonEvent;
|
using cardboy::sdk::AppButtonEvent;
|
||||||
|
using cardboy::sdk::AppTimeoutEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
using cardboy::sdk::AppTimerEvent;
|
using cardboy::sdk::AppTimerEvent;
|
||||||
|
|
||||||
@@ -27,19 +28,13 @@ constexpr std::uint32_t kUnlockHoldMs = 1500;
|
|||||||
using Framebuffer = typename AppContext::Framebuffer;
|
using Framebuffer = typename AppContext::Framebuffer;
|
||||||
using Clock = typename AppContext::Clock;
|
using Clock = typename AppContext::Clock;
|
||||||
|
|
||||||
constexpr std::array<std::uint8_t, font16x8::kGlyphHeight> kArrowUpGlyph{0b00011000, 0b00111100, 0b01111110,
|
constexpr std::array<std::uint8_t, font16x8::kGlyphHeight> kArrowUpGlyph{
|
||||||
0b11111111, 0b00011000, 0b00011000,
|
0b00011000, 0b00111100, 0b01111110, 0b11111111, 0b00011000, 0b00011000, 0b00011000, 0b00011000,
|
||||||
0b00011000, 0b00011000, 0b00011000,
|
0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00000000, 0b00000000};
|
||||||
0b00011000, 0b00011000, 0b00011000,
|
|
||||||
0b00011000, 0b00011000, 0b00000000,
|
|
||||||
0b00000000};
|
|
||||||
|
|
||||||
constexpr std::array<std::uint8_t, font16x8::kGlyphHeight> kArrowDownGlyph{0b00000000, 0b00000000, 0b00011000,
|
constexpr std::array<std::uint8_t, font16x8::kGlyphHeight> kArrowDownGlyph{
|
||||||
0b00011000, 0b00011000, 0b00011000,
|
0b00000000, 0b00000000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000,
|
||||||
0b00011000, 0b00011000, 0b00011000,
|
0b00011000, 0b00011000, 0b00011000, 0b11111111, 0b01111110, 0b00111100, 0b00011000, 0b00000000};
|
||||||
0b00011000, 0b00011000, 0b11111111,
|
|
||||||
0b01111110, 0b00111100, 0b00011000,
|
|
||||||
0b00000000};
|
|
||||||
|
|
||||||
struct TimeSnapshot {
|
struct TimeSnapshot {
|
||||||
bool hasWallTime = false;
|
bool hasWallTime = false;
|
||||||
@@ -60,10 +55,10 @@ public:
|
|||||||
|
|
||||||
void onStart() override {
|
void onStart() override {
|
||||||
cancelRefreshTimer();
|
cancelRefreshTimer();
|
||||||
lastSnapshot = {};
|
lastSnapshot = {};
|
||||||
holdActive = false;
|
holdActive = false;
|
||||||
holdProgressMs = 0;
|
holdProgressMs = 0;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
lastNotificationInteractionMs = clock.millis();
|
lastNotificationInteractionMs = clock.millis();
|
||||||
refreshNotifications();
|
refreshNotifications();
|
||||||
const auto snap = captureTime();
|
const auto snap = captureTime();
|
||||||
@@ -75,33 +70,34 @@ public:
|
|||||||
|
|
||||||
void onStop() override { cancelRefreshTimer(); }
|
void onStop() override { cancelRefreshTimer(); }
|
||||||
|
|
||||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
std::optional<std::uint32_t> handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
event.visit(cardboy::sdk::overload(
|
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||||
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
[this](const AppTimerEvent& timer) {
|
||||||
[this](const AppTimerEvent& timer) {
|
if (timer.handle == refreshTimer) {
|
||||||
if (timer.handle == refreshTimer) {
|
advanceHoldProgress();
|
||||||
advanceHoldProgress();
|
updateDisplay();
|
||||||
updateDisplay();
|
}
|
||||||
}
|
},
|
||||||
}));
|
[](const AppTimeoutEvent&) { /* ignore */ }));
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr std::size_t kMaxDisplayedNotifications = 5;
|
static constexpr std::size_t kMaxDisplayedNotifications = 5;
|
||||||
static constexpr std::uint32_t kNotificationHideMs = 8000;
|
static constexpr std::uint32_t kNotificationHideMs = 8000;
|
||||||
AppContext& context;
|
AppContext& context;
|
||||||
Framebuffer& framebuffer;
|
Framebuffer& framebuffer;
|
||||||
Clock& clock;
|
Clock& clock;
|
||||||
cardboy::sdk::INotificationCenter* notificationCenter = nullptr;
|
cardboy::sdk::INotificationCenter* notificationCenter = nullptr;
|
||||||
std::uint32_t lastNotificationRevision = 0;
|
std::uint32_t lastNotificationRevision = 0;
|
||||||
std::vector<cardboy::sdk::INotificationCenter::Notification> notifications;
|
std::vector<cardboy::sdk::INotificationCenter::Notification> notifications;
|
||||||
std::size_t selectedNotification = 0;
|
std::size_t selectedNotification = 0;
|
||||||
|
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
cardboy::sdk::AppTimerHandle refreshTimer = cardboy::sdk::kInvalidAppTimer;
|
cardboy::sdk::AppTimerHandle refreshTimer = cardboy::sdk::kInvalidAppTimer;
|
||||||
TimeSnapshot lastSnapshot{};
|
TimeSnapshot lastSnapshot{};
|
||||||
bool holdActive = false;
|
bool holdActive = false;
|
||||||
std::uint32_t holdProgressMs = 0;
|
std::uint32_t holdProgressMs = 0;
|
||||||
std::uint32_t lastNotificationInteractionMs = 0;
|
std::uint32_t lastNotificationInteractionMs = 0;
|
||||||
|
|
||||||
void cancelRefreshTimer() {
|
void cancelRefreshTimer() {
|
||||||
@@ -120,7 +116,7 @@ private:
|
|||||||
bool navPressed = false;
|
bool navPressed = false;
|
||||||
|
|
||||||
if (!notifications.empty() && (upPressed || downPressed)) {
|
if (!notifications.empty() && (upPressed || downPressed)) {
|
||||||
const std::size_t count = notifications.size();
|
const std::size_t count = notifications.size();
|
||||||
lastNotificationInteractionMs = clock.millis();
|
lastNotificationInteractionMs = clock.millis();
|
||||||
navPressed = true;
|
navPressed = true;
|
||||||
if (count > 1) {
|
if (count > 1) {
|
||||||
@@ -133,8 +129,8 @@ private:
|
|||||||
|
|
||||||
const bool deletePressed = button.current.b && !button.previous.b;
|
const bool deletePressed = button.current.b && !button.previous.b;
|
||||||
if (deletePressed && notificationCenter && !notifications.empty()) {
|
if (deletePressed && notificationCenter && !notifications.empty()) {
|
||||||
const std::size_t index = std::min<std::size_t>(selectedNotification, notifications.size() - 1);
|
const std::size_t index = std::min<std::size_t>(selectedNotification, notifications.size() - 1);
|
||||||
const auto& note = notifications[index];
|
const auto& note = notifications[index];
|
||||||
std::size_t preferredIndex = index;
|
std::size_t preferredIndex = index;
|
||||||
if (index + 1 < notifications.size())
|
if (index + 1 < notifications.size())
|
||||||
preferredIndex = index + 1;
|
preferredIndex = index + 1;
|
||||||
@@ -144,9 +140,9 @@ private:
|
|||||||
notificationCenter->removeByExternalId(note.externalId);
|
notificationCenter->removeByExternalId(note.externalId);
|
||||||
else
|
else
|
||||||
notificationCenter->removeById(note.id);
|
notificationCenter->removeById(note.id);
|
||||||
selectedNotification = preferredIndex;
|
selectedNotification = preferredIndex;
|
||||||
lastNotificationInteractionMs = clock.millis();
|
lastNotificationInteractionMs = clock.millis();
|
||||||
dirty = true;
|
dirty = true;
|
||||||
refreshNotifications();
|
refreshNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,9 +157,9 @@ private:
|
|||||||
if (!notificationCenter) {
|
if (!notificationCenter) {
|
||||||
if (!notifications.empty() || lastNotificationRevision != 0) {
|
if (!notifications.empty() || lastNotificationRevision != 0) {
|
||||||
notifications.clear();
|
notifications.clear();
|
||||||
selectedNotification = 0;
|
selectedNotification = 0;
|
||||||
lastNotificationRevision = 0;
|
lastNotificationRevision = 0;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -175,7 +171,7 @@ private:
|
|||||||
const std::uint64_t previousId =
|
const std::uint64_t previousId =
|
||||||
(selectedNotification < notifications.size()) ? notifications[selectedNotification].id : 0;
|
(selectedNotification < notifications.size()) ? notifications[selectedNotification].id : 0;
|
||||||
|
|
||||||
auto latest = notificationCenter->recent(kMaxDisplayedNotifications);
|
auto latest = notificationCenter->recent(kMaxDisplayedNotifications);
|
||||||
notifications = std::move(latest);
|
notifications = std::move(latest);
|
||||||
|
|
||||||
if (notifications.empty()) {
|
if (notifications.empty()) {
|
||||||
@@ -193,7 +189,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastNotificationInteractionMs = clock.millis();
|
lastNotificationInteractionMs = clock.millis();
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateHoldState(bool comboNow) {
|
void updateHoldState(bool comboNow) {
|
||||||
@@ -213,8 +209,7 @@ private:
|
|||||||
|
|
||||||
void advanceHoldProgress() {
|
void advanceHoldProgress() {
|
||||||
if (holdActive) {
|
if (holdActive) {
|
||||||
const std::uint32_t next =
|
const std::uint32_t next = std::min<std::uint32_t>(holdProgressMs + kRefreshIntervalMs, kUnlockHoldMs);
|
||||||
std::min<std::uint32_t>(holdProgressMs + kRefreshIntervalMs, kUnlockHoldMs);
|
|
||||||
if (next != holdProgressMs) {
|
if (next != holdProgressMs) {
|
||||||
holdProgressMs = next;
|
holdProgressMs = next;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
@@ -239,8 +234,8 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool sameSnapshot(const TimeSnapshot& a, const TimeSnapshot& b) {
|
static bool sameSnapshot(const TimeSnapshot& a, const TimeSnapshot& b) {
|
||||||
return a.hasWallTime == b.hasWallTime && a.hour24 == b.hour24 && a.minute == b.minute &&
|
return a.hasWallTime == b.hasWallTime && a.hour24 == b.hour24 && a.minute == b.minute && a.second == b.second &&
|
||||||
a.second == b.second && a.day == b.day && a.month == b.month && a.year == b.year;
|
a.day == b.day && a.month == b.month && a.year == b.year;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSnapshot captureTime() const {
|
TimeSnapshot captureTime() const {
|
||||||
@@ -327,7 +322,7 @@ private:
|
|||||||
if (font16x8::measureText(text, scale, letterSpacing) <= maxWidth)
|
if (font16x8::measureText(text, scale, letterSpacing) <= maxWidth)
|
||||||
return std::string(text);
|
return std::string(text);
|
||||||
|
|
||||||
std::string result(text.begin(), text.end());
|
std::string result(text.begin(), text.end());
|
||||||
const std::string ellipsis = "...";
|
const std::string ellipsis = "...";
|
||||||
while (!result.empty()) {
|
while (!result.empty()) {
|
||||||
result.pop_back();
|
result.pop_back();
|
||||||
@@ -369,8 +364,7 @@ private:
|
|||||||
|
|
||||||
if (!word.empty()) {
|
if (!word.empty()) {
|
||||||
std::string candidate = current.empty() ? word : current + " " + word;
|
std::string candidate = current.empty() ? word : current + " " + word;
|
||||||
if (!current.empty() &&
|
if (!current.empty() && font16x8::measureText(candidate, scale, letterSpacing) > maxWidth) {
|
||||||
font16x8::measureText(candidate, scale, letterSpacing) > maxWidth) {
|
|
||||||
flushCurrent();
|
flushCurrent();
|
||||||
if (lines.size() >= static_cast<std::size_t>(maxLines)) {
|
if (lines.size() >= static_cast<std::size_t>(maxLines)) {
|
||||||
truncated = true;
|
truncated = true;
|
||||||
@@ -432,20 +426,20 @@ private:
|
|||||||
|
|
||||||
framebuffer.frameReady();
|
framebuffer.frameReady();
|
||||||
|
|
||||||
const int scaleTime = 4;
|
const int scaleTime = 4;
|
||||||
const int scaleSeconds = 2;
|
const int scaleSeconds = 2;
|
||||||
const int scaleSmall = 1;
|
const int scaleSmall = 1;
|
||||||
const int textLineHeight = font16x8::kGlyphHeight * scaleSmall;
|
const int textLineHeight = font16x8::kGlyphHeight * scaleSmall;
|
||||||
|
|
||||||
const int cardMarginTop = 4;
|
const int cardMarginTop = 4;
|
||||||
const int cardMarginSide = 8;
|
const int cardMarginSide = 8;
|
||||||
const int cardPadding = 6;
|
const int cardPadding = 6;
|
||||||
const int cardLineSpacing = 4;
|
const int cardLineSpacing = 4;
|
||||||
int cardHeight = 0;
|
int cardHeight = 0;
|
||||||
const int cardWidth = framebuffer.width() - cardMarginSide * 2;
|
const int cardWidth = framebuffer.width() - cardMarginSide * 2;
|
||||||
|
|
||||||
const std::uint32_t nowMs = clock.millis();
|
const std::uint32_t nowMs = clock.millis();
|
||||||
const bool hasNotifications = !notifications.empty();
|
const bool hasNotifications = !notifications.empty();
|
||||||
const bool showNotificationDetails =
|
const bool showNotificationDetails =
|
||||||
hasNotifications && (nowMs - lastNotificationInteractionMs <= kNotificationHideMs);
|
hasNotifications && (nowMs - lastNotificationInteractionMs <= kNotificationHideMs);
|
||||||
|
|
||||||
@@ -481,7 +475,8 @@ private:
|
|||||||
std::snprintf(counter, sizeof(counter), "%zu/%zu", selectedNotification + 1, notifications.size());
|
std::snprintf(counter, sizeof(counter), "%zu/%zu", selectedNotification + 1, notifications.size());
|
||||||
const int counterWidth = font16x8::measureText(counter, scaleSmall, 1);
|
const int counterWidth = font16x8::measureText(counter, scaleSmall, 1);
|
||||||
const int counterX = cardMarginSide + cardWidth - cardPadding - counterWidth;
|
const int counterX = cardMarginSide + cardWidth - cardPadding - counterWidth;
|
||||||
font16x8::drawText(framebuffer, counterX, cardMarginTop + cardPadding, counter, scaleSmall, true, 1);
|
font16x8::drawText(framebuffer, counterX, cardMarginTop + cardPadding, counter, scaleSmall, true,
|
||||||
|
1);
|
||||||
const int arrowWidth = font16x8::kGlyphWidth * scaleSmall;
|
const int arrowWidth = font16x8::kGlyphWidth * scaleSmall;
|
||||||
const int arrowSpacing = std::max(1, scaleSmall);
|
const int arrowSpacing = std::max(1, scaleSmall);
|
||||||
const int arrowsTotalWide = arrowWidth * 2 + arrowSpacing;
|
const int arrowsTotalWide = arrowWidth * 2 + arrowSpacing;
|
||||||
@@ -495,7 +490,7 @@ private:
|
|||||||
|
|
||||||
if (!bodyLines.empty()) {
|
if (!bodyLines.empty()) {
|
||||||
int bodyY = cardMarginTop + cardPadding + textLineHeight + cardLineSpacing;
|
int bodyY = cardMarginTop + cardPadding + textLineHeight + cardLineSpacing;
|
||||||
for (const auto& line : bodyLines) {
|
for (const auto& line: bodyLines) {
|
||||||
font16x8::drawText(framebuffer, cardMarginSide + cardPadding, bodyY, line, scaleSmall, true, 1);
|
font16x8::drawText(framebuffer, cardMarginSide + cardPadding, bodyY, line, scaleSmall, true, 1);
|
||||||
bodyY += textLineHeight + cardLineSpacing;
|
bodyY += textLineHeight + cardLineSpacing;
|
||||||
}
|
}
|
||||||
@@ -531,9 +526,8 @@ private:
|
|||||||
if (cardHeight > 0)
|
if (cardHeight > 0)
|
||||||
timeY = cardMarginTop + cardHeight + 16;
|
timeY = cardMarginTop + cardHeight + 16;
|
||||||
const int minTimeY = (cardHeight > 0) ? (cardMarginTop + cardHeight + 12) : 16;
|
const int minTimeY = (cardHeight > 0) ? (cardMarginTop + cardHeight + 12) : 16;
|
||||||
const int maxTimeY =
|
const int maxTimeY = std::max(minTimeY, framebuffer.height() - font16x8::kGlyphHeight * scaleTime - 48);
|
||||||
std::max(minTimeY, framebuffer.height() - font16x8::kGlyphHeight * scaleTime - 48);
|
timeY = std::clamp(timeY, minTimeY, maxTimeY);
|
||||||
timeY = std::clamp(timeY, minTimeY, maxTimeY);
|
|
||||||
|
|
||||||
char hoursMinutes[6];
|
char hoursMinutes[6];
|
||||||
std::snprintf(hoursMinutes, sizeof(hoursMinutes), "%02d:%02d", snap.hour24, snap.minute);
|
std::snprintf(hoursMinutes, sizeof(hoursMinutes), "%02d:%02d", snap.hour24, snap.minute);
|
||||||
@@ -541,7 +535,7 @@ private:
|
|||||||
const int timeX = (framebuffer.width() - mainW) / 2;
|
const int timeX = (framebuffer.width() - mainW) / 2;
|
||||||
const int secX = timeX + mainW + 12;
|
const int secX = timeX + mainW + 12;
|
||||||
const int secY = timeY + font16x8::kGlyphHeight * scaleTime - font16x8::kGlyphHeight * scaleSeconds;
|
const int secY = timeY + font16x8::kGlyphHeight * scaleTime - font16x8::kGlyphHeight * scaleSeconds;
|
||||||
char secs[3];
|
char secs[3];
|
||||||
std::snprintf(secs, sizeof(secs), "%02d", snap.second);
|
std::snprintf(secs, sizeof(secs), "%02d", snap.second);
|
||||||
|
|
||||||
font16x8::drawText(framebuffer, timeX, timeY, hoursMinutes, scaleTime, true, 0);
|
font16x8::drawText(framebuffer, timeX, timeY, hoursMinutes, scaleTime, true, 0);
|
||||||
@@ -550,7 +544,7 @@ private:
|
|||||||
const std::string dateLine = formatDate(snap);
|
const std::string dateLine = formatDate(snap);
|
||||||
drawCenteredText(framebuffer, timeY + font16x8::kGlyphHeight * scaleTime + 16, dateLine, scaleSmall, 1);
|
drawCenteredText(framebuffer, timeY + font16x8::kGlyphHeight * scaleTime + 16, dateLine, scaleSmall, 1);
|
||||||
|
|
||||||
const std::string instruction = holdActive ? "KEEP HOLDING A+SELECT" : "HOLD A+SELECT";
|
const std::string instruction = holdActive ? "KEEP HOLDING A+SELECT" : "HOLD A+SELECT";
|
||||||
const int instructionWidth = font16x8::measureText(instruction, scaleSmall, 1);
|
const int instructionWidth = font16x8::measureText(instruction, scaleSmall, 1);
|
||||||
const int barHeight = 14;
|
const int barHeight = 14;
|
||||||
const int barY = framebuffer.height() - 24;
|
const int barY = framebuffer.height() - 24;
|
||||||
@@ -568,11 +562,10 @@ private:
|
|||||||
drawRectOutline(framebuffer, barX, barY, barWidth, barHeight);
|
drawRectOutline(framebuffer, barX, barY, barWidth, barHeight);
|
||||||
|
|
||||||
if (holdActive || holdProgressMs > 0) {
|
if (holdActive || holdProgressMs > 0) {
|
||||||
const int innerWidth = barWidth - 2;
|
const int innerWidth = barWidth - 2;
|
||||||
const int innerHeight = barHeight - 2;
|
const int innerHeight = barHeight - 2;
|
||||||
const float ratio =
|
const float ratio = std::clamp(holdProgressMs / static_cast<float>(kUnlockHoldMs), 0.0f, 1.0f);
|
||||||
std::clamp(holdProgressMs / static_cast<float>(kUnlockHoldMs), 0.0f, 1.0f);
|
const int fillWidth = static_cast<int>(ratio * innerWidth + 0.5f);
|
||||||
const int fillWidth = static_cast<int>(ratio * innerWidth + 0.5f);
|
|
||||||
if (fillWidth > 0)
|
if (fillWidth > 0)
|
||||||
fillRect(framebuffer, barX + 1, barY + 1, fillWidth, innerHeight);
|
fillRect(framebuffer, barX + 1, barY + 1, fillWidth, innerHeight);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ namespace apps {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using cardboy::sdk::AppButtonEvent;
|
using cardboy::sdk::AppButtonEvent;
|
||||||
|
using cardboy::sdk::AppTimeoutEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
using cardboy::sdk::AppEvent;
|
using cardboy::sdk::AppEvent;
|
||||||
using cardboy::sdk::AppTimerEvent;
|
using cardboy::sdk::AppTimerEvent;
|
||||||
@@ -44,15 +45,16 @@ public:
|
|||||||
|
|
||||||
void onStop() override { cancelInactivityTimer(); }
|
void onStop() override { cancelInactivityTimer(); }
|
||||||
|
|
||||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
std::optional<std::uint32_t> handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
event.visit(cardboy::sdk::overload(
|
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
||||||
[this](const AppButtonEvent& button) { handleButtonEvent(button); },
|
[this](const AppTimerEvent& timer) {
|
||||||
[this](const AppTimerEvent& timer) {
|
if (timer.handle == inactivityTimer) {
|
||||||
if (timer.handle == inactivityTimer) {
|
cancelInactivityTimer();
|
||||||
cancelInactivityTimer();
|
context.requestAppSwitchByName(kLockscreenAppName);
|
||||||
context.requestAppSwitchByName(kLockscreenAppName);
|
}
|
||||||
}
|
},
|
||||||
}));
|
[](const AppTimeoutEvent&) { /* ignore */ }));
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -38,42 +38,44 @@ public:
|
|||||||
renderIfNeeded();
|
renderIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
std::optional<std::uint32_t> handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
const auto* buttonEvent = event.button();
|
event.visit(cardboy::sdk::overload(
|
||||||
if (!buttonEvent)
|
[this](const cardboy::sdk::AppButtonEvent& button) {
|
||||||
return;
|
const auto& current = button.current;
|
||||||
|
const auto& previous = button.previous;
|
||||||
|
|
||||||
const auto& current = buttonEvent->current;
|
const bool previousAvailable = buzzerAvailable;
|
||||||
const auto& previous = buttonEvent->previous;
|
syncBuzzerState();
|
||||||
|
if (previousAvailable != buzzerAvailable)
|
||||||
|
dirty = true;
|
||||||
|
|
||||||
const bool previousAvailable = buzzerAvailable;
|
if (current.b && !previous.b) {
|
||||||
syncBuzzerState();
|
context.requestAppSwitchByName(kMenuAppName);
|
||||||
if (previousAvailable != buzzerAvailable)
|
return;
|
||||||
dirty = true;
|
}
|
||||||
|
|
||||||
if (current.b && !previous.b) {
|
bool moved = false;
|
||||||
context.requestAppSwitchByName(kMenuAppName);
|
if (current.down && !previous.down) {
|
||||||
return;
|
moveSelection(+1);
|
||||||
}
|
moved = true;
|
||||||
|
} else if (current.up && !previous.up) {
|
||||||
|
moveSelection(-1);
|
||||||
|
moved = true;
|
||||||
|
}
|
||||||
|
|
||||||
bool moved = false;
|
const bool togglePressed = (current.a && !previous.a) || (current.start && !previous.start) ||
|
||||||
if (current.down && !previous.down) {
|
(current.select && !previous.select);
|
||||||
moveSelection(+1);
|
if (togglePressed)
|
||||||
moved = true;
|
handleToggle();
|
||||||
} else if (current.up && !previous.up) {
|
|
||||||
moveSelection(-1);
|
|
||||||
moved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool togglePressed = (current.a && !previous.a) || (current.start && !previous.start) ||
|
if (moved)
|
||||||
(current.select && !previous.select);
|
dirty = true;
|
||||||
if (togglePressed)
|
|
||||||
handleToggle();
|
|
||||||
|
|
||||||
if (moved)
|
renderIfNeeded();
|
||||||
dirty = true;
|
},
|
||||||
|
[](const cardboy::sdk::AppTimerEvent&) { /* ignore */ },
|
||||||
renderIfNeeded();
|
[](const cardboy::sdk::AppTimeoutEvent&) { /* ignore */ }));
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace apps {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using cardboy::sdk::AppButtonEvent;
|
using cardboy::sdk::AppButtonEvent;
|
||||||
|
using cardboy::sdk::AppTimeoutEvent;
|
||||||
using cardboy::sdk::AppContext;
|
using cardboy::sdk::AppContext;
|
||||||
using cardboy::sdk::AppEvent;
|
using cardboy::sdk::AppEvent;
|
||||||
using cardboy::sdk::AppTimerEvent;
|
using cardboy::sdk::AppTimerEvent;
|
||||||
@@ -28,13 +29,13 @@ using cardboy::sdk::InputState;
|
|||||||
|
|
||||||
constexpr char kSnakeAppName[] = "Snake";
|
constexpr char kSnakeAppName[] = "Snake";
|
||||||
|
|
||||||
constexpr int kBoardWidth = 32;
|
constexpr int kBoardWidth = 32;
|
||||||
constexpr int kBoardHeight = 20;
|
constexpr int kBoardHeight = 20;
|
||||||
constexpr int kCellSize = 10;
|
constexpr int kCellSize = 10;
|
||||||
constexpr int kInitialSnakeLength = 5;
|
constexpr int kInitialSnakeLength = 5;
|
||||||
constexpr int kScorePerFood = 10;
|
constexpr int kScorePerFood = 10;
|
||||||
constexpr int kMinMoveIntervalMs = 80;
|
constexpr int kMinMoveIntervalMs = 80;
|
||||||
constexpr int kBaseMoveIntervalMs = 220;
|
constexpr int kBaseMoveIntervalMs = 220;
|
||||||
constexpr int kIntervalSpeedupPerSegment = 4;
|
constexpr int kIntervalSpeedupPerSegment = 4;
|
||||||
|
|
||||||
struct Point {
|
struct Point {
|
||||||
@@ -69,11 +70,12 @@ public:
|
|||||||
|
|
||||||
void onStop() { cancelMoveTimer(); }
|
void onStop() { cancelMoveTimer(); }
|
||||||
|
|
||||||
void handleEvent(const AppEvent& event) {
|
std::optional<std::uint32_t> handleEvent(const AppEvent& event) {
|
||||||
event.visit(cardboy::sdk::overload(
|
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtons(button); },
|
||||||
[this](const AppButtonEvent& button) { handleButtons(button); },
|
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); },
|
||||||
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); }));
|
[](const AppTimeoutEvent&) { /* ignore */ }));
|
||||||
renderIfNeeded();
|
renderIfNeeded();
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -89,8 +91,8 @@ private:
|
|||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
int score = 0;
|
int score = 0;
|
||||||
int highScore = 0;
|
int highScore = 0;
|
||||||
AppTimerHandle moveTimer = cardboy::sdk::kInvalidAppTimer;
|
AppTimerHandle moveTimer = cardboy::sdk::kInvalidAppTimer;
|
||||||
std::mt19937 rng;
|
std::mt19937 rng;
|
||||||
|
|
||||||
void handleButtons(const AppButtonEvent& evt) {
|
void handleButtons(const AppButtonEvent& evt) {
|
||||||
const auto& cur = evt.current;
|
const auto& cur = evt.current;
|
||||||
@@ -158,7 +160,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void advance() {
|
void advance() {
|
||||||
direction = queuedDirection;
|
direction = queuedDirection;
|
||||||
Point nextHead = snake.front();
|
Point nextHead = snake.front();
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case Direction::Up:
|
case Direction::Up:
|
||||||
@@ -299,9 +301,7 @@ private:
|
|||||||
framebuffer.sendFrame();
|
framebuffer.sendFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] int boardOriginX() const {
|
[[nodiscard]] int boardOriginX() const { return (cardboy::sdk::kDisplayWidth - kBoardWidth * kCellSize) / 2; }
|
||||||
return (cardboy::sdk::kDisplayWidth - kBoardWidth * kCellSize) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] int boardOriginY() const {
|
[[nodiscard]] int boardOriginY() const {
|
||||||
const int centered = (cardboy::sdk::kDisplayHeight - kBoardHeight * kCellSize) / 2;
|
const int centered = (cardboy::sdk::kDisplayHeight - kBoardHeight * kCellSize) / 2;
|
||||||
@@ -406,9 +406,9 @@ class SnakeApp final : public cardboy::sdk::IApp {
|
|||||||
public:
|
public:
|
||||||
explicit SnakeApp(AppContext& ctx) : game(ctx) {}
|
explicit SnakeApp(AppContext& ctx) : game(ctx) {}
|
||||||
|
|
||||||
void onStart() override { game.onStart(); }
|
void onStart() override { game.onStart(); }
|
||||||
void onStop() override { game.onStop(); }
|
void onStop() override { game.onStop(); }
|
||||||
void handleEvent(const AppEvent& event) override { game.handleEvent(event); }
|
std::optional<std::uint32_t> handleEvent(const AppEvent& event) override { return game.handleEvent(event); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SnakeGame game;
|
SnakeGame game;
|
||||||
|
|||||||
@@ -148,11 +148,12 @@ public:
|
|||||||
|
|
||||||
void onStop() { cancelTimers(); }
|
void onStop() { cancelTimers(); }
|
||||||
|
|
||||||
void handleEvent(const AppEvent& event) {
|
std::optional<std::uint32_t> handleEvent(const AppEvent& event) {
|
||||||
event.visit(cardboy::sdk::overload(
|
event.visit(cardboy::sdk::overload([this](const AppButtonEvent& button) { handleButtons(button); },
|
||||||
[this](const AppButtonEvent& button) { handleButtons(button); },
|
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); },
|
||||||
[this](const AppTimerEvent& timer) { handleTimer(timer.handle); }));
|
[](const cardboy::sdk::AppTimeoutEvent&) { /* ignore */ }));
|
||||||
renderIfNeeded();
|
renderIfNeeded();
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -634,9 +635,9 @@ class TetrisApp final : public cardboy::sdk::IApp {
|
|||||||
public:
|
public:
|
||||||
explicit TetrisApp(AppContext& ctx) : game(ctx) {}
|
explicit TetrisApp(AppContext& ctx) : game(ctx) {}
|
||||||
|
|
||||||
void onStart() override { game.onStart(); }
|
void onStart() override { game.onStart(); }
|
||||||
void onStop() override { game.onStop(); }
|
void onStop() override { game.onStop(); }
|
||||||
void handleEvent(const AppEvent& event) override { game.handleEvent(event); }
|
std::optional<std::uint32_t> handleEvent(const AppEvent& event) override { return game.handleEvent(event); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TetrisGame game;
|
TetrisGame game;
|
||||||
|
|||||||
@@ -21,14 +21,17 @@ struct AppTimerEvent {
|
|||||||
AppTimerHandle handle = kInvalidAppTimer;
|
AppTimerHandle handle = kInvalidAppTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AppTimeoutEvent {};
|
||||||
|
|
||||||
struct AppEvent {
|
struct AppEvent {
|
||||||
using Data = std::variant<AppButtonEvent, AppTimerEvent>;
|
using Data = std::variant<AppButtonEvent, AppTimerEvent, AppTimeoutEvent>;
|
||||||
|
|
||||||
std::uint32_t timestamp_ms = 0;
|
std::uint32_t timestamp_ms = 0;
|
||||||
Data data{AppButtonEvent{}};
|
Data data{AppButtonEvent{}};
|
||||||
|
|
||||||
[[nodiscard]] bool isButton() const { return std::holds_alternative<AppButtonEvent>(data); }
|
[[nodiscard]] bool isButton() const { return std::holds_alternative<AppButtonEvent>(data); }
|
||||||
[[nodiscard]] bool isTimer() const { return std::holds_alternative<AppTimerEvent>(data); }
|
[[nodiscard]] bool isTimer() const { return std::holds_alternative<AppTimerEvent>(data); }
|
||||||
|
[[nodiscard]] bool isTimeout() const { return std::holds_alternative<AppTimeoutEvent>(data); }
|
||||||
|
|
||||||
[[nodiscard]] const AppButtonEvent* button() const { return std::get_if<AppButtonEvent>(&data); }
|
[[nodiscard]] const AppButtonEvent* button() const { return std::get_if<AppButtonEvent>(&data); }
|
||||||
[[nodiscard]] AppButtonEvent* button() { return std::get_if<AppButtonEvent>(&data); }
|
[[nodiscard]] AppButtonEvent* button() { return std::get_if<AppButtonEvent>(&data); }
|
||||||
@@ -36,6 +39,9 @@ struct AppEvent {
|
|||||||
[[nodiscard]] const AppTimerEvent* timer() const { return std::get_if<AppTimerEvent>(&data); }
|
[[nodiscard]] const AppTimerEvent* timer() const { return std::get_if<AppTimerEvent>(&data); }
|
||||||
[[nodiscard]] AppTimerEvent* timer() { return std::get_if<AppTimerEvent>(&data); }
|
[[nodiscard]] AppTimerEvent* timer() { return std::get_if<AppTimerEvent>(&data); }
|
||||||
|
|
||||||
|
[[nodiscard]] const AppTimeoutEvent* timeout() const { return std::get_if<AppTimeoutEvent>(&data); }
|
||||||
|
[[nodiscard]] AppTimeoutEvent* timeout() { return std::get_if<AppTimeoutEvent>(&data); }
|
||||||
|
|
||||||
template<typename Visitor>
|
template<typename Visitor>
|
||||||
decltype(auto) visit(Visitor&& visitor) {
|
decltype(auto) visit(Visitor&& visitor) {
|
||||||
return std::visit(std::forward<Visitor>(visitor), data);
|
return std::visit(std::forward<Visitor>(visitor), data);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -97,8 +98,8 @@ class IEventBus {
|
|||||||
public:
|
public:
|
||||||
virtual ~IEventBus() = default;
|
virtual ~IEventBus() = default;
|
||||||
|
|
||||||
virtual void post(const AppEvent& event) = 0;
|
virtual void post(const AppEvent& event) = 0;
|
||||||
virtual AppEvent pop() = 0;
|
virtual std::optional<AppEvent> pop(std::optional<std::uint32_t> timeout_ms = std::nullopt) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AppScopedServices {
|
struct AppScopedServices {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -76,9 +77,9 @@ private:
|
|||||||
class IApp {
|
class IApp {
|
||||||
public:
|
public:
|
||||||
virtual ~IApp() = default;
|
virtual ~IApp() = default;
|
||||||
virtual void onStart() {}
|
virtual void onStart() {}
|
||||||
virtual void onStop() {}
|
virtual void onStop() {}
|
||||||
virtual void handleEvent(const AppEvent& event) = 0;
|
virtual std::optional<std::uint32_t> handleEvent(const AppEvent& event) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class IAppFactory {
|
class IAppFactory {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ private:
|
|||||||
std::size_t _activeIndex = static_cast<std::size_t>(-1);
|
std::size_t _activeIndex = static_cast<std::size_t>(-1);
|
||||||
std::unique_ptr<AppScopedServices> _scopedServices;
|
std::unique_ptr<AppScopedServices> _scopedServices;
|
||||||
std::uint64_t _nextScopedGeneration = 1;
|
std::uint64_t _nextScopedGeneration = 1;
|
||||||
|
std::optional<std::uint32_t> _currentTimeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cardboy::sdk
|
} // namespace cardboy::sdk
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "cardboy/sdk/status_bar.hpp"
|
#include "cardboy/sdk/status_bar.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace cardboy::sdk {
|
namespace cardboy::sdk {
|
||||||
@@ -66,6 +67,7 @@ void AppSystem::startAppByIndex(std::size_t index) {
|
|||||||
_context._pendingSwitchByName = false;
|
_context._pendingSwitchByName = false;
|
||||||
_context._pendingAppName.clear();
|
_context._pendingAppName.clear();
|
||||||
_current = std::move(app);
|
_current = std::move(app);
|
||||||
|
_currentTimeout = std::nullopt;
|
||||||
StatusBar::instance().setServices(_context.services);
|
StatusBar::instance().setServices(_context.services);
|
||||||
StatusBar::instance().setCurrentAppName(_activeFactory ? _activeFactory->name() : "");
|
StatusBar::instance().setCurrentAppName(_activeFactory ? _activeFactory->name() : "");
|
||||||
_current->onStart();
|
_current->onStart();
|
||||||
@@ -81,14 +83,21 @@ void AppSystem::run() {
|
|||||||
if (auto* hooks = _context.loopHooks())
|
if (auto* hooks = _context.loopHooks())
|
||||||
hooks->onLoopIteration();
|
hooks->onLoopIteration();
|
||||||
|
|
||||||
auto event = _context.eventBus()->pop();
|
AppEvent event;
|
||||||
|
auto event_opt = _context.eventBus()->pop(_currentTimeout);
|
||||||
|
if (!event_opt) {
|
||||||
|
event = AppEvent{_context.clock.millis(), AppTimeoutEvent{}};
|
||||||
|
} else {
|
||||||
|
event = *event_opt;
|
||||||
|
}
|
||||||
|
|
||||||
if (const auto* btn = event.button()) {
|
if (const auto* btn = event.button()) {
|
||||||
const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(btn->current, btn->previous);
|
const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(btn->current, btn->previous);
|
||||||
if (consumedByStatusToggle) {
|
if (consumedByStatusToggle) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_current->handleEvent(event);
|
_currentTimeout = _current->handleEvent(event);
|
||||||
if (_context._pendingSwitch) {
|
if (_context._pendingSwitch) {
|
||||||
handlePendingSwitchRequest();
|
handlePendingSwitchRequest();
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user