mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
202 lines
6.5 KiB
C++
202 lines
6.5 KiB
C++
#include "cardboy/apps/settings_app.hpp"
|
|
|
|
#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>
|
|
|
|
namespace apps {
|
|
|
|
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 {
|
|
loadSettings();
|
|
dirty = true;
|
|
renderIfNeeded();
|
|
}
|
|
|
|
std::optional<std::uint32_t> handleEvent(const cardboy::sdk::AppEvent& event) override {
|
|
event.visit(cardboy::sdk::overload(
|
|
[this](const cardboy::sdk::AppButtonEvent& button) {
|
|
const auto& current = button.current;
|
|
const auto& previous = button.previous;
|
|
|
|
const bool previousAvailable = buzzerAvailable;
|
|
syncBuzzerState();
|
|
if (previousAvailable != buzzerAvailable)
|
|
dirty = true;
|
|
|
|
if (current.b && !previous.b) {
|
|
context.requestAppSwitchByName(kMenuAppName);
|
|
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.start && !previous.start) ||
|
|
(current.select && !previous.select);
|
|
if (togglePressed)
|
|
handleToggle();
|
|
|
|
if (moved)
|
|
dirty = true;
|
|
|
|
renderIfNeeded();
|
|
},
|
|
[](const cardboy::sdk::AppTimerEvent&) { /* ignore */ },
|
|
[](const cardboy::sdk::AppTimeoutEvent&) { /* ignore */ }));
|
|
return std::nullopt;
|
|
}
|
|
|
|
private:
|
|
AppContext& context;
|
|
Framebuffer& framebuffer;
|
|
|
|
bool buzzerAvailable = false;
|
|
cardboy::sdk::PersistentSettings settings{};
|
|
std::size_t selectedIndex = 0;
|
|
bool dirty = 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 toggleSound() {
|
|
if (!buzzerAvailable)
|
|
return;
|
|
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;
|
|
}
|
|
|
|
static void drawCenteredText(Framebuffer& fb, int y, std::string_view text, int scale, int letterSpacing = 1) {
|
|
const int width = font16x8::measureText(text, scale, letterSpacing);
|
|
const int x = (fb.width() - width) / 2;
|
|
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;
|
|
dirty = false;
|
|
|
|
framebuffer.frameReady();
|
|
framebuffer.clear(false);
|
|
|
|
drawCenteredText(framebuffer, 24, "SETTINGS", 1, 1);
|
|
|
|
const std::string soundValue = buzzerAvailable ? (settings.mute ? "OFF" : "ON") : "N/A";
|
|
drawOptionRow(0, "SOUND", soundValue, selectedIndex == 0);
|
|
|
|
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();
|
|
}
|
|
};
|
|
|
|
class SettingsAppFactory final : public cardboy::sdk::IAppFactory {
|
|
public:
|
|
const char* name() const override { return kSettingsAppName; }
|
|
std::unique_ptr<cardboy::sdk::IApp> create(cardboy::sdk::AppContext& context) override {
|
|
return std::make_unique<SettingsApp>(context);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<cardboy::sdk::IAppFactory> createSettingsAppFactory() { return std::make_unique<SettingsAppFactory>(); }
|
|
|
|
} // namespace apps
|