diff --git a/Firmware/components/sdk-esp/CMakeLists.txt b/Firmware/components/sdk-esp/CMakeLists.txt index bd9fc90..26172ac 100644 --- a/Firmware/components/sdk-esp/CMakeLists.txt +++ b/Firmware/components/sdk-esp/CMakeLists.txt @@ -2,4 +2,4 @@ idf_component_register() add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/../../sdk" cb-sdk-build) -target_link_libraries(${COMPONENT_LIB} INTERFACE cbsdk) \ No newline at end of file +target_link_libraries(${COMPONENT_LIB} INTERFACE cardboy_sdk cardboy_sdk) \ No newline at end of file diff --git a/Firmware/main/CMakeLists.txt b/Firmware/main/CMakeLists.txt index 24eb470..5e231a5 100644 --- a/Firmware/main/CMakeLists.txt +++ b/Firmware/main/CMakeLists.txt @@ -1,9 +1,8 @@ idf_component_register(SRCS src/app_main.cpp - src/app_system.cpp - src/apps/menu_app.cpp - src/apps/clock_app.cpp - src/apps/tetris_app.cpp + ../sdk/apps/menu_app.cpp + ../sdk/apps/clock_app.cpp + ../sdk/apps/tetris_app.cpp src/apps/gameboy_app.cpp src/display.cpp src/bat_mon.cpp @@ -16,8 +15,9 @@ idf_component_register(SRCS src/power_helper.cpp src/buzzer.cpp src/fs_helper.cpp + ../sdk/src/app_system.cpp PRIV_REQUIRES spi_flash esp_driver_i2c driver sdk-esp esp_timer nvs_flash littlefs - INCLUDE_DIRS "include" + INCLUDE_DIRS "include" "../sdk/include" EMBED_FILES "roms/builtin_demo1.gb" "roms/builtin_demo2.gb") -littlefs_create_partition_image(littlefs ../flash_data FLASH_IN_PROJECT) \ No newline at end of file +littlefs_create_partition_image(littlefs ../flash_data FLASH_IN_PROJECT) diff --git a/Firmware/main/include/app_framework.hpp b/Firmware/main/include/app_framework.hpp index a16ccc4..2971284 100644 --- a/Firmware/main/include/app_framework.hpp +++ b/Firmware/main/include/app_framework.hpp @@ -1,118 +1,27 @@ #pragma once -#include "app_platform.hpp" -#include "input_state.hpp" +#include "cardboy/sdk/app_framework.hpp" +#include "cardboy/sdk/platform.hpp" -#include -#include -#include -#include -#include +using AppTimerHandle = cardboy::sdk::AppTimerHandle; +constexpr AppTimerHandle kInvalidAppTimer = cardboy::sdk::kInvalidAppTimer; -class AppSystem; - -using AppTimerHandle = std::uint32_t; -constexpr AppTimerHandle kInvalidAppTimer = 0; - -enum class AppEventType { - Button, - Timer, -}; - -struct AppButtonEvent { - InputState current{}; - InputState previous{}; -}; - -struct AppTimerEvent { - AppTimerHandle handle = kInvalidAppTimer; -}; - -struct AppEvent { - AppEventType type; - std::uint32_t timestamp_ms = 0; - AppButtonEvent button{}; - AppTimerEvent timer{}; -}; +using AppEventType = cardboy::sdk::AppEventType; +using AppButtonEvent = cardboy::sdk::AppButtonEvent; +using AppTimerEvent = cardboy::sdk::AppTimerEvent; +using AppEvent = cardboy::sdk::AppEvent; template -struct BasicAppContext { - using Framebuffer = FramebufferT; - using Input = InputT; - using Clock = ClockT; +using BasicAppContext = cardboy::sdk::BasicAppContext; - BasicAppContext() = delete; - BasicAppContext(FramebufferT& fb, InputT& in, ClockT& clk) : framebuffer(fb), input(in), clock(clk) {} +using AppContext = cardboy::sdk::AppContext; - FramebufferT& framebuffer; - InputT& input; - ClockT& clock; - AppSystem* system = nullptr; - - void requestAppSwitchByIndex(std::size_t index) { - pendingAppIndex = index; - pendingAppName.clear(); - pendingSwitchByName = false; - pendingSwitch = true; - } - - void requestAppSwitchByName(std::string_view name) { - pendingAppName.assign(name.begin(), name.end()); - pendingSwitchByName = true; - pendingSwitch = true; - } - - bool hasPendingAppSwitch() const { return pendingSwitch; } - - AppTimerHandle scheduleTimer(uint32_t delay_ms, bool repeat = false) { - if (!system) - return kInvalidAppTimer; - return scheduleTimerInternal(delay_ms, repeat); - } - - AppTimerHandle scheduleRepeatingTimer(uint32_t interval_ms) { - if (!system) - return kInvalidAppTimer; - return scheduleTimerInternal(interval_ms, true); - } - - void cancelTimer(AppTimerHandle handle) { - if (!system) - return; - cancelTimerInternal(handle); - } - - void cancelAllTimers() { - if (!system) - return; - cancelAllTimersInternal(); - } - -private: - friend class AppSystem; - bool pendingSwitch = false; - bool pendingSwitchByName = false; - std::size_t pendingAppIndex = 0; - std::string pendingAppName; - - AppTimerHandle scheduleTimerInternal(uint32_t delay_ms, bool repeat); - void cancelTimerInternal(AppTimerHandle handle); - void cancelAllTimersInternal(); -}; - -using AppContext = BasicAppContext; - -class IApp { -public: - virtual ~IApp() = default; - virtual void onStart() {} - virtual void onStop() {} - virtual void handleEvent(const AppEvent& event) = 0; -}; - -class IAppFactory { -public: - virtual ~IAppFactory() = default; - virtual const char* name() const = 0; - virtual std::unique_ptr create(AppContext& context) = 0; -}; +using IApp = cardboy::sdk::IApp; +using IAppFactory = cardboy::sdk::IAppFactory; +using Services = cardboy::sdk::Services; +using IBuzzer = cardboy::sdk::IBuzzer; +using IBatteryMonitor = cardboy::sdk::IBatteryMonitor; +using IStorage = cardboy::sdk::IStorage; +using IRandom = cardboy::sdk::IRandom; +using IHighResClock = cardboy::sdk::IHighResClock; +using IPowerManager = cardboy::sdk::IPowerManager; diff --git a/Firmware/main/include/app_platform.hpp b/Firmware/main/include/app_platform.hpp index 93902be..65b265f 100644 --- a/Firmware/main/include/app_platform.hpp +++ b/Firmware/main/include/app_platform.hpp @@ -1,7 +1,8 @@ #pragma once +#include "cardboy/sdk/display_spec.hpp" +#include "cardboy/sdk/platform.hpp" #include "config.hpp" -#include "input_state.hpp" #include #include @@ -10,29 +11,33 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -class PlatformFramebuffer { +class PlatformFramebuffer final : public cardboy::sdk::IFramebuffer { public: - int width() const { return DISP_WIDTH; } - int height() const { return DISP_HEIGHT; } + int width() const override { return cardboy::sdk::kDisplayWidth; } + int height() const override { return cardboy::sdk::kDisplayHeight; } - void drawPixel(int x, int y, bool on) { + void drawPixel(int x, int y, bool on) override { if (x < 0 || y < 0 || x >= width() || y >= height()) return; DispTools::set_pixel(x, y, on); } - void clear(bool on) { + void clear(bool on) override { for (int y = 0; y < height(); ++y) for (int x = 0; x < width(); ++x) DispTools::set_pixel(x, y, on); } + + void beginFrame() override { DispTools::draw_to_display_async_wait(); } + void endFrame() override { DispTools::draw_to_display_async_start(); } + bool isFrameInFlight() const override { return DispTools::draw_to_display_async_busy(); } }; -class PlatformInput { +class PlatformInput final : public cardboy::sdk::IInput { public: - InputState readState() { - InputState state{}; - const uint8_t pressed = Buttons::get().get_pressed(); + cardboy::sdk::InputState readState() override { + cardboy::sdk::InputState state{}; + const uint8_t pressed = Buttons::get().get_pressed(); if (pressed & BTN_UP) state.up = true; if (pressed & BTN_LEFT) @@ -53,12 +58,16 @@ public: } }; -class PlatformClock { +class PlatformClock final : public cardboy::sdk::IClock { public: - uint32_t millis() { + std::uint32_t millis() override { TickType_t ticks = xTaskGetTickCount(); - return static_cast((static_cast(ticks) * 1000ULL) / configTICK_RATE_HZ); + return static_cast((static_cast(ticks) * 1000ULL) / configTICK_RATE_HZ); } - void sleep_ms(uint32_t ms) { PowerHelper::get().delay(static_cast(ms), static_cast(ms)); } + void sleep_ms(std::uint32_t ms) override { + if (ms == 0) + return; + PowerHelper::get().delay(static_cast(ms), static_cast(ms)); + } }; diff --git a/Firmware/main/include/app_system.hpp b/Firmware/main/include/app_system.hpp index 7b7bb4a..94bbcf9 100644 --- a/Firmware/main/include/app_system.hpp +++ b/Firmware/main/include/app_system.hpp @@ -1,79 +1,5 @@ #pragma once -#include "app_framework.hpp" +#include "cardboy/sdk/app_system.hpp" -#include -#include -#include -#include - -class AppSystem { -public: - explicit AppSystem(AppContext context); - - void registerApp(std::unique_ptr factory); - bool startApp(const std::string& name); - bool startAppByIndex(std::size_t index); - - void run(); - - [[nodiscard]] std::size_t appCount() const { return factories.size(); } - [[nodiscard]] const IAppFactory* factoryAt(std::size_t index) const; - [[nodiscard]] std::size_t indexOfFactory(const IAppFactory* factory) const; - [[nodiscard]] std::size_t currentFactoryIndex() const { return activeIndex; } - - [[nodiscard]] const IApp* currentApp() const { return current.get(); } - [[nodiscard]] const IAppFactory* currentFactory() const { return activeFactory; } - -private: - template - friend struct BasicAppContext; - - struct TimerRecord { - AppTimerHandle id = kInvalidAppTimer; - std::uint32_t generation = 0; - std::uint32_t due_ms = 0; - std::uint32_t interval_ms = 0; - bool repeat = false; - bool active = false; - }; - - AppTimerHandle scheduleTimer(uint32_t delay_ms, bool repeat); - void cancelTimer(AppTimerHandle handle); - void cancelAllTimers(); - - void dispatchEvent(const AppEvent& event); - - void processDueTimers(std::uint32_t now, std::vector& outEvents); - std::uint32_t nextTimerDueMs(std::uint32_t now) const; - void clearTimersForCurrentApp(); - TimerRecord* findTimer(AppTimerHandle handle); - bool handlePendingSwitchRequest(); - - AppContext context; - std::vector> factories; - std::unique_ptr current; - IAppFactory* activeFactory = nullptr; - std::size_t activeIndex = static_cast(-1); - std::vector timers; - AppTimerHandle nextTimerId = 1; - std::uint32_t currentGeneration = 0; - InputState lastInputState{}; -}; - -template -AppTimerHandle BasicAppContext::scheduleTimerInternal(uint32_t delay_ms, bool repeat) { - return system ? system->scheduleTimer(delay_ms, repeat) : kInvalidAppTimer; -} - -template -void BasicAppContext::cancelTimerInternal(AppTimerHandle handle) { - if (system) - system->cancelTimer(handle); -} - -template -void BasicAppContext::cancelAllTimersInternal() { - if (system) - system->cancelAllTimers(); -} +using AppSystem = cardboy::sdk::AppSystem; diff --git a/Firmware/main/include/apps/clock_app.hpp b/Firmware/main/include/apps/clock_app.hpp index 232ec65..eb23da4 100644 --- a/Firmware/main/include/apps/clock_app.hpp +++ b/Firmware/main/include/apps/clock_app.hpp @@ -1,12 +1,4 @@ #pragma once -#include "app_framework.hpp" - -#include - -namespace apps { - -std::unique_ptr createClockAppFactory(); - -} +#include "cardboy/apps/clock_app.hpp" diff --git a/Firmware/main/include/apps/menu_app.hpp b/Firmware/main/include/apps/menu_app.hpp index 7e11d52..702a0e3 100644 --- a/Firmware/main/include/apps/menu_app.hpp +++ b/Firmware/main/include/apps/menu_app.hpp @@ -1,15 +1,3 @@ #pragma once -#include "app_framework.hpp" - -#include -#include - -namespace apps { - -inline constexpr char kMenuAppName[] = "Menu"; -inline constexpr std::string_view kMenuAppNameView = kMenuAppName; - -std::unique_ptr createMenuAppFactory(); - -} // namespace apps +#include "cardboy/apps/menu_app.hpp" diff --git a/Firmware/main/include/apps/tetris_app.hpp b/Firmware/main/include/apps/tetris_app.hpp index e38e1e9..bcd1be1 100644 --- a/Firmware/main/include/apps/tetris_app.hpp +++ b/Firmware/main/include/apps/tetris_app.hpp @@ -1,11 +1,3 @@ #pragma once -#include "app_framework.hpp" - -#include - -namespace apps { - -std::unique_ptr createTetrisAppFactory(); - -} +#include "cardboy/apps/tetris_app.hpp" diff --git a/Firmware/main/include/config.hpp b/Firmware/main/include/config.hpp index 97be0a9..16bcc91 100644 --- a/Firmware/main/include/config.hpp +++ b/Firmware/main/include/config.hpp @@ -15,8 +15,10 @@ #define SPI_BUS SPI2_HOST -#define DISP_WIDTH 400 -#define DISP_HEIGHT 240 +#include "cardboy/sdk/display_spec.hpp" + +#define DISP_WIDTH cardboy::sdk::kDisplayWidth +#define DISP_HEIGHT cardboy::sdk::kDisplayHeight #define BUZZER_PIN GPIO_NUM_25 diff --git a/Firmware/main/include/display.hpp b/Firmware/main/include/display.hpp index 24f7dc6..8eca365 100644 --- a/Firmware/main/include/display.hpp +++ b/Firmware/main/include/display.hpp @@ -13,8 +13,6 @@ #include #include -#include "Surface.hpp" -#include "Window.hpp" namespace SMD { static constexpr size_t kLineBytes = DISP_WIDTH / 8; diff --git a/Firmware/main/include/input_state.hpp b/Firmware/main/include/input_state.hpp index 4842f20..c6c8c09 100644 --- a/Firmware/main/include/input_state.hpp +++ b/Firmware/main/include/input_state.hpp @@ -1,12 +1,5 @@ #pragma once -struct InputState { - bool up = false; - bool left = false; - bool right = false; - bool down = false; - bool a = false; - bool b = false; - bool select = false; - bool start = false; -}; +#include "cardboy/sdk/input_state.hpp" + +using InputState = cardboy::sdk::InputState; diff --git a/Firmware/main/src/app_main.cpp b/Firmware/main/src/app_main.cpp index 5fa3147..3da7ca4 100644 --- a/Firmware/main/src/app_main.cpp +++ b/Firmware/main/src/app_main.cpp @@ -3,11 +3,13 @@ #include "app_system.hpp" #include "app_framework.hpp" +#include "app_platform.hpp" #include "apps/clock_app.hpp" #include "apps/gameboy_app.hpp" #include "apps/menu_app.hpp" #include "apps/tetris_app.hpp" #include "config.hpp" +#include "cardboy/sdk/services.hpp" #include #include @@ -16,6 +18,8 @@ #include #include #include +#include +#include #include #include #include @@ -26,6 +30,8 @@ #include "driver/gpio.h" #include "esp_err.h" +#include "esp_random.h" +#include "esp_timer.h" #include "esp_pm.h" #include "esp_sleep.h" #include "sdkconfig.h" @@ -36,8 +42,84 @@ #include #include #include +#include #include +namespace { + +class EspBuzzer final : public cardboy::sdk::IBuzzer { +public: + void tone(std::uint32_t freq, std::uint32_t duration_ms, std::uint32_t gap_ms = 0) override { + Buzzer::get().tone(freq, duration_ms, gap_ms); + } + + void beepRotate() override { Buzzer::get().beepRotate(); } + void beepMove() override { Buzzer::get().beepMove(); } + void beepLock() override { Buzzer::get().beepLock(); } + void beepLines(int lines) override { Buzzer::get().beepLines(lines); } + void beepLevelUp(int level) override { Buzzer::get().beepLevelUp(level); } + void beepGameOver() override { Buzzer::get().beepGameOver(); } + + void setMuted(bool muted) override { Buzzer::get().setMuted(muted); } + void toggleMuted() override { Buzzer::get().toggleMuted(); } + [[nodiscard]] bool isMuted() const override { return Buzzer::get().isMuted(); } +}; + +class EspBatteryMonitor final : public cardboy::sdk::IBatteryMonitor { +public: + [[nodiscard]] bool hasData() const override { return true; } + [[nodiscard]] float voltage() const override { return BatMon::get().get_voltage(); } + [[nodiscard]] float charge() const override { return BatMon::get().get_charge(); } + [[nodiscard]] float current() const override { return BatMon::get().get_current(); } +}; + +class EspStorage final : public cardboy::sdk::IStorage { +public: + [[nodiscard]] bool readUint32(std::string_view ns, std::string_view key, std::uint32_t& out) override { + nvs_handle_t handle; + std::string nsStr(ns); + std::string keyStr(key); + if (nvs_open(nsStr.c_str(), NVS_READONLY, &handle) != ESP_OK) + return false; + std::uint32_t value = 0; + esp_err_t err = nvs_get_u32(handle, keyStr.c_str(), &value); + nvs_close(handle); + if (err != ESP_OK) + return false; + out = value; + return true; + } + + void writeUint32(std::string_view ns, std::string_view key, std::uint32_t value) override { + nvs_handle_t handle; + std::string nsStr(ns); + std::string keyStr(key); + if (nvs_open(nsStr.c_str(), NVS_READWRITE, &handle) != ESP_OK) + return; + nvs_set_u32(handle, keyStr.c_str(), value); + nvs_commit(handle); + nvs_close(handle); + } +}; + +class EspRandom final : public cardboy::sdk::IRandom { +public: + [[nodiscard]] std::uint32_t nextUint32() override { return esp_random(); } +}; + +class EspHighResClock final : public cardboy::sdk::IHighResClock { +public: + [[nodiscard]] std::uint64_t micros() override { return static_cast(esp_timer_get_time()); } +}; + +class EspPowerManager final : public cardboy::sdk::IPowerManager { +public: + void setSlowMode(bool enable) override { PowerHelper::get().set_slow(enable); } + [[nodiscard]] bool isSlowMode() const override { return PowerHelper::get().is_slow(); } +}; + +} // namespace + #if CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS && CONFIG_FREERTOS_USE_TRACE_FACILITY namespace { @@ -217,8 +299,24 @@ extern "C" void app_main() { static PlatformInput input; static PlatformClock clock; + static EspBuzzer buzzerService; + static EspBatteryMonitor batteryService; + static EspStorage storageService; + static EspRandom randomService; + static EspHighResClock highResClockService; + static EspPowerManager powerService; + + static cardboy::sdk::Services services{}; + services.buzzer = &buzzerService; + services.battery = &batteryService; + services.storage = &storageService; + services.random = &randomService; + services.highResClock = &highResClockService; + services.powerManager = &powerService; + AppContext context(framebuffer, input, clock); - AppSystem system(context); + context.services = &services; + AppSystem system(context); context.system = &system; system.registerApp(apps::createMenuAppFactory()); diff --git a/Firmware/main/src/apps/gameboy_app.cpp b/Firmware/main/src/apps/gameboy_app.cpp index ecf2b65..f0acdb0 100644 --- a/Firmware/main/src/apps/gameboy_app.cpp +++ b/Firmware/main/src/apps/gameboy_app.cpp @@ -4,7 +4,8 @@ #include "app_framework.hpp" #include "app_system.hpp" -#include "font16x8.hpp" +#include "cardboy/gfx/font16x8.hpp" +#include "input_state.hpp" #include #include diff --git a/Firmware/main/src/apps/tetris_app.cpp b/Firmware/main/src/apps/tetris_app.cpp deleted file mode 100644 index 19e3276..0000000 --- a/Firmware/main/src/apps/tetris_app.cpp +++ /dev/null @@ -1,1252 +0,0 @@ -// 400x240 1-bit Tetris — centered board, HUD, correct polarity, upright font. -// Logical ON = BLACK, OFF = WHITE. No hidden inversion anywhere. - -#include "apps/tetris_app.hpp" - -#include "app_framework.hpp" -#include "app_system.hpp" -#include "apps/menu_app.hpp" -#include "font16x8.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "esp_random.h" -#include "esp_timer.h" - -namespace apps { -namespace tetris { - -namespace cfg { -constexpr int BoardW = 10; -constexpr int BoardH = 20; -// Reduce CellPx to 11 (was 12) so BoardH * CellPx = 220, leaving 10px vertical margin top & bottom (240-220)/2 -constexpr int CellPx = 11; // creates vertical margins when centered -constexpr int FrameW = 400; -constexpr int FrameH = 240; - -// game feel -// Faster gravity & soft drop -constexpr int DropMsStart = 520; // was 650 -constexpr int DropMsMin = 60; // was 90 -constexpr int DropFastMs = 1; // soft drop interval (was 5) -constexpr int LevelStepClr = 10; -constexpr int LockDelayMs = 380; - -// input repeat (snappy) -constexpr int DAS_ms = 90; -constexpr int ARR_ms = 15; - -// frame pacing -constexpr int FrameMs = 12; -} // namespace cfg - -static inline uint32_t elapsed_ms(uint32_t a, uint32_t b) { return b - a; } - -// High precision performance stats (microsecond accumulation via esp_timer) -struct PerfStats { - // Accumulators (microseconds) - uint64_t overlayUs = 0, inputUs = 0, logicUs = 0, renderUs = 0, stepUs = 0; - uint64_t rBoardUs = 0, rPiecesUs = 0, rHUDUs = 0, rOverlayUs = 0, rBlitQueueUs = 0, rBlitWaitUs = 0; - // Counters - uint32_t steps = 0; // number of step() iterations during interval - uint32_t renders = 0; // number of actual renders (paintHUD calls) during interval - uint64_t lastPrintUs = 0; - // Last-step / last-render snapshots - uint64_t lastOverlayUs = 0, lastInputUs = 0, lastLogicUs = 0, lastRenderUs = 0, lastStepUs = 0; - uint64_t lastRBoardUs = 0, lastRPiecesUs = 0, lastRHUDUs = 0, lastROverlayUs = 0; - uint64_t lastRBlitQueueUs = 0, lastRBlitWaitUs = 0; - bool lastDidRender = false; - void maybePrint(uint64_t nowUs) { - if (!lastPrintUs) - lastPrintUs = nowUs; - uint64_t span = nowUs - lastPrintUs; - if (span >= 1000000ULL && (steps || renders)) { - double dSteps = steps ? (double) steps : 1.0; - double dRenders = renders ? (double) renders : 1.0; - auto avS = [&](uint64_t v) { return (double) v / 1000.0 / dSteps; }; - auto avR = [&](uint64_t v) { return (double) v / 1000.0 / dRenders; }; - double fps = renders * 1000000.0 / (double) span; // real display frame rate - printf("PERF steps=%lu frames=%lu span=%.3fs | stepAvg overlay=%.3fms input=%.3fms logic=%.3fms " - "step=%.3fms || renderAvg=%.3fms [brd=%.3f pcs=%.3f hud=%.3f ovl=%.3f bltQ=%.3f bltW=%.3f] " - "fps=%.1f\n", - (unsigned long) steps, (unsigned long) renders, (double) span / 1e6, avS(overlayUs), avS(inputUs), - avS(logicUs), avS(stepUs), avR(renderUs), avR(rBoardUs), avR(rPiecesUs), avR(rHUDUs), - avR(rOverlayUs), avR(rBlitQueueUs), avR(rBlitWaitUs), fps); - overlayUs = inputUs = logicUs = renderUs = stepUs = 0; - rBoardUs = rPiecesUs = rHUDUs = rOverlayUs = rBlitQueueUs = rBlitWaitUs = 0; - steps = renders = 0; - lastPrintUs = nowUs; - } - } - static PerfStats& get() { - static PerfStats ps; - return ps; - } -}; - -// ───────────────────────────────────────────────────────────────────────────── -// Pieces -using RotGrid = std::array; -using PieceR = std::array; -constexpr char _ = '.', X = '#'; - -using Framebuffer = typename AppContext::Framebuffer; -using InputDevice = typename AppContext::Input; -using Clock = typename AppContext::Clock; - -static const std::array TET = {{ - PieceR{{RotGrid{_, _, _, _, X, X, X, X, _, _, _, _, _, _, _, _}, - RotGrid{_, _, X, _, _, _, X, _, _, _, X, _, _, _, X, _}, - RotGrid{_, _, _, _, X, X, X, X, _, _, _, _, _, _, _, _}, - RotGrid{_, X, _, _, _, X, _, _, _, X, _, _, _, X, _, _}}}, - PieceR{{RotGrid{X, _, _, _, X, X, X, _, _, _, _, _, _, _, _, _}, - RotGrid{_, X, X, _, _, X, _, _, _, X, _, _, _, _, _, _}, - RotGrid{_, _, _, _, X, X, X, _, _, _, X, _, _, _, _, _}, - RotGrid{_, X, _, _, _, X, _, _, X, _, _, _, _, _, _, _}}}, - PieceR{{RotGrid{_, _, X, _, X, X, X, _, _, _, _, _, _, _, _, _}, - RotGrid{_, X, _, _, _, X, _, _, _, X, X, _, _, _, _, _}, - RotGrid{_, _, _, _, X, X, X, _, X, _, _, _, _, _, _, _}, - RotGrid{X, X, _, _, _, X, _, _, _, X, _, _, _, _, _, _}}}, - PieceR{{RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _}, - RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _}, - RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _}, - RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _}}}, - PieceR{{RotGrid{_, X, X, _, X, X, _, _, _, _, _, _, _, _, _, _}, - RotGrid{_, X, _, _, _, X, X, _, _, _, X, _, _, _, _, _}, - RotGrid{_, _, _, _, _, X, X, _, X, X, _, _, _, _, _, _}, - RotGrid{X, _, _, _, X, X, _, _, _, X, _, _, _, _, _, _}}}, - PieceR{{RotGrid{_, X, _, _, X, X, X, _, _, _, _, _, _, _, _, _}, - RotGrid{_, X, _, _, _, X, X, _, _, X, _, _, _, _, _, _}, - RotGrid{_, _, _, _, X, X, X, _, _, X, _, _, _, _, _, _}, - RotGrid{_, X, _, _, X, X, _, _, _, X, _, _, _, _, _, _}}}, - PieceR{{RotGrid{X, X, _, _, _, X, X, _, _, _, _, _, _, _, _, _}, - RotGrid{_, _, X, _, _, X, X, _, _, X, _, _, _, _, _, _}, - RotGrid{_, _, _, _, X, X, _, _, _, X, X, _, _, _, _, _}, - RotGrid{_, X, _, _, X, X, _, _, X, _, _, _, _, _, _, _}}}, -}}; - -static inline bool cell_of(const PieceR& p, int rot, int x, int y) { return p[rot][y * 4 + x] == X; } - -// ───────────────────────────────────────────────────────────────────────────── -// Board -class Board { -public: - Board() { clear(); } - void clear() { a.fill(0); } - uint8_t get(int x, int y) const { return a[y * cfg::BoardW + x]; } - void set(int x, int y, uint8_t v) { a[y * cfg::BoardW + x] = v; } - - bool collides(int px, int py, int rot, int pidx) const { - for (int yy = 0; yy < 4; ++yy) - for (int xx = 0; xx < 4; ++xx) - if (cell_of(TET[pidx], rot, xx, yy)) { - int x = px + xx, y = py + yy; - if (x < 0 || x >= cfg::BoardW || y >= cfg::BoardH) - return true; - if (y >= 0 && get(x, y)) - return true; - } - return false; - } - void lock(int px, int py, int rot, int pidx) { - for (int yy = 0; yy < 4; ++yy) - for (int xx = 0; xx < 4; ++xx) - if (cell_of(TET[pidx], rot, xx, yy)) { - int x = px + xx, y = py + yy; - if (x >= 0 && x < cfg::BoardW && y >= 0 && y < cfg::BoardH) - set(x, y, static_cast(pidx + 1)); // store piece id (1..7) - } - } - int clearLines() { - int cleared = 0; - for (int y = cfg::BoardH - 1; y >= 0; --y) { - bool full = true; - for (int x = 0; x < cfg::BoardW; ++x) { - if (!get(x, y)) { - full = false; - break; - } - } - if (full) { - ++cleared; - for (int yy = y; yy > 0; --yy) - for (int x = 0; x < cfg::BoardW; ++x) - set(x, yy, get(x, yy - 1)); - for (int x = 0; x < cfg::BoardW; ++x) - set(x, 0, 0); - ++y; - } - } - return cleared; - } - -private: - std::array a{}; -}; - -// ───────────────────────────────────────────────────────────────────────────── -// HUD font helpers (Terminess Powerline, 8x16 glyphs shared with the rest of the UI) -constexpr int kHudFontScale = 1; -constexpr int kHudLetterSpacing = 1; -constexpr int kHudLabelGap = 2; -constexpr int kHudBlockGap = 8; -constexpr int kHudLineGapBattery = 4; - -inline void drawHudText(Framebuffer& fb, int x, int y, std::string_view text) { - font16x8::drawText(fb, x, y, text, kHudFontScale, true, kHudLetterSpacing); -} - -inline int hudFontHeight() { return font16x8::kGlyphHeight * kHudFontScale; } - -// ───────────────────────────────────────────────────────────────────────────── -// Renderer (centered board + HUD) -class Renderer { -public: - Renderer(Framebuffer& fb) : fb(fb) { - bw = cfg::BoardW * cfg::CellPx; // 10 * 11 = 110 - bh = cfg::BoardH * cfg::CellPx; // 20 * 11 = 220 (leaves 10px margins vertically) - ox = (fb.width() - bw) / 2; // centered horizontally - oy = (fb.height() - bh) / 2; // centered vertically with margin - } - - void render(const Board& b, int px, int py, int prot, int pidx, int score, int highScore, int level, int lines, - int nextIdx, bool ghost, bool paused, bool gameOver, int fps, int avgFrameMs10) { - auto& ps = PerfStats::get(); - auto tus = []() { return esp_timer_get_time(); }; - uint64_t t0 = tus(); // frame start (no full clear; buffer already zeroed by async post-blit) - drawBatteryOverlay(); - rect(ox - 2, oy - 2, bw + 4, bh + 4, true); - uint64_t t2 = tus(); - for (int y = 0; y < cfg::BoardH; ++y) - for (int x = 0; x < cfg::BoardW; ++x) { - int v = b.get(x, y); - if (v) - drawCellFull(x, y, v, false); - } - uint64_t t3 = tus(); - ps.rBoardUs += (t3 - t2); - ps.lastRBoardUs = (t3 - t2); - if (ghost) - drawGhost(b, px, py, prot, pidx); - for (int yy = 0; yy < 4; ++yy) - for (int xx = 0; xx < 4; ++xx) - if (cell_of(TET[pidx], prot, xx, yy)) { - int gx = px + xx, gy = py + yy; - if (gx >= 0 && gx < cfg::BoardW && gy >= 0 && gy < cfg::BoardH) - drawCellFull(gx, gy, pidx + 1, true); - } - uint64_t t4 = tus(); - ps.rPiecesUs += (t4 - t3); - ps.lastRPiecesUs = (t4 - t3); - int hudX = ox + bw + 16; - int yHUD = oy + 8; - drawLabel(hudX, yHUD, "SCORE"); - yHUD += hudFontHeight() + kHudLabelGap; - drawNumber(hudX, yHUD, score); - yHUD += hudFontHeight() + kHudLabelGap; - drawLabel(hudX, yHUD, "BEST"); - yHUD += hudFontHeight() + kHudLabelGap; - drawNumber(hudX, yHUD, highScore); - yHUD += hudFontHeight() + kHudBlockGap; - drawLabel(hudX, yHUD, "LEVEL"); - yHUD += hudFontHeight() + kHudLabelGap; - drawNumber(hudX, yHUD, level); - yHUD += hudFontHeight() + kHudBlockGap; - drawLabel(hudX, yHUD, "LINES"); - yHUD += hudFontHeight() + kHudLabelGap; - drawNumber(hudX, yHUD, lines); - yHUD += hudFontHeight() + kHudBlockGap; - drawLabel(hudX, yHUD, "NEXT"); - yHUD += hudFontHeight() + kHudLabelGap; - const int p = cfg::CellPx; - const int nx = hudX; - const int ny = yHUD + 4; - rect(nx - 2, ny - 2, 4 * p + 4, 4 * p + 4, true); - for (int yy = 0; yy < 4; ++yy) - for (int xx = 0; xx < 4; ++xx) - if (cell_of(TET[nextIdx], 0, xx, yy)) - drawCellFullPreview(nx + xx * p, ny + yy * p, nextIdx + 1); - // (Removed on request) Previously displayed FPS / frame ms here. - uint64_t t5 = tus(); - ps.rHUDUs += (t5 - t4); - ps.lastRHUDUs = (t5 - t4); - if (gameOver) - drawGameOverOverlay(); - else if (paused) - drawPausedOverlay(); - uint64_t t6 = tus(); - ps.rOverlayUs += (t6 - t5); - ps.lastROverlayUs = (t6 - t5); - // Queue async SPI transfer (DMA) and measure only queue overhead here. - uint64_t bqStart = tus(); - DispTools::draw_to_display_async_start(); - uint64_t t7 = tus(); - uint64_t qdur = (t7 - bqStart); - ps.rBlitQueueUs += qdur; // enqueue overhead - ps.lastRBlitQueueUs = qdur; - } - - // Consistent full bordered cell drawing (square regardless of neighbors) - void drawCellFull(int cx, int cy, int type, bool active) { - int p = cfg::CellPx; - int x0 = ox + cx * p; - int y0 = oy + cy * p; - rect(x0, y0, p, p, true); // outer border - int ix0 = x0 + 1, iy0 = y0 + 1; - int w = p - 2, h = p - 2; - if (w <= 0 || h <= 0) - return; - for (int yy = 0; yy < h; ++yy) - for (int xx = 0; xx < w; ++xx) - if (patternOn(type, w, h, xx, yy)) - putPixel(ix0 + xx, iy0 + yy, true); - if (active && p >= 8) - rect(x0 + 2, y0 + 2, p - 4, p - 4, true); // subtle highlight - } - - // Preview cell - void drawCellFullPreview(int x0, int y0, int type) { - int p = cfg::CellPx; - rect(x0, y0, p, p, true); - int ix0 = x0 + 1, iy0 = y0 + 1, w = p - 2, h = p - 2; - if (w <= 0 || h <= 0) - return; - for (int yy = 0; yy < h; ++yy) - for (int xx = 0; xx < w; ++xx) - if (patternOn(type, w, h, xx, yy)) - putPixel(ix0 + xx, iy0 + yy, true); - } - - void drawPausedOverlay() { - const std::string_view txt = "PAUSED"; - int w = font16x8::measureText(txt, kHudFontScale, kHudLetterSpacing); - int h = hudFontHeight(); - int cx = (fb.width() - w) / 2; - int cy = (fb.height() - h) / 2 - 10; - // Background wipe (white) then border - int padX = 6, padY = 6; - for (int y = -padY; y < h + padY; ++y) - for (int x = -padX; x < w + padX; ++x) - fb.drawPixel(cx + x, cy + y, false); - // Solid border - for (int x = -padX; x < w + padX; ++x) { - fb.drawPixel(cx + x, cy - padY, true); - fb.drawPixel(cx + x, cy + h + padY - 1, true); - } - for (int y = -padY; y < h + padY; ++y) { - fb.drawPixel(cx - padX, cy + y, true); - fb.drawPixel(cx + w + padX - 1, cy + y, true); - } - drawText(cx, cy, txt); - } - - void drawGameOverOverlay() { - const std::string_view l1 = "GAME"; - const std::string_view l2 = "OVER"; - int w1 = font16x8::measureText(l1, kHudFontScale, kHudLetterSpacing); - int w2 = font16x8::measureText(l2, kHudFontScale, kHudLetterSpacing); - int w = std::max(w1, w2); - int h = hudFontHeight(); - int gap = 4; - int totalH = h * 2 + gap; - int cx = (fb.width() - w) / 2; - int cy = (fb.height() - totalH) / 2; - int padX = 8, padY = 6; - // Clear background region - for (int y = -padY; y < totalH + padY; ++y) - for (int x = -padX; x < w + padX; ++x) - fb.drawPixel(cx + x, cy + y, false); - // Border - for (int x = -padX; x < w + padX; ++x) { - fb.drawPixel(cx + x, cy - padY, true); - fb.drawPixel(cx + x, cy + totalH + padY - 1, true); - } - for (int y = -padY; y < totalH + padY; ++y) { - fb.drawPixel(cx - padX, cy + y, true); - fb.drawPixel(cx + w + padX - 1, cy + y, true); - } - drawText(cx, cy, l1); - drawText(cx, cy + h + gap, l2); - } - -private: - Framebuffer& fb; - int ox = 0, oy = 0, bw = 0, bh = 0; - - // Pattern helper: returns true if pixel (xx,yy) inside w x h interior should be filled for piece type - bool patternOn(int type, int w, int h, int xx, int yy) const { - switch (type) { - case 1: { // I — symmetric vertical stripes (centered for even widths) - int spacing = 3; - // center stripes by choosing an offset that balances margins - int offset = ((w - 1) % spacing) / 2; // distributes leftover space equally - return ((xx - offset) % spacing) == 0; - } - case 2: { // J — dense textured (≈75%) top half + solid left spine - if (xx == 0) - return true; // spine - if (yy < h / 2) { - // Omit only pixels where both coords are odd -> small isolated holes - return !((xx & 1) && (yy & 1)); - } - return false; - } - case 3: { // L — dense textured (≈75%) bottom half + solid right spine - if (xx == w - 1) - return true; // spine - if (yy >= h / 2) { - return !((xx & 1) && (yy & 1)); - } - return false; - } - case 4: - return (((xx + yy) & 1) == 0); // O - case 5: - return (((xx + yy) % 3) == 0); // S - case 6: - return ((((xx - yy) % 3) + 3) % 3 == 0); // Z - case 7: { // T — centered diamond (handles even dimensions without bias) - bool evenW = (w % 2) == 0; - bool evenH = (h % 2) == 0; - int dx, dy, radius; - if (evenW) { - int midLx = w / 2 - 1; - int midRx = w / 2; - dx = std::min(std::abs(xx - midLx), std::abs(xx - midRx)); - } else { - int mid = (w - 1) / 2; - dx = std::abs(xx - mid); - } - if (evenH) { - int midLy = h / 2 - 1; - int midRy = h / 2; - dy = std::min(std::abs(yy - midLy), std::abs(yy - midRy)); - } else { - int mid = (h - 1) / 2; - dy = std::abs(yy - mid); - } - if (evenW) { - radius = std::max(1, w / 2 - 2); - } else { - radius = std::max(1, (w - 1) / 2 - 1); - } - return (dx + dy) <= radius; - } - default: - return false; - } - } - - void putPixel(int x, int y, bool on) { fb.drawPixel(x, y, on); } - void hline(int x, int y, int w, bool on) { - for (int i = 0; i < w; ++i) - putPixel(x + i, y, on); - } - void vline(int x, int y, int h, bool on) { - for (int i = 0; i < h; ++i) - putPixel(x, y + i, on); - } - void rect(int x, int y, int w, int h, bool on) { - hline(x, y, w, on); - hline(x, y + h - 1, w, on); - vline(x, y, h, on); - vline(x + w - 1, y, h, on); - } - - void dashedH(int x, int y, int w, bool on) { - for (int i = 0; i < w; ++i) - if ((i & 1) == 0) - putPixel(x + i, y, on); - } - void dashedV(int x, int y, int h, bool on) { - for (int i = 0; i < h; ++i) - if ((i & 1) == 0) - putPixel(x, y + i, on); - } - - void drawGhost(const Board& b, int px, int py, int prot, int pidx) { - // Determine landing position (same as before) - int gy = py; - while (true) { - bool col = false; - for (int yy = 0; yy < 4 && !col; ++yy) - for (int xx = 0; xx < 4; ++xx) - if (cell_of(TET[pidx], prot, xx, yy)) { - int nx = px + xx, ny = gy + yy + 1; - if (ny >= cfg::BoardH || (ny >= 0 && nx >= 0 && nx < cfg::BoardW && b.get(nx, ny))) { - col = true; - break; - } - } - if (col) - break; - ++gy; - } - // Build occupancy mask of the piece (4x4 local) - bool occ[4][4] = {}; - int minCol = 4, maxCol = -1, minRow = 4, maxRow = -1; - for (int yy = 0; yy < 4; ++yy) - for (int xx = 0; xx < 4; ++xx) - if (cell_of(TET[pidx], prot, xx, yy)) { - occ[yy][xx] = true; - minCol = std::min(minCol, xx); - maxCol = std::max(maxCol, xx); - minRow = std::min(minRow, yy); - maxRow = std::max(maxRow, yy); - } - if (maxCol < 0) - return; // nothing - int psize = cfg::CellPx; - - auto drawHorizRun = [&](int boardY, int boardXCellStart, int cells, bool topEdge) { - int yPix = oy + boardY * psize + (topEdge ? 0 : (psize - 1)); - int xPixStart = ox + boardXCellStart * psize; - int totalPixels = cells * psize; - for (int i = 0; i < totalPixels; ++i) { - int xPix = xPixStart + i; - // Parity-based sparse edge: ensures corners never double-thicken - if (((xPix + yPix) & 1) == 0) - putPixel(xPix, yPix, true); - } - }; - auto drawVertRun = [&](int boardX, int boardYCellStart, int cells, bool leftEdge) { - int xPix = ox + boardX * psize + (leftEdge ? 0 : (psize - 1)); - int yPixStart = oy + boardYCellStart * psize; - int totalPixels = cells * psize; - for (int i = 0; i < totalPixels; ++i) { - int yPix = yPixStart + i; - if (((xPix + yPix) & 1) == 0) - putPixel(xPix, yPix, true); - } - }; - - // Horizontal top edges - for (int r = minRow; r <= maxRow; ++r) { - int c = minCol; - while (c <= maxCol) { - if (occ[r][c] && (r == minRow || !occ[r - 1][c])) { - int runStart = c; - int end = c; - while (end <= maxCol && occ[r][end] && (r == minRow || !occ[r - 1][end])) - ++end; - drawHorizRun(gy + r, px + runStart, end - runStart, true); - c = end; - } else - ++c; - } - } - // Horizontal bottom edges - for (int r = minRow; r <= maxRow; ++r) { - int c = minCol; - while (c <= maxCol) { - if (occ[r][c] && (r == maxRow || !occ[r + 1][c])) { - int runStart = c; - int end = c; - while (end <= maxCol && occ[r][end] && (r == maxRow || !occ[r + 1][end])) - ++end; - drawHorizRun(gy + r, px + runStart, end - runStart, false); - c = end; - } else - ++c; - } - } - // Vertical left edges - for (int c = minCol; c <= maxCol; ++c) { - int r = minRow; - while (r <= maxRow) { - if (occ[r][c] && (c == minCol || !occ[r][c - 1])) { - int runStart = r; - int end = r; - while (end <= maxRow && occ[end][c] && (c == minCol || !occ[end][c - 1])) - ++end; - drawVertRun(px + c, gy + runStart, end - runStart, true); - r = end; - } else - ++r; - } - } - // Vertical right edges - for (int c = minCol; c <= maxCol; ++c) { - int r = minRow; - while (r <= maxRow) { - if (occ[r][c] && (c == maxCol || !occ[r][c + 1])) { - int runStart = r; - int end = r; - while (end <= maxRow && occ[end][c] && (c == maxCol || !occ[end][c + 1])) - ++end; - drawVertRun(px + c, gy + runStart, end - runStart, false); - r = end; - } else - ++r; - } - } - } - - void drawLabel(int x, int y, std::string_view text) { drawHudText(fb, x, y, text); } - - void drawNumber(int x, int y, int n) { - char buf[16]; - int len = snprintf(buf, sizeof(buf), "%d", n); - if (len <= 0) - drawHudText(fb, x, y, std::string_view{}); - else - drawHudText(fb, x, y, std::string_view{buf, static_cast(len)}); - } - - void drawText(int x, int y, std::string_view text) { drawHudText(fb, x, y, text); } - void drawBatteryOverlay() { - float cur = BatMon::get().get_current(); // mA, may be negative (charging) - float volt = BatMon::get().get_voltage(); // V - float ch = BatMon::get().get_charge(); // mAh (accumulated) - uint8_t btnMask = Buttons::get().get_pressed(); - if (cur != cur) - cur = 0.f; - if (volt != volt) - volt = 0.f; - if (ch != ch) - ch = 0.f; - char line1[64]; - char line2[64]; - char line3[64]; - // NOTE: Font only has uppercase letters; labels rendered uppercase. - // Format values one decimal. Current may be negative. - snprintf(line1, sizeof(line1), "CURRENT: %5.1fMA %3.1fV", cur, volt); - snprintf(line2, sizeof(line2), "CHARGE: %5.1fMAH", ch); - strcpy(line3, "BTN: "); - size_t base = strlen(line3); - for (int i = 0; i < 8; ++i) - line3[base + i] = (btnMask & (1 << (7 - i))) ? '1' : '0'; - line3[base + 8] = '\0'; - int x = 5; - int y = 4; - drawText(x, y, std::string_view{line1}); - y += hudFontHeight() + kHudLineGapBattery; - drawText(x, y, std::string_view{line2}); - y += hudFontHeight() + kHudLineGapBattery; - drawText(x, y, std::string_view{line3}); - if (Buzzer::get().isMuted()) { - const char* muted = "MUTED"; - int textWidth = font16x8::measureText(muted, kHudFontScale, kHudLetterSpacing); - int mx = fb.width() - textWidth - 4; - int my = 4; - drawText(mx, my, muted); - } - } -}; - -// ───────────────────────────────────────────────────────────────────────────── -// Bag -class Bag { -public: - Bag() { - reseed(); - refill(); - } - int next() { - if (bag.empty()) - refill(); - int t = bag.back(); - bag.pop_back(); - return t; - } - void reset() { // reseed and refill new 7-bag - reseed(); - refill(); - } - -private: - void reseed() { - // Use hardware RNG (non-blocking) instead of std::random_device (may block or be unsupported) - uint32_t seed = esp_random(); - rng.seed(seed); - } - void refill() { - bag = {0, 1, 2, 3, 4, 5, 6}; - std::shuffle(bag.begin(), bag.end(), rng); - } - std::vector bag; - std::mt19937 rng; -}; - -// ───────────────────────────────────────────────────────────────────────────── -// Game -struct ScoreState { - int level = 0; - int score = 0; - int highScore = 0; - int lines = 0; - int dropMs = cfg::DropMsStart; -}; - -class Game { -public: - explicit Game(AppContext& ctx) : - appContext(ctx), fb(ctx.framebuffer), input(ctx.input), clock(ctx.clock), renderer(ctx.framebuffer) { - loadHighScore(); - nextPiece = bag.next(); - spawn(); - lastFall = clock.millis(); - touchTime = lastFall; - lastOverlayUpd = lastFall; - dirty = true; - } - - void step() { - uint64_t stepStartUs = esp_timer_get_time(); - uint32_t now = clock.millis(); - PerfStats::get().lastDidRender = false; // reset per-frame render flag - // Complete any previous in-flight async display transfer and attribute its duration to blit time - if (DispTools::draw_to_display_async_busy()) { - uint64_t bwStart = esp_timer_get_time(); - DispTools::draw_to_display_async_wait(); - uint64_t bwEnd = esp_timer_get_time(); - auto& ps = PerfStats::get(); - uint64_t dur = (bwEnd - bwStart); - ps.rBlitWaitUs += dur; // actual DMA completion time - ps.lastRBlitWaitUs = dur; - } else { - PerfStats::get().lastRBlitWaitUs = 0; - } - const uint32_t oStart = clock.millis(); - overlayTick(now); - const uint32_t oEnd = clock.millis(); - { - auto& ps = PerfStats::get(); - uint64_t dur = (uint64_t) (oEnd - oStart) * 1000ULL; - ps.overlayUs += dur; - ps.lastOverlayUs = dur; - } - const uint32_t iStart = clock.millis(); - InputState st = input.readState(); - const uint32_t iEnd = clock.millis(); - { - auto& ps = PerfStats::get(); - uint64_t dur = (uint64_t) (iEnd - iStart) * 1000ULL; - ps.inputUs += dur; - ps.lastInputUs = dur; - } - uint64_t logicStartUs = esp_timer_get_time(); - const bool exitCombo = st.b && st.select; - if (exitCombo && !exitComboPrev) { - appContext.requestAppSwitchByName(apps::kMenuAppName); - exitComboPrev = exitCombo; - return; - } - exitComboPrev = exitCombo; - enforceSlowMode(); - if (!running) { - bool anyPressed = st.left || st.right || st.down || st.a || st.b; - uint32_t sinceGameOver = elapsed_ms(gameOverTime, now); - if (sinceGameOver >= gameOverRestartDelayMs) { - // Arm restart only after buttons are released once past delay - if (!anyPressed) - gameOverPrevPressed = false; // ready to accept a fresh press - else if (anyPressed && !gameOverPrevPressed) { - restart(); - return; - } - } - // Maintain pressed state (prevents holding through the delay from triggering restart) - if (anyPressed) - gameOverPrevPressed = true; - uint64_t logicEndUs = esp_timer_get_time(); - { - auto& ps = PerfStats::get(); - uint64_t dur = (logicEndUs - logicStartUs); - ps.logicUs += dur; - ps.lastLogicUs = dur; - } - if (dirty) - paintHUD(); - uint64_t stepEndUs = esp_timer_get_time(); - { - auto& ps = PerfStats::get(); - uint64_t dur = (stepEndUs - stepStartUs); - ps.stepUs += dur; - ps.lastStepUs = dur; - // Print per-frame breakdown (no render this frame if not dirty) - auto ms = [](uint64_t us) { return (double) us / 1000.0; }; - double waitMs = ms(ps.lastRBlitWaitUs); - if (waitMs > 0.0005) { - printf("STEP overlay=%.3fms input=%.3fms logic=%.3fms bltW=%.3fms step=%.3fms\n", - ms(ps.lastOverlayUs), ms(ps.lastInputUs), ms(ps.lastLogicUs), waitMs, ms(ps.lastStepUs)); - } else { - printf("STEP overlay=%.3fms input=%.3fms logic=%.3fms step=%.3fms\n", ms(ps.lastOverlayUs), - ms(ps.lastInputUs), ms(ps.lastLogicUs), ms(ps.lastStepUs)); - } - ps.steps++; - ps.maybePrint(stepEndUs); - } - return; - } - // Pause toggle - if (st.b && !backPrev) { - paused = !paused; - dirty = true; - } - backPrev = st.b; - // Mute toggle (Select button) - if (st.select && !selectPrev) { - Buzzer::get().toggleMuted(); - } - selectPrev = st.select; - if (paused) { - uint64_t logicEndUs = esp_timer_get_time(); - { - auto& ps = PerfStats::get(); - uint64_t dur = (logicEndUs - logicStartUs); - ps.logicUs += dur; - ps.lastLogicUs = dur; - } - if (dirty) - paintHUD(); - uint64_t stepEndUs = esp_timer_get_time(); - { - auto& ps = PerfStats::get(); - uint64_t dur = (stepEndUs - stepStartUs); - ps.stepUs += dur; - ps.lastStepUs = dur; - auto ms = [](uint64_t us) { return (double) us / 1000.0; }; - double waitMs = ms(ps.lastRBlitWaitUs); - if (waitMs > 0.0005) { - printf("STEP overlay=%.3fms input=%.3fms logic=%.3fms bltW=%.3fms step=%.3fms\n", - ms(ps.lastOverlayUs), ms(ps.lastInputUs), ms(ps.lastLogicUs), waitMs, ms(ps.lastStepUs)); - } else { - printf("STEP overlay=%.3fms input=%.3fms logic=%.3fms step=%.3fms\n", ms(ps.lastOverlayUs), - ms(ps.lastInputUs), ms(ps.lastLogicUs), ms(ps.lastStepUs)); - } - ps.steps++; - ps.maybePrint(stepEndUs); - } - return; - } - // Rotation - if (st.a && !rotPrev) { - if (tryRotate(+1)) { - dirty = true; - Buzzer::get().beepRotate(); - } - } - rotPrev = st.a; - // Horizontal - handleHorizontal(st, now); - // Gravity - const int g = st.down ? cfg::DropFastMs : score.dropMs; - if (elapsed_ms(lastFall, now) >= (uint32_t) g) { - lastFall = now; - if (!tryMoveInternal(0, 1)) { - if (!touchingGround) { - touchingGround = true; - touchTime = now; - } else if (elapsed_ms(touchTime, now) >= (uint32_t) cfg::LockDelayMs) { - lockAndAdvance(); - } - } else - touchingGround = false; - } - uint64_t logicEndUs = esp_timer_get_time(); - { - auto& ps = PerfStats::get(); - uint64_t dur = (logicEndUs - logicStartUs); - ps.logicUs += dur; - ps.lastLogicUs = dur; - } - if (dirty) - paintHUD(); - uint64_t stepEndUs = esp_timer_get_time(); - { - auto& ps = PerfStats::get(); - uint64_t dur = (stepEndUs - stepStartUs); - ps.stepUs += dur; - ps.lastStepUs = dur; - auto ms = [](uint64_t us) { return (double) us / 1000.0; }; - double waitMs = ms(ps.lastRBlitWaitUs); - if (waitMs > 0.0005) { - printf("STEP overlay=%.3fms input=%.3fms logic=%.3fms bltW=%.3fms step=%.3fms\n", ms(ps.lastOverlayUs), - ms(ps.lastInputUs), ms(ps.lastLogicUs), waitMs, ms(ps.lastStepUs)); - } else { - printf("STEP overlay=%.3fms input=%.3fms logic=%.3fms step=%.3fms\n", ms(ps.lastOverlayUs), - ms(ps.lastInputUs), ms(ps.lastLogicUs), ms(ps.lastStepUs)); - } - ps.steps++; - ps.maybePrint(stepEndUs); - } - } - - struct SleepPlan { - uint32_t slow_ms; // long sleep allowing battery/UI periodic refresh - uint32_t normal_ms; // short sleep for responsiveness on input wake - }; - SleepPlan recommendedSleepMs(uint32_t now) const { - SleepPlan plan{0, 0}; - if (dirty) - return plan; // both zero => no sleep - bool idleState = paused || !running; - if (idleState) { - uint32_t interval = overlayIntervalMs * 8; // extended overlay refresh gap - uint32_t since = elapsed_ms(lastOverlayUpd, now); - uint32_t untilOverlay = (since >= interval) ? 0 : (interval - since); - if (untilOverlay > idleLongCapMs) - untilOverlay = idleLongCapMs; - plan.slow_ms = untilOverlay ? untilOverlay : idleShortPollMs; - plan.normal_ms = idleActivePollMs; - return plan; - } - // Active gameplay - uint32_t g = score.dropMs; - uint32_t sinceFall = elapsed_ms(lastFall, now); - uint32_t untilDrop = (sinceFall >= g) ? 0 : (g - sinceFall); - if (touchingGround) { - uint32_t sinceTouch = elapsed_ms(touchTime, now); - uint32_t untilLock = (sinceTouch >= (uint32_t) cfg::LockDelayMs) ? 0 : (cfg::LockDelayMs - sinceTouch); - untilDrop = std::min(untilDrop, untilLock); - } - uint32_t interval = overlayIntervalMs; - uint32_t sinceOverlay = elapsed_ms(lastOverlayUpd, now); - uint32_t untilOverlay = (sinceOverlay >= interval) ? 0 : (interval - sinceOverlay); - uint32_t sleep = std::min(untilDrop, untilOverlay); - if (sleep > maxIdleSleepMs) - sleep = maxIdleSleepMs; - plan.slow_ms = sleep; - plan.normal_ms = std::min(sleep, activeNormalCapMs); - return plan; - } - -private: - // Power-aware overlay throttling - static constexpr uint32_t overlayIntervalMs = 1000; // base interval (paused uses multiplier) - static constexpr uint32_t maxIdleSleepMs = 120; // active state cap to keep input responsive - static constexpr uint32_t gameOverRestartDelayMs = 2000; // ms grace period before restart allowed - // Idle (paused/game over) timing tunables - static constexpr uint32_t idleLongCapMs = 5000; // longest deep sleep slice while idle (<= overlay 4s) - static constexpr uint32_t idleShortPollMs = 50; // minimal periodic wake to check overlay refresh/input - static constexpr uint32_t idleActivePollMs = 40; // normal_ms provided to PowerHelper for responsiveness - static constexpr uint32_t activeNormalCapMs = 40; // cap for normal_ms during active play - - AppContext& appContext; - Framebuffer& fb; - InputDevice& input; - Clock& clock; - Renderer renderer; - Board board; - Bag bag; - ScoreState score; - bool running = true, paused = false, touchingGround = false; - bool rotPrev = false, lHeld = false, rHeld = false, backPrev = false, selectPrev = false, exitComboPrev = false; - uint32_t lHoldStart = 0, rHoldStart = 0, lLastRep = 0, rLastRep = 0, lastFall = 0, touchTime = 0; - int current = 0, nextPiece = 0, px = 3, py = -2, rot = 0; - // Game over restart gating - uint32_t gameOverTime = 0; // time when game over occurred - bool gameOverPrevPressed = false; // tracks button hold through delay - - // Dirty rendering & overlay - bool dirty = false; - uint32_t lastOverlayUpd = 0; - - // Performance stats - uint32_t frameCount = 0, frameAccumMs = 0, lastFrameTime = 0, lastStatTime = 0; - int statFps = 0, statAvgFrameMs10 = 0; - - void overlayTick(uint32_t now) { - uint32_t interval = (paused || !running) ? overlayIntervalMs * 8 : overlayIntervalMs; // 4s paused or game over - if (elapsed_ms(lastOverlayUpd, now) >= interval) { - lastOverlayUpd = now; - dirty = true; - } - } - void enforceSlowMode() { - auto& ph = PowerHelper::get(); - bool wantSlow = paused || !running; - if (wantSlow && !ph.is_slow()) - ph.set_slow(true); - else if (!wantSlow && ph.is_slow()) - ph.set_slow(false); - } - void loadHighScore() { - nvs_handle_t h; - if (nvs_open("tetris", NVS_READONLY, &h) == ESP_OK) { - uint32_t stored = 0; - if (nvs_get_u32(h, "best", &stored) == ESP_OK) { - uint32_t capped = std::min(stored, static_cast(std::numeric_limits::max())); - score.highScore = static_cast(capped); - } - nvs_close(h); - } - } - void persistHighScore() { - nvs_handle_t h; - if (nvs_open("tetris", NVS_READWRITE, &h) == ESP_OK) { - uint32_t value = score.highScore > 0 ? static_cast(score.highScore) : 0; - nvs_set_u32(h, "best", value); - nvs_commit(h); - nvs_close(h); - } - } - void updateHighScoreIfNeeded() { - if (score.score > score.highScore) { - score.highScore = score.score; - persistHighScore(); - } - } - void paintHUD() { - // Complete previous frame transfer & implicit clear so dma_buf pixel area is zeroed before we draw - SMD::async_draw_wait(); - uint64_t rStartUs = esp_timer_get_time(); - uint32_t rStart = clock.millis(); - // Frame boundary at render start - if (lastFrameTime != 0) { - uint32_t dt = elapsed_ms(lastFrameTime, rStart); - frameAccumMs += dt; - } - frameCount++; - lastFrameTime = rStart; - if (elapsed_ms(lastStatTime, rStart) >= 1000) { - statFps = frameCount; - statAvgFrameMs10 = frameCount ? (int) ((frameAccumMs * 10) / frameCount) : 0; - frameCount = 0; - frameAccumMs = 0; - lastStatTime = rStart; - } - renderer.render(board, px, py, rot, current, score.score, score.highScore, score.level, score.lines, nextPiece, - true, paused, !running, statFps, statAvgFrameMs10); - uint64_t rEndUs = esp_timer_get_time(); - { - auto& ps = PerfStats::get(); - uint64_t dur = (rEndUs - rStartUs); - ps.renderUs += dur; - ps.lastRenderUs = dur; - ps.lastDidRender = true; - ps.renders++; - // Pure per-frame render metrics (does not include asynchronous blit wait from previous frame) - auto ms = [](uint64_t us) { return (double) us / 1000.0; }; - printf("FRAME render=%.3fms [brd=%.3f pcs=%.3f hud=%.3f ovl=%.3f bltQ=%.3f]\n", ms(ps.lastRenderUs), - ms(ps.lastRBoardUs), ms(ps.lastRPiecesUs), ms(ps.lastRHUDUs), ms(ps.lastROverlayUs), - ms(ps.lastRBlitQueueUs)); - } - dirty = false; - } - void restart() { - updateHighScoreIfNeeded(); - board.clear(); - int best = score.highScore; - score = ScoreState{}; - score.highScore = best; - bag.reset(); // new randomized sequence without reconstructing (avoids potential random_device issues) - nextPiece = bag.next(); - running = true; - paused = false; - touchingGround = false; - rotPrev = lHeld = rHeld = backPrev = selectPrev = exitComboPrev = false; - // slow mode will be re-evaluated centrally on next step - gameOverTime = 0; - gameOverPrevPressed = false; - spawn(); - dirty = true; - } - void spawn() { - current = nextPiece; - nextPiece = bag.next(); - px = 3; - py = -2; - rot = 0; // spawn above board - touchingGround = false; - dirty = true; - // Game over if immediate collision at spawn position OR unable to move one row down - if (board.collides(px, py, rot, current) || board.collides(px, py + 1, rot, current)) { - running = false; - gameOverTime = clock.millis(); - gameOverPrevPressed = true; // require a release after delay - // slow mode applied centrally next step - updateHighScoreIfNeeded(); - Buzzer::get().beepGameOver(); - } - } - - bool tryMoveInternal(int dx, int dy) { - if (!board.collides(px + dx, py + dy, rot, current)) { - px += dx; - py += dy; - dirty = true; - return true; - } - return false; - } - bool tryRotate(int d) { - int nr = (rot + d + 4) % 4; - static const int kicks[][2] = {{0, 0}, {-1, 0}, {1, 0}, {-2, 0}, {2, 0}, {0, -1}}; - for (auto& k: kicks) { - if (!board.collides(px + k[0], py + k[1], nr, current)) { - px += k[0]; - py += k[1]; - rot = nr; - dirty = true; - return true; - } - } - return false; - } - void handleHorizontal(const InputState& st, uint32_t now) { - if (st.left && st.right) { - lHeld = rHeld = false; - return; - } - if (st.left) { - if (!lHeld) { - lHeld = true; - rHeld = false; - if (tryMoveInternal(-1, 0)) { - Buzzer::get().beepMove(); - } - lHoldStart = now; - lLastRep = now; - } else { - uint32_t s = elapsed_ms(lHoldStart, now), r = elapsed_ms(lLastRep, now); - if (s >= (uint32_t) cfg::DAS_ms && r >= (uint32_t) cfg::ARR_ms) { - (void) tryMoveInternal(-1, 0); - lLastRep = now; - } - } - } else - lHeld = false; - if (st.right) { - if (!rHeld) { - rHeld = true; - lHeld = false; - if (tryMoveInternal(+1, 0)) { - Buzzer::get().beepMove(); - } - rHoldStart = now; - rLastRep = now; - } else { - uint32_t s = elapsed_ms(rHoldStart, now), r = elapsed_ms(rLastRep, now); - if (s >= (uint32_t) cfg::DAS_ms && r >= (uint32_t) cfg::ARR_ms) { - (void) tryMoveInternal(+1, 0); - rLastRep = now; - } - } - } else - rHeld = false; - } - void lockAndAdvance() { - // Check if any part of current piece is above visible area before locking (for classic top-out) - bool above = false; - for (int yy = 0; yy < 4 && !above; ++yy) - for (int xx = 0; xx < 4 && !above; ++xx) - if (cell_of(TET[current], rot, xx, yy)) { - int gy = py + yy; - if (gy < 0) - above = true; - } - board.lock(px, py, rot, current); - dirty = true; - if (above) { // immediate game over (do not spawn new piece) - running = false; - gameOverTime = clock.millis(); - gameOverPrevPressed = true; - // slow mode applied centrally next step - updateHighScoreIfNeeded(); - Buzzer::get().beepGameOver(); - return; - } - int c = board.clearLines(); - if (c) { - static const int pts[5] = {0, 100, 300, 500, 800}; - score.lines += c; - score.score += pts[c] * (score.level + 1); - updateHighScoreIfNeeded(); - int nl = score.lines / cfg::LevelStepClr; - if (nl != score.level) { - score.level = nl; - score.dropMs = std::max(cfg::DropMsMin, cfg::DropMsStart - score.level * 50); - Buzzer::get().beepLevelUp(score.level); - } - Buzzer::get().beepLines(c); - } else { - Buzzer::get().beepLock(); - } - spawn(); - } -}; - -} // namespace tetris - -namespace { - -class TetrisApp final : public IApp { -public: - explicit TetrisApp(AppContext& ctx) : context(ctx), game(ctx) {} - - void onStart() override { - cancelTick(); - scheduleNextTick(0); - } - - void onStop() override { cancelTick(); } - - void handleEvent(const AppEvent& event) override { - if (event.type == AppEventType::Timer && event.timer.handle == tickTimer) { - tickTimer = kInvalidAppTimer; - runStep(); - return; - } - if (event.type == AppEventType::Button) { - scheduleNextTick(0); - } - } - -private: - AppContext& context; - tetris::Game game; - AppTimerHandle tickTimer = kInvalidAppTimer; - - void runStep() { - game.step(); - const auto now = context.clock.millis(); - const auto plan = game.recommendedSleepMs(now); - uint32_t delay = plan.normal_ms != 0 ? plan.normal_ms : plan.slow_ms; - scheduleNextTick(delay); - } - - void scheduleNextTick(uint32_t delayMs) { - if (tickTimer != kInvalidAppTimer) - context.cancelTimer(tickTimer); - tickTimer = context.scheduleTimer(delayMs, false); - } - - void cancelTick() { - if (tickTimer != kInvalidAppTimer) { - context.cancelTimer(tickTimer); - tickTimer = kInvalidAppTimer; - } - } -}; - -class TetrisAppFactory final : public IAppFactory { -public: - const char* name() const override { return "Tetris"; } - - std::unique_ptr create(AppContext& context) override { return std::make_unique(context); } -}; - -} // namespace - -std::unique_ptr createTetrisAppFactory() { return std::make_unique(); } - -} // namespace apps diff --git a/Firmware/main/src/disp_tty.cpp b/Firmware/main/src/disp_tty.cpp index 44091fd..03dd8ae 100644 --- a/Firmware/main/src/disp_tty.cpp +++ b/Firmware/main/src/disp_tty.cpp @@ -6,7 +6,7 @@ #include -#include "Fonts.hpp" +#include "cardboy/gfx/Fonts.hpp" void FbTty::draw_char(int col, int row) { for (int x = 0; x < 8; x++) { diff --git a/Firmware/sdk/CMakeLists.txt b/Firmware/sdk/CMakeLists.txt index d8611bf..c324f2c 100644 --- a/Firmware/sdk/CMakeLists.txt +++ b/Firmware/sdk/CMakeLists.txt @@ -1,11 +1,69 @@ -cmake_minimum_required(VERSION 3.10) -project(sdk-top) +cmake_minimum_required(VERSION 3.16) +project(cardboy_sdk LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS NO) -add_subdirectory(library) -if (NOT CMAKE_CROSSCOMPILING) - add_subdirectory(sfml-port) - add_subdirectory(examples) +add_library(cardboy_sdk STATIC + src/app_system.cpp +) + +target_include_directories(cardboy_sdk + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_compile_features(cardboy_sdk PUBLIC cxx_std_20) + +add_library(cardboy_apps STATIC + apps/menu_app.cpp + apps/clock_app.cpp + apps/tetris_app.cpp +) + +target_include_directories(cardboy_apps + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries(cardboy_apps + PUBLIC + cardboy_sdk +) + +target_compile_features(cardboy_apps PUBLIC cxx_std_20) + +option(CARDBOY_BUILD_SFML "Build SFML harness" OFF) + +if (CARDBOY_BUILD_SFML) + include(FetchContent) + + set(SFML_BUILD_AUDIO OFF CACHE BOOL "Disable SFML audio module" FORCE) + set(SFML_BUILD_NETWORK OFF CACHE BOOL "Disable SFML network module" FORCE) + set(SFML_BUILD_EXAMPLES OFF CACHE BOOL "Disable SFML examples" FORCE) + set(SFML_BUILD_TESTS OFF CACHE BOOL "Disable SFML tests" FORCE) + set(SFML_USE_SYSTEM_DEPS OFF CACHE BOOL "Use bundled SFML dependencies" FORCE) + + FetchContent_Declare( + SFML + GIT_REPOSITORY https://github.com/SFML/SFML.git + GIT_TAG 3.0.2 + GIT_SHALLOW ON + ) + FetchContent_MakeAvailable(SFML) + + add_executable(cardboy_desktop + hosts/sfml_main.cpp + ) + + target_link_libraries(cardboy_desktop + PRIVATE + cardboy_apps + SFML::Graphics + SFML::Window + SFML::System + ) + + target_compile_features(cardboy_desktop PRIVATE cxx_std_20) endif () diff --git a/Firmware/main/src/apps/clock_app.cpp b/Firmware/sdk/apps/clock_app.cpp similarity index 81% rename from Firmware/main/src/apps/clock_app.cpp rename to Firmware/sdk/apps/clock_app.cpp index 2435fb8..31c6a56 100644 --- a/Firmware/main/src/apps/clock_app.cpp +++ b/Firmware/sdk/apps/clock_app.cpp @@ -1,10 +1,10 @@ -#include "apps/clock_app.hpp" +#include "cardboy/apps/clock_app.hpp" -#include "app_system.hpp" -#include "apps/menu_app.hpp" -#include "font16x8.hpp" +#include "cardboy/apps/menu_app.hpp" +#include "cardboy/sdk/app_framework.hpp" +#include "cardboy/sdk/app_system.hpp" -#include +#include "cardboy/gfx/font16x8.hpp" #include #include @@ -17,6 +17,8 @@ namespace apps { namespace { +using cardboy::sdk::AppContext; + constexpr const char* kClockAppName = "Clock"; using Framebuffer = typename AppContext::Framebuffer; @@ -31,10 +33,10 @@ struct TimeSnapshot { int month = 0; int day = 0; int weekday = 0; - uint64_t uptimeSeconds = 0; + std::uint64_t uptimeSeconds = 0; }; -class ClockApp final : public IApp { +class ClockApp final : public cardboy::sdk::IApp { public: explicit ClockApp(AppContext& ctx) : context(ctx), framebuffer(ctx.framebuffer), clock(ctx.clock) {} @@ -50,12 +52,12 @@ public: void onStop() override { cancelRefreshTimer(); } - void handleEvent(const AppEvent& event) override { + void handleEvent(const cardboy::sdk::AppEvent& event) override { switch (event.type) { - case AppEventType::Button: + case cardboy::sdk::AppEventType::Button: handleButtonEvent(event.button); break; - case AppEventType::Timer: + case cardboy::sdk::AppEventType::Timer: if (event.timer.handle == refreshTimer) updateDisplay(); break; @@ -69,18 +71,18 @@ private: bool use24Hour = true; bool dirty = false; - AppTimerHandle refreshTimer = kInvalidAppTimer; + cardboy::sdk::AppTimerHandle refreshTimer = cardboy::sdk::kInvalidAppTimer; TimeSnapshot lastSnapshot{}; void cancelRefreshTimer() { - if (refreshTimer != kInvalidAppTimer) { + if (refreshTimer != cardboy::sdk::kInvalidAppTimer) { context.cancelTimer(refreshTimer); - refreshTimer = kInvalidAppTimer; + refreshTimer = cardboy::sdk::kInvalidAppTimer; } } - void handleButtonEvent(const AppButtonEvent& button) { + void handleButtonEvent(const cardboy::sdk::AppButtonEvent& button) { const auto& current = button.current; const auto& previous = button.previous; @@ -113,8 +115,8 @@ private: TimeSnapshot snap{}; snap.uptimeSeconds = clock.millis() / 1000ULL; - time_t raw = 0; - if (time(&raw) != static_cast(-1) && raw > 0) { + std::time_t raw = 0; + if (std::time(&raw) != static_cast(-1) && raw > 0) { std::tm tm{}; if (localtime_r(&raw, &tm) != nullptr) { snap.hasWallTime = true; @@ -158,7 +160,7 @@ private: return; dirty = false; - DispTools::draw_to_display_async_wait(); + framebuffer.beginFrame(); framebuffer.clear(false); const int scaleLarge = 3; @@ -198,11 +200,11 @@ private: drawCenteredText(framebuffer, timeY + font16x8::kGlyphHeight * scaleLarge + 28, dateLine, scaleSmall, 1); if (!snap.hasWallTime) { - char uptimeLine[32]; - const uint64_t days = snap.uptimeSeconds / 86400ULL; - const uint64_t hrs = (snap.uptimeSeconds / 3600ULL) % 24ULL; - const uint64_t mins = (snap.uptimeSeconds / 60ULL) % 60ULL; - const uint64_t secs = snap.uptimeSeconds % 60ULL; + char uptimeLine[32]; + const std::uint64_t days = snap.uptimeSeconds / 86400ULL; + const std::uint64_t hrs = (snap.uptimeSeconds / 3600ULL) % 24ULL; + const std::uint64_t mins = (snap.uptimeSeconds / 60ULL) % 60ULL; + const std::uint64_t secs = snap.uptimeSeconds % 60ULL; if (days > 0) { std::snprintf(uptimeLine, sizeof(uptimeLine), "%llud %02llu:%02llu:%02llu UP", static_cast(days), static_cast(hrs), @@ -218,18 +220,20 @@ private: drawCenteredText(framebuffer, framebuffer.height() - 36, "SELECT TOGGLE 12/24H", 1, 1); drawCenteredText(framebuffer, framebuffer.height() - 18, "B BACK", 1, 1); - DispTools::draw_to_display_async_start(); + framebuffer.endFrame(); } }; -class ClockAppFactory final : public IAppFactory { +class ClockAppFactory final : public cardboy::sdk::IAppFactory { public: const char* name() const override { return kClockAppName; } - std::unique_ptr create(AppContext& context) override { return std::make_unique(context); } + std::unique_ptr create(cardboy::sdk::AppContext& context) override { + return std::make_unique(context); + } }; } // namespace -std::unique_ptr createClockAppFactory() { return std::make_unique(); } +std::unique_ptr createClockAppFactory() { return std::make_unique(); } } // namespace apps diff --git a/Firmware/main/src/apps/menu_app.cpp b/Firmware/sdk/apps/menu_app.cpp similarity index 85% rename from Firmware/main/src/apps/menu_app.cpp rename to Firmware/sdk/apps/menu_app.cpp index 56e4287..4cf57e1 100644 --- a/Firmware/main/src/apps/menu_app.cpp +++ b/Firmware/sdk/apps/menu_app.cpp @@ -1,9 +1,9 @@ -#include "apps/menu_app.hpp" +#include "cardboy/apps/menu_app.hpp" -#include "app_system.hpp" -#include "font16x8.hpp" +#include "cardboy/sdk/app_framework.hpp" +#include "cardboy/sdk/app_system.hpp" -#include +#include "cardboy/gfx/font16x8.hpp" #include #include @@ -15,6 +15,8 @@ namespace apps { namespace { +using cardboy::sdk::AppContext; + using Framebuffer = typename AppContext::Framebuffer; struct MenuEntry { @@ -22,7 +24,7 @@ struct MenuEntry { std::size_t index = 0; }; -class MenuApp final : public IApp { +class MenuApp final : public cardboy::sdk::IApp { public: explicit MenuApp(AppContext& ctx) : context(ctx), framebuffer(ctx.framebuffer) { refreshEntries(); } @@ -32,11 +34,11 @@ public: renderIfNeeded(); } - void handleEvent(const AppEvent& event) override { - if (event.type != AppEventType::Button) + void handleEvent(const cardboy::sdk::AppEvent& event) override { + if (event.type != cardboy::sdk::AppEventType::Button) return; - const auto& current = event.button.current; + const auto& current = event.button.current; const auto& previous = event.button.previous; if (current.left && !previous.left) { @@ -85,7 +87,7 @@ private: return; const std::size_t total = context.system->appCount(); for (std::size_t i = 0; i < total; ++i) { - const IAppFactory* factory = context.system->factoryAt(i); + const cardboy::sdk::IAppFactory* factory = context.system->factoryAt(i); if (!factory) continue; const char* name = factory->name(); @@ -133,7 +135,7 @@ private: return; dirty = false; - DispTools::draw_to_display_async_wait(); + framebuffer.beginFrame(); framebuffer.clear(false); drawCenteredText(framebuffer, 24, "APPS", 1, 1); @@ -156,18 +158,20 @@ private: drawCenteredText(framebuffer, framebuffer.height() - 28, "L/R CHOOSE", 1, 1); } - DispTools::draw_to_display_async_start(); + framebuffer.endFrame(); } }; -class MenuAppFactory final : public IAppFactory { +class MenuAppFactory final : public cardboy::sdk::IAppFactory { public: const char* name() const override { return kMenuAppName; } - std::unique_ptr create(AppContext& context) override { return std::make_unique(context); } + std::unique_ptr create(cardboy::sdk::AppContext& context) override { + return std::make_unique(context); + } }; } // namespace -std::unique_ptr createMenuAppFactory() { return std::make_unique(); } +std::unique_ptr createMenuAppFactory() { return std::make_unique(); } } // namespace apps diff --git a/Firmware/sdk/apps/tetris_app.cpp b/Firmware/sdk/apps/tetris_app.cpp new file mode 100644 index 0000000..47fdecc --- /dev/null +++ b/Firmware/sdk/apps/tetris_app.cpp @@ -0,0 +1,624 @@ +#include "cardboy/apps/tetris_app.hpp" + +#include "cardboy/apps/menu_app.hpp" +#include "cardboy/gfx/font16x8.hpp" +#include "cardboy/sdk/app_framework.hpp" +#include "cardboy/sdk/app_system.hpp" +#include "cardboy/sdk/display_spec.hpp" +#include "cardboy/sdk/input_state.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace apps { +namespace { + +using cardboy::sdk::AppButtonEvent; +using cardboy::sdk::AppContext; +using cardboy::sdk::AppEvent; +using cardboy::sdk::AppEventType; +using cardboy::sdk::AppTimerHandle; +using cardboy::sdk::InputState; + +constexpr char kTetrisAppName[] = "Tetris"; + +constexpr int kBoardWidth = 10; +constexpr int kBoardHeight = 20; +constexpr int kCellSize = 10; + +constexpr std::array kLineScores = {0, 40, 100, 300, 1200}; + +struct BlockOffset { + int x = 0; + int y = 0; +}; + +struct Tetromino { + std::array, 4> rotations{}; +}; + +constexpr std::array makeOffsets(std::initializer_list blocks) { + std::array out{}; + std::size_t idx = 0; + for (const auto& b: blocks) { + out[idx++] = b; + } + return out; +} + +constexpr std::array rotate(const std::array& src) { + std::array out{}; + for (std::size_t i = 0; i < src.size(); ++i) { + out[i].x = -src[i].y; + out[i].y = src[i].x; + } + return out; +} + +constexpr Tetromino makeTetromino(std::initializer_list baseBlocks) { + Tetromino tet{}; + tet.rotations[0] = makeOffsets(baseBlocks); + for (int r = 1; r < 4; ++r) + tet.rotations[r] = rotate(tet.rotations[r - 1]); + return tet; +} + +constexpr std::array kPieces = {{ + makeTetromino({{-1, 0}, {0, 0}, {1, 0}, {2, 0}}), // I + makeTetromino({{-1, 0}, {0, 0}, {1, 0}, {1, 1}}), // J + makeTetromino({{-1, 1}, {-1, 0}, {0, 0}, {1, 0}}), // L + makeTetromino({{0, 0}, {1, 0}, {0, 1}, {1, 1}}), // O + makeTetromino({{-1, 0}, {0, 0}, {0, 1}, {1, 1}}), // S + makeTetromino({{-1, 0}, {0, 0}, {1, 0}, {0, 1}}), // T + makeTetromino({{-1, 1}, {0, 1}, {0, 0}, {1, 0}}), // Z +}}; + +class RandomBag { +public: + RandomBag() { refill(); } + + void seed(std::uint32_t value) { rng.seed(value); } + + int next() { + if (bag.empty()) + refill(); + int val = bag.back(); + bag.pop_back(); + return val; + } + +private: + std::vector bag; + std::mt19937 rng{std::random_device{}()}; + + void refill() { + bag.clear(); + bag.reserve(7); + for (int i = 0; i < 7; ++i) + bag.push_back(i); + std::shuffle(bag.begin(), bag.end(), rng); + } +}; + +struct ActivePiece { + int type = 0; + int rotation = 0; + int x = 0; + int y = 0; +}; + +struct GameState { + std::array board{}; + ActivePiece current{}; + int nextPiece = 0; + int level = 1; + int linesCleared = 0; + int score = 0; + int highScore = 0; + bool paused = false; + bool gameOver = false; +}; + +[[nodiscard]] std::uint32_t randomSeed(AppContext& ctx) { + if (auto* rnd = ctx.random()) + return rnd->nextUint32(); + static std::random_device rd; + return rd(); +} + +class TetrisGame { +public: + explicit TetrisGame(AppContext& ctx) : context(ctx), framebuffer(ctx.framebuffer) { + bag.seed(randomSeed(context)); + loadHighScore(); + reset(); + } + + void onStart() { + scheduleDropTimer(); + dirty = true; + renderIfNeeded(); + } + + void onStop() { cancelTimers(); } + + void handleEvent(const AppEvent& event) { + switch (event.type) { + case AppEventType::Button: + handleButtons(event.button); + break; + case AppEventType::Timer: + handleTimer(event.timer.handle); + break; + } + renderIfNeeded(); + } + +private: + AppContext& context; + typename AppContext::Framebuffer& framebuffer; + + GameState state; + RandomBag bag; + InputState lastInput{}; + bool dirty = false; + AppTimerHandle dropTimer = cardboy::sdk::kInvalidAppTimer; + AppTimerHandle softTimer = cardboy::sdk::kInvalidAppTimer; + + void reset() { + cancelTimers(); + int oldHigh = state.highScore; + state = {}; + state.highScore = oldHigh; + state.current.type = bag.next(); + state.nextPiece = bag.next(); + state.current.x = kBoardWidth / 2; + state.current.y = 0; + state.level = 1; + state.gameOver = false; + state.paused = false; + dirty = true; + scheduleDropTimer(); + if (auto* power = context.powerManager()) + power->setSlowMode(false); + } + + void handleButtons(const AppButtonEvent& evt) { + const auto& cur = evt.current; + const auto& prev = evt.previous; + lastInput = cur; + + if (cur.b && !prev.b) { + context.requestAppSwitchByName(kMenuAppName); + return; + } + + if (cur.start && !prev.start) { + if (state.gameOver) { + reset(); + } else { + state.paused = !state.paused; + if (auto* power = context.powerManager()) + power->setSlowMode(state.paused); + } + dirty = true; + } + + if (state.paused || state.gameOver) + return; + + if (cur.left && !prev.left) + tryMove(-1, 0); + if (cur.right && !prev.right) + tryMove(1, 0); + if (cur.a && !prev.a) + rotate(1); + if (cur.select && !prev.select) + hardDrop(); + + if (cur.down && !prev.down) { + softDropStep(); + scheduleSoftDropTimer(); + } else if (!cur.down && prev.down) { + cancelSoftDropTimer(); + } + } + + void handleTimer(AppTimerHandle handle) { + if (handle == dropTimer) { + if (!state.paused && !state.gameOver) + gravityStep(); + } else if (handle == softTimer) { + if (lastInput.down && !state.paused && !state.gameOver) + softDropStep(); + else + cancelSoftDropTimer(); + } + } + + void cancelTimers() { + if (dropTimer != cardboy::sdk::kInvalidAppTimer) { + context.cancelTimer(dropTimer); + dropTimer = cardboy::sdk::kInvalidAppTimer; + } + cancelSoftDropTimer(); + } + + void cancelSoftDropTimer() { + if (softTimer != cardboy::sdk::kInvalidAppTimer) { + context.cancelTimer(softTimer); + softTimer = cardboy::sdk::kInvalidAppTimer; + } + } + + void scheduleDropTimer() { + cancelDropTimer(); + const std::uint32_t interval = dropIntervalMs(); + dropTimer = context.scheduleRepeatingTimer(interval); + } + + void cancelDropTimer() { + if (dropTimer != cardboy::sdk::kInvalidAppTimer) { + context.cancelTimer(dropTimer); + dropTimer = cardboy::sdk::kInvalidAppTimer; + } + } + + void scheduleSoftDropTimer() { + cancelSoftDropTimer(); + softTimer = context.scheduleRepeatingTimer(60); + } + + [[nodiscard]] std::uint32_t dropIntervalMs() const { + const int base = 700; + const int step = 50; + int interval = base - (state.level - 1) * step; + if (interval < 120) + interval = 120; + return static_cast(interval); + } + + [[nodiscard]] const Tetromino& currentPiece() const { return kPieces[state.current.type]; } + + bool canPlace(int nx, int ny, int rot) const { + const auto& piece = kPieces[state.current.type]; + rot = ((rot % 4) + 4) % 4; + for (const auto& block: piece.rotations[rot]) { + int gx = nx + block.x; + int gy = ny + block.y; + if (gx < 0 || gx >= kBoardWidth) + return false; + if (gy >= kBoardHeight) + return false; + if (gy >= 0 && cellAt(gx, gy) != 0) + return false; + } + return true; + } + + [[nodiscard]] int cellAt(int x, int y) const { return state.board[y * kBoardWidth + x]; } + + void setCell(int x, int y, int value) { state.board[y * kBoardWidth + x] = value; } + + void tryMove(int dx, int dy) { + int nx = state.current.x + dx; + int ny = state.current.y + dy; + if (canPlace(nx, ny, state.current.rotation)) { + state.current.x = nx; + state.current.y = ny; + dirty = true; + if (dx != 0) { + if (auto* buzzer = context.buzzer()) + buzzer->beepMove(); + } + } + } + + void rotate(int direction) { + int nextRot = state.current.rotation + (direction >= 0 ? 1 : -1); + nextRot = ((nextRot % 4) + 4) % 4; + if (canPlace(state.current.x, state.current.y, nextRot)) { + state.current.rotation = nextRot; + dirty = true; + if (auto* buzzer = context.buzzer()) + buzzer->beepRotate(); + } + } + + void gravityStep() { + if (!canPlace(state.current.x, state.current.y + 1, state.current.rotation)) { + lockPiece(); + } else { + state.current.y++; + dirty = true; + } + } + + void softDropStep() { + if (canPlace(state.current.x, state.current.y + 1, state.current.rotation)) { + state.current.y++; + state.score += 1; + updateHighScore(); + dirty = true; + if (auto* buzzer = context.buzzer()) + buzzer->beepMove(); + } else { + lockPiece(); + } + } + + void hardDrop() { + int distance = 0; + while (canPlace(state.current.x, state.current.y + distance + 1, state.current.rotation)) + ++distance; + if (distance > 0) { + state.current.y += distance; + state.score += distance * 2; + updateHighScore(); + dirty = true; + if (auto* buzzer = context.buzzer()) + buzzer->beepMove(); + } + lockPiece(); + } + + void lockPiece() { + for (const auto& block: currentPiece().rotations[state.current.rotation]) { + int gx = state.current.x + block.x; + int gy = state.current.y + block.y; + if (gy >= 0 && gy < kBoardHeight && gx >= 0 && gx < kBoardWidth) + setCell(gx, gy, state.current.type + 1); + if (gy < 0) + state.gameOver = true; + } + + handleLineClear(); + spawnNext(); + dirty = true; + + if (state.gameOver) { + cancelSoftDropTimer(); + cancelDropTimer(); + if (auto* buzzer = context.buzzer()) + buzzer->beepGameOver(); + if (auto* power = context.powerManager()) + power->setSlowMode(true); + } else { + if (auto* buzzer = context.buzzer()) + buzzer->beepLock(); + } + } + + void handleLineClear() { + int cleared = 0; + for (int y = kBoardHeight - 1; y >= 0; --y) { + bool full = true; + for (int x = 0; x < kBoardWidth; ++x) { + if (cellAt(x, y) == 0) { + full = false; + break; + } + } + if (full) { + ++cleared; + for (int pull = y; pull > 0; --pull) + for (int x = 0; x < kBoardWidth; ++x) + setCell(x, pull, cellAt(x, pull - 1)); + for (int x = 0; x < kBoardWidth; ++x) + setCell(x, 0, 0); + ++y; // re-check same row after collapse + } + } + + if (cleared > 0) { + state.linesCleared += cleared; + if (cleared < static_cast(kLineScores.size())) + state.score += kLineScores[cleared] * state.level; + else + state.score += kLineScores.back() * state.level; + + int newLevel = 1 + state.linesCleared / 10; + if (newLevel != state.level) { + state.level = newLevel; + scheduleDropTimer(); + if (auto* buzzer = context.buzzer()) + buzzer->beepLevelUp(state.level); + } + updateHighScore(); + if (auto* buzzer = context.buzzer()) + buzzer->beepLines(cleared); + } + } + + void spawnNext() { + state.current.type = state.nextPiece; + state.current.rotation = 0; + state.current.x = kBoardWidth / 2; + state.current.y = 0; + state.nextPiece = bag.next(); + if (!canPlace(state.current.x, state.current.y, state.current.rotation)) + state.gameOver = true; + } + + void updateHighScore() { + if (state.score > state.highScore) { + state.highScore = state.score; + if (auto* storage = context.storage()) + storage->writeUint32("tetris", "best", static_cast(state.highScore)); + } + } + + void loadHighScore() { + if (auto* storage = context.storage()) { + std::uint32_t stored = 0; + if (storage->readUint32("tetris", "best", stored)) + state.highScore = static_cast(stored); + } + } + + void renderIfNeeded() { + if (!dirty) + return; + dirty = false; + + framebuffer.beginFrame(); + framebuffer.clear(false); + + drawBoard(); + drawActivePiece(); + drawNextPreview(); + drawHUD(); + + framebuffer.endFrame(); + } + + void drawBoard() { + const int originX = (cardboy::sdk::kDisplayWidth - kBoardWidth * kCellSize) / 2; + const int originY = (cardboy::sdk::kDisplayHeight - kBoardHeight * kCellSize) / 2; + + for (int y = 0; y < kBoardHeight; ++y) { + for (int x = 0; x < kBoardWidth; ++x) { + if (int value = cellAt(x, y); value != 0) + drawCell(originX, originY, x, y, value, true); + } + } + + drawGuides(originX, originY); + } + + void drawActivePiece() { + if (state.gameOver) + return; + const int originX = (cardboy::sdk::kDisplayWidth - kBoardWidth * kCellSize) / 2; + const int originY = (cardboy::sdk::kDisplayHeight - kBoardHeight * kCellSize) / 2; + + for (const auto& block: currentPiece().rotations[state.current.rotation]) { + int gx = state.current.x + block.x; + int gy = state.current.y + block.y; + if (gy < 0) + continue; + drawCell(originX, originY, gx, gy, state.current.type + 1, false); + } + } + + void drawCell(int originX, int originY, int cx, int cy, int value, bool solid) { + const int x0 = originX + cx * kCellSize; + const int y0 = originY + cy * kCellSize; + for (int dy = 0; dy < kCellSize; ++dy) { + for (int dx = 0; dx < kCellSize; ++dx) { + bool on = solid ? true : (dx == 0 || dx == kCellSize - 1 || dy == 0 || dy == kCellSize - 1); + framebuffer.drawPixel(x0 + dx, y0 + dy, on); + } + } + (void) value; // value currently unused (monochrome display) + } + + void drawGuides(int originX, int originY) { + for (int y = 0; y <= kBoardHeight; ++y) { + const int py = originY + y * kCellSize; + for (int x = 0; x < kBoardWidth * kCellSize; ++x) + framebuffer.drawPixel(originX + x, py, (y % 5) == 0); + } + for (int x = 0; x <= kBoardWidth; ++x) { + const int px = originX + x * kCellSize; + for (int y = 0; y < kBoardHeight * kCellSize; ++y) + framebuffer.drawPixel(px, originY + y, (x % 5) == 0); + } + } + + void drawNextPreview() { + const int blockSize = kCellSize; + const int boxSize = blockSize * 4; + const int originX = (cardboy::sdk::kDisplayWidth + kBoardWidth * kCellSize) / 2 + 24; + const int originY = (cardboy::sdk::kDisplayHeight - boxSize) / 2; + + for (int dy = 0; dy < boxSize; ++dy) + for (int dx = 0; dx < boxSize; ++dx) + framebuffer.drawPixel(originX + dx, originY + dy, (dy == 0 || dy == boxSize - 1 || dx == 0 || dx == boxSize - 1)); + + const auto& piece = kPieces[state.nextPiece]; + for (const auto& block: piece.rotations[0]) { + const int px = originX + (block.x + 1) * blockSize; + const int py = originY + (block.y + 1) * blockSize; + for (int dy = 1; dy < blockSize - 1; ++dy) + for (int dx = 1; dx < blockSize - 1; ++dx) + framebuffer.drawPixel(px + dx, py + dy, true); + } + } + + void drawLabel(int x, int y, std::string_view text, int scale = 1) { + font16x8::drawText(framebuffer, x, y, text, scale, true, 1); + } + + void drawHUD() { + const int margin = 16; + drawLabel(margin, margin, "SCORE", 1); + drawLabel(margin, margin + 16, std::to_string(state.score), 1); + + drawLabel(margin, margin + 40, "BEST", 1); + drawLabel(margin, margin + 56, std::to_string(state.highScore), 1); + + drawLabel(margin, margin + 80, "LEVEL", 1); + drawLabel(margin, margin + 96, std::to_string(state.level), 1); + + if (auto* battery = context.battery(); battery && battery->hasData()) { + char line[32]; + std::snprintf(line, sizeof(line), "BAT %.2fV", battery->voltage()); + drawLabel(margin, margin + 120, line, 1); + } + + drawLabel(margin, cardboy::sdk::kDisplayHeight - 48, "A ROTATE", 1); + drawLabel(margin, cardboy::sdk::kDisplayHeight - 32, "DOWN DROP", 1); + drawLabel(margin, cardboy::sdk::kDisplayHeight - 16, "B MENU", 1); + + if (state.paused) + drawCenteredBanner("PAUSED"); + else if (state.gameOver) + drawCenteredBanner("GAME OVER"); + } + + void drawCenteredBanner(std::string_view text) { + const int w = font16x8::measureText(text, 2, 1); + const int h = font16x8::kGlyphHeight * 2; + const int x = (cardboy::sdk::kDisplayWidth - w) / 2; + const int y = (cardboy::sdk::kDisplayHeight - h) / 2; + for (int yy = -4; yy < h + 4; ++yy) + for (int xx = -6; xx < w + 6; ++xx) + framebuffer.drawPixel(x + xx, y + yy, yy == -4 || yy == h + 3 || xx == -6 || xx == w + 5); + font16x8::drawText(framebuffer, x, y, text, 2, true, 1); + } +}; + +class TetrisApp final : public cardboy::sdk::IApp { +public: + explicit TetrisApp(AppContext& ctx) : game(ctx) {} + + void onStart() override { game.onStart(); } + void onStop() override { game.onStop(); } + void handleEvent(const AppEvent& event) override { game.handleEvent(event); } + +private: + TetrisGame game; +}; + +class TetrisFactory final : public cardboy::sdk::IAppFactory { +public: + const char* name() const override { return kTetrisAppName; } + std::unique_ptr create(AppContext& context) override { + return std::make_unique(context); + } +}; + +} // namespace + +std::unique_ptr createTetrisAppFactory() { + return std::make_unique(); +} + +} // namespace apps diff --git a/Firmware/sdk/examples/CMakeLists.txt b/Firmware/sdk/examples/CMakeLists.txt deleted file mode 100644 index e69de29..0000000 diff --git a/Firmware/sdk/hosts/sfml_main.cpp b/Firmware/sdk/hosts/sfml_main.cpp new file mode 100644 index 0000000..05cb992 --- /dev/null +++ b/Firmware/sdk/hosts/sfml_main.cpp @@ -0,0 +1,364 @@ +#include "cardboy/apps/clock_app.hpp" +#include "cardboy/apps/menu_app.hpp" +#include "cardboy/apps/tetris_app.hpp" +#include "cardboy/sdk/app_system.hpp" +#include "cardboy/sdk/display_spec.hpp" +#include "cardboy/sdk/services.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int kPixelScale = 2; + +class DesktopBuzzer final : public cardboy::sdk::IBuzzer { +public: + void tone(std::uint32_t, std::uint32_t, std::uint32_t) override {} + void beepRotate() override {} + void beepMove() override {} + void beepLock() override {} + void beepLines(int) override {} + void beepLevelUp(int) override {} + void beepGameOver() override {} +}; + +class DesktopBattery final : public cardboy::sdk::IBatteryMonitor { +public: + [[nodiscard]] bool hasData() const override { return false; } +}; + +class DesktopStorage final : public cardboy::sdk::IStorage { +public: + [[nodiscard]] bool readUint32(std::string_view ns, std::string_view key, std::uint32_t& out) override { + auto it = data.find(composeKey(ns, key)); + if (it == data.end()) + return false; + out = it->second; + return true; + } + + void writeUint32(std::string_view ns, std::string_view key, std::uint32_t value) override { + data[composeKey(ns, key)] = value; + } + +private: + std::unordered_map data; + + static std::string composeKey(std::string_view ns, std::string_view key) { + std::string result; + result.reserve(ns.size() + key.size() + 1); + result.append(ns.begin(), ns.end()); + result.push_back(':'); + result.append(key.begin(), key.end()); + return result; + } +}; + +class DesktopRandom final : public cardboy::sdk::IRandom { +public: + DesktopRandom() : rng(std::random_device{}()), dist(0u, std::numeric_limits::max()) {} + + [[nodiscard]] std::uint32_t nextUint32() override { return dist(rng); } + +private: + std::mt19937 rng; + std::uniform_int_distribution dist; +}; + +class DesktopHighResClock final : public cardboy::sdk::IHighResClock { +public: + DesktopHighResClock() : start(std::chrono::steady_clock::now()) {} + + [[nodiscard]] std::uint64_t micros() override { + const auto now = std::chrono::steady_clock::now(); + return static_cast( + std::chrono::duration_cast(now - start).count()); + } + +private: + const std::chrono::steady_clock::time_point start; +}; + +class DesktopPowerManager final : public cardboy::sdk::IPowerManager { +public: + void setSlowMode(bool enable) override { slowMode = enable; } + [[nodiscard]] bool isSlowMode() const override { return slowMode; } + +private: + bool slowMode = false; +}; + +class DesktopRuntime; + +class DesktopFramebuffer final : public cardboy::sdk::IFramebuffer { +public: + explicit DesktopFramebuffer(DesktopRuntime& runtime) : runtime(runtime) {} + + int width() const override; + int height() const override; + void drawPixel(int x, int y, bool on) override; + void clear(bool on) override; + void beginFrame() override {} + void endFrame() override; + +private: + DesktopRuntime& runtime; +}; + +class DesktopInput final : public cardboy::sdk::IInput { +public: + explicit DesktopInput(DesktopRuntime& runtime) : runtime(runtime) {} + + cardboy::sdk::InputState readState() override { return state; } + + void handleKey(sf::Keyboard::Key key, bool pressed); + +private: + DesktopRuntime& runtime; + cardboy::sdk::InputState state{}; +}; + +class DesktopClock final : public cardboy::sdk::IClock { +public: + explicit DesktopClock(DesktopRuntime& runtime) + : runtime(runtime), start(std::chrono::steady_clock::now()) {} + + std::uint32_t millis() override { + const auto now = std::chrono::steady_clock::now(); + return static_cast( + std::chrono::duration_cast(now - start).count()); + } + + void sleep_ms(std::uint32_t ms) override; + +private: + DesktopRuntime& runtime; + const std::chrono::steady_clock::time_point start; +}; + +class DesktopRuntime { +private: + friend class DesktopFramebuffer; + friend class DesktopInput; + friend class DesktopClock; + + sf::RenderWindow window; + sf::Texture texture; + sf::Sprite sprite; + std::vector pixels; // RGBA buffer + bool dirty = true; + bool running = true; + + DesktopBuzzer buzzerService; + DesktopBattery batteryService; + DesktopStorage storageService; + DesktopRandom randomService; + DesktopHighResClock highResService; + DesktopPowerManager powerService; + cardboy::sdk::Services services{}; + +public: + DesktopRuntime(); + + DesktopFramebuffer framebuffer; + DesktopInput input; + DesktopClock clock; + + cardboy::sdk::Services& serviceRegistry() { return services; } + + void processEvents(); + void presentIfNeeded(); + void sleepFor(std::uint32_t ms); + + [[nodiscard]] bool isRunning() const { return running; } + +private: + void setPixel(int x, int y, bool on); + void clearPixels(bool on); +}; + +DesktopRuntime::DesktopRuntime() + : window(sf::VideoMode(sf::Vector2u{cardboy::sdk::kDisplayWidth * kPixelScale, + cardboy::sdk::kDisplayHeight * kPixelScale}), + "Cardboy Desktop"), + texture(), + sprite(texture), + pixels(static_cast(cardboy::sdk::kDisplayWidth * cardboy::sdk::kDisplayHeight) * 4, 0), + framebuffer(*this), + input(*this), + clock(*this) { + window.setFramerateLimit(60); + if (!texture.resize(sf::Vector2u{cardboy::sdk::kDisplayWidth, cardboy::sdk::kDisplayHeight})) + throw std::runtime_error("Failed to allocate texture for desktop framebuffer"); + sprite.setTexture(texture, true); + sprite.setScale(sf::Vector2f{static_cast(kPixelScale), static_cast(kPixelScale)}); + clearPixels(false); + presentIfNeeded(); + + services.buzzer = &buzzerService; + services.battery = &batteryService; + services.storage = &storageService; + services.random = &randomService; + services.highResClock = &highResService; + services.powerManager = &powerService; +} + +void DesktopRuntime::setPixel(int x, int y, bool on) { + if (x < 0 || y < 0 || x >= cardboy::sdk::kDisplayWidth || y >= cardboy::sdk::kDisplayHeight) + return; + const std::size_t idx = static_cast(y * cardboy::sdk::kDisplayWidth + x) * 4; + const std::uint8_t value = on ? static_cast(255) : static_cast(0); + pixels[idx + 0] = value; + pixels[idx + 1] = value; + pixels[idx + 2] = value; + pixels[idx + 3] = 255; + dirty = true; +} + +void DesktopRuntime::clearPixels(bool on) { + const std::uint8_t value = on ? static_cast(255) : static_cast(0); + for (std::size_t i = 0; i < pixels.size(); i += 4) { + pixels[i + 0] = value; + pixels[i + 1] = value; + pixels[i + 2] = value; + pixels[i + 3] = 255; + } + dirty = true; +} + +void DesktopRuntime::processEvents() { + while (auto eventOpt = window.pollEvent()) { + const sf::Event& event = *eventOpt; + + if (event.is()) { + running = false; + window.close(); + continue; + } + + if (const auto* keyPressed = event.getIf()) { + input.handleKey(keyPressed->code, true); + continue; + } + + if (const auto* keyReleased = event.getIf()) { + input.handleKey(keyReleased->code, false); + continue; + } + } +} + +void DesktopRuntime::presentIfNeeded() { + if (!dirty) + return; + texture.update(pixels.data()); + window.clear(sf::Color::Black); + window.draw(sprite); + window.display(); + dirty = false; +} + +void DesktopRuntime::sleepFor(std::uint32_t ms) { + const auto target = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms); + do { + processEvents(); + presentIfNeeded(); + if (!running) + std::exit(0); + if (ms == 0) + return; + const auto now = std::chrono::steady_clock::now(); + if (now >= target) + return; + const auto remaining = std::chrono::duration_cast(target - now); + if (remaining.count() > 2) + std::this_thread::sleep_for(std::min(remaining, std::chrono::milliseconds(8))); + else + std::this_thread::yield(); + } while (true); +} + +int DesktopFramebuffer::width() const { return cardboy::sdk::kDisplayWidth; } + +int DesktopFramebuffer::height() const { return cardboy::sdk::kDisplayHeight; } + +void DesktopFramebuffer::drawPixel(int x, int y, bool on) { runtime.setPixel(x, y, on); } + +void DesktopFramebuffer::clear(bool on) { runtime.clearPixels(on); } + +void DesktopFramebuffer::endFrame() { runtime.dirty = true; } + +void DesktopInput::handleKey(sf::Keyboard::Key key, bool pressed) { + switch (key) { + case sf::Keyboard::Key::Up: + state.up = pressed; + break; + case sf::Keyboard::Key::Down: + state.down = pressed; + break; + case sf::Keyboard::Key::Left: + state.left = pressed; + break; + case sf::Keyboard::Key::Right: + state.right = pressed; + break; + case sf::Keyboard::Key::Z: + case sf::Keyboard::Key::A: + state.a = pressed; + break; + case sf::Keyboard::Key::X: + case sf::Keyboard::Key::S: + state.b = pressed; + break; + case sf::Keyboard::Key::Space: + state.select = pressed; + break; + case sf::Keyboard::Key::Enter: + state.start = pressed; + break; + default: + break; + } +} + +void DesktopClock::sleep_ms(std::uint32_t ms) { runtime.sleepFor(ms); } + +} // namespace + +int main() { + try { + DesktopRuntime runtime; + + cardboy::sdk::AppContext context(runtime.framebuffer, runtime.input, runtime.clock); + context.services = &runtime.serviceRegistry(); + cardboy::sdk::AppSystem system(context); + + system.registerApp(apps::createMenuAppFactory()); + system.registerApp(apps::createClockAppFactory()); + system.registerApp(apps::createTetrisAppFactory()); + + system.run(); + } catch (const std::exception& ex) { + std::fprintf(stderr, "Cardboy desktop runtime failed: %s\n", ex.what()); + return 1; + } + + return 0; +} diff --git a/Firmware/sdk/include/cardboy/apps/clock_app.hpp b/Firmware/sdk/include/cardboy/apps/clock_app.hpp new file mode 100644 index 0000000..333d80a --- /dev/null +++ b/Firmware/sdk/include/cardboy/apps/clock_app.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "cardboy/sdk/app_framework.hpp" + +#include + +namespace apps { + +std::unique_ptr createClockAppFactory(); + +} // namespace apps + diff --git a/Firmware/sdk/include/cardboy/apps/menu_app.hpp b/Firmware/sdk/include/cardboy/apps/menu_app.hpp new file mode 100644 index 0000000..e529103 --- /dev/null +++ b/Firmware/sdk/include/cardboy/apps/menu_app.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "cardboy/sdk/app_framework.hpp" + +#include +#include + +namespace apps { + +inline constexpr char kMenuAppName[] = "Menu"; +inline constexpr std::string_view kMenuAppNameView = kMenuAppName; + +std::unique_ptr createMenuAppFactory(); + +} // namespace apps + diff --git a/Firmware/sdk/include/cardboy/apps/tetris_app.hpp b/Firmware/sdk/include/cardboy/apps/tetris_app.hpp new file mode 100644 index 0000000..eabeebf --- /dev/null +++ b/Firmware/sdk/include/cardboy/apps/tetris_app.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "cardboy/sdk/app_framework.hpp" + +#include + +namespace apps { + +std::unique_ptr createTetrisAppFactory(); + +} // namespace apps + diff --git a/Firmware/main/include/Fonts.hpp b/Firmware/sdk/include/cardboy/gfx/Fonts.hpp similarity index 100% rename from Firmware/main/include/Fonts.hpp rename to Firmware/sdk/include/cardboy/gfx/Fonts.hpp diff --git a/Firmware/main/include/font16x8.hpp b/Firmware/sdk/include/cardboy/gfx/font16x8.hpp similarity index 97% rename from Firmware/main/include/font16x8.hpp rename to Firmware/sdk/include/cardboy/gfx/font16x8.hpp index fba4a29..dd975d1 100644 --- a/Firmware/main/include/font16x8.hpp +++ b/Firmware/sdk/include/cardboy/gfx/font16x8.hpp @@ -1,7 +1,6 @@ #pragma once -#include "Fonts.hpp" -#include "app_framework.hpp" +#include "cardboy/gfx/Fonts.hpp" #include #include diff --git a/Firmware/sdk/include/cardboy/sdk/app_framework.hpp b/Firmware/sdk/include/cardboy/sdk/app_framework.hpp new file mode 100644 index 0000000..41315d2 --- /dev/null +++ b/Firmware/sdk/include/cardboy/sdk/app_framework.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include "platform.hpp" +#include "services.hpp" + +#include +#include +#include +#include +#include + +namespace cardboy::sdk { + +class AppSystem; + +using AppTimerHandle = std::uint32_t; +constexpr AppTimerHandle kInvalidAppTimer = 0; + +enum class AppEventType { + Button, + Timer, +}; + +struct AppButtonEvent { + InputState current{}; + InputState previous{}; +}; + +struct AppTimerEvent { + AppTimerHandle handle = kInvalidAppTimer; +}; + +struct AppEvent { + AppEventType type; + std::uint32_t timestamp_ms = 0; + AppButtonEvent button{}; + AppTimerEvent timer{}; +}; + +template +struct BasicAppContext { + using Framebuffer = FramebufferT; + using Input = InputT; + using Clock = ClockT; + + BasicAppContext() = delete; + BasicAppContext(FramebufferT& fb, InputT& in, ClockT& clk) : framebuffer(fb), input(in), clock(clk) {} + + FramebufferT& framebuffer; + InputT& input; + ClockT& clock; + AppSystem* system = nullptr; + Services* services = nullptr; + + [[nodiscard]] Services* getServices() const { return services; } + + [[nodiscard]] IBuzzer* buzzer() const { return services ? services->buzzer : nullptr; } + [[nodiscard]] IBatteryMonitor* battery() const { return services ? services->battery : nullptr; } + [[nodiscard]] IStorage* storage() const { return services ? services->storage : nullptr; } + [[nodiscard]] IRandom* random() const { return services ? services->random : nullptr; } + [[nodiscard]] IHighResClock* highResClock() const { return services ? services->highResClock : nullptr; } + [[nodiscard]] IPowerManager* powerManager() const { return services ? services->powerManager : nullptr; } + + void requestAppSwitchByIndex(std::size_t index) { + pendingAppIndex = index; + pendingAppName.clear(); + pendingSwitchByName = false; + pendingSwitch = true; + } + + void requestAppSwitchByName(std::string_view name) { + pendingAppName.assign(name.begin(), name.end()); + pendingSwitchByName = true; + pendingSwitch = true; + } + + [[nodiscard]] bool hasPendingAppSwitch() const { return pendingSwitch; } + + AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat = false) { + if (!system) + return kInvalidAppTimer; + return scheduleTimerInternal(delay_ms, repeat); + } + + AppTimerHandle scheduleRepeatingTimer(std::uint32_t interval_ms) { + if (!system) + return kInvalidAppTimer; + return scheduleTimerInternal(interval_ms, true); + } + + void cancelTimer(AppTimerHandle handle) { + if (!system) + return; + cancelTimerInternal(handle); + } + + void cancelAllTimers() { + if (!system) + return; + cancelAllTimersInternal(); + } + +private: + friend class AppSystem; + bool pendingSwitch = false; + bool pendingSwitchByName = false; + std::size_t pendingAppIndex = 0; + std::string pendingAppName; + + AppTimerHandle scheduleTimerInternal(std::uint32_t delay_ms, bool repeat); + void cancelTimerInternal(AppTimerHandle handle); + void cancelAllTimersInternal(); +}; + +using AppContext = BasicAppContext; + +class IApp { +public: + virtual ~IApp() = default; + virtual void onStart() {} + virtual void onStop() {} + virtual void handleEvent(const AppEvent& event) = 0; +}; + +class IAppFactory { +public: + virtual ~IAppFactory() = default; + virtual const char* name() const = 0; + virtual std::unique_ptr create(AppContext& context) = 0; +}; + +} // namespace cardboy::sdk diff --git a/Firmware/sdk/include/cardboy/sdk/app_system.hpp b/Firmware/sdk/include/cardboy/sdk/app_system.hpp new file mode 100644 index 0000000..1459ab0 --- /dev/null +++ b/Firmware/sdk/include/cardboy/sdk/app_system.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "app_framework.hpp" + +#include +#include +#include +#include + +namespace cardboy::sdk { + +class AppSystem { +public: + explicit AppSystem(AppContext context); + + void registerApp(std::unique_ptr factory); + bool startApp(const std::string& name); + bool startAppByIndex(std::size_t index); + + void run(); + + [[nodiscard]] std::size_t appCount() const { return factories.size(); } + [[nodiscard]] const IAppFactory* factoryAt(std::size_t index) const; + [[nodiscard]] std::size_t indexOfFactory(const IAppFactory* factory) const; + [[nodiscard]] std::size_t currentFactoryIndex() const { return activeIndex; } + + [[nodiscard]] const IApp* currentApp() const { return current.get(); } + [[nodiscard]] const IAppFactory* currentFactory() const { return activeFactory; } + +private: + template + friend struct BasicAppContext; + + struct TimerRecord { + AppTimerHandle id = kInvalidAppTimer; + std::uint32_t generation = 0; + std::uint32_t due_ms = 0; + std::uint32_t interval_ms = 0; + bool repeat = false; + bool active = false; + }; + + AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat); + void cancelTimer(AppTimerHandle handle); + void cancelAllTimers(); + + void dispatchEvent(const AppEvent& event); + + void processDueTimers(std::uint32_t now, std::vector& outEvents); + std::uint32_t nextTimerDueMs(std::uint32_t now) const; + void clearTimersForCurrentApp(); + TimerRecord* findTimer(AppTimerHandle handle); + bool handlePendingSwitchRequest(); + + AppContext context; + std::vector> factories; + std::unique_ptr current; + IAppFactory* activeFactory = nullptr; + std::size_t activeIndex = static_cast(-1); + std::vector timers; + AppTimerHandle nextTimerId = 1; + std::uint32_t currentGeneration = 0; + InputState lastInputState{}; +}; + +template +AppTimerHandle BasicAppContext::scheduleTimerInternal(std::uint32_t delay_ms, + bool repeat) { + return system ? system->scheduleTimer(delay_ms, repeat) : kInvalidAppTimer; +} + +template +void BasicAppContext::cancelTimerInternal(AppTimerHandle handle) { + if (system) + system->cancelTimer(handle); +} + +template +void BasicAppContext::cancelAllTimersInternal() { + if (system) + system->cancelAllTimers(); +} + +} // namespace cardboy::sdk + diff --git a/Firmware/sdk/include/cardboy/sdk/display_spec.hpp b/Firmware/sdk/include/cardboy/sdk/display_spec.hpp new file mode 100644 index 0000000..7af1ce3 --- /dev/null +++ b/Firmware/sdk/include/cardboy/sdk/display_spec.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace cardboy::sdk { + +inline constexpr int kDisplayWidth = 400; +inline constexpr int kDisplayHeight = 240; + +} // namespace cardboy::sdk + diff --git a/Firmware/sdk/include/cardboy/sdk/input_state.hpp b/Firmware/sdk/include/cardboy/sdk/input_state.hpp new file mode 100644 index 0000000..502089e --- /dev/null +++ b/Firmware/sdk/include/cardboy/sdk/input_state.hpp @@ -0,0 +1,17 @@ +#pragma once + +namespace cardboy::sdk { + +struct InputState { + bool up = false; + bool left = false; + bool right = false; + bool down = false; + bool a = false; + bool b = false; + bool select = false; + bool start = false; +}; + +} // namespace cardboy::sdk + diff --git a/Firmware/sdk/include/cardboy/sdk/platform.hpp b/Firmware/sdk/include/cardboy/sdk/platform.hpp new file mode 100644 index 0000000..d3c1fca --- /dev/null +++ b/Firmware/sdk/include/cardboy/sdk/platform.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "input_state.hpp" + +#include + +namespace cardboy::sdk { + +class IFramebuffer { +public: + virtual ~IFramebuffer() = default; + + virtual int width() const = 0; + virtual int height() const = 0; + virtual void drawPixel(int x, int y, bool on) = 0; + virtual void clear(bool on) = 0; + + // Optional hooks for async display pipelines; default to no-ops. + virtual void beginFrame() {} + virtual void endFrame() {} + virtual bool isFrameInFlight() const { return false; } +}; + +class IInput { +public: + virtual ~IInput() = default; + + virtual InputState readState() = 0; +}; + +class IClock { +public: + virtual ~IClock() = default; + + virtual std::uint32_t millis() = 0; + virtual void sleep_ms(std::uint32_t ms) = 0; +}; + +} // namespace cardboy::sdk + diff --git a/Firmware/sdk/include/cardboy/sdk/services.hpp b/Firmware/sdk/include/cardboy/sdk/services.hpp new file mode 100644 index 0000000..92552f6 --- /dev/null +++ b/Firmware/sdk/include/cardboy/sdk/services.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +namespace cardboy::sdk { + +class IBuzzer { +public: + virtual ~IBuzzer() = default; + + virtual void init() {} + virtual void tone(std::uint32_t freq, std::uint32_t duration_ms, std::uint32_t gap_ms = 0) = 0; + + virtual void beepRotate() {} + virtual void beepMove() {} + virtual void beepLock() {} + virtual void beepLines(int /*lines*/) {} + virtual void beepLevelUp(int /*level*/) {} + virtual void beepGameOver() {} + + virtual void setMuted(bool /*muted*/) {} + virtual void toggleMuted() {} + [[nodiscard]] virtual bool isMuted() const { return false; } +}; + +class IBatteryMonitor { +public: + virtual ~IBatteryMonitor() = default; + + [[nodiscard]] virtual bool hasData() const { return false; } + [[nodiscard]] virtual float voltage() const { return 0.0f; } + [[nodiscard]] virtual float charge() const { return 0.0f; } + [[nodiscard]] virtual float current() const { return 0.0f; } +}; + +class IStorage { +public: + virtual ~IStorage() = default; + + [[nodiscard]] virtual bool readUint32(std::string_view ns, std::string_view key, std::uint32_t& out) = 0; + virtual void writeUint32(std::string_view ns, std::string_view key, std::uint32_t value) = 0; +}; + +class IRandom { +public: + virtual ~IRandom() = default; + + [[nodiscard]] virtual std::uint32_t nextUint32() = 0; +}; + +class IHighResClock { +public: + virtual ~IHighResClock() = default; + + [[nodiscard]] virtual std::uint64_t micros() = 0; +}; + +class IPowerManager { +public: + virtual ~IPowerManager() = default; + + virtual void setSlowMode(bool enable) = 0; + [[nodiscard]] virtual bool isSlowMode() const = 0; +}; + +struct Services { + IBuzzer* buzzer = nullptr; + IBatteryMonitor* battery = nullptr; + IStorage* storage = nullptr; + IRandom* random = nullptr; + IHighResClock* highResClock = nullptr; + IPowerManager* powerManager = nullptr; +}; + +} // namespace cardboy::sdk + diff --git a/Firmware/sdk/library/CMakeLists.txt b/Firmware/sdk/library/CMakeLists.txt deleted file mode 100644 index 1eac6a5..0000000 --- a/Firmware/sdk/library/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -cmake_minimum_required(VERSION 3.10) - -add_library(cbsdk - src/Window.cpp - include_public/Window.hpp - include_public/Pixel.hpp - src/Event.cpp - include_public/Event.hpp - include_public/StandardEvents.hpp - include_public/Surface.hpp - include_public/Fonts.hpp - src/TextWindow.cpp - include_public/TextWindow.hpp - include_public/utils.hpp - include_public/SubSurface.hpp) - -target_include_directories(cbsdk PUBLIC include_public) -target_include_directories(cbsdk PRIVATE include) - -if (NOT CMAKE_CROSSCOMPILING) - add_subdirectory(test) -endif () \ No newline at end of file diff --git a/Firmware/sdk/library/include_public/Event.hpp b/Firmware/sdk/library/include_public/Event.hpp deleted file mode 100644 index 4643151..0000000 --- a/Firmware/sdk/library/include_public/Event.hpp +++ /dev/null @@ -1,139 +0,0 @@ -// -// Created by Stepan Usatiuk on 26.07.2025. -// - -#ifndef EVENT_HPP -#define EVENT_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -enum class EventHandlingResult { DONE, IGNORE, CONTINUE }; - -class Event {}; - -struct LoopQuitEvent : public Event {}; - -template -concept IsEvent = std::is_base_of_v; - -template -concept HasHandleFor = requires(H h, E e) { - { h.handle(e) } -> std::same_as; -}; - -template -concept HandlesAll = (HasHandleFor && ...); - -template - requires(IsEvent && ...) -class EventHandler { -public: - EventHandler() { static_assert(HandlesAll); } -}; - -class EventLoop; - -class EventQueueBase { -public: - virtual void process_events() = 0; - - virtual ~EventQueueBase() = default; -}; - -template -class EventQueue : public EventQueueBase { -public: - EventQueue(EventLoop* loop, HandlerType* handler) : _loop(loop), _handler(handler) {}; - - std::optional> poll(); - - void process_events() override { - while (auto event = poll()) { - std::visit([this](auto&& e) { _handler->handle(e); }, *event); - } - } - - template - requires std::disjunction_v...> - void push(T&& event); - -private: - EventLoop* _loop; - HandlerType* _handler; - std::list> _events; -}; - -class EventLoop : EventHandler, public EventQueue { -public: - EventLoop() : EventQueue(this, this) {} - - template - void notify_pending(EventQueue* queue) { - std::lock_guard lock(_mutex); - // TODO: - if (std::find(_events.begin(), _events.end(), queue) != _events.end()) { - return; // Already registered - } - _events.push_back(queue); - _condition.notify_all(); - } - - void run(std::function after_callback) { - while (_running) { - std::list new_events; - - { - std::unique_lock lock(_mutex); - _condition.wait(lock, [this] { return !_events.empty() || !_running; }); - std::swap(new_events, _events); - } - - for (auto queue: new_events) { - queue->process_events(); - } - - after_callback(); - } - } - - EventHandlingResult handle(LoopQuitEvent event) { - _running = false; - _condition.notify_all(); - return EventHandlingResult::DONE; - } - -private: - std::list _events; - std::mutex _mutex; - std::condition_variable _condition; - bool _running = true; -}; - -template -std::optional> EventQueue::poll() { - if (_events.empty()) { - return std::nullopt; - } - - auto event = std::move(_events.front()); - _events.pop_front(); - return event; -} - -template -template - requires std::disjunction_v...> -void EventQueue::push(T&& event) { - _events.emplace_back(std::forward(event)); - _loop->notify_pending(static_cast(this)); -} - -#endif // EVENT_HPP diff --git a/Firmware/sdk/library/include_public/Fonts.hpp b/Firmware/sdk/library/include_public/Fonts.hpp deleted file mode 100644 index 2dbb766..0000000 --- a/Firmware/sdk/library/include_public/Fonts.hpp +++ /dev/null @@ -1,4879 +0,0 @@ -// -// Created by Stepan Usatiuk on 26.04.2024. -// - -#ifndef CB_SDK_FONTS_HPP -#define CB_SDK_FONTS_HPP - -#include -#include - -// Terminess Powerline -static constexpr std::array, 256> fonts_Terminess_Powerline{ - std::array{ - // ++---000-0x00----- - 0b00000000, - 0b00000000, - 0b00110000, - 0b01001000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---001-0x01----- - 0b00000000, - 0b00000000, - 0b01001000, - 0b01001000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---002-0x02----- - 0b00000000, - 0b00000000, - 0b00101000, - 0b00010000, - 0b00101000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---003-0x03----- - 0b00000000, - 0b00000000, - 0b00110010, - 0b01001100, - 0b00000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---004-0x04----- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00001000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---005-0x05----- - 0b00000000, - 0b00000000, - 0b00001000, - 0b00010000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---006-0x06----- - 0b00000000, - 0b00000000, - 0b00011000, - 0b00100100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---007-0x07----- - 0b00000000, - 0b00000000, - 0b00110010, - 0b01001100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---008-0x08----- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00100100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---009-0x09----- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b01111100, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---010-0x0a----- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000010, - 0b00111100, - 0b01000110, - 0b01001010, - 0b01010010, - 0b01100010, - 0b01000010, - 0b10111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---011-0x0b----- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00001000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---012-0x0c----- - 0b00000000, - 0b00000000, - 0b00001000, - 0b00010000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---013-0x0d----- - 0b00000000, - 0b00000000, - 0b00011000, - 0b00100100, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---014-0x0e----- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00100100, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---015-0x0f----- - 0b00000000, - 0b00000000, - 0b00001000, - 0b00010000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000010, - 0b00000010, - 0b00111100, - 0b00000000, - }, - std::array{ - // ++---016-0x10----- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111100, - 0b01000000, - 0b01000000, - 0b01000000, - 0b00000000, - }, - std::array{ - // ++---017-0x11----- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00100100, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000010, - 0b00000010, - 0b00111100, - 0b00000000, - }, - std::array{ - // ++---018-0x12----- - 0b00111100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---019-0x13----- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b00000000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---020-0x14----- - 0b00100100, - 0b00011000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---021-0x15----- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00011000, - 0b00000000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---022-0x16----- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000010, - 0b00000100, - 0b00000011, - 0b00000000, - }, - std::array{ - // ++---023-0x17----- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000010, - 0b00000100, - 0b00000011, - 0b00000000, - }, - std::array{ - // ++---024-0x18----- - 0b00001000, - 0b00010000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---025-0x19----- - 0b00000000, - 0b00000000, - 0b00001000, - 0b00010000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---026-0x1a----- - 0b00100100, - 0b00011000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---027-0x1b----- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00011000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---028-0x1c----- - 0b00100100, - 0b00011000, - 0b00000000, - 0b01111000, - 0b01000100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000100, - 0b01111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---029-0x1d----- - 0b00100100, - 0b00011000, - 0b00000010, - 0b00000010, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---030-0x1e----- - 0b00000000, - 0b00000000, - 0b00000010, - 0b00001111, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---031-0x1f----- - 0b00111100, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---032-0x20-' '- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---033-0x21-'!'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---034-0x22-'"'- - 0b00000000, - 0b00100100, - 0b00100100, - 0b00100100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---035-0x23-'#'- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00100100, - 0b00100100, - 0b01111110, - 0b00100100, - 0b00100100, - 0b01111110, - 0b00100100, - 0b00100100, - 0b00100100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---036-0x24-'$'- - 0b00000000, - 0b00010000, - 0b00010000, - 0b01111100, - 0b10010010, - 0b10010000, - 0b10010000, - 0b01111100, - 0b00010010, - 0b00010010, - 0b10010010, - 0b01111100, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---037-0x25-'%'- - 0b00000000, - 0b00000000, - 0b01100100, - 0b10010100, - 0b01101000, - 0b00001000, - 0b00010000, - 0b00010000, - 0b00100000, - 0b00101100, - 0b01010010, - 0b01001100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---038-0x26-'&'- - 0b00000000, - 0b00000000, - 0b00011000, - 0b00100100, - 0b00100100, - 0b00011000, - 0b00110000, - 0b01001010, - 0b01000100, - 0b01000100, - 0b01000100, - 0b00111010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---039-0x27-'''- - 0b00000000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---040-0x28-'('- - 0b00000000, - 0b00000000, - 0b00001000, - 0b00010000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---041-0x29-')'- - 0b00000000, - 0b00000000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00010000, - 0b00100000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---042-0x2a-'*'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00100100, - 0b00011000, - 0b01111110, - 0b00011000, - 0b00100100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---043-0x2b-'+'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b01111100, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---044-0x2c-','- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00100000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---045-0x2d-'-'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---046-0x2e-'.'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---047-0x2f-'/'- - 0b00000000, - 0b00000000, - 0b00000100, - 0b00000100, - 0b00001000, - 0b00001000, - 0b00010000, - 0b00010000, - 0b00100000, - 0b00100000, - 0b01000000, - 0b01000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---048-0x30-'0'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000110, - 0b01001010, - 0b01010010, - 0b01100010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---049-0x31-'1'- - 0b00000000, - 0b00000000, - 0b00001000, - 0b00011000, - 0b00101000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---050-0x32-'2'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b00000010, - 0b00000100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---051-0x33-'3'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b00000010, - 0b00011100, - 0b00000010, - 0b00000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---052-0x34-'4'- - 0b00000000, - 0b00000000, - 0b00000010, - 0b00000110, - 0b00001010, - 0b00010010, - 0b00100010, - 0b01000010, - 0b01111110, - 0b00000010, - 0b00000010, - 0b00000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---053-0x35-'5'- - 0b00000000, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111100, - 0b00000010, - 0b00000010, - 0b00000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---054-0x36-'6'- - 0b00000000, - 0b00000000, - 0b00011100, - 0b00100000, - 0b01000000, - 0b01000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---055-0x37-'7'- - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000010, - 0b00000010, - 0b00000100, - 0b00000100, - 0b00001000, - 0b00001000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---056-0x38-'8'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---057-0x39-'9'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000010, - 0b00000010, - 0b00000100, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---058-0x3a-':'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---059-0x3b-';'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00100000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---060-0x3c-'<'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b01000000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00000100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---061-0x3d-'='- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---062-0x3e-'>'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b01000000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00000100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b01000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---063-0x3f-'?'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000100, - 0b00001000, - 0b00001000, - 0b00000000, - 0b00001000, - 0b00001000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---064-0x40-'@'- - 0b00000000, - 0b00000000, - 0b01111100, - 0b10000010, - 0b10011110, - 0b10100010, - 0b10100010, - 0b10100010, - 0b10100110, - 0b10011010, - 0b10000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---065-0x41-'A'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---066-0x42-'B'- - 0b00000000, - 0b00000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---067-0x43-'C'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---068-0x44-'D'- - 0b00000000, - 0b00000000, - 0b01111000, - 0b01000100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000100, - 0b01111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---069-0x45-'E'- - 0b00000000, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---070-0x46-'F'- - 0b00000000, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---071-0x47-'G'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01001110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---072-0x48-'H'- - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---073-0x49-'I'- - 0b00000000, - 0b00000000, - 0b00111000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---074-0x4a-'J'- - 0b00000000, - 0b00000000, - 0b00001110, - 0b00000100, - 0b00000100, - 0b00000100, - 0b00000100, - 0b00000100, - 0b00000100, - 0b01000100, - 0b01000100, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---075-0x4b-'K'- - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000100, - 0b01001000, - 0b01010000, - 0b01100000, - 0b01100000, - 0b01010000, - 0b01001000, - 0b01000100, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---076-0x4c-'L'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---077-0x4d-'M'- - 0b00000000, - 0b00000000, - 0b10000010, - 0b11000110, - 0b10101010, - 0b10010010, - 0b10010010, - 0b10000010, - 0b10000010, - 0b10000010, - 0b10000010, - 0b10000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---078-0x4e-'N'- - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01100010, - 0b01010010, - 0b01001010, - 0b01000110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---079-0x4f-'O'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---080-0x50-'P'- - 0b00000000, - 0b00000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111100, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---081-0x51-'Q'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01001010, - 0b00111100, - 0b00000010, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---082-0x52-'R'- - 0b00000000, - 0b00000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111100, - 0b01010000, - 0b01001000, - 0b01000100, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---083-0x53-'S'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000010, - 0b00000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---084-0x54-'T'- - 0b00000000, - 0b00000000, - 0b11111110, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---085-0x55-'U'- - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---086-0x56-'V'- - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00100100, - 0b00100100, - 0b00100100, - 0b00011000, - 0b00011000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---087-0x57-'W'- - 0b00000000, - 0b00000000, - 0b10000010, - 0b10000010, - 0b10000010, - 0b10000010, - 0b10000010, - 0b10010010, - 0b10010010, - 0b10101010, - 0b11000110, - 0b10000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---088-0x58-'X'- - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b00100100, - 0b00100100, - 0b00011000, - 0b00011000, - 0b00100100, - 0b00100100, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---089-0x59-'Y'- - 0b00000000, - 0b00000000, - 0b10000010, - 0b10000010, - 0b01000100, - 0b01000100, - 0b00101000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---090-0x5a-'Z'- - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000010, - 0b00000010, - 0b00000100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---091-0x5b-'['- - 0b00000000, - 0b00000000, - 0b00111000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---092-0x5c-'\'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b00100000, - 0b00100000, - 0b00010000, - 0b00010000, - 0b00001000, - 0b00001000, - 0b00000100, - 0b00000100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---093-0x5d-']'- - 0b00000000, - 0b00000000, - 0b00111000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---094-0x5e-'^'- - 0b00000000, - 0b00010000, - 0b00101000, - 0b01000100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---095-0x5f-'_'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---096-0x60-'`'- - 0b00010000, - 0b00001000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---097-0x61-'a'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---098-0x62-'b'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---099-0x63-'c'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---100-0x64-'d'- - 0b00000000, - 0b00000000, - 0b00000010, - 0b00000010, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---101-0x65-'e'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---102-0x66-'f'- - 0b00000000, - 0b00000000, - 0b00001110, - 0b00010000, - 0b00010000, - 0b01111100, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---103-0x67-'g'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000010, - 0b00000010, - 0b00111100, - 0b00000000, - }, - std::array{ - // ++---104-0x68-'h'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---105-0x69-'i'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---106-0x6a-'j'- - 0b00000000, - 0b00000000, - 0b00000100, - 0b00000100, - 0b00000000, - 0b00001100, - 0b00000100, - 0b00000100, - 0b00000100, - 0b00000100, - 0b00000100, - 0b00000100, - 0b01000100, - 0b01000100, - 0b00111000, - 0b00000000, - }, - std::array{ - // ++---107-0x6b-'k'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b01000100, - 0b01001000, - 0b01110000, - 0b01001000, - 0b01000100, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---108-0x6c-'l'- - 0b00000000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---109-0x6d-'m'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b11111100, - 0b10010010, - 0b10010010, - 0b10010010, - 0b10010010, - 0b10010010, - 0b10010010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---110-0x6e-'n'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---111-0x6f-'o'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---112-0x70-'p'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111100, - 0b01000000, - 0b01000000, - 0b01000000, - 0b00000000, - }, - std::array{ - // ++---113-0x71-'q'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000010, - 0b00000010, - 0b00000010, - 0b00000000, - }, - std::array{ - // ++---114-0x72-'r'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01011110, - 0b01100000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---115-0x73-'s'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000010, - 0b00000010, - 0b01111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---116-0x74-'t'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b01111100, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00001110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---117-0x75-'u'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---118-0x76-'v'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00100100, - 0b00100100, - 0b00011000, - 0b00011000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---119-0x77-'w'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b10000010, - 0b10000010, - 0b10010010, - 0b10010010, - 0b10010010, - 0b10010010, - 0b01111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---120-0x78-'x'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b00100100, - 0b00011000, - 0b00100100, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---121-0x79-'y'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000010, - 0b00000010, - 0b00111100, - 0b00000000, - }, - std::array{ - // ++---122-0x7a-'z'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---123-0x7b-'{'- - 0b00000000, - 0b00000000, - 0b00001100, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00100000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00001100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---124-0x7c-'|'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---125-0x7d-'}'- - 0b00000000, - 0b00000000, - 0b00110000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00000100, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00001000, - 0b00110000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---126-0x7e-'~'- - 0b00000000, - 0b01100010, - 0b10010010, - 0b10001100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---127-0x7f-''- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---128-0x80-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b01111100, - 0b10010010, - 0b10010000, - 0b10010000, - 0b10010000, - 0b10010010, - 0b01111100, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---129-0x81-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01000100, - 0b00111000, - 0b01000100, - 0b01000100, - 0b01000100, - 0b00111000, - 0b01000100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---130-0x82-'�'- - 0b00000000, - 0b00000000, - 0b10000010, - 0b10000010, - 0b01000100, - 0b00101000, - 0b00010000, - 0b01111100, - 0b00010000, - 0b01111100, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---131-0x83-'�'- - 0b00000000, - 0b00111000, - 0b01000100, - 0b01000000, - 0b00110000, - 0b01001000, - 0b01000100, - 0b01000100, - 0b00100100, - 0b00011000, - 0b00000100, - 0b01000100, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---132-0x84-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111110, - 0b10000001, - 0b10011001, - 0b10100101, - 0b10100001, - 0b10100101, - 0b10011001, - 0b10000001, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---133-0x85-'�'- - 0b00000000, - 0b00111000, - 0b00000100, - 0b00111100, - 0b01000100, - 0b00111100, - 0b00000000, - 0b01111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---134-0x86-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010010, - 0b00100100, - 0b01001000, - 0b10010000, - 0b01001000, - 0b00100100, - 0b00010010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---135-0x87-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111110, - 0b10000001, - 0b10111001, - 0b10100101, - 0b10111001, - 0b10101001, - 0b10100101, - 0b10000001, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---136-0x88-'�'- - 0b00000000, - 0b00011000, - 0b00100100, - 0b00001000, - 0b00010000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---137-0x89-'�'- - 0b10000000, - 0b01000000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00000100, - 0b00000010, - 0b00000001, - 0b00000001, - 0b00000010, - 0b00000100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b01000000, - 0b10000000, - }, - std::array{ - // ++---138-0x8a-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000110, - 0b01111010, - 0b01000000, - 0b01000000, - 0b01000000, - 0b00000000, - }, - std::array{ - // ++---139-0x8b-'�'- - 0b00000000, - 0b00000000, - 0b01111110, - 0b10010010, - 0b10010010, - 0b10010010, - 0b10010010, - 0b01110010, - 0b00010010, - 0b00010010, - 0b00010010, - 0b00010010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---140-0x8c-'�'- - 0b00000000, - 0b00111000, - 0b01000100, - 0b01000100, - 0b01000100, - 0b00111000, - 0b00000000, - 0b01111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---141-0x8d-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b10010000, - 0b01001000, - 0b00100100, - 0b00010010, - 0b00100100, - 0b01001000, - 0b10010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---142-0x8e-'�'- - 0b00000000, - 0b00100000, - 0b01100000, - 0b00100000, - 0b00100010, - 0b00100100, - 0b00001000, - 0b00010000, - 0b00100010, - 0b01000110, - 0b10001010, - 0b00011110, - 0b00000010, - 0b00000010, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---143-0x8f-'�'- - 0b00000000, - 0b00100000, - 0b01100000, - 0b00100000, - 0b00100010, - 0b00100100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b01001100, - 0b10010010, - 0b00000100, - 0b00001000, - 0b00011110, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---144-0x90-'�'- - 0b00000001, - 0b00000010, - 0b00000100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b01000000, - 0b10000000, - 0b10000000, - 0b01000000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00000100, - 0b00000010, - 0b00000001, - }, - std::array{ - // ++---145-0x91-'�'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00100000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---146-0x92-'�'- - 0b00010000, - 0b00001000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---147-0x93-'�'- - 0b00001000, - 0b00010000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---148-0x94-'�'- - 0b00011000, - 0b00100100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---149-0x95-'�'- - 0b00110010, - 0b01001100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---150-0x96-'�'- - 0b00100100, - 0b00100100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---151-0x97-'�'- - 0b00011000, - 0b00100100, - 0b00011000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---152-0x98-'�'- - 0b00000000, - 0b00000000, - 0b01111110, - 0b10010000, - 0b10010000, - 0b10010000, - 0b11111100, - 0b10010000, - 0b10010000, - 0b10010000, - 0b10010000, - 0b10011110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---153-0x99-'�'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00010000, - 0b00010000, - 0b00100000, - 0b00000000, - }, - std::array{ - // ++---154-0x9a-'�'- - 0b00010000, - 0b00001000, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---155-0x9b-'�'- - 0b00001000, - 0b00010000, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---156-0x9c-'�'- - 0b00011000, - 0b00100100, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---157-0x9d-'�'- - 0b00100100, - 0b00100100, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---158-0x9e-'�'- - 0b00100000, - 0b00010000, - 0b00000000, - 0b00111000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---159-0x9f-'�'- - 0b00001000, - 0b00010000, - 0b00000000, - 0b00111000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---160-0xa0-'�'- - 0b00011000, - 0b00100100, - 0b00000000, - 0b00111000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---161-0xa1-'�'- - 0b01000100, - 0b01000100, - 0b00000000, - 0b00111000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---162-0xa2-'�'- - 0b00000000, - 0b00000000, - 0b01111000, - 0b01000100, - 0b01000010, - 0b01000010, - 0b11110010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000100, - 0b01111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---163-0xa3-'�'- - 0b00110010, - 0b01001100, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01100010, - 0b01010010, - 0b01001010, - 0b01000110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---164-0xa4-'�'- - 0b00010000, - 0b00001000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---165-0xa5-'�'- - 0b00001000, - 0b00010000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---166-0xa6-'�'- - 0b00011000, - 0b00100100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---167-0xa7-'�'- - 0b00110010, - 0b01001100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---168-0xa8-'�'- - 0b00100100, - 0b00100100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---169-0xa9-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01000010, - 0b00100100, - 0b00011000, - 0b00011000, - 0b00100100, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---170-0xaa-'�'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000011, - 0b01000010, - 0b01000110, - 0b01001010, - 0b01010010, - 0b01100010, - 0b01000010, - 0b11000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---171-0xab-'�'- - 0b00010000, - 0b00001000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---172-0xac-'�'- - 0b00001000, - 0b00010000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---173-0xad-'�'- - 0b00011000, - 0b00100100, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---174-0xae-'�'- - 0b00100100, - 0b00100100, - 0b00000000, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---175-0xaf-'�'- - 0b00001000, - 0b00010000, - 0b10000010, - 0b10000010, - 0b01000100, - 0b01000100, - 0b00101000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---176-0xb0-'�'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01111100, - 0b01000000, - 0b01000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---177-0xb1-'�'- - 0b00000000, - 0b00000000, - 0b00111000, - 0b01000100, - 0b01000100, - 0b01001000, - 0b01111100, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01100010, - 0b01011100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---178-0xb2-'�'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00001000, - 0b00000000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---179-0xb3-'�'- - 0b00000000, - 0b00000000, - 0b00001000, - 0b00010000, - 0b00000000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---180-0xb4-'�'- - 0b00000000, - 0b00000000, - 0b00011000, - 0b00100100, - 0b00000000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---181-0xb5-'�'- - 0b00000000, - 0b00000000, - 0b00110010, - 0b01001100, - 0b00000000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---182-0xb6-'�'- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00100100, - 0b00000000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---183-0xb7-'�'- - 0b00000000, - 0b00000000, - 0b00011000, - 0b00100100, - 0b00011000, - 0b00111100, - 0b00000010, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---184-0xb8-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01101100, - 0b00010010, - 0b01110010, - 0b10011110, - 0b10010000, - 0b10010000, - 0b01101100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---185-0xb9-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b00111100, - 0b00010000, - 0b00010000, - 0b00100000, - 0b00000000, - }, - std::array{ - // ++---186-0xba-'�'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00001000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---187-0xbb-'�'- - 0b00000000, - 0b00000000, - 0b00001000, - 0b00010000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---188-0xbc-'�'- - 0b00000000, - 0b00000000, - 0b00011000, - 0b00100100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---189-0xbd-'�'- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00100100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---190-0xbe-'�'- - 0b00000000, - 0b00000000, - 0b00100000, - 0b00010000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---191-0xbf-'�'- - 0b00000000, - 0b00000000, - 0b00001000, - 0b00010000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---192-0xc0-'�'- - 0b00000000, - 0b00000000, - 0b00011000, - 0b00100100, - 0b00100000, - 0b00100000, - 0b01111000, - 0b00100000, - 0b00100000, - 0b00100000, - 0b00100010, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---193-0xc1-'�'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b01010100, - 0b00111000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---194-0xc2-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000011, - 0b00001111, - 0b00111111, - 0b11111111, - 0b00111111, - 0b00001111, - 0b00000011, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---195-0xc3-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b11000000, - 0b11110000, - 0b11111100, - 0b11111111, - 0b11111100, - 0b11110000, - 0b11000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---196-0xc4-'�'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00111000, - 0b01010100, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---197-0xc5-'�'- - 0b10001000, - 0b00100010, - 0b10001000, - 0b00100010, - 0b10001000, - 0b00100010, - 0b10001000, - 0b00100010, - 0b10001000, - 0b00100010, - 0b10001000, - 0b00100010, - 0b10001000, - 0b00100010, - 0b10001000, - 0b00100010, - }, - std::array{ - // ++---198-0xc6-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b00111100, - 0b00111100, - 0b00111100, - 0b00111100, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---199-0xc7-'�'- - 0b10101010, - 0b01010101, - 0b10101010, - 0b01010101, - 0b10101010, - 0b01010101, - 0b10101010, - 0b01010101, - 0b10101010, - 0b01010101, - 0b10101010, - 0b01010101, - 0b10101010, - 0b01010101, - 0b10101010, - 0b01010101, - }, - std::array{ - // ++---200-0xc8-'�'- - 0b00000000, - 0b00011000, - 0b00100100, - 0b00100100, - 0b00011000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---201-0xc9-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00111000, - 0b01111100, - 0b11111110, - 0b01111100, - 0b00111000, - 0b00010000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---202-0xca-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00000100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b00000000, - 0b01111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---203-0xcb-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---204-0xcc-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b11111111, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---205-0xcd-'�'- - 0b00101000, - 0b00101000, - 0b00101000, - 0b00101000, - 0b00101000, - 0b00101000, - 0b11101111, - 0b00000000, - 0b11101111, - 0b00101000, - 0b00101000, - 0b00101000, - 0b00101000, - 0b00101000, - 0b00101000, - 0b00101000, - }, - std::array{ - // ++---206-0xce-'�'- - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b11111111, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - }, - std::array{ - // ++---207-0xcf-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000100, - 0b00001000, - 0b00010000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00000100, - 0b00000000, - 0b00111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---208-0xd0-'�'- - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00011111, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---209-0xd1-'�'- - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b11110000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---210-0xd2-'�'- - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b11111111, - 0b00010000, - 0b11111111, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - }, - std::array{ - // ++---211-0xd3-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b01111100, - 0b00010000, - 0b00010000, - 0b00000000, - 0b01111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---212-0xd4-'�'- - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---213-0xd5-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---214-0xd6-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b11111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---215-0xd7-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---216-0xd8-'�'- - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - 0b11111111, - }, - std::array{ - // ++---217-0xd9-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b11111111, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - }, - std::array{ - // ++---218-0xda-'�'- - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b11110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - }, - std::array{ - // ++---219-0xdb-'�'- - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00011111, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - }, - std::array{ - // ++---220-0xdc-'�'- - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b11111111, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---221-0xdd-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00011111, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - }, - std::array{ - // ++---222-0xde-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b11110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - }, - std::array{ - // ++---223-0xdf-'�'- - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - }, - std::array{ - // ++---224-0xe0-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---225-0xe1-'�'- - 0b00010000, - 0b00010000, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---226-0xe2-'�'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---227-0xe3-'�'- - 0b00000000, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000010, - 0b00000100, - 0b00000011, - 0b00000000, - }, - std::array{ - // ++---228-0xe4-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00001000, - 0b00010000, - 0b00001100, - 0b00000000, - }, - std::array{ - // ++---229-0xe5-'�'- - 0b00100100, - 0b00011000, - 0b00000000, - 0b01111110, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---230-0xe6-'�'- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00011000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01111110, - 0b01000000, - 0b01000000, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---231-0xe7-'�'- - 0b00100100, - 0b00011000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000000, - 0b01001110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---232-0xe8-'�'- - 0b00000000, - 0b00000000, - 0b00100100, - 0b00011000, - 0b00000000, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000010, - 0b00000010, - 0b00111100, - 0b00000000, - }, - std::array{ - // ++---233-0xe9-'�'- - 0b00000000, - 0b00000000, - 0b00111100, - 0b01000010, - 0b01000010, - 0b01000000, - 0b01000000, - 0b01001110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111100, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00100000, - }, - std::array{ - // ++---234-0xea-'�'- - 0b00000000, - 0b00000100, - 0b00001000, - 0b00001000, - 0b00000000, - 0b00111110, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b01000010, - 0b00111110, - 0b00000010, - 0b00000010, - 0b00111100, - 0b00000000, - }, - std::array{ - // ++---235-0xeb-'�'- - 0b00110010, - 0b01001100, - 0b00000000, - 0b00111000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---236-0xec-'�'- - 0b00000000, - 0b00000000, - 0b00110100, - 0b01011000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---237-0xed-'�'- - 0b01111100, - 0b00000000, - 0b00111000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---238-0xee-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b01111000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---239-0xef-'�'- - 0b00000000, - 0b00000000, - 0b00111000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00010000, - 0b00100000, - 0b00011000, - 0b00000000, - }, - std::array{ - // ++---240-0xf0-'�'- - 0b00000000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00010000, - 0b00100000, - 0b00011000, - 0b00000000, - }, - std::array{ - // ++---241-0xf1-'�'- - 0b00010000, - 0b00010000, - 0b00000000, - 0b00111000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---242-0xf2-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---243-0xf3-'�'- - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000100, - 0b01001000, - 0b01010000, - 0b01100000, - 0b01100000, - 0b01010000, - 0b01001000, - 0b01000100, - 0b01000010, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00100000, - }, - std::array{ - // ++---244-0xf4-'�'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000010, - 0b01000100, - 0b01001000, - 0b01110000, - 0b01001000, - 0b01000100, - 0b01000010, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00100000, - }, - std::array{ - // ++---245-0xf5-'�'- - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b01000010, - 0b01000100, - 0b01001000, - 0b01110000, - 0b01001000, - 0b01000100, - 0b01000010, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---246-0xf6-'�'- - 0b00100000, - 0b01000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---247-0xf7-'�'- - 0b00001000, - 0b00010000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---248-0xf8-'�'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00100000, - }, - std::array{ - // ++---249-0xf9-'�'- - 0b00000000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00010000, - 0b00010000, - 0b00100000, - }, - std::array{ - // ++---250-0xfa-'�'- - 0b00100100, - 0b00011000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---251-0xfb-'�'- - 0b01001000, - 0b00110000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---252-0xfc-'�'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000100, - 0b01000100, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---253-0xfd-'�'- - 0b00000000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00010001, - 0b00010001, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---254-0xfe-'�'- - 0b00000000, - 0b00000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01100000, - 0b11000000, - 0b01000000, - 0b01000000, - 0b01000000, - 0b01111110, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }, - std::array{ - // ++---255-0xff-'�'- - 0b00000000, - 0b00000000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00011000, - 0b00110000, - 0b00010000, - 0b00010000, - 0b00010000, - 0b00111000, - 0b00000000, - 0b00000000, - 0b00000000, - 0b00000000, - }}; - - -#endif //FONTS_HPP diff --git a/Firmware/sdk/library/include_public/GridWindow.hpp b/Firmware/sdk/library/include_public/GridWindow.hpp deleted file mode 100644 index a1f5881..0000000 --- a/Firmware/sdk/library/include_public/GridWindow.hpp +++ /dev/null @@ -1,128 +0,0 @@ -// -// Created by Stepan Usatiuk on 26.07.2025. -// - -#ifndef GRIDWINDOW_HPP -#define GRIDWINDOW_HPP -#include - -#include "Fonts.hpp" -#include "SubSurface.hpp" -#include "Window.hpp" -#include "utils.hpp" - -template -class GridWindow : public Window { -public: - using PixelType = typename SurfaceType::PixelType; - - explicit GridWindow(SurfaceType* owner) : Window(owner) { - for (int i = 0; i < nWidth; ++i) { - for (int j = 0; j < nHeight; ++j) { - _grid[i][j].emplace(owner); - } - } - } - - EventHandlingResult handle_v(KeyboardEvent keyboardEvent) override { - if (keyboardEvent.key_code == Key::Escape) { - if (!_has_focus) { - return EventHandlingResult::CONTINUE; - } else { - auto res = _grid[_current_focus_x][_current_focus_y]->get_window()->handle(keyboardEvent); - if (res == EventHandlingResult::DONE) { - return EventHandlingResult::DONE; - } else { - _has_focus = false; - } - } - } else if (keyboardEvent.key_code == Key::Enter) { - if (!_has_focus) { - _has_focus = true; - } else { - return _grid[_current_focus_x][_current_focus_y]->get_window()->handle(keyboardEvent); - } - } else { - if (_has_focus) { - return _grid[_current_focus_x][_current_focus_y]->get_window()->handle(keyboardEvent); - } - - if (keyboardEvent.key_code == Key::Left) { - if (_current_focus_x > 0) { - _current_focus_x--; - } - } else if (keyboardEvent.key_code == Key::Right) { - if (_current_focus_x < nWidth - 1) { - _current_focus_x++; - } - } else if (keyboardEvent.key_code == Key::Up) { - if (_current_focus_y > 0) { - _current_focus_y--; - } - } else if (keyboardEvent.key_code == Key::Down) { - if (_current_focus_y < nHeight - 1) { - _current_focus_y++; - } - } - } - refresh(); - return EventHandlingResult::DONE; - } - - EventHandlingResult handle_v(SurfaceResizeEvent resize) override { - _cell_width = this->_owner->get_width() / nWidth; - _cell_height = this->_owner->get_height() / nHeight; - for (int i = 0; i < nWidth; ++i) { - for (int j = 0; j < nHeight; ++j) { - if constexpr (is_specialization_of::value) { - _grid[i][j]->set_pos(this->_owner->get_x_offset() + i * _cell_width + 1, - this->_owner->get_y_offset() + j * _cell_height + 1, _cell_width - 2, - _cell_height - 2); - } else { - _grid[i][j]->set_pos(i * _cell_width + 1, j * _cell_height + 1, _cell_width - 2, _cell_height - 2); - } - } - } - refresh(); - return EventHandlingResult::DONE; - } - - template - void set_window(unsigned x, unsigned y, Args&&... args) { - _grid[x][y]->template set_window(std::forward(args)...); - } - - SubSurface& get_subsurface(unsigned x, unsigned y) { - // assert(x >= nWidth && y >= nHeight); - return *_grid[x][y]; - } - - void refresh() { - for (int i = 0; i < nWidth; ++i) { - for (int j = 0; j < nHeight; ++j) { - if (i == _current_focus_x && j == _current_focus_y) { - this->_owner->draw_rect(i * _cell_width, j * _cell_height, _cell_width, _cell_height, - PixelType(true)); - } else { - this->_owner->draw_rect(i * _cell_width, j * _cell_height, _cell_width, _cell_height, - PixelType(false)); - } - } - } - } - -private: - using SubType = std::conditional_t::value, SurfaceType, - SubSurface>; - - std::array, nWidth>, nHeight> _grid; - - unsigned _cell_width = 0; - unsigned _cell_height = 0; - - unsigned _current_focus_x = 0; - unsigned _current_focus_y = 0; - bool _has_focus = false; -}; - -#endif // TEXTWINDOW_HPP diff --git a/Firmware/sdk/library/include_public/Pixel.hpp b/Firmware/sdk/library/include_public/Pixel.hpp deleted file mode 100644 index 60a297c..0000000 --- a/Firmware/sdk/library/include_public/Pixel.hpp +++ /dev/null @@ -1,21 +0,0 @@ -// -// Created by Stepan Usatiuk on 26.07.2025. -// - -#ifndef PIXEL_HPP -#define PIXEL_HPP - -class Pixel { -}; - -struct BwPixel : public Pixel { - bool on = false; - - BwPixel() = default; - BwPixel(bool on) : on(on) {} - - bool operator==(const BwPixel& other) const { return on == other.on; } - bool operator!=(const BwPixel& other) const { return !(*this == other); } -}; - -#endif //PIXEL_HPP diff --git a/Firmware/sdk/library/include_public/StandardEvents.hpp b/Firmware/sdk/library/include_public/StandardEvents.hpp deleted file mode 100644 index bcb1a5c..0000000 --- a/Firmware/sdk/library/include_public/StandardEvents.hpp +++ /dev/null @@ -1,163 +0,0 @@ -// -// Created by Stepan Usatiuk on 26.07.2025. -// - -#ifndef STANDARDEVENTS_HPP -#define STANDARDEVENTS_HPP - -#include "Event.hpp" - -// TODO: rewrite this -//////////////////////////////////////////////////////////// -// -// SFML - Simple and Fast Multimedia Library -// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) -// -// This software is provided 'as-is', without any express or implied warranty. -// In no event will the authors be held liable for any damages arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it freely, -// subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; -// you must not claim that you wrote the original software. -// If you use this software in a product, an acknowledgment -// in the product documentation would be appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, -// and must not be misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source distribution. -// -//////////////////////////////////////////////////////////// -enum class Key { - Unknown = -1, //!< Unhandled key - A = 0, //!< The A key - B, //!< The B key - C, //!< The C key - D, //!< The D key - E, //!< The E key - F, //!< The F key - G, //!< The G key - H, //!< The H key - I, //!< The I key - J, //!< The J key - K, //!< The K key - L, //!< The L key - M, //!< The M key - N, //!< The N key - O, //!< The O key - P, //!< The P key - Q, //!< The Q key - R, //!< The R key - S, //!< The S key - T, //!< The T key - U, //!< The U key - V, //!< The V key - W, //!< The W key - X, //!< The X key - Y, //!< The Y key - Z, //!< The Z key - Num0, //!< The 0 key - Num1, //!< The 1 key - Num2, //!< The 2 key - Num3, //!< The 3 key - Num4, //!< The 4 key - Num5, //!< The 5 key - Num6, //!< The 6 key - Num7, //!< The 7 key - Num8, //!< The 8 key - Num9, //!< The 9 key - Escape, //!< The Escape key - LControl, //!< The left Control key - LShift, //!< The left Shift key - LAlt, //!< The left Alt key - LSystem, //!< The left OS specific key: window (Windows and Linux), apple (macOS), ... - RControl, //!< The right Control key - RShift, //!< The right Shift key - RAlt, //!< The right Alt key - RSystem, //!< The right OS specific key: window (Windows and Linux), apple (macOS), ... - Menu, //!< The Menu key - LBracket, //!< The [ key - RBracket, //!< The ] key - Semicolon, //!< The ; key - Comma, //!< The , key - Period, //!< The . key - Apostrophe, //!< The ' key - Slash, //!< The / key - Backslash, //!< The \ key - Grave, //!< The ` key - Equal, //!< The = key - Hyphen, //!< The - key (hyphen) - Space, //!< The Space key - Enter, //!< The Enter/Return keys - Backspace, //!< The Backspace key - Tab, //!< The Tabulation key - PageUp, //!< The Page up key - PageDown, //!< The Page down key - End, //!< The End key - Home, //!< The Home key - Insert, //!< The Insert key - Delete, //!< The Delete key - Add, //!< The + key - Subtract, //!< The - key (minus, usually from numpad) - Multiply, //!< The * key - Divide, //!< The / key - Left, //!< Left arrow - Right, //!< Right arrow - Up, //!< Up arrow - Down, //!< Down arrow - Numpad0, //!< The numpad 0 key - Numpad1, //!< The numpad 1 key - Numpad2, //!< The numpad 2 key - Numpad3, //!< The numpad 3 key - Numpad4, //!< The numpad 4 key - Numpad5, //!< The numpad 5 key - Numpad6, //!< The numpad 6 key - Numpad7, //!< The numpad 7 key - Numpad8, //!< The numpad 8 key - Numpad9, //!< The numpad 9 key - F1, //!< The F1 key - F2, //!< The F2 key - F3, //!< The F3 key - F4, //!< The F4 key - F5, //!< The F5 key - F6, //!< The F6 key - F7, //!< The F7 key - F8, //!< The F8 key - F9, //!< The F9 key - F10, //!< The F10 key - F11, //!< The F11 key - F12, //!< The F12 key - F13, //!< The F13 key - F14, //!< The F14 key - F15, //!< The F15 key - Pause, //!< The Pause key -}; - - -struct KeyboardEvent : public Event { - KeyboardEvent(Key key_code) : key_code(key_code) {} - Key key_code; -}; - -struct SurfaceEvent : public Event { - enum class EventType { CLOSED, OPENED }; - - EventType type; -}; - -struct SurfaceResizeEvent : public Event { - SurfaceResizeEvent(unsigned int width, unsigned int height) : width(width), height(height) {} - unsigned width; - unsigned height; -}; - -template -using StandardEventHandler = EventHandler; - -template -using StandardEventQueue = EventQueue; - -#endif // STANDARDEVENTS_HPP diff --git a/Firmware/sdk/library/include_public/SubSurface.hpp b/Firmware/sdk/library/include_public/SubSurface.hpp deleted file mode 100644 index 010b00f..0000000 --- a/Firmware/sdk/library/include_public/SubSurface.hpp +++ /dev/null @@ -1,58 +0,0 @@ -// -// Created by Stepan Usatiuk on 27.07.2025. -// - -#ifndef SUBSURFACE_HPP -#define SUBSURFACE_HPP - -#include -#include - -#include "Pixel.hpp" -#include "StandardEvents.hpp" -#include "Surface.hpp" -#include "Window.hpp" -#include "utils.hpp" - -template -class SubSurface : public Surface, typename SurfaceParent::PixelType> { -public: - using PixelType = typename SurfaceParent::PixelType; - - SubSurface(SurfaceParent* parent) : _parent(parent) {} - SubSurface(SubSurface* parent) : _parent(parent->_parent) {} - - void draw_pixel_impl(unsigned x, unsigned y, const PixelType& pixel) { - if (x >= _x_size || y >= _y_size) { - assert(false); - } - - _parent->draw_pixel(x + _x_offset, y + _y_offset, pixel); - } - - unsigned get_x_offset() const { return _x_offset; } - - unsigned get_y_offset() const { return _y_offset; } - - unsigned get_width_impl() const { return _x_size; } - - unsigned get_height_impl() const { return _y_size; } - - void set_pos(unsigned x_offset, unsigned y_offset, unsigned x_size, unsigned y_size) { - _x_offset = x_offset; - _y_offset = y_offset; - _x_size = x_size; - _y_size = y_size; - this->handle(SurfaceResizeEvent(x_size, y_size)); - } - -private: - unsigned _x_offset = 0; - unsigned _y_offset = 0; - unsigned _x_size = 0; - unsigned _y_size = 0; - - SurfaceParent* _parent; -}; - -#endif // SUBSURFACE_HPP diff --git a/Firmware/sdk/library/include_public/Surface.hpp b/Firmware/sdk/library/include_public/Surface.hpp deleted file mode 100644 index 8b7d873..0000000 --- a/Firmware/sdk/library/include_public/Surface.hpp +++ /dev/null @@ -1,81 +0,0 @@ -// -// Created by Stepan Usatiuk on 26.07.2025. -// - -#ifndef SURFACE_HPP -#define SURFACE_HPP - -#include -#include -#include - -#include "Pixel.hpp" -#include "StandardEvents.hpp" -#include "Window.hpp" -#include "utils.hpp" - -template - requires std::is_base_of_v -class Surface : public StandardEventHandler { -public: - Surface() { static_assert(std::is_same_v); } - - void draw_pixel(unsigned x, unsigned y, const BwPixel& pixel) { - static_cast(this)->draw_pixel_impl(x, y, pixel); - } - - void draw_rect(unsigned x, unsigned y, unsigned width, unsigned height, const BwPixel& pixel) { - for (unsigned i = 0; i < width; ++i) { - draw_pixel(x + i, y, pixel); - draw_pixel(x + i, y + height - 1, pixel); - } - for (unsigned i = 0; i < height; ++i) { - draw_pixel(x, y + i, pixel); - draw_pixel(x + width - 1, y + i, pixel); - } - } - - void clear() { - for (unsigned x = 0; x < get_width(); x++) { - for (unsigned y = 0; y < get_height(); y++) { - draw_pixel(x, y, PixelType()); - } - } - } - - int get_width() const { return static_cast(this)->get_width_impl(); } - - int get_height() const { return static_cast(this)->get_height_impl(); } - - template - EventHandlingResult handle(const T& event) { - if (_window.get()) - return _window->handle(event); - return EventHandlingResult::CONTINUE; - } - - template - void set_window(Args&&... args) { - _window = std::make_unique(static_cast(this), std::forward(args)...); - } - - Surface(const Surface& other) = delete; - - Surface(Surface&& other) noexcept = delete; - - Surface& operator=(const Surface& other) = delete; - - Surface& operator=(Surface&& other) noexcept = delete; - - bool has_window() const { return _window != nullptr; } - - Window* get_window() { - assert(has_window()); - return _window.get(); - } - -protected: - std::unique_ptr> _window = nullptr; -}; - -#endif // SURFACE_HPP diff --git a/Firmware/sdk/library/include_public/TextWindow.hpp b/Firmware/sdk/library/include_public/TextWindow.hpp deleted file mode 100644 index 3935ee7..0000000 --- a/Firmware/sdk/library/include_public/TextWindow.hpp +++ /dev/null @@ -1,72 +0,0 @@ -// -// Created by Stepan Usatiuk on 26.07.2025. -// - -#ifndef TEXTWINDOW_HPP -#define TEXTWINDOW_HPP -#include - -#include "Fonts.hpp" -#include "Window.hpp" -#include "utils.hpp" - -template -struct TextUpdateEvent : public Event { - TextUpdateEvent(StringType text) : new_text(std::move(text)) {} - StringType new_text; -}; - -template -class TextWindow : public Window, - public EventHandler>, - public EventQueue, TextUpdateEvent> { -public: - using PixelType = typename SurfaceType::PixelType; - - explicit TextWindow(SurfaceType* owner, EventLoop* loop, StringType text = "") : - Window(owner), EventQueue>(loop, this), - _text(std::move(text)) {} - - EventHandlingResult handle_v(SurfaceResizeEvent resize) override { - refresh(); - return EventHandlingResult::DONE; - } - - EventHandlingResult handle(TextUpdateEvent event) { - _text = std::move(event.new_text); - refresh(); - return EventHandlingResult::DONE; - } - - void refresh() { - this->_owner->clear(); - size_t _max_col = this->_owner->get_width() / 8; - size_t _max_row = this->_owner->get_height() / 16; - int col = 0, row = 0; - for (char c: _text) { - if (c == '\n' || col >= _max_col) { - row++; - col = 0; - if (c == '\n') - continue; - } - - if (row >= _max_row) { - break; - } - - for (int x = 0; x < 8; x++) { - for (int y = 0; y < 16; y++) { - bool color = fonts_Terminess_Powerline[c][y] & (1 << (8 - x)); - this->_owner->draw_pixel(col * 8 + x, row * 16 + y, PixelType(color)); - } - } - col++; - } - } - -private: - StringType _text; -}; - -#endif // TEXTWINDOW_HPP diff --git a/Firmware/sdk/library/include_public/Window.hpp b/Firmware/sdk/library/include_public/Window.hpp deleted file mode 100644 index c6788b4..0000000 --- a/Firmware/sdk/library/include_public/Window.hpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by Stepan Usatiuk on 26.07.2025. -// - -#ifndef WINDOW_HPP -#define WINDOW_HPP - -#include - -#include "Event.hpp" -#include "Pixel.hpp" -#include "StandardEvents.hpp" -#include "utils.hpp" - -template - requires std::is_base_of_v -class Surface; - -template -class Window : StandardEventHandler> { -public: - using PixelType = typename SurfaceType::PixelType; - - explicit Window(SurfaceType* owner) : _owner(owner) { - // static_assert(is_specialization_of::value); - } - - virtual ~Window() = default; - - EventHandlingResult handle(auto Event) { return handle_v(Event); } - - virtual EventHandlingResult handle_v(KeyboardEvent) { return EventHandlingResult::CONTINUE; } - virtual EventHandlingResult handle_v(SurfaceEvent) { return EventHandlingResult::CONTINUE; } - virtual EventHandlingResult handle_v(SurfaceResizeEvent) { return EventHandlingResult::CONTINUE; } - -protected: - SurfaceType* _owner = nullptr; -}; - -#endif // SURFACE_HPP diff --git a/Firmware/sdk/library/include_public/utils.hpp b/Firmware/sdk/library/include_public/utils.hpp deleted file mode 100644 index e83ee0c..0000000 --- a/Firmware/sdk/library/include_public/utils.hpp +++ /dev/null @@ -1,14 +0,0 @@ -// -// Created by Stepan Usatiuk on 27.07.2025. -// - -#ifndef UTILS_HPP -#define UTILS_HPP - -template