mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
lockscreen app
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
#include "cardboy/apps/clock_app.hpp"
|
#include "cardboy/apps/clock_app.hpp"
|
||||||
#include "cardboy/apps/gameboy_app.hpp"
|
#include "cardboy/apps/gameboy_app.hpp"
|
||||||
#include "cardboy/apps/menu_app.hpp"
|
#include "cardboy/apps/menu_app.hpp"
|
||||||
|
#include "cardboy/apps/lockscreen_app.hpp"
|
||||||
#include "cardboy/apps/settings_app.hpp"
|
#include "cardboy/apps/settings_app.hpp"
|
||||||
#include "cardboy/apps/snake_app.hpp"
|
#include "cardboy/apps/snake_app.hpp"
|
||||||
#include "cardboy/apps/tetris_app.hpp"
|
#include "cardboy/apps/tetris_app.hpp"
|
||||||
@@ -233,6 +234,7 @@ extern "C" void app_main() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
system.registerApp(apps::createMenuAppFactory());
|
system.registerApp(apps::createMenuAppFactory());
|
||||||
|
system.registerApp(apps::createLockscreenAppFactory());
|
||||||
system.registerApp(apps::createSettingsAppFactory());
|
system.registerApp(apps::createSettingsAppFactory());
|
||||||
system.registerApp(apps::createClockAppFactory());
|
system.registerApp(apps::createClockAppFactory());
|
||||||
system.registerApp(apps::createSnakeAppFactory());
|
system.registerApp(apps::createSnakeAppFactory());
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ target_link_libraries(cardboy_apps
|
|||||||
target_compile_features(cardboy_apps PUBLIC cxx_std_20)
|
target_compile_features(cardboy_apps PUBLIC cxx_std_20)
|
||||||
|
|
||||||
add_subdirectory(menu)
|
add_subdirectory(menu)
|
||||||
|
add_subdirectory(lockscreen)
|
||||||
add_subdirectory(clock)
|
add_subdirectory(clock)
|
||||||
add_subdirectory(settings)
|
add_subdirectory(settings)
|
||||||
add_subdirectory(gameboy)
|
add_subdirectory(gameboy)
|
||||||
|
|||||||
10
Firmware/sdk/apps/lockscreen/CMakeLists.txt
Normal file
10
Firmware/sdk/apps/lockscreen/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
target_sources(cardboy_apps
|
||||||
|
PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/lockscreen_app.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(cardboy_apps
|
||||||
|
PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
|
)
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cardboy/sdk/app_framework.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace apps {
|
||||||
|
|
||||||
|
inline constexpr char kLockscreenAppName[] = "Lockscreen";
|
||||||
|
inline constexpr std::string_view kLockscreenAppNameView = kLockscreenAppName;
|
||||||
|
|
||||||
|
std::unique_ptr<cardboy::sdk::IAppFactory> createLockscreenAppFactory();
|
||||||
|
|
||||||
|
} // namespace apps
|
||||||
|
|
||||||
192
Firmware/sdk/apps/lockscreen/src/lockscreen_app.cpp
Normal file
192
Firmware/sdk/apps/lockscreen/src/lockscreen_app.cpp
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
#include "cardboy/apps/lockscreen_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 <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <ctime>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace apps {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using cardboy::sdk::AppContext;
|
||||||
|
|
||||||
|
constexpr std::uint32_t kRefreshIntervalMs = 500;
|
||||||
|
|
||||||
|
using Framebuffer = typename AppContext::Framebuffer;
|
||||||
|
using Clock = typename AppContext::Clock;
|
||||||
|
|
||||||
|
struct TimeSnapshot {
|
||||||
|
bool hasWallTime = false;
|
||||||
|
int hour24 = 0;
|
||||||
|
int minute = 0;
|
||||||
|
int second = 0;
|
||||||
|
int year = 0;
|
||||||
|
int month = 0;
|
||||||
|
int day = 0;
|
||||||
|
int weekday = 0;
|
||||||
|
std::uint64_t uptimeSeconds = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LockscreenApp final : public cardboy::sdk::IApp {
|
||||||
|
public:
|
||||||
|
explicit LockscreenApp(AppContext& ctx) : context(ctx), framebuffer(ctx.framebuffer), clock(ctx.clock) {}
|
||||||
|
|
||||||
|
void onStart() override {
|
||||||
|
cancelRefreshTimer();
|
||||||
|
lastSnapshot = {};
|
||||||
|
dirty = true;
|
||||||
|
const auto snap = captureTime();
|
||||||
|
renderIfNeeded(snap);
|
||||||
|
lastSnapshot = snap;
|
||||||
|
refreshTimer = context.scheduleRepeatingTimer(kRefreshIntervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onStop() override { cancelRefreshTimer(); }
|
||||||
|
|
||||||
|
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
|
switch (event.type) {
|
||||||
|
case cardboy::sdk::AppEventType::Button:
|
||||||
|
if (anyNewPress(event.button))
|
||||||
|
context.requestAppSwitchByName(kMenuAppName);
|
||||||
|
break;
|
||||||
|
case cardboy::sdk::AppEventType::Timer:
|
||||||
|
if (event.timer.handle == refreshTimer)
|
||||||
|
updateDisplay();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
AppContext& context;
|
||||||
|
Framebuffer& framebuffer;
|
||||||
|
Clock& clock;
|
||||||
|
|
||||||
|
bool dirty = false;
|
||||||
|
cardboy::sdk::AppTimerHandle refreshTimer = cardboy::sdk::kInvalidAppTimer;
|
||||||
|
TimeSnapshot lastSnapshot{};
|
||||||
|
|
||||||
|
void cancelRefreshTimer() {
|
||||||
|
if (refreshTimer != cardboy::sdk::kInvalidAppTimer) {
|
||||||
|
context.cancelTimer(refreshTimer);
|
||||||
|
refreshTimer = cardboy::sdk::kInvalidAppTimer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool anyNewPress(const cardboy::sdk::AppButtonEvent& button) {
|
||||||
|
const auto& current = button.current;
|
||||||
|
const auto& previous = button.previous;
|
||||||
|
return (current.a && !previous.a) || (current.b && !previous.b) || (current.start && !previous.start) ||
|
||||||
|
(current.select && !previous.select) || (current.up && !previous.up) || (current.down && !previous.down) ||
|
||||||
|
(current.left && !previous.left) || (current.right && !previous.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDisplay() {
|
||||||
|
const auto snap = captureTime();
|
||||||
|
if (!sameSnapshot(snap, lastSnapshot))
|
||||||
|
dirty = true;
|
||||||
|
renderIfNeeded(snap);
|
||||||
|
lastSnapshot = snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sameSnapshot(const TimeSnapshot& a, const TimeSnapshot& b) {
|
||||||
|
return a.hasWallTime == b.hasWallTime && a.hour24 == b.hour24 && a.minute == b.minute &&
|
||||||
|
a.second == b.second && a.day == b.day && a.month == b.month && a.year == b.year;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSnapshot captureTime() const {
|
||||||
|
TimeSnapshot snap{};
|
||||||
|
snap.uptimeSeconds = clock.millis() / 1000ULL;
|
||||||
|
|
||||||
|
std::time_t raw = 0;
|
||||||
|
if (std::time(&raw) != static_cast<std::time_t>(-1) && raw > 0) {
|
||||||
|
std::tm tm{};
|
||||||
|
if (localtime_r(&raw, &tm) != nullptr) {
|
||||||
|
snap.hasWallTime = true;
|
||||||
|
snap.hour24 = tm.tm_hour;
|
||||||
|
snap.minute = tm.tm_min;
|
||||||
|
snap.second = tm.tm_sec;
|
||||||
|
snap.year = tm.tm_year + 1900;
|
||||||
|
snap.month = tm.tm_mon + 1;
|
||||||
|
snap.day = tm.tm_mday;
|
||||||
|
snap.weekday = tm.tm_wday;
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snap.hasWallTime = false;
|
||||||
|
snap.hour24 = static_cast<int>((snap.uptimeSeconds / 3600ULL) % 24ULL);
|
||||||
|
snap.minute = static_cast<int>((snap.uptimeSeconds / 60ULL) % 60ULL);
|
||||||
|
snap.second = static_cast<int>(snap.uptimeSeconds % 60ULL);
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawCenteredText(Framebuffer& fb, int y, std::string_view text, int scale, int letterSpacing = 0) {
|
||||||
|
const int width = font16x8::measureText(text, scale, letterSpacing);
|
||||||
|
const int x = (fb.width() - width) / 2;
|
||||||
|
font16x8::drawText(fb, x, y, text, scale, true, letterSpacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string formatDate(const TimeSnapshot& snap) {
|
||||||
|
static const char* kWeekdays[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
|
||||||
|
if (!snap.hasWallTime)
|
||||||
|
return "UPTIME MODE";
|
||||||
|
const char* weekday = (snap.weekday >= 0 && snap.weekday < 7) ? kWeekdays[snap.weekday] : "";
|
||||||
|
char buffer[32];
|
||||||
|
std::snprintf(buffer, sizeof(buffer), "%s %04d-%02d-%02d", weekday, snap.year, snap.month, snap.day);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderIfNeeded(const TimeSnapshot& snap) {
|
||||||
|
if (!dirty)
|
||||||
|
return;
|
||||||
|
dirty = false;
|
||||||
|
|
||||||
|
framebuffer.frameReady();
|
||||||
|
|
||||||
|
const int scaleTime = 4;
|
||||||
|
const int scaleSeconds = 2;
|
||||||
|
const int scaleSmall = 1;
|
||||||
|
|
||||||
|
char hoursMinutes[6];
|
||||||
|
std::snprintf(hoursMinutes, sizeof(hoursMinutes), "%02d:%02d", snap.hour24, snap.minute);
|
||||||
|
const int mainW = font16x8::measureText(hoursMinutes, scaleTime, 0);
|
||||||
|
const int timeY = (framebuffer.height() - font16x8::kGlyphHeight * scaleTime) / 2 - 8;
|
||||||
|
const int timeX = (framebuffer.width() - mainW) / 2;
|
||||||
|
const int secX = timeX + mainW + 12;
|
||||||
|
const int secY = timeY + font16x8::kGlyphHeight * scaleTime - font16x8::kGlyphHeight * scaleSeconds;
|
||||||
|
char secs[3];
|
||||||
|
std::snprintf(secs, sizeof(secs), "%02d", snap.second);
|
||||||
|
|
||||||
|
font16x8::drawText(framebuffer, timeX, timeY, hoursMinutes, scaleTime, true, 0);
|
||||||
|
font16x8::drawText(framebuffer, secX, secY, secs, scaleSeconds, true, 0);
|
||||||
|
|
||||||
|
const std::string dateLine = formatDate(snap);
|
||||||
|
drawCenteredText(framebuffer, timeY + font16x8::kGlyphHeight * scaleTime + 24, dateLine, scaleSmall, 1);
|
||||||
|
drawCenteredText(framebuffer, framebuffer.height() - 40, "PRESS ANY BUTTON", scaleSmall, 1);
|
||||||
|
|
||||||
|
framebuffer.sendFrame();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class LockscreenAppFactory final : public cardboy::sdk::IAppFactory {
|
||||||
|
public:
|
||||||
|
const char* name() const override { return kLockscreenAppName; }
|
||||||
|
std::unique_ptr<cardboy::sdk::IApp> create(cardboy::sdk::AppContext& context) override {
|
||||||
|
return std::make_unique<LockscreenApp>(context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<cardboy::sdk::IAppFactory> createLockscreenAppFactory() {
|
||||||
|
return std::make_unique<LockscreenAppFactory>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace apps
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "cardboy/apps/menu_app.hpp"
|
#include "cardboy/apps/menu_app.hpp"
|
||||||
|
#include "cardboy/apps/lockscreen_app.hpp"
|
||||||
|
|
||||||
#include "cardboy/sdk/app_framework.hpp"
|
#include "cardboy/sdk/app_framework.hpp"
|
||||||
#include "cardboy/sdk/app_system.hpp"
|
#include "cardboy/sdk/app_system.hpp"
|
||||||
@@ -6,6 +7,7 @@
|
|||||||
#include "cardboy/gfx/font16x8.hpp"
|
#include "cardboy/gfx/font16x8.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -19,6 +21,8 @@ using cardboy::sdk::AppContext;
|
|||||||
|
|
||||||
using Framebuffer = typename AppContext::Framebuffer;
|
using Framebuffer = typename AppContext::Framebuffer;
|
||||||
|
|
||||||
|
constexpr std::uint32_t kIdleTimeoutMs = 15000;
|
||||||
|
|
||||||
struct MenuEntry {
|
struct MenuEntry {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::size_t index = 0;
|
std::size_t index = 0;
|
||||||
@@ -31,15 +35,46 @@ public:
|
|||||||
void onStart() override {
|
void onStart() override {
|
||||||
refreshEntries();
|
refreshEntries();
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
resetInactivityTimer();
|
||||||
renderIfNeeded();
|
renderIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
void onStop() override { cancelInactivityTimer(); }
|
||||||
if (event.type != cardboy::sdk::AppEventType::Button)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto& current = event.button.current;
|
void handleEvent(const cardboy::sdk::AppEvent& event) override {
|
||||||
const auto& previous = event.button.previous;
|
switch (event.type) {
|
||||||
|
case cardboy::sdk::AppEventType::Button:
|
||||||
|
handleButtonEvent(event.button);
|
||||||
|
break;
|
||||||
|
case cardboy::sdk::AppEventType::Timer:
|
||||||
|
if (event.timer.handle == inactivityTimer) {
|
||||||
|
cancelInactivityTimer();
|
||||||
|
context.requestAppSwitchByName(kLockscreenAppName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
AppContext& context;
|
||||||
|
Framebuffer& framebuffer;
|
||||||
|
std::vector<MenuEntry> entries;
|
||||||
|
std::size_t selected = 0;
|
||||||
|
|
||||||
|
bool dirty = false;
|
||||||
|
|
||||||
|
cardboy::sdk::AppTimerHandle inactivityTimer = cardboy::sdk::kInvalidAppTimer;
|
||||||
|
|
||||||
|
void handleButtonEvent(const cardboy::sdk::AppButtonEvent& button) {
|
||||||
|
resetInactivityTimer();
|
||||||
|
|
||||||
|
const auto& current = button.current;
|
||||||
|
const auto& previous = button.previous;
|
||||||
|
|
||||||
|
if (current.b && !previous.b) {
|
||||||
|
context.requestAppSwitchByName(kLockscreenAppName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (current.left && !previous.left) {
|
if (current.left && !previous.left) {
|
||||||
moveSelection(-1);
|
moveSelection(-1);
|
||||||
@@ -54,14 +89,6 @@ public:
|
|||||||
renderIfNeeded();
|
renderIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
AppContext& context;
|
|
||||||
Framebuffer& framebuffer;
|
|
||||||
std::vector<MenuEntry> entries;
|
|
||||||
std::size_t selected = 0;
|
|
||||||
|
|
||||||
bool dirty = false;
|
|
||||||
|
|
||||||
void moveSelection(int step) {
|
void moveSelection(int step) {
|
||||||
if (entries.empty())
|
if (entries.empty())
|
||||||
return;
|
return;
|
||||||
@@ -93,7 +120,8 @@ private:
|
|||||||
const char* name = factory->name();
|
const char* name = factory->name();
|
||||||
if (!name)
|
if (!name)
|
||||||
continue;
|
continue;
|
||||||
if (std::string_view(name) == kMenuAppNameView)
|
const std::string_view appName(name);
|
||||||
|
if (appName == kMenuAppNameView || appName == kLockscreenAppNameView)
|
||||||
continue;
|
continue;
|
||||||
entries.push_back(MenuEntry{std::string(name), i});
|
entries.push_back(MenuEntry{std::string(name), i});
|
||||||
}
|
}
|
||||||
@@ -159,6 +187,18 @@ private:
|
|||||||
|
|
||||||
framebuffer.sendFrame();
|
framebuffer.sendFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cancelInactivityTimer() {
|
||||||
|
if (inactivityTimer != cardboy::sdk::kInvalidAppTimer) {
|
||||||
|
context.cancelTimer(inactivityTimer);
|
||||||
|
inactivityTimer = cardboy::sdk::kInvalidAppTimer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetInactivityTimer() {
|
||||||
|
cancelInactivityTimer();
|
||||||
|
inactivityTimer = context.scheduleTimer(kIdleTimeoutMs);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class MenuAppFactory final : public cardboy::sdk::IAppFactory {
|
class MenuAppFactory final : public cardboy::sdk::IAppFactory {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "cardboy/apps/clock_app.hpp"
|
#include "cardboy/apps/clock_app.hpp"
|
||||||
#include "cardboy/apps/gameboy_app.hpp"
|
#include "cardboy/apps/gameboy_app.hpp"
|
||||||
#include "cardboy/apps/menu_app.hpp"
|
#include "cardboy/apps/menu_app.hpp"
|
||||||
|
#include "cardboy/apps/lockscreen_app.hpp"
|
||||||
#include "cardboy/apps/settings_app.hpp"
|
#include "cardboy/apps/settings_app.hpp"
|
||||||
#include "cardboy/apps/snake_app.hpp"
|
#include "cardboy/apps/snake_app.hpp"
|
||||||
#include "cardboy/apps/tetris_app.hpp"
|
#include "cardboy/apps/tetris_app.hpp"
|
||||||
@@ -27,6 +28,7 @@ int main() {
|
|||||||
buzzer->setMuted(persistentSettings.mute);
|
buzzer->setMuted(persistentSettings.mute);
|
||||||
|
|
||||||
system.registerApp(apps::createMenuAppFactory());
|
system.registerApp(apps::createMenuAppFactory());
|
||||||
|
system.registerApp(apps::createLockscreenAppFactory());
|
||||||
system.registerApp(apps::createSettingsAppFactory());
|
system.registerApp(apps::createSettingsAppFactory());
|
||||||
system.registerApp(apps::createClockAppFactory());
|
system.registerApp(apps::createClockAppFactory());
|
||||||
system.registerApp(apps::createGameboyAppFactory());
|
system.registerApp(apps::createGameboyAppFactory());
|
||||||
|
|||||||
Reference in New Issue
Block a user