mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
more settings
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
40
Firmware/sdk/core/src/persistent_settings.cpp
Normal file
40
Firmware/sdk/core/src/persistent_settings.cpp
Normal 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
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user