more settings

This commit is contained in:
2025-10-12 17:11:22 +02:00
parent 1b6e9a0f78
commit aaac0514c0
8 changed files with 227 additions and 119 deletions

View File

@@ -3,7 +3,10 @@
#include "cardboy/apps/menu_app.hpp"
#include "cardboy/gfx/font16x8.hpp"
#include "cardboy/sdk/app_framework.hpp"
#include "cardboy/sdk/persistent_settings.hpp"
#include <array>
#include <cstddef>
#include <memory>
#include <string>
#include <string_view>
@@ -15,12 +18,22 @@ namespace {
using cardboy::sdk::AppContext;
using Framebuffer = typename AppContext::Framebuffer;
enum class SettingOption {
Sound,
AutoLightSleep,
};
constexpr std::array<SettingOption, 2> kOptions = {
SettingOption::Sound,
SettingOption::AutoLightSleep,
};
class SettingsApp final : public cardboy::sdk::IApp {
public:
explicit SettingsApp(AppContext& ctx) : context(ctx), framebuffer(ctx.framebuffer) {}
void onStart() override {
refreshState();
loadSettings();
dirty = true;
renderIfNeeded();
}
@@ -32,10 +45,9 @@ public:
const auto& current = event.button.current;
const auto& previous = event.button.previous;
const bool prevAvailable = buzzerAvailable;
const bool prevMuted = muted;
refreshState();
if (prevAvailable != buzzerAvailable || prevMuted != muted)
const bool previousAvailable = buzzerAvailable;
syncBuzzerState();
if (previousAvailable != buzzerAvailable)
dirty = true;
if (current.b && !previous.b) {
@@ -43,10 +55,22 @@ public:
return;
}
bool moved = false;
if (current.down && !previous.down) {
moveSelection(+1);
moved = true;
} else if (current.up && !previous.up) {
moveSelection(-1);
moved = true;
}
const bool togglePressed = (current.a && !previous.a) || (current.select && !previous.select) ||
(current.start && !previous.start);
if (togglePressed)
toggleMute();
handleToggle();
if (moved)
dirty = true;
renderIfNeeded();
}
@@ -55,29 +79,63 @@ private:
AppContext& context;
Framebuffer& framebuffer;
bool buzzerAvailable = false;
bool muted = false;
bool dirty = false;
bool buzzerAvailable = false;
cardboy::sdk::PersistentSettings settings{};
std::size_t selectedIndex = 0;
bool dirty = false;
void refreshState() {
if (auto* buzzer = context.buzzer()) {
buzzerAvailable = true;
muted = buzzer->isMuted();
} else {
buzzerAvailable = false;
muted = false;
void loadSettings() {
settings = cardboy::sdk::loadPersistentSettings(context.getServices());
syncBuzzerState();
}
void syncBuzzerState() {
auto* buzzer = context.buzzer();
buzzerAvailable = (buzzer != nullptr);
if (!buzzer)
return;
if (buzzer->isMuted() != settings.mute)
buzzer->setMuted(settings.mute);
}
void moveSelection(int delta) {
const int count = static_cast<int>(kOptions.size());
if (count == 0)
return;
const int current = static_cast<int>(selectedIndex);
int next = (current + delta) % count;
if (next < 0)
next += count;
selectedIndex = static_cast<std::size_t>(next);
}
void handleToggle() {
switch (kOptions[selectedIndex]) {
case SettingOption::Sound:
toggleSound();
break;
case SettingOption::AutoLightSleep:
toggleAutoLightSleep();
break;
}
}
void toggleMute() {
auto* buzzer = context.buzzer();
if (!buzzer)
void toggleSound() {
if (!buzzerAvailable)
return;
const bool targetMuted = !muted;
buzzer->setMuted(targetMuted);
muted = buzzer->isMuted();
if (!muted)
buzzer->beepMove();
settings.mute = !settings.mute;
cardboy::sdk::savePersistentSettings(context.getServices(), settings);
syncBuzzerState();
if (!settings.mute) {
if (auto* buzzer = context.buzzer())
buzzer->beepMove();
}
dirty = true;
}
void toggleAutoLightSleep() {
settings.autoLightSleep = !settings.autoLightSleep;
cardboy::sdk::savePersistentSettings(context.getServices(), settings);
dirty = true;
}
@@ -87,6 +145,17 @@ private:
font16x8::drawText(fb, x, y, text, scale, true, letterSpacing);
}
void drawOptionRow(int row, std::string_view label, std::string_view value, bool selected) {
std::string prefix = selected ? "> " : " ";
std::string line = prefix;
line.append(label);
line.append(": ");
line.append(value);
const int x = 24;
const int y = 56 + row * 24;
font16x8::drawText(framebuffer, x, y, line, 1, true, 1);
}
void renderIfNeeded() {
if (!dirty)
return;
@@ -97,19 +166,18 @@ private:
drawCenteredText(framebuffer, 24, "SETTINGS", 1, 1);
const int centerY = framebuffer.height() / 2;
if (!buzzerAvailable) {
drawCenteredText(framebuffer, centerY - 12, "BUZZER SERVICE", 1, 1);
drawCenteredText(framebuffer, centerY + 8, "UNAVAILABLE", 1, 1);
} else {
const char* stateText = muted ? "ON" : "OFF";
std::string line = std::string("MUTE: ") + stateText;
drawCenteredText(framebuffer, centerY - 20, line, 2, 0);
drawCenteredText(framebuffer, centerY + 26, "A TOGGLE", 1, 1);
drawCenteredText(framebuffer, centerY + 44, "START/SELECT ALSO", 1, 1);
}
const std::string soundValue = buzzerAvailable ? (settings.mute ? "OFF" : "ON") : "N/A";
drawOptionRow(0, "SOUND", soundValue, selectedIndex == 0);
drawCenteredText(framebuffer, framebuffer.height() - 28, "B BACK", 1, 1);
const std::string lightSleepValue = settings.autoLightSleep ? "ON" : "OFF";
drawOptionRow(1, "AUTO LIGHT SLEEP", lightSleepValue, selectedIndex == 1);
if (!buzzerAvailable)
drawCenteredText(framebuffer, 120, "SOUND CONTROL UNAVAILABLE", 1, 1);
drawCenteredText(framebuffer, framebuffer.height() - 54, "UP/DOWN MOVE", 1, 1);
drawCenteredText(framebuffer, framebuffer.height() - 36, "A/START/SELECT TOGGLE", 1, 1);
drawCenteredText(framebuffer, framebuffer.height() - 18, "B BACK | LIGHT SLEEP AFTER RESET", 1, 1);
framebuffer.sendFrame();
}

View File

@@ -4,6 +4,7 @@ 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
${CMAKE_CURRENT_SOURCE_DIR}/src/persistent_settings.cpp
)
set_target_properties(cardboy_sdk PROPERTIES

View File

@@ -0,0 +1,15 @@
#pragma once
#include "cardboy/sdk/services.hpp"
namespace cardboy::sdk {
struct PersistentSettings {
bool mute = false;
bool autoLightSleep = false;
};
PersistentSettings loadPersistentSettings(Services* services);
void savePersistentSettings(Services* services, const PersistentSettings& settings);
} // namespace cardboy::sdk

View File

@@ -0,0 +1,40 @@
#include "cardboy/sdk/persistent_settings.hpp"
#include <cstdint>
#include <string_view>
namespace cardboy::sdk {
namespace {
constexpr std::string_view kNamespace = "settings";
constexpr std::string_view kMuteKey = "mute";
constexpr std::string_view kAutoLightSleepKey = "auto_light_sleep";
[[nodiscard]] std::uint32_t boolToStorage(bool value) { return value ? 1U : 0U; }
[[nodiscard]] bool storageToBool(std::uint32_t value) { return value != 0U; }
} // namespace
PersistentSettings loadPersistentSettings(Services* services) {
PersistentSettings settings{};
if (!services || !services->storage)
return settings;
std::uint32_t raw = 0;
if (services->storage->readUint32(kNamespace, kMuteKey, raw))
settings.mute = storageToBool(raw);
if (services->storage->readUint32(kNamespace, kAutoLightSleepKey, raw))
settings.autoLightSleep = storageToBool(raw);
return settings;
}
void savePersistentSettings(Services* services, const PersistentSettings& settings) {
if (!services || !services->storage)
return;
services->storage->writeUint32(kNamespace, kMuteKey, boolToStorage(settings.mute));
services->storage->writeUint32(kNamespace, kAutoLightSleepKey, boolToStorage(settings.autoLightSleep));
}
} // namespace cardboy::sdk

View File

@@ -5,6 +5,7 @@
#include "cardboy/apps/tetris_app.hpp"
#include "cardboy/backend/desktop_backend.hpp"
#include "cardboy/sdk/app_system.hpp"
#include "cardboy/sdk/persistent_settings.hpp"
#include <cstdio>
#include <exception>
@@ -19,6 +20,11 @@ int main() {
context.services = &runtime.serviceRegistry();
cardboy::sdk::AppSystem system(context);
const cardboy::sdk::PersistentSettings persistentSettings =
cardboy::sdk::loadPersistentSettings(context.getServices());
if (auto* buzzer = context.buzzer())
buzzer->setMuted(persistentSettings.mute);
system.registerApp(apps::createMenuAppFactory());
system.registerApp(apps::createSettingsAppFactory());
system.registerApp(apps::createClockAppFactory());