Migrate to Waveshare ESP32-S3-Touch-AMOLED-2.16 with auto-rotation, splash, and battery
Full hardware swap from Panlee SC01 Plus (480×320 IPS) to Waveshare 2.16" square AMOLED (480×480, CO5300 + CST9220 + AXP2101 + QMI8658). Library stack moves to Arduino_GFX, SensorLib, XPowersLib on the pioarduino platform 55.03.38-1 (Arduino Core 3.x). UI: - 4 screens (splash, usage, controller, bluetooth) with 3-button physical navigation: GPIO 0 = prev, AXP PKEY = cycle, GPIO 18 = next. - IMU-driven 90° auto-rotation. CO5300 can't rotate via MADCTL, so flush callback does CPU strip-rotation in PARTIAL render mode. Rotation transitions use AMOLED brightness flash (instant black → redraw → ramp). - Battery indicator (Lucide icons) in upper-right, RGB565A8 alpha so it blends over the splash animations. - USB plugged/unplugged auto-switches between Usage and Controller screens (suppressed while on splash). - Fonts and icons re-scaled ~1.9× for the higher-DPI panel; 20px margins to clear rounded corners. Splash: - 13 × 20×20 pixel-art creature animations sourced from claudepix.vercel.app via tools/scrape_claudepix.js (scraper handles both PRESET creature-engine and standalone FRAMES+PAL formats). tools/convert_to_c.js emits firmware/src/splash_animations.h. Attribution preserved in README and the generated header. Tooling: - tools/png_to_lvgl.js converts alpha PNGs to LVGL RGB565A8 (planar layout, with --tint to colorize Lucide black-on-transparent sources). - tools/scrape_claudepix.js, tools/convert_to_c.js for the splash pipeline. Docs: - New worktree CLAUDE.md with hardware pin map, file inventory, build commands, and the 8 critical gotchas (CO5300 rotation, OPI PSRAM, pioarduino requirement, LVGL 9 font patching, centralized touch read, even-aligned flush regions, touch swap/mirror values, RGB565A8 layout). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
10b8052bcc
commit
97a5443601
+161
-85
@@ -1,15 +1,17 @@
|
||||
#include "ui.h"
|
||||
#include "splash.h"
|
||||
#include <lvgl.h>
|
||||
#include "logo.h"
|
||||
#include "icons.h"
|
||||
#include "display_cfg.h"
|
||||
|
||||
// Custom fonts
|
||||
LV_FONT_DECLARE(font_tiempos_34);
|
||||
// Custom fonts (scaled for 314 PPI, ~1.9x from original 165 PPI)
|
||||
LV_FONT_DECLARE(font_tiempos_56);
|
||||
LV_FONT_DECLARE(font_styrene_48);
|
||||
LV_FONT_DECLARE(font_styrene_28);
|
||||
LV_FONT_DECLARE(font_styrene_16);
|
||||
LV_FONT_DECLARE(font_styrene_14);
|
||||
LV_FONT_DECLARE(font_styrene_12);
|
||||
LV_FONT_DECLARE(font_mono_18);
|
||||
LV_FONT_DECLARE(font_styrene_24);
|
||||
LV_FONT_DECLARE(font_styrene_20);
|
||||
LV_FONT_DECLARE(font_mono_32);
|
||||
|
||||
// Anthropic brand palette
|
||||
#define COL_BG lv_color_hex(0x141413)
|
||||
@@ -24,6 +26,14 @@ LV_FONT_DECLARE(font_mono_18);
|
||||
#define COL_ZONE_BG lv_color_hex(0x252524)
|
||||
#define COL_ZONE_BRD lv_color_hex(0x3a3a38)
|
||||
|
||||
// ---- Layout constants for 480x480 (scaled for 2.16" high-DPI + rounded corners) ----
|
||||
#define SCR_W 480
|
||||
#define SCR_H 480
|
||||
#define MARGIN 20 // wider margin for rounded display corners
|
||||
#define TITLE_Y 30
|
||||
#define CONTENT_Y 100
|
||||
#define CONTENT_W (SCR_W - 2 * MARGIN) // 440
|
||||
|
||||
// ---- Usage screen widgets ----
|
||||
static lv_obj_t* usage_container;
|
||||
static lv_obj_t* lbl_title;
|
||||
@@ -49,6 +59,11 @@ static lv_obj_t* lbl_ble_status;
|
||||
static lv_obj_t* lbl_ble_device;
|
||||
static lv_obj_t* lbl_ble_mac;
|
||||
|
||||
// ---- Battery indicator (shared, on top) ----
|
||||
static lv_obj_t* battery_img;
|
||||
static lv_obj_t* logo_img;
|
||||
static lv_image_dsc_t battery_dscs[5]; // empty, low, medium, full, charging
|
||||
|
||||
// ---- Shared ----
|
||||
static lv_image_dsc_t logo_dsc;
|
||||
static screen_t current_screen = SCREEN_USAGE;
|
||||
@@ -128,10 +143,10 @@ static lv_obj_t* make_panel(lv_obj_t* parent, int x, int y, int w, int h) {
|
||||
lv_obj_set_style_bg_opa(panel, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_radius(panel, 8, 0);
|
||||
lv_obj_set_style_border_width(panel, 0, 0);
|
||||
lv_obj_set_style_pad_left(panel, 12, 0);
|
||||
lv_obj_set_style_pad_right(panel, 12, 0);
|
||||
lv_obj_set_style_pad_top(panel, 10, 0);
|
||||
lv_obj_set_style_pad_bottom(panel, 16, 0);
|
||||
lv_obj_set_style_pad_left(panel, 16, 0);
|
||||
lv_obj_set_style_pad_right(panel, 16, 0);
|
||||
lv_obj_set_style_pad_top(panel, 12, 0);
|
||||
lv_obj_set_style_pad_bottom(panel, 12, 0);
|
||||
lv_obj_clear_flag(panel, LV_OBJ_FLAG_SCROLLABLE);
|
||||
return panel;
|
||||
}
|
||||
@@ -151,7 +166,6 @@ static lv_obj_t* make_bar(lv_obj_t* parent, int x, int y, int w, int h) {
|
||||
return bar;
|
||||
}
|
||||
|
||||
// Create a touch zone button on the controller screen
|
||||
static lv_obj_t* make_zone(lv_obj_t* parent, int x, int y, int w, int h, const char* text) {
|
||||
lv_obj_t* zone = lv_obj_create(parent);
|
||||
lv_obj_set_pos(zone, x, y);
|
||||
@@ -164,14 +178,14 @@ static lv_obj_t* make_zone(lv_obj_t* parent, int x, int y, int w, int h, const c
|
||||
|
||||
lv_obj_t* lbl = lv_label_create(zone);
|
||||
lv_label_set_text(lbl, text);
|
||||
lv_obj_set_style_text_font(lbl, &font_styrene_14, 0);
|
||||
lv_obj_set_style_text_font(lbl, &font_styrene_24, 0);
|
||||
lv_obj_set_style_text_color(lbl, COL_DIM, 0);
|
||||
lv_obj_center(lbl);
|
||||
|
||||
return zone;
|
||||
}
|
||||
|
||||
// Icon image descriptors (initialized in init_controller_screen)
|
||||
// Icon image descriptors
|
||||
static lv_image_dsc_t icon_escape_dsc;
|
||||
static lv_image_dsc_t icon_delete_dsc;
|
||||
static lv_image_dsc_t icon_arrow_left_dsc;
|
||||
@@ -186,7 +200,17 @@ static void init_icon_dsc(lv_image_dsc_t* dsc, int w, int h, const uint16_t* dat
|
||||
dsc->data_size = w * h * 2;
|
||||
}
|
||||
|
||||
// Create a touch zone with an icon centered in it
|
||||
// RGB565A8: planar — w*h RGB565 pixels followed by w*h alpha bytes.
|
||||
// Stride is RGB565-only (w*2); LVGL infers alpha plane location from header.
|
||||
static void init_icon_dsc_rgb565a8(lv_image_dsc_t* dsc, int w, int h, const uint8_t* data) {
|
||||
dsc->header.w = w;
|
||||
dsc->header.h = h;
|
||||
dsc->header.cf = LV_COLOR_FORMAT_RGB565A8;
|
||||
dsc->header.stride = w * 2;
|
||||
dsc->data = data;
|
||||
dsc->data_size = w * h * 3;
|
||||
}
|
||||
|
||||
static lv_obj_t* make_zone_icon(lv_obj_t* parent, int x, int y, int w, int h, lv_image_dsc_t* icon) {
|
||||
lv_obj_t* zone = lv_obj_create(parent);
|
||||
lv_obj_set_pos(zone, x, y);
|
||||
@@ -204,19 +228,32 @@ static lv_obj_t* make_zone_icon(lv_obj_t* parent, int x, int y, int w, int h, lv
|
||||
return zone;
|
||||
}
|
||||
|
||||
// Create a dim hint label for swipe directions
|
||||
static lv_obj_t* make_hint(lv_obj_t* parent, int x, int y, const char* text) {
|
||||
lv_obj_t* lbl = lv_label_create(parent);
|
||||
lv_label_set_text(lbl, text);
|
||||
lv_obj_set_style_text_font(lbl, &font_styrene_14, 0);
|
||||
lv_obj_set_style_text_font(lbl, &font_styrene_24, 0);
|
||||
lv_obj_set_style_text_color(lbl, COL_DIM, 0);
|
||||
lv_obj_set_pos(lbl, x, y);
|
||||
return lbl;
|
||||
}
|
||||
|
||||
// ---- Battery icon initialization ----
|
||||
static void init_battery_icons(void) {
|
||||
init_icon_dsc_rgb565a8(&battery_dscs[0], ICON_BATTERY_W, ICON_BATTERY_H, icon_battery_data);
|
||||
init_icon_dsc_rgb565a8(&battery_dscs[1], ICON_BATTERY_LOW_W, ICON_BATTERY_LOW_H, icon_battery_low_data);
|
||||
init_icon_dsc_rgb565a8(&battery_dscs[2], ICON_BATTERY_MEDIUM_W, ICON_BATTERY_MEDIUM_H, icon_battery_medium_data);
|
||||
init_icon_dsc_rgb565a8(&battery_dscs[3], ICON_BATTERY_FULL_W, ICON_BATTERY_FULL_H, icon_battery_full_data);
|
||||
init_icon_dsc_rgb565a8(&battery_dscs[4], ICON_BATTERY_CHARGING_W, ICON_BATTERY_CHARGING_H, icon_battery_charging_data);
|
||||
}
|
||||
|
||||
// ======== Usage Screen (480x480) ========
|
||||
|
||||
#define PANEL_H 150
|
||||
#define PANEL_GAP 16
|
||||
|
||||
static void init_usage_screen(lv_obj_t* scr) {
|
||||
usage_container = lv_obj_create(scr);
|
||||
lv_obj_set_size(usage_container, 480, 320);
|
||||
lv_obj_set_size(usage_container, SCR_W, SCR_H);
|
||||
lv_obj_set_pos(usage_container, 0, 0);
|
||||
lv_obj_set_style_bg_opa(usage_container, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(usage_container, 0, 0);
|
||||
@@ -226,67 +263,75 @@ static void init_usage_screen(lv_obj_t* scr) {
|
||||
// Title
|
||||
lbl_title = lv_label_create(usage_container);
|
||||
lv_label_set_text(lbl_title, "Claude Usage");
|
||||
lv_obj_set_style_text_font(lbl_title, &font_tiempos_34, 0);
|
||||
lv_obj_set_style_text_font(lbl_title, &font_tiempos_56, 0);
|
||||
lv_obj_set_style_text_color(lbl_title, COL_TEXT, 0);
|
||||
lv_obj_set_pos(lbl_title, 66, 12);
|
||||
lv_obj_set_pos(lbl_title, 106, TITLE_Y);
|
||||
|
||||
// Session panel
|
||||
lv_obj_t* p_session = make_panel(usage_container, 8, 56, 464, 100);
|
||||
lv_obj_t* p_session = make_panel(usage_container, MARGIN, CONTENT_Y, CONTENT_W, PANEL_H);
|
||||
|
||||
lbl_session_pct = lv_label_create(p_session);
|
||||
lv_label_set_text(lbl_session_pct, "---%");
|
||||
lv_obj_set_style_text_font(lbl_session_pct, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_font(lbl_session_pct, &font_styrene_48, 0);
|
||||
lv_obj_set_style_text_color(lbl_session_pct, COL_TEXT, 0);
|
||||
lv_obj_set_pos(lbl_session_pct, 0, 0);
|
||||
|
||||
bar_session = make_bar(p_session, 0, 36, 440, 20);
|
||||
bar_session = make_bar(p_session, 0, 56, CONTENT_W - 40, 24);
|
||||
|
||||
lbl_session_label = lv_label_create(p_session);
|
||||
lv_label_set_text(lbl_session_label, "Current Session");
|
||||
lv_obj_set_style_text_font(lbl_session_label, &font_styrene_16, 0);
|
||||
lv_obj_set_style_text_font(lbl_session_label, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_color(lbl_session_label, COL_DIM, 0);
|
||||
lv_obj_set_pos(lbl_session_label, 0, 62);
|
||||
lv_obj_set_pos(lbl_session_label, 0, 94);
|
||||
|
||||
lbl_session_reset = lv_label_create(p_session);
|
||||
lv_label_set_text(lbl_session_reset, "---");
|
||||
lv_obj_set_style_text_font(lbl_session_reset, &font_styrene_16, 0);
|
||||
lv_obj_set_style_text_font(lbl_session_reset, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_color(lbl_session_reset, COL_DIM, 0);
|
||||
lv_obj_align(lbl_session_reset, LV_ALIGN_TOP_RIGHT, 0, 62);
|
||||
lv_obj_align(lbl_session_reset, LV_ALIGN_TOP_RIGHT, 0, 94);
|
||||
|
||||
// Weekly panel
|
||||
lv_obj_t* p_weekly = make_panel(usage_container, 8, 166, 464, 100);
|
||||
int weekly_y = CONTENT_Y + PANEL_H + PANEL_GAP;
|
||||
lv_obj_t* p_weekly = make_panel(usage_container, MARGIN, weekly_y, CONTENT_W, PANEL_H);
|
||||
|
||||
lbl_weekly_pct = lv_label_create(p_weekly);
|
||||
lv_label_set_text(lbl_weekly_pct, "---%");
|
||||
lv_obj_set_style_text_font(lbl_weekly_pct, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_font(lbl_weekly_pct, &font_styrene_48, 0);
|
||||
lv_obj_set_style_text_color(lbl_weekly_pct, COL_TEXT, 0);
|
||||
lv_obj_set_pos(lbl_weekly_pct, 0, 0);
|
||||
|
||||
bar_weekly = make_bar(p_weekly, 0, 36, 440, 20);
|
||||
bar_weekly = make_bar(p_weekly, 0, 56, CONTENT_W - 40, 24);
|
||||
|
||||
lbl_weekly_label = lv_label_create(p_weekly);
|
||||
lv_label_set_text(lbl_weekly_label, "Current Week");
|
||||
lv_obj_set_style_text_font(lbl_weekly_label, &font_styrene_16, 0);
|
||||
lv_obj_set_style_text_font(lbl_weekly_label, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_color(lbl_weekly_label, COL_DIM, 0);
|
||||
lv_obj_set_pos(lbl_weekly_label, 0, 62);
|
||||
lv_obj_set_pos(lbl_weekly_label, 0, 94);
|
||||
|
||||
lbl_weekly_reset = lv_label_create(p_weekly);
|
||||
lv_label_set_text(lbl_weekly_reset, "---");
|
||||
lv_obj_set_style_text_font(lbl_weekly_reset, &font_styrene_16, 0);
|
||||
lv_obj_set_style_text_font(lbl_weekly_reset, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_color(lbl_weekly_reset, COL_DIM, 0);
|
||||
lv_obj_align(lbl_weekly_reset, LV_ALIGN_TOP_RIGHT, 0, 62);
|
||||
lv_obj_align(lbl_weekly_reset, LV_ALIGN_TOP_RIGHT, 0, 94);
|
||||
|
||||
// Animation
|
||||
lbl_anim = lv_label_create(usage_container);
|
||||
lv_label_set_text(lbl_anim, "");
|
||||
lv_obj_set_style_text_font(lbl_anim, &font_mono_18, 0);
|
||||
lv_obj_set_style_text_font(lbl_anim, &font_mono_32, 0);
|
||||
lv_obj_set_style_text_color(lbl_anim, COL_ACCENT, 0);
|
||||
lv_obj_align(lbl_anim, LV_ALIGN_BOTTOM_MID, 0, -16);
|
||||
lv_obj_align(lbl_anim, LV_ALIGN_BOTTOM_MID, 0, -24);
|
||||
}
|
||||
|
||||
// ======== Controller Screen (480x480) ========
|
||||
|
||||
#define CTRL_SIDE_W 110
|
||||
#define CTRL_SIDE_H 138
|
||||
#define CTRL_GAP 10
|
||||
#define CTRL_CONTENT_H (2 * CTRL_SIDE_H + CTRL_GAP) // 306
|
||||
|
||||
static void init_controller_screen(lv_obj_t* scr) {
|
||||
ctrl_container = lv_obj_create(scr);
|
||||
lv_obj_set_size(ctrl_container, 480, 320);
|
||||
lv_obj_set_size(ctrl_container, SCR_W, SCR_H);
|
||||
lv_obj_set_pos(ctrl_container, 0, 0);
|
||||
lv_obj_set_style_bg_opa(ctrl_container, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(ctrl_container, 0, 0);
|
||||
@@ -296,20 +341,9 @@ static void init_controller_screen(lv_obj_t* scr) {
|
||||
// Title
|
||||
lv_obj_t* lbl_ctrl_title = lv_label_create(ctrl_container);
|
||||
lv_label_set_text(lbl_ctrl_title, "Claude Controller");
|
||||
lv_obj_set_style_text_font(lbl_ctrl_title, &font_tiempos_34, 0);
|
||||
lv_obj_set_style_text_font(lbl_ctrl_title, &font_tiempos_56, 0);
|
||||
lv_obj_set_style_text_color(lbl_ctrl_title, COL_TEXT, 0);
|
||||
lv_obj_set_pos(lbl_ctrl_title, 66, 12);
|
||||
|
||||
// Shared layout math — matches usage screen geometry
|
||||
#define MARGIN 8
|
||||
#define CONTENT_Y 56
|
||||
#define PANEL_H 100
|
||||
#define GAP 10
|
||||
#define CONTENT_W (480 - 2 * MARGIN) // 464
|
||||
#define CONTENT_BOT (CONTENT_Y + 2 * PANEL_H + GAP) // 266
|
||||
#define CONTENT_H (CONTENT_BOT - CONTENT_Y) // 210
|
||||
#define SIDE_H ((CONTENT_H - GAP) / 2) // 100 (square)
|
||||
#define SIDE_W SIDE_H
|
||||
lv_obj_set_pos(lbl_ctrl_title, 66, TITLE_Y);
|
||||
|
||||
// Initialize icon descriptors
|
||||
init_icon_dsc(&icon_escape_dsc, ICON_ESCAPE_W, ICON_ESCAPE_H, icon_escape_data);
|
||||
@@ -318,17 +352,18 @@ static void init_controller_screen(lv_obj_t* scr) {
|
||||
init_icon_dsc(&icon_arrow_right_dsc, ICON_ARROW_RIGHT_W, ICON_ARROW_RIGHT_H, icon_arrow_right_data);
|
||||
|
||||
// Left column: ESC (top), < (bottom)
|
||||
make_zone_icon(ctrl_container, MARGIN, CONTENT_Y, SIDE_W, SIDE_H, &icon_escape_dsc);
|
||||
make_zone_icon(ctrl_container, MARGIN, CONTENT_Y + SIDE_H + GAP, SIDE_W, SIDE_H, &icon_arrow_left_dsc);
|
||||
make_zone_icon(ctrl_container, MARGIN, CONTENT_Y, CTRL_SIDE_W, CTRL_SIDE_H, &icon_escape_dsc);
|
||||
make_zone_icon(ctrl_container, MARGIN, CONTENT_Y + CTRL_SIDE_H + CTRL_GAP, CTRL_SIDE_W, CTRL_SIDE_H, &icon_arrow_left_dsc);
|
||||
|
||||
// Right column: DEL (top), > (bottom)
|
||||
make_zone_icon(ctrl_container, 480 - MARGIN - SIDE_W, CONTENT_Y, SIDE_W, SIDE_H, &icon_delete_dsc);
|
||||
make_zone_icon(ctrl_container, 480 - MARGIN - SIDE_W, CONTENT_Y + SIDE_H + GAP, SIDE_W, SIDE_H, &icon_arrow_right_dsc);
|
||||
int right_x = SCR_W - MARGIN - CTRL_SIDE_W;
|
||||
make_zone_icon(ctrl_container, right_x, CONTENT_Y, CTRL_SIDE_W, CTRL_SIDE_H, &icon_delete_dsc);
|
||||
make_zone_icon(ctrl_container, right_x, CONTENT_Y + CTRL_SIDE_H + CTRL_GAP, CTRL_SIDE_W, CTRL_SIDE_H, &icon_arrow_right_dsc);
|
||||
|
||||
// Center hints box (full content height)
|
||||
int cx = MARGIN + SIDE_W + GAP;
|
||||
int cw = CONTENT_W - 2 * (SIDE_W + GAP);
|
||||
lv_obj_t* center = make_zone(ctrl_container, cx, CONTENT_Y, cw, CONTENT_H, "");
|
||||
// Center hints box
|
||||
int cx = MARGIN + CTRL_SIDE_W + CTRL_GAP;
|
||||
int cw = CONTENT_W - 2 * (CTRL_SIDE_W + CTRL_GAP);
|
||||
lv_obj_t* center = make_zone(ctrl_container, cx, CONTENT_Y, cw, CTRL_CONTENT_H, "");
|
||||
|
||||
// Vertically centered hint list
|
||||
lv_obj_t* hint_list = lv_obj_create(center);
|
||||
@@ -363,7 +398,7 @@ static void init_controller_screen(lv_obj_t* scr) {
|
||||
|
||||
lv_obj_t* gestures_lbl = lv_label_create(header_row);
|
||||
lv_label_set_text(gestures_lbl, "Gestures");
|
||||
lv_obj_set_style_text_font(gestures_lbl, &font_styrene_16, 0);
|
||||
lv_obj_set_style_text_font(gestures_lbl, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_color(gestures_lbl, COL_TEXT, 0);
|
||||
|
||||
// Hint entries
|
||||
@@ -372,31 +407,33 @@ static void init_controller_screen(lv_obj_t* scr) {
|
||||
make_hint(hint_list, 0, 0, "Swipe right: Enter key");
|
||||
make_hint(hint_list, 0, 0, "Hold: Voice dictation");
|
||||
|
||||
// Usage info below content area: labels then bar
|
||||
int info_y = CONTENT_BOT + GAP;
|
||||
// Usage info below content area
|
||||
int info_y = CONTENT_Y + CTRL_CONTENT_H + CTRL_GAP + 4;
|
||||
|
||||
ctrl_session_lbl = lv_label_create(ctrl_container);
|
||||
lv_label_set_text(ctrl_session_lbl, "---% of current session");
|
||||
lv_obj_set_style_text_font(ctrl_session_lbl, &font_styrene_14, 0);
|
||||
lv_obj_set_style_text_font(ctrl_session_lbl, &font_styrene_24, 0);
|
||||
lv_obj_set_style_text_color(ctrl_session_lbl, COL_DIM, 0);
|
||||
lv_obj_set_pos(ctrl_session_lbl, MARGIN, info_y);
|
||||
|
||||
ctrl_reset_lbl = lv_label_create(ctrl_container);
|
||||
lv_label_set_text(ctrl_reset_lbl, "---");
|
||||
lv_obj_set_style_text_font(ctrl_reset_lbl, &font_styrene_14, 0);
|
||||
lv_obj_set_style_text_font(ctrl_reset_lbl, &font_styrene_24, 0);
|
||||
lv_obj_set_style_text_color(ctrl_reset_lbl, COL_DIM, 0);
|
||||
lv_obj_align(ctrl_reset_lbl, LV_ALIGN_TOP_RIGHT, -MARGIN, info_y);
|
||||
|
||||
int bar_y = info_y + 18;
|
||||
int bar_y = info_y + 28;
|
||||
ctrl_session_bar = make_bar(ctrl_container, MARGIN, bar_y, CONTENT_W, 16);
|
||||
|
||||
// Start hidden
|
||||
lv_obj_add_flag(ctrl_container, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
// ======== Bluetooth Screen (480x480) ========
|
||||
|
||||
static void init_bluetooth_screen(lv_obj_t* scr) {
|
||||
ble_container = lv_obj_create(scr);
|
||||
lv_obj_set_size(ble_container, 480, 320);
|
||||
lv_obj_set_size(ble_container, SCR_W, SCR_H);
|
||||
lv_obj_set_pos(ble_container, 0, 0);
|
||||
lv_obj_set_style_bg_opa(ble_container, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(ble_container, 0, 0);
|
||||
@@ -406,12 +443,12 @@ static void init_bluetooth_screen(lv_obj_t* scr) {
|
||||
// Title
|
||||
lv_obj_t* lbl_ble_title = lv_label_create(ble_container);
|
||||
lv_label_set_text(lbl_ble_title, "Bluetooth");
|
||||
lv_obj_set_style_text_font(lbl_ble_title, &font_tiempos_34, 0);
|
||||
lv_obj_set_style_text_font(lbl_ble_title, &font_tiempos_56, 0);
|
||||
lv_obj_set_style_text_color(lbl_ble_title, COL_TEXT, 0);
|
||||
lv_obj_set_pos(lbl_ble_title, 66, 12);
|
||||
lv_obj_set_pos(lbl_ble_title, 66, TITLE_Y);
|
||||
|
||||
// Info panel
|
||||
lv_obj_t* p_info = make_panel(ble_container, 8, 56, 464, 120);
|
||||
// Info panel (taller for 480x480)
|
||||
lv_obj_t* p_info = make_panel(ble_container, MARGIN, CONTENT_Y, CONTENT_W, 160);
|
||||
|
||||
// Bluetooth icon + status row
|
||||
static lv_image_dsc_t icon_bt_dsc;
|
||||
@@ -423,26 +460,27 @@ static void init_bluetooth_screen(lv_obj_t* scr) {
|
||||
|
||||
lbl_ble_status = lv_label_create(p_info);
|
||||
lv_label_set_text(lbl_ble_status, "Initializing...");
|
||||
lv_obj_set_style_text_font(lbl_ble_status, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_font(lbl_ble_status, &font_styrene_48, 0);
|
||||
lv_obj_set_style_text_color(lbl_ble_status, COL_DIM, 0);
|
||||
lv_obj_set_pos(lbl_ble_status, 40, 2);
|
||||
lv_obj_set_pos(lbl_ble_status, 56, 2);
|
||||
|
||||
lbl_ble_device = lv_label_create(p_info);
|
||||
lv_label_set_text(lbl_ble_device, "Device: ---");
|
||||
lv_obj_set_style_text_font(lbl_ble_device, &font_styrene_16, 0);
|
||||
lv_obj_set_style_text_font(lbl_ble_device, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_color(lbl_ble_device, COL_DIM, 0);
|
||||
lv_obj_set_pos(lbl_ble_device, 0, 50);
|
||||
lv_obj_set_pos(lbl_ble_device, 0, 64);
|
||||
|
||||
lbl_ble_mac = lv_label_create(p_info);
|
||||
lv_label_set_text(lbl_ble_mac, "Address: ---");
|
||||
lv_obj_set_style_text_font(lbl_ble_mac, &font_styrene_16, 0);
|
||||
lv_obj_set_style_text_font(lbl_ble_mac, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_color(lbl_ble_mac, COL_DIM, 0);
|
||||
lv_obj_set_pos(lbl_ble_mac, 0, 74);
|
||||
lv_obj_set_pos(lbl_ble_mac, 0, 100);
|
||||
|
||||
// Reset Bluetooth tap zone with trash icon
|
||||
int reset_y = CONTENT_Y + 160 + 16;
|
||||
lv_obj_t* reset_zone = lv_obj_create(ble_container);
|
||||
lv_obj_set_pos(reset_zone, 8, 190);
|
||||
lv_obj_set_size(reset_zone, 464, 60);
|
||||
lv_obj_set_pos(reset_zone, MARGIN, reset_y);
|
||||
lv_obj_set_size(reset_zone, CONTENT_W, 70);
|
||||
lv_obj_set_style_bg_color(reset_zone, COL_PANEL, 0);
|
||||
lv_obj_set_style_bg_opa(reset_zone, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_radius(reset_zone, 8, 0);
|
||||
@@ -459,26 +497,28 @@ static void init_bluetooth_screen(lv_obj_t* scr) {
|
||||
|
||||
lv_obj_t* reset_lbl = lv_label_create(reset_zone);
|
||||
lv_label_set_text(reset_lbl, "Reset Bluetooth");
|
||||
lv_obj_set_style_text_font(reset_lbl, &font_styrene_16, 0);
|
||||
lv_obj_set_style_text_font(reset_lbl, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_color(reset_lbl, COL_DIM, 0);
|
||||
|
||||
// Footer
|
||||
lv_obj_t* lbl_footer = lv_label_create(ble_container);
|
||||
lv_label_set_text(lbl_footer, "Bluetooth Low Energy");
|
||||
lv_obj_set_style_text_font(lbl_footer, &font_styrene_14, 0);
|
||||
lv_obj_set_style_text_font(lbl_footer, &font_styrene_24, 0);
|
||||
lv_obj_set_style_text_color(lbl_footer, COL_DIM, 0);
|
||||
lv_obj_align(lbl_footer, LV_ALIGN_BOTTOM_MID, 0, -16);
|
||||
lv_obj_align(lbl_footer, LV_ALIGN_BOTTOM_MID, 0, -24);
|
||||
|
||||
// Start hidden
|
||||
lv_obj_add_flag(ble_container, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
// ======== Public API ========
|
||||
|
||||
void ui_init(void) {
|
||||
lv_obj_t* scr = lv_screen_active();
|
||||
lv_obj_set_style_bg_color(scr, COL_BG, 0);
|
||||
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||||
|
||||
// Logo (shared, always visible, on top of both containers)
|
||||
// Logo (shared, always visible, on top of all containers)
|
||||
logo_dsc.header.w = LOGO_WIDTH;
|
||||
logo_dsc.header.h = LOGO_HEIGHT;
|
||||
logo_dsc.header.cf = LV_COLOR_FORMAT_RGB565;
|
||||
@@ -486,14 +526,23 @@ void ui_init(void) {
|
||||
logo_dsc.data = (const uint8_t*)logo_data;
|
||||
logo_dsc.data_size = LOGO_WIDTH * LOGO_HEIGHT * 2;
|
||||
|
||||
// Initialize battery icon descriptors
|
||||
init_battery_icons();
|
||||
|
||||
init_usage_screen(scr);
|
||||
init_controller_screen(scr);
|
||||
init_bluetooth_screen(scr);
|
||||
splash_init(scr);
|
||||
|
||||
// Logo on top of all containers
|
||||
lv_obj_t* img = lv_image_create(scr);
|
||||
lv_image_set_src(img, &logo_dsc);
|
||||
lv_obj_set_pos(img, 10, 4);
|
||||
// Logo on top of all containers (inset for rounded corners)
|
||||
logo_img = lv_image_create(scr);
|
||||
lv_image_set_src(logo_img, &logo_dsc);
|
||||
lv_obj_set_pos(logo_img, MARGIN, TITLE_Y - 10);
|
||||
|
||||
// Battery indicator on top of all containers (upper-right, inset)
|
||||
battery_img = lv_image_create(scr);
|
||||
lv_image_set_src(battery_img, &battery_dscs[0]);
|
||||
lv_obj_set_pos(battery_img, SCR_W - 48 - MARGIN, TITLE_Y);
|
||||
}
|
||||
|
||||
void ui_update(const UsageData* data) {
|
||||
@@ -529,7 +578,7 @@ void ui_update(const UsageData* data) {
|
||||
}
|
||||
|
||||
void ui_tick_anim(void) {
|
||||
if (current_screen != SCREEN_USAGE) return; // animation only on usage screen
|
||||
if (current_screen != SCREEN_USAGE) return;
|
||||
|
||||
uint32_t now = lv_tick_get();
|
||||
|
||||
@@ -554,13 +603,22 @@ void ui_show_screen(screen_t screen) {
|
||||
lv_obj_add_flag(usage_container, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(ctrl_container, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(ble_container, LV_OBJ_FLAG_HIDDEN);
|
||||
splash_hide();
|
||||
|
||||
switch (screen) {
|
||||
case SCREEN_SPLASH: splash_show(); break;
|
||||
case SCREEN_USAGE: lv_obj_clear_flag(usage_container, LV_OBJ_FLAG_HIDDEN); break;
|
||||
case SCREEN_CONTROLLER: lv_obj_clear_flag(ctrl_container, LV_OBJ_FLAG_HIDDEN); break;
|
||||
case SCREEN_BLUETOOTH: lv_obj_clear_flag(ble_container, LV_OBJ_FLAG_HIDDEN); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Hide the logo overlay on the splash screen so the animation has a clean canvas
|
||||
if (logo_img) {
|
||||
if (screen == SCREEN_SPLASH) lv_obj_add_flag(logo_img, LV_OBJ_FLAG_HIDDEN);
|
||||
else lv_obj_clear_flag(logo_img, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
current_screen = screen;
|
||||
}
|
||||
|
||||
@@ -603,3 +661,21 @@ void ui_update_ble_status(ble_state_t state, const char* name, const char* mac)
|
||||
lv_label_set_text(lbl_ble_mac, mbuf);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_update_battery(int percent, bool charging) {
|
||||
int idx;
|
||||
if (charging) {
|
||||
idx = 4; // charging icon
|
||||
} else if (percent < 0) {
|
||||
idx = 0; // no battery / unknown
|
||||
} else if (percent <= 10) {
|
||||
idx = 0; // empty
|
||||
} else if (percent <= 35) {
|
||||
idx = 1; // low
|
||||
} else if (percent <= 75) {
|
||||
idx = 2; // medium
|
||||
} else {
|
||||
idx = 3; // full
|
||||
}
|
||||
lv_image_set_src(battery_img, &battery_dscs[idx]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user