mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
janky notifications
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace apps {
|
||||
|
||||
@@ -37,7 +38,8 @@ struct TimeSnapshot {
|
||||
|
||||
class LockscreenApp final : public cardboy::sdk::IApp {
|
||||
public:
|
||||
explicit LockscreenApp(AppContext& ctx) : context(ctx), framebuffer(ctx.framebuffer), clock(ctx.clock) {}
|
||||
explicit LockscreenApp(AppContext& ctx) :
|
||||
context(ctx), framebuffer(ctx.framebuffer), clock(ctx.clock), notificationCenter(ctx.notificationCenter()) {}
|
||||
|
||||
void onStart() override {
|
||||
cancelRefreshTimer();
|
||||
@@ -45,6 +47,8 @@ public:
|
||||
holdActive = false;
|
||||
holdProgressMs = 0;
|
||||
dirty = true;
|
||||
lastNotificationInteractionMs = clock.millis();
|
||||
refreshNotifications();
|
||||
const auto snap = captureTime();
|
||||
renderIfNeeded(snap);
|
||||
lastSnapshot = snap;
|
||||
@@ -68,15 +72,22 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
Clock& clock;
|
||||
static constexpr std::size_t kMaxDisplayedNotifications = 5;
|
||||
static constexpr std::uint32_t kNotificationHideMs = 8000;
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
Clock& clock;
|
||||
cardboy::sdk::INotificationCenter* notificationCenter = nullptr;
|
||||
std::uint32_t lastNotificationRevision = 0;
|
||||
std::vector<cardboy::sdk::INotificationCenter::Notification> notifications;
|
||||
std::size_t selectedNotification = 0;
|
||||
|
||||
bool dirty = false;
|
||||
cardboy::sdk::AppTimerHandle refreshTimer = cardboy::sdk::kInvalidAppTimer;
|
||||
TimeSnapshot lastSnapshot{};
|
||||
bool holdActive = false;
|
||||
std::uint32_t holdProgressMs = 0;
|
||||
std::uint32_t lastNotificationInteractionMs = 0;
|
||||
|
||||
void cancelRefreshTimer() {
|
||||
if (refreshTimer != cardboy::sdk::kInvalidAppTimer) {
|
||||
@@ -88,11 +99,68 @@ private:
|
||||
static bool comboPressed(const cardboy::sdk::InputState& state) { return state.a && state.select; }
|
||||
|
||||
void handleButtonEvent(const cardboy::sdk::AppButtonEvent& button) {
|
||||
const bool upPressed = button.current.up && !button.previous.up;
|
||||
const bool downPressed = button.current.down && !button.previous.down;
|
||||
bool navPressed = false;
|
||||
|
||||
if (!notifications.empty() && (upPressed || downPressed)) {
|
||||
const std::size_t count = notifications.size();
|
||||
lastNotificationInteractionMs = clock.millis();
|
||||
navPressed = true;
|
||||
if (count > 1) {
|
||||
if (upPressed)
|
||||
selectedNotification = (selectedNotification + count - 1) % count;
|
||||
else if (downPressed)
|
||||
selectedNotification = (selectedNotification + 1) % count;
|
||||
}
|
||||
}
|
||||
|
||||
const bool comboNow = comboPressed(button.current);
|
||||
updateHoldState(comboNow);
|
||||
if (navPressed)
|
||||
dirty = true;
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
void refreshNotifications() {
|
||||
if (!notificationCenter) {
|
||||
if (!notifications.empty() || lastNotificationRevision != 0) {
|
||||
notifications.clear();
|
||||
selectedNotification = 0;
|
||||
lastNotificationRevision = 0;
|
||||
dirty = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const std::uint32_t revision = notificationCenter->revision();
|
||||
if (revision == lastNotificationRevision)
|
||||
return;
|
||||
lastNotificationRevision = revision;
|
||||
|
||||
const std::uint64_t previousId =
|
||||
(selectedNotification < notifications.size()) ? notifications[selectedNotification].id : 0;
|
||||
|
||||
auto latest = notificationCenter->recent(kMaxDisplayedNotifications);
|
||||
notifications = std::move(latest);
|
||||
|
||||
if (notifications.empty()) {
|
||||
selectedNotification = 0;
|
||||
} else if (previousId != 0) {
|
||||
auto it = std::find_if(notifications.begin(), notifications.end(),
|
||||
[previousId](const auto& note) { return note.id == previousId; });
|
||||
if (it != notifications.end()) {
|
||||
selectedNotification = static_cast<std::size_t>(std::distance(notifications.begin(), it));
|
||||
} else {
|
||||
selectedNotification = 0;
|
||||
}
|
||||
} else {
|
||||
selectedNotification = 0;
|
||||
}
|
||||
|
||||
lastNotificationInteractionMs = clock.millis();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void updateHoldState(bool comboNow) {
|
||||
if (comboNow) {
|
||||
if (!holdActive) {
|
||||
@@ -127,6 +195,7 @@ private:
|
||||
}
|
||||
|
||||
void updateDisplay() {
|
||||
refreshNotifications();
|
||||
const auto snap = captureTime();
|
||||
if (!sameSnapshot(snap, lastSnapshot))
|
||||
dirty = true;
|
||||
@@ -195,6 +264,98 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
static std::string truncateWithEllipsis(std::string_view text, int maxWidth, int scale, int letterSpacing) {
|
||||
if (font16x8::measureText(text, scale, letterSpacing) <= maxWidth)
|
||||
return std::string(text);
|
||||
|
||||
std::string result(text.begin(), text.end());
|
||||
const std::string ellipsis = "...";
|
||||
while (!result.empty()) {
|
||||
result.pop_back();
|
||||
std::string candidate = result + ellipsis;
|
||||
if (font16x8::measureText(candidate, scale, letterSpacing) <= maxWidth)
|
||||
return candidate;
|
||||
}
|
||||
return ellipsis;
|
||||
}
|
||||
|
||||
static std::vector<std::string> wrapText(std::string_view text, int maxWidth, int scale, int letterSpacing,
|
||||
int maxLines) {
|
||||
std::vector<std::string> lines;
|
||||
if (text.empty() || maxWidth <= 0 || maxLines <= 0)
|
||||
return lines;
|
||||
|
||||
std::string current;
|
||||
std::string word;
|
||||
bool truncated = false;
|
||||
|
||||
auto flushCurrent = [&]() {
|
||||
if (current.empty())
|
||||
return;
|
||||
if (lines.size() < static_cast<std::size_t>(maxLines)) {
|
||||
lines.push_back(current);
|
||||
} else {
|
||||
truncated = true;
|
||||
}
|
||||
current.clear();
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i <= text.size(); ++i) {
|
||||
char ch = (i < text.size()) ? text[i] : ' ';
|
||||
const bool isBreak = (ch == ' ' || ch == '\n' || ch == '\r' || i == text.size());
|
||||
if (!isBreak) {
|
||||
word.push_back(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!word.empty()) {
|
||||
std::string candidate = current.empty() ? word : current + " " + word;
|
||||
if (!current.empty() &&
|
||||
font16x8::measureText(candidate, scale, letterSpacing) > maxWidth) {
|
||||
flushCurrent();
|
||||
if (lines.size() >= static_cast<std::size_t>(maxLines)) {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
candidate = word;
|
||||
}
|
||||
|
||||
if (font16x8::measureText(candidate, scale, letterSpacing) > maxWidth) {
|
||||
std::string shortened = truncateWithEllipsis(word, maxWidth, scale, letterSpacing);
|
||||
flushCurrent();
|
||||
if (lines.size() < static_cast<std::size_t>(maxLines)) {
|
||||
lines.push_back(shortened);
|
||||
} else {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
current.clear();
|
||||
} else {
|
||||
current = candidate;
|
||||
}
|
||||
word.clear();
|
||||
}
|
||||
|
||||
if (ch == '\n' || ch == '\r') {
|
||||
flushCurrent();
|
||||
if (lines.size() >= static_cast<std::size_t>(maxLines)) {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flushCurrent();
|
||||
if (lines.size() > static_cast<std::size_t>(maxLines)) {
|
||||
truncated = true;
|
||||
lines.resize(maxLines);
|
||||
}
|
||||
if (truncated && !lines.empty()) {
|
||||
lines.back() = truncateWithEllipsis(lines.back(), maxWidth, scale, letterSpacing);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
static std::string formatDate(const TimeSnapshot& snap) {
|
||||
static const char* kWeekdays[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
|
||||
if (!snap.hasWallTime)
|
||||
@@ -215,14 +376,105 @@ private:
|
||||
const int scaleTime = 4;
|
||||
const int scaleSeconds = 2;
|
||||
const int scaleSmall = 1;
|
||||
const int textLineHeight = font16x8::kGlyphHeight * scaleSmall;
|
||||
|
||||
const int cardMarginTop = 4;
|
||||
const int cardMarginSide = 8;
|
||||
const int cardPadding = 6;
|
||||
const int cardLineSpacing = 4;
|
||||
int cardHeight = 0;
|
||||
const int cardWidth = framebuffer.width() - cardMarginSide * 2;
|
||||
|
||||
const std::uint32_t nowMs = clock.millis();
|
||||
const bool hasNotifications = !notifications.empty();
|
||||
const bool showNotificationDetails =
|
||||
hasNotifications && (nowMs - lastNotificationInteractionMs <= kNotificationHideMs);
|
||||
|
||||
if (hasNotifications) {
|
||||
const auto& note = notifications[selectedNotification];
|
||||
if (showNotificationDetails) {
|
||||
std::string title = note.title.empty() ? std::string("Notification") : note.title;
|
||||
title = truncateWithEllipsis(title, cardWidth - cardPadding * 2, scaleSmall, 1);
|
||||
|
||||
auto bodyLines = wrapText(note.body, cardWidth - cardPadding * 2, scaleSmall, 1, 4);
|
||||
|
||||
cardHeight = cardPadding * 2 + textLineHeight;
|
||||
if (!bodyLines.empty()) {
|
||||
cardHeight += cardLineSpacing;
|
||||
cardHeight += static_cast<int>(bodyLines.size()) * textLineHeight;
|
||||
if (bodyLines.size() > 1)
|
||||
cardHeight += (static_cast<int>(bodyLines.size()) - 1) * cardLineSpacing;
|
||||
}
|
||||
|
||||
if (notifications.size() > 1) {
|
||||
cardHeight = std::max(cardHeight, cardPadding * 2 + textLineHeight * 2 + cardLineSpacing + 8);
|
||||
}
|
||||
|
||||
drawRectOutline(framebuffer, cardMarginSide, cardMarginTop, cardWidth, cardHeight);
|
||||
if (cardWidth > 2 && cardHeight > 2)
|
||||
drawRectOutline(framebuffer, cardMarginSide + 1, cardMarginTop + 1, cardWidth - 2, cardHeight - 2);
|
||||
|
||||
font16x8::drawText(framebuffer, cardMarginSide + cardPadding, cardMarginTop + cardPadding, title,
|
||||
scaleSmall, true, 1);
|
||||
|
||||
if (notifications.size() > 1) {
|
||||
char counter[16];
|
||||
std::snprintf(counter, sizeof(counter), "%zu/%zu", selectedNotification + 1, notifications.size());
|
||||
const int counterWidth = font16x8::measureText(counter, scaleSmall, 1);
|
||||
const int counterX = cardMarginSide + cardWidth - cardPadding - counterWidth;
|
||||
font16x8::drawText(framebuffer, counterX, cardMarginTop + cardPadding, counter, scaleSmall, true, 1);
|
||||
const int arrowX = counterX + (counterWidth - 8) / 2;
|
||||
int arrowY = cardMarginTop + cardPadding + textLineHeight + 1;
|
||||
font16x8::drawText(framebuffer, arrowX, arrowY, "^", scaleSmall, true, 0);
|
||||
arrowY += textLineHeight + 2;
|
||||
font16x8::drawText(framebuffer, arrowX, arrowY, "v", scaleSmall, true, 0);
|
||||
}
|
||||
|
||||
if (!bodyLines.empty()) {
|
||||
int bodyY = cardMarginTop + cardPadding + textLineHeight + cardLineSpacing;
|
||||
for (const auto& line : bodyLines) {
|
||||
font16x8::drawText(framebuffer, cardMarginSide + cardPadding, bodyY, line, scaleSmall, true, 1);
|
||||
bodyY += textLineHeight + cardLineSpacing;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cardHeight = textLineHeight + cardPadding * 2;
|
||||
char summary[32];
|
||||
if (notifications.size() == 1)
|
||||
std::snprintf(summary, sizeof(summary), "1 NOTIFICATION");
|
||||
else
|
||||
std::snprintf(summary, sizeof(summary), "%zu NOTIFICATIONS", notifications.size());
|
||||
const int summaryWidth = font16x8::measureText(summary, scaleSmall, 1);
|
||||
const int summaryX = (framebuffer.width() - summaryWidth) / 2;
|
||||
const int summaryY = cardMarginTop;
|
||||
font16x8::drawText(framebuffer, summaryX, summaryY, summary, scaleSmall, true, 1);
|
||||
|
||||
if (notifications.size() > 1) {
|
||||
int arrowX = (framebuffer.width() - 8) / 2;
|
||||
int arrowY = summaryY + textLineHeight + 1;
|
||||
font16x8::drawText(framebuffer, arrowX, arrowY, "^", scaleSmall, true, 0);
|
||||
arrowY += textLineHeight + 2;
|
||||
font16x8::drawText(framebuffer, arrowX, arrowY, "v", scaleSmall, true, 0);
|
||||
cardHeight = std::max(cardHeight, arrowY + textLineHeight - cardMarginTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int defaultTimeY = (framebuffer.height() - font16x8::kGlyphHeight * scaleTime) / 2 - 8;
|
||||
int timeY = defaultTimeY;
|
||||
if (cardHeight > 0)
|
||||
timeY = cardMarginTop + cardHeight + 16;
|
||||
const int minTimeY = (cardHeight > 0) ? (cardMarginTop + cardHeight + 12) : 16;
|
||||
const int maxTimeY =
|
||||
std::max(minTimeY, framebuffer.height() - font16x8::kGlyphHeight * scaleTime - 48);
|
||||
timeY = std::clamp(timeY, minTimeY, maxTimeY);
|
||||
|
||||
char hoursMinutes[6];
|
||||
std::snprintf(hoursMinutes, sizeof(hoursMinutes), "%02d:%02d", snap.hour24, snap.minute);
|
||||
const int mainW = font16x8::measureText(hoursMinutes, scaleTime, 0);
|
||||
const int timeY = (framebuffer.height() - font16x8::kGlyphHeight * scaleTime) / 2 - 8;
|
||||
const int timeX = (framebuffer.width() - mainW) / 2;
|
||||
const int secX = timeX + mainW + 12;
|
||||
const int secY = timeY + font16x8::kGlyphHeight * scaleTime - font16x8::kGlyphHeight * scaleSeconds;
|
||||
const int mainW = font16x8::measureText(hoursMinutes, scaleTime, 0);
|
||||
const int timeX = (framebuffer.width() - mainW) / 2;
|
||||
const int secX = timeX + mainW + 12;
|
||||
const int secY = timeY + font16x8::kGlyphHeight * scaleTime - font16x8::kGlyphHeight * scaleSeconds;
|
||||
char secs[3];
|
||||
std::snprintf(secs, sizeof(secs), "%02d", snap.second);
|
||||
|
||||
@@ -230,19 +482,28 @@ private:
|
||||
font16x8::drawText(framebuffer, secX, secY, secs, scaleSeconds, true, 0);
|
||||
|
||||
const std::string dateLine = formatDate(snap);
|
||||
drawCenteredText(framebuffer, timeY + font16x8::kGlyphHeight * scaleTime + 24, dateLine, scaleSmall, 1);
|
||||
const char* instruction = holdActive ? "KEEP HOLDING A+SELECT" : "HOLD A+SELECT";
|
||||
drawCenteredText(framebuffer, framebuffer.height() - 52, instruction, 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 int instructionWidth = font16x8::measureText(instruction, scaleSmall, 1);
|
||||
const int barHeight = 14;
|
||||
const int barY = framebuffer.height() - 24;
|
||||
const int textY = barY + (barHeight - textLineHeight) / 2;
|
||||
const int textX = 8;
|
||||
font16x8::drawText(framebuffer, textX, textY, instruction, scaleSmall, true, 1);
|
||||
|
||||
int barX = textX + instructionWidth + 12;
|
||||
int barWidth = framebuffer.width() - barX - 8;
|
||||
if (barWidth < 40) {
|
||||
barWidth = 40;
|
||||
barX = std::min(barX, framebuffer.width() - barWidth - 8);
|
||||
}
|
||||
|
||||
drawRectOutline(framebuffer, barX, barY, barWidth, barHeight);
|
||||
|
||||
if (holdActive || holdProgressMs > 0) {
|
||||
const int barWidth = framebuffer.width() - 64;
|
||||
const int barHeight = 14;
|
||||
const int barX = (framebuffer.width() - barWidth) / 2;
|
||||
const int barY = framebuffer.height() - 32;
|
||||
const int innerWidth = barWidth - 2;
|
||||
const int innerWidth = barWidth - 2;
|
||||
const int innerHeight = barHeight - 2;
|
||||
drawRectOutline(framebuffer, barX, barY, barWidth, barHeight);
|
||||
|
||||
const float ratio =
|
||||
std::clamp(holdProgressMs / static_cast<float>(kUnlockHoldMs), 0.0f, 1.0f);
|
||||
const int fillWidth = static_cast<int>(ratio * innerWidth + 0.5f);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace cardboy::sdk {
|
||||
|
||||
@@ -69,6 +70,25 @@ public:
|
||||
[[nodiscard]] virtual std::string basePath() const = 0;
|
||||
};
|
||||
|
||||
class INotificationCenter {
|
||||
public:
|
||||
struct Notification {
|
||||
std::uint64_t id = 0;
|
||||
std::uint64_t timestamp = 0;
|
||||
std::string title;
|
||||
std::string body;
|
||||
bool unread = true;
|
||||
};
|
||||
|
||||
virtual ~INotificationCenter() = default;
|
||||
|
||||
virtual void pushNotification(Notification notification) = 0;
|
||||
[[nodiscard]] virtual std::uint32_t revision() const = 0;
|
||||
[[nodiscard]] virtual std::vector<Notification> recent(std::size_t limit) const = 0;
|
||||
virtual void markAllRead() = 0;
|
||||
virtual void clear() = 0;
|
||||
};
|
||||
|
||||
struct Services {
|
||||
IBuzzer* buzzer = nullptr;
|
||||
IBatteryMonitor* battery = nullptr;
|
||||
@@ -78,6 +98,7 @@ struct Services {
|
||||
IFilesystem* filesystem = nullptr;
|
||||
IEventBus* eventBus = nullptr;
|
||||
ILoopHooks* loopHooks = nullptr;
|
||||
INotificationCenter* notifications = nullptr;
|
||||
};
|
||||
|
||||
} // namespace cardboy::sdk
|
||||
|
||||
@@ -86,6 +86,23 @@ private:
|
||||
bool mounted = false;
|
||||
};
|
||||
|
||||
class DesktopNotificationCenter final : public cardboy::sdk::INotificationCenter {
|
||||
public:
|
||||
void pushNotification(Notification notification) override;
|
||||
[[nodiscard]] std::uint32_t revision() const override;
|
||||
[[nodiscard]] std::vector<Notification> recent(std::size_t limit) const override;
|
||||
void markAllRead() override;
|
||||
void clear() override;
|
||||
|
||||
private:
|
||||
static constexpr std::size_t kMaxEntries = 8;
|
||||
|
||||
mutable std::mutex mutex;
|
||||
std::vector<Notification> entries;
|
||||
std::uint64_t nextId = 1;
|
||||
std::uint32_t revisionCounter = 0;
|
||||
};
|
||||
|
||||
class DesktopEventBus final : public cardboy::sdk::IEventBus {
|
||||
public:
|
||||
explicit DesktopEventBus(DesktopRuntime& owner);
|
||||
@@ -187,6 +204,7 @@ private:
|
||||
DesktopHighResClock highResService;
|
||||
DesktopFilesystem filesystemService;
|
||||
DesktopEventBus eventBusService;
|
||||
DesktopNotificationCenter notificationService;
|
||||
cardboy::sdk::Services services{};
|
||||
};
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
namespace cardboy::backend::desktop {
|
||||
|
||||
@@ -149,6 +151,58 @@ bool DesktopFilesystem::mount() {
|
||||
return mounted;
|
||||
}
|
||||
|
||||
void DesktopNotificationCenter::pushNotification(Notification notification) {
|
||||
if (notification.timestamp == 0) {
|
||||
notification.timestamp = static_cast<std::uint64_t>(std::time(nullptr));
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
notification.id = nextId++;
|
||||
notification.unread = true;
|
||||
|
||||
if (entries.size() >= kMaxEntries)
|
||||
entries.erase(entries.begin());
|
||||
entries.push_back(std::move(notification));
|
||||
++revisionCounter;
|
||||
}
|
||||
|
||||
std::uint32_t DesktopNotificationCenter::revision() const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return revisionCounter;
|
||||
}
|
||||
|
||||
std::vector<DesktopNotificationCenter::Notification> DesktopNotificationCenter::recent(std::size_t limit) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
const std::size_t count = std::min<std::size_t>(limit, entries.size());
|
||||
std::vector<Notification> result;
|
||||
result.reserve(count);
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
result.push_back(entries[entries.size() - 1 - i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void DesktopNotificationCenter::markAllRead() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
bool changed = false;
|
||||
for (auto& entry : entries) {
|
||||
if (entry.unread) {
|
||||
entry.unread = false;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
++revisionCounter;
|
||||
}
|
||||
|
||||
void DesktopNotificationCenter::clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if (entries.empty())
|
||||
return;
|
||||
entries.clear();
|
||||
++revisionCounter;
|
||||
}
|
||||
|
||||
DesktopFramebuffer::DesktopFramebuffer(DesktopRuntime& runtime) : runtime(runtime) {}
|
||||
|
||||
int DesktopFramebuffer::width_impl() const { return cardboy::sdk::kDisplayWidth; }
|
||||
@@ -244,6 +298,7 @@ DesktopRuntime::DesktopRuntime() :
|
||||
services.filesystem = &filesystemService;
|
||||
services.eventBus = &eventBusService;
|
||||
services.loopHooks = nullptr;
|
||||
services.notifications = ¬ificationService;
|
||||
}
|
||||
|
||||
cardboy::sdk::Services& DesktopRuntime::serviceRegistry() { return services; }
|
||||
|
||||
@@ -64,6 +64,9 @@ struct AppContext {
|
||||
[[nodiscard]] IFilesystem* filesystem() const { return services ? services->filesystem : nullptr; }
|
||||
[[nodiscard]] IEventBus* eventBus() const { return services ? services->eventBus : nullptr; }
|
||||
[[nodiscard]] ILoopHooks* loopHooks() const { return services ? services->loopHooks : nullptr; }
|
||||
[[nodiscard]] INotificationCenter* notificationCenter() const {
|
||||
return services ? services->notifications : nullptr;
|
||||
}
|
||||
|
||||
void requestAppSwitchByIndex(std::size_t index) {
|
||||
pendingAppIndex = index;
|
||||
|
||||
Reference in New Issue
Block a user