statusbar

This commit is contained in:
2025-10-12 15:03:34 +02:00
parent 6d8834d9b2
commit 5ab8662332
9 changed files with 257 additions and 4 deletions

View File

@@ -0,0 +1,18 @@
#pragma once
namespace cardboy::sdk {
class FramebufferHooks {
public:
using PreSendHook = void (*)(void* framebuffer, void* userData);
static void setPreSendHook(PreSendHook hook, void* userData);
static void clearPreSendHook();
static void invokePreSend(void* framebuffer);
private:
static PreSendHook hook_;
static void* userData_;
};
} // namespace cardboy::sdk

View File

@@ -1,6 +1,7 @@
#pragma once
#include "input_state.hpp"
#include "cardboy/sdk/framebuffer_hooks.hpp"
#include <concepts>
#include <cstdint>
@@ -67,9 +68,11 @@ public:
}
__attribute__((always_inline)) void sendFrame(bool clearDrawBuffer = true) {
if constexpr (detail::HasSendFrameImpl<Impl>)
if constexpr (detail::HasSendFrameImpl<Impl>) {
FramebufferHooks::invokePreSend(&impl());
impl().sendFrame_impl(clearDrawBuffer);
}
}
[[nodiscard]] __attribute__((always_inline)) bool isFrameInFlight() const {
if constexpr (detail::HasFrameInFlightImpl<Impl>)

View File

@@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.16)
add_library(cardboy_sdk STATIC
${CMAKE_CURRENT_SOURCE_DIR}/src/app_system.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/status_bar.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/framebuffer_hooks.cpp
)
set_target_properties(cardboy_sdk PROPERTIES

View File

@@ -12,6 +12,7 @@ namespace cardboy::sdk {
class AppSystem {
public:
explicit AppSystem(AppContext context);
~AppSystem();
void registerApp(std::unique_ptr<IAppFactory> factory);
bool startApp(const std::string& name);

View File

@@ -0,0 +1,20 @@
#pragma once
#include <cstddef>
namespace cardboy::sdk {
class FramebufferHooks {
public:
using PreSendHook = void (*)(void* framebuffer, void* userData);
static void setPreSendHook(PreSendHook hook, void* userData);
static void clearPreSendHook();
static void invokePreSend(void* framebuffer);
private:
static PreSendHook hook_;
static void* userData_;
};
} // namespace cardboy::sdk

View File

@@ -0,0 +1,83 @@
#pragma once
#include "cardboy/sdk/input_state.hpp"
#include "cardboy/sdk/services.hpp"
#include "cardboy/gfx/font16x8.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
namespace cardboy::sdk {
class StatusBar {
public:
static StatusBar& instance();
void setServices(Services* services) { services_ = services; }
void setEnabled(bool value);
void toggle();
[[nodiscard]] bool isEnabled() const { return enabled_; }
void setCurrentAppName(std::string_view name);
[[nodiscard]] bool handleToggleInput(const InputState& current, const InputState& previous);
template<typename Framebuffer>
void renderIfEnabled(Framebuffer& fb) {
if (!enabled_)
return;
renderBar(fb);
}
private:
StatusBar() = default;
template<typename Framebuffer>
void renderBar(Framebuffer& fb) {
const int width = fb.width();
if (width <= 0)
return;
const std::string leftText = prepareLeftText(width);
const std::string rightText = prepareRightText();
for (int x = 0; x < width; ++x)
fb.drawPixel(x, 0, true);
const int textY = 1;
const int barHeight = font16x8::kGlyphHeight + 2;
const int bottomSeparatorY = textY + font16x8::kGlyphHeight + 1;
if (bottomSeparatorY < barHeight && bottomSeparatorY < fb.height()) {
for (int x = 0; x < width; ++x)
fb.drawPixel(x, bottomSeparatorY, (x % 2) == 0);
}
const int leftX = 2;
if (!leftText.empty())
font16x8::drawText(fb, leftX, textY, leftText, 1, true, 1);
if (!rightText.empty()) {
int rightWidth = font16x8::measureText(rightText, 1, 1);
int rightX = width - rightWidth - 2;
const int minRightX = leftX + font16x8::measureText(leftText, 1, 1) + 6;
if (rightX < minRightX)
rightX = std::max(minRightX, width / 2);
if (rightX < width)
font16x8::drawText(fb, rightX, textY, rightText, 1, true, 1);
}
}
[[nodiscard]] std::string prepareLeftText(int displayWidth) const;
[[nodiscard]] std::string prepareRightText() const;
bool enabled_ = false;
Services* services_ = nullptr;
std::string appName_{};
};
} // namespace cardboy::sdk

View File

@@ -1,4 +1,6 @@
#include "cardboy/sdk/app_system.hpp"
#include "cardboy/sdk/framebuffer_hooks.hpp"
#include "cardboy/sdk/status_bar.hpp"
#include <algorithm>
#include <limits>
@@ -12,9 +14,25 @@ namespace {
}
constexpr std::uint32_t kIdlePollMs = 16;
template<typename Framebuffer>
void statusBarPreSendHook(void* framebuffer, void* userData) {
auto* fb = static_cast<Framebuffer*>(framebuffer);
auto* status = static_cast<StatusBar*>(userData);
if (fb && status)
status->renderIfEnabled(*fb);
}
} // namespace
AppSystem::AppSystem(AppContext ctx) : context(std::move(ctx)) { context.system = this; }
AppSystem::AppSystem(AppContext ctx) : context(std::move(ctx)) {
context.system = this;
auto& statusBar = StatusBar::instance();
statusBar.setServices(context.services);
using FBType = typename AppContext::Framebuffer;
FramebufferHooks::setPreSendHook(&statusBarPreSendHook<FBType>, &statusBar);
}
AppSystem::~AppSystem() { FramebufferHooks::clearPreSendHook(); }
void AppSystem::registerApp(std::unique_ptr<IAppFactory> factory) {
if (!factory)
@@ -53,6 +71,8 @@ bool AppSystem::startAppByIndex(std::size_t index) {
clearTimersForCurrentApp();
current = std::move(app);
lastInputState = context.input.readState();
StatusBar::instance().setServices(context.services);
StatusBar::instance().setCurrentAppName(activeFactory ? activeFactory->name() : "");
current->onStart();
return true;
}
@@ -72,7 +92,9 @@ void AppSystem::run() {
processDueTimers(now, events);
const InputState inputNow = context.input.readState();
if (inputsDiffer(inputNow, lastInputState)) {
const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(inputNow, lastInputState);
if (!consumedByStatusToggle && inputsDiffer(inputNow, lastInputState)) {
AppEvent evt{};
evt.type = AppEventType::Button;
evt.timestamp_ms = now;
@@ -80,6 +102,8 @@ void AppSystem::run() {
evt.button.previous = lastInputState;
events.push_back(evt);
lastInputState = inputNow;
} else if (consumedByStatusToggle) {
lastInputState = inputNow;
}
for (const auto& evt: events) {

View File

@@ -0,0 +1,23 @@
#include "cardboy/sdk/framebuffer_hooks.hpp"
namespace cardboy::sdk {
FramebufferHooks::PreSendHook FramebufferHooks::hook_ = nullptr;
void* FramebufferHooks::userData_ = nullptr;
void FramebufferHooks::setPreSendHook(PreSendHook hook, void* userData) {
hook_ = hook;
userData_ = userData;
}
void FramebufferHooks::clearPreSendHook() {
hook_ = nullptr;
userData_ = nullptr;
}
void FramebufferHooks::invokePreSend(void* framebuffer) {
if (hook_)
hook_(framebuffer, userData_);
}
} // namespace cardboy::sdk

View File

@@ -0,0 +1,79 @@
#include "cardboy/sdk/status_bar.hpp"
#include <algorithm>
#include <cctype>
#include <cstdio>
namespace cardboy::sdk {
StatusBar& StatusBar::instance() {
static StatusBar bar;
return bar;
}
void StatusBar::setEnabled(bool value) { enabled_ = value; }
void StatusBar::toggle() {
enabled_ = !enabled_;
if (services_ && services_->buzzer)
services_->buzzer->beepMove();
}
void StatusBar::setCurrentAppName(std::string_view name) {
appName_.assign(name.begin(), name.end());
std::transform(appName_.begin(), appName_.end(), appName_.begin(), [](unsigned char ch) {
return static_cast<char>(std::toupper(ch));
});
}
bool StatusBar::handleToggleInput(const InputState& current, const InputState& previous) {
const bool comboNow = current.start && current.select && current.up;
const bool comboPrev = previous.start && previous.select && previous.up;
if (comboNow && !comboPrev) {
toggle();
return true;
}
return false;
}
std::string StatusBar::prepareLeftText(int displayWidth) const {
std::string text = appName_.empty() ? std::string("CARDBOY") : appName_;
int maxWidth = std::max(0, displayWidth - 32);
while (!text.empty() && font16x8::measureText(text, 1, 1) > maxWidth)
text.pop_back();
return text;
}
std::string StatusBar::prepareRightText() const {
if (!services_)
return {};
std::string right;
if (services_->battery && services_->battery->hasData()) {
const float charge = services_->battery->charge();
char buf[32];
if (charge > 0.0f && charge <= 1.5f) {
const int pct = std::clamp(static_cast<int>(charge * 100.0f + 0.5f), 0, 100);
std::snprintf(buf, sizeof(buf), "BAT %d%%", pct);
} else {
std::snprintf(buf, sizeof(buf), "BAT %.2fV", static_cast<double>(services_->battery->voltage()));
}
right.assign(buf);
}
if (services_->powerManager && services_->powerManager->isSlowMode()) {
if (!right.empty())
right.append(" ");
right.append("SLOW");
}
if (services_->buzzer && services_->buzzer->isMuted()) {
if (!right.empty())
right.append(" ");
right.append("MUTE");
}
return right;
}
} // namespace cardboy::sdk