feat(ui): Workbench shell + boot zoom intro
This commit is contained in:
@@ -201,6 +201,34 @@ static SemaphoreHandle_t s_cam_mutex;
|
||||
static volatile bool s_cam_fresh = false;
|
||||
static bool s_cam_visible = false;
|
||||
|
||||
// ─── Amiga Workbench shell (port of ui_amiga_shell, same grid metrics) ───────
|
||||
#define SHELL_GRID_COLS 4 // = AmigaUIShell::GRID_COLS
|
||||
#define SHELL_ICON_SIZE 64 // = ICON_SIZE
|
||||
#define SHELL_ICON_SPACING 16 // = ICON_SPACING
|
||||
#define SHELL_GRID_X 16 // = GRID_START_X
|
||||
#define SHELL_GRID_Y 48 // GRID_START_Y(32) + our taller title bar
|
||||
#define SHELL_APP_COUNT 4
|
||||
static lv_obj_t *s_scr_shell;
|
||||
static lv_obj_t *s_shell_tiles[SHELL_APP_COUNT];
|
||||
// Written by the buttons task, applied by the display task.
|
||||
static volatile uint8_t s_shell_sel = 0;
|
||||
static volatile bool s_shell_open = false;
|
||||
// Accent colors from the original shell palette (ui_amiga_shell.cpp:280-287).
|
||||
static const struct { const char *name; uint32_t color; }
|
||||
kShellApps[SHELL_APP_COUNT] = {
|
||||
{"Statut", 0x00FFFF}, // cyan
|
||||
{"Scene", 0xFF8800}, // orange
|
||||
{"Auto", 0x00FF88}, // green
|
||||
{"Lumiere", 0xFFFF00}, // yellow
|
||||
};
|
||||
|
||||
// ─── Intro (zoom/fade evocation of the original 3D intro sequence) ───────────
|
||||
// The original ui_manager_intro.cpp (2475 lines, parallax planes) is NOT
|
||||
// ported; this is the boot-zoom essence on LVGL anims.
|
||||
static lv_obj_t *s_scr_intro;
|
||||
static lv_obj_t *s_intro_title;
|
||||
static volatile bool s_intro_done = false;
|
||||
|
||||
static lv_obj_t *make_row(lv_obj_t *parent, int y, const char *label_txt,
|
||||
dui_row_t *out) {
|
||||
lv_obj_t *lbl = lv_label_create(parent);
|
||||
@@ -360,6 +388,81 @@ static void build_scene_screen(void) {
|
||||
lv_obj_add_flag(s_gyro_arc, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
static void build_shell_screen(void) {
|
||||
s_scr_shell = lv_obj_create(NULL);
|
||||
lv_obj_set_style_bg_color(s_scr_shell, lv_color_hex(COL_BG), 0);
|
||||
lv_obj_set_style_bg_opa(s_scr_shell, LV_OPA_COVER, 0);
|
||||
|
||||
lv_obj_t *bar = lv_obj_create(s_scr_shell);
|
||||
lv_obj_remove_style_all(bar);
|
||||
lv_obj_set_size(bar, DUI_HOR_RES, 36);
|
||||
lv_obj_set_style_bg_color(bar, lv_color_hex(COL_TITLEBAR), 0);
|
||||
lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, 0);
|
||||
lv_obj_t *t = lv_label_create(bar);
|
||||
lv_obj_set_style_text_color(t, lv_color_hex(COL_TITLE_TXT), 0);
|
||||
lv_obj_set_style_text_font(t, &lv_font_montserrat_24, 0);
|
||||
lv_label_set_text(t, "Zacus Workbench");
|
||||
lv_obj_align(t, LV_ALIGN_LEFT_MID, 8, 0);
|
||||
|
||||
for (int i = 0; i < SHELL_APP_COUNT; i++) {
|
||||
const int col = i % SHELL_GRID_COLS;
|
||||
const int row = i / SHELL_GRID_COLS;
|
||||
lv_obj_t *tile = lv_obj_create(s_scr_shell);
|
||||
lv_obj_remove_style_all(tile);
|
||||
lv_obj_set_size(tile, SHELL_ICON_SIZE, SHELL_ICON_SIZE);
|
||||
lv_obj_set_pos(tile,
|
||||
SHELL_GRID_X + col * (SHELL_ICON_SIZE + SHELL_ICON_SPACING),
|
||||
SHELL_GRID_Y + row * (SHELL_ICON_SIZE + SHELL_ICON_SPACING));
|
||||
lv_obj_set_style_bg_color(tile, lv_color_hex(kShellApps[i].color), 0);
|
||||
lv_obj_set_style_bg_opa(tile, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_radius(tile, 4, 0); // like the original
|
||||
lv_obj_set_style_border_color(tile, lv_color_white(), 0);
|
||||
lv_obj_set_style_border_width(tile, 1, 0);
|
||||
|
||||
lv_obj_t *lbl = lv_label_create(tile);
|
||||
lv_obj_set_style_text_color(lbl, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0);
|
||||
lv_label_set_text(lbl, kShellApps[i].name);
|
||||
lv_obj_align(lbl, LV_ALIGN_BOTTOM_MID, 0, -4);
|
||||
|
||||
s_shell_tiles[i] = tile;
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight the selected tile (display task only).
|
||||
static void apply_shell_selection(void) {
|
||||
for (int i = 0; i < SHELL_APP_COUNT; i++) {
|
||||
lv_obj_set_style_border_width(s_shell_tiles[i],
|
||||
(i == s_shell_sel) ? 4 : 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void intro_zoom_cb(void *obj, int32_t v) {
|
||||
lv_obj_set_style_transform_zoom((lv_obj_t *) obj, (uint16_t) v, 0);
|
||||
lv_obj_set_style_opa((lv_obj_t *) obj, (lv_opa_t) ((v * 255) / 256), 0);
|
||||
}
|
||||
|
||||
static void build_intro_screen(void) {
|
||||
s_scr_intro = lv_obj_create(NULL);
|
||||
lv_obj_set_style_bg_color(s_scr_intro, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_bg_opa(s_scr_intro, LV_OPA_COVER, 0);
|
||||
|
||||
s_intro_title = lv_label_create(s_scr_intro);
|
||||
lv_obj_set_style_text_color(s_intro_title, lv_color_hex(COL_CODE), 0);
|
||||
lv_obj_set_style_text_font(s_intro_title, &lv_font_orbitron_40, 0);
|
||||
lv_label_set_text(s_intro_title, "ZACUS");
|
||||
lv_obj_center(s_intro_title);
|
||||
|
||||
lv_anim_t a;
|
||||
lv_anim_init(&a);
|
||||
lv_anim_set_var(&a, s_intro_title);
|
||||
lv_anim_set_exec_cb(&a, intro_zoom_cb);
|
||||
lv_anim_set_values(&a, 32, 256); // 12.5% → 100% zoom-in
|
||||
lv_anim_set_time(&a, 1400);
|
||||
lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
|
||||
lv_anim_start(&a);
|
||||
}
|
||||
|
||||
static void build_status_screen(void) {
|
||||
lv_obj_t *scr = s_scr_status;
|
||||
lv_obj_set_style_bg_color(scr, lv_color_hex(COL_BG), 0);
|
||||
@@ -454,7 +557,11 @@ static void apply_status(const display_status_t *s) {
|
||||
// original ui_manager default transition (SceneTransition::kFade, 240).
|
||||
extern volatile uint8_t g_dui_view_override;
|
||||
lv_obj_t *want;
|
||||
switch (g_dui_view_override) {
|
||||
if (!s_intro_done) {
|
||||
want = s_scr_intro; // boot intro plays out first
|
||||
} else if (s_shell_open) {
|
||||
want = s_scr_shell; // Workbench shell on top
|
||||
} else switch (g_dui_view_override) {
|
||||
case 1: want = s_scr_status; break;
|
||||
case 2: want = s_scr_scene; break;
|
||||
default: want = s->step_id[0] ? s_scr_scene : s_scr_status; break;
|
||||
@@ -556,13 +663,36 @@ static void display_task(void *arg) {
|
||||
s_scr_status = lv_scr_act();
|
||||
build_status_screen();
|
||||
build_scene_screen();
|
||||
build_shell_screen();
|
||||
build_intro_screen();
|
||||
|
||||
// Boot intro: shown immediately, hands over after ~1.8 s.
|
||||
lv_scr_load(s_scr_intro);
|
||||
TickType_t intro_until = xTaskGetTickCount() + pdMS_TO_TICKS(1800);
|
||||
|
||||
display_status_t local_status;
|
||||
memset(&local_status, 0, sizeof(local_status));
|
||||
|
||||
TickType_t last_cam = 0;
|
||||
uint8_t shell_sel_applied = 0xFF;
|
||||
bool shell_open_applied = false;
|
||||
|
||||
for (;;) {
|
||||
// Intro handover.
|
||||
if (!s_intro_done && xTaskGetTickCount() >= intro_until) {
|
||||
s_intro_done = true;
|
||||
s_dirty = true; // re-evaluate the wanted screen
|
||||
}
|
||||
|
||||
// Shell state (selection highlight / open-close) from the buttons task.
|
||||
if (s_shell_sel != shell_sel_applied ||
|
||||
s_shell_open != shell_open_applied) {
|
||||
shell_sel_applied = s_shell_sel;
|
||||
shell_open_applied = s_shell_open;
|
||||
apply_shell_selection();
|
||||
s_dirty = true;
|
||||
}
|
||||
|
||||
if (s_dirty &&
|
||||
xSemaphoreTake(s_mutex, pdMS_TO_TICKS(20)) == pdTRUE) {
|
||||
memcpy(&local_status, &s_status, sizeof(local_status));
|
||||
@@ -694,6 +824,50 @@ extern "C" void display_ui_set_status(const display_status_t *s) {
|
||||
|
||||
extern "C" void display_ui_handle_key(uint8_t key) {
|
||||
static uint8_t s_brightness = 100;
|
||||
|
||||
// ── Shell open: navigate the Workbench grid (original key semantics:
|
||||
// 1=SELECT launch, 2=DOWN row, 3=MENU close, 4=LEFT/RIGHT, 5=UP row).
|
||||
if (s_shell_open) {
|
||||
switch (key) {
|
||||
case 1: { // SELECT — launch tile
|
||||
const uint8_t sel = s_shell_sel;
|
||||
s_shell_open = false;
|
||||
switch (sel) {
|
||||
case 0: g_dui_view_override = 1; break; // Statut
|
||||
case 1: g_dui_view_override = 2; break; // Scene
|
||||
case 2: g_dui_view_override = 0; break; // Auto
|
||||
case 3: // Lumiere — cycle
|
||||
s_brightness = (s_brightness <= 20) ? 100
|
||||
: (uint8_t)(s_brightness - 40);
|
||||
display_ui_set_brightness(s_brightness);
|
||||
s_shell_open = true; // stay in the shell for feedback
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: // DOWN — next row
|
||||
if (s_shell_sel + SHELL_GRID_COLS < SHELL_APP_COUNT)
|
||||
s_shell_sel = (uint8_t)(s_shell_sel + SHELL_GRID_COLS);
|
||||
break;
|
||||
case 5: // UP — previous row
|
||||
if (s_shell_sel >= SHELL_GRID_COLS)
|
||||
s_shell_sel = (uint8_t)(s_shell_sel - SHELL_GRID_COLS);
|
||||
break;
|
||||
case 4: // LEFT/RIGHT — next column (wraps)
|
||||
s_shell_sel = (uint8_t)((s_shell_sel + 1) % SHELL_APP_COUNT);
|
||||
break;
|
||||
case 3: // MENU — close shell
|
||||
s_shell_open = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
s_dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Shell closed: view shortcuts + brightness.
|
||||
switch (key) {
|
||||
case 1: // SELECT — toggle scene↔status manually
|
||||
case 4: { // LEFT/RIGHT — same toggle
|
||||
@@ -702,8 +876,8 @@ extern "C" void display_ui_handle_key(uint8_t key) {
|
||||
s_dirty = true; // wake the display task decision
|
||||
break;
|
||||
}
|
||||
case 3: // MENU — back to automatic view selection
|
||||
g_dui_view_override = 0;
|
||||
case 3: // MENU — open the Workbench shell
|
||||
s_shell_open = true;
|
||||
s_dirty = true;
|
||||
break;
|
||||
case 5: // UP — brightness +
|
||||
|
||||
@@ -68,8 +68,10 @@ void display_ui_camera_frame(const uint8_t *gray, int width, int height);
|
||||
* @brief Handle a 5-way key press (called from the buttons scan task).
|
||||
*
|
||||
* Original key numbering: 1=SELECT 2=DOWN 3=MENU 4=LEFT/RIGHT 5=UP.
|
||||
* SELECT / LEFT-RIGHT toggle the scene↔status view manually; MENU returns
|
||||
* to automatic view selection; UP/DOWN step the backlight brightness.
|
||||
* Shell closed: SELECT / LEFT-RIGHT toggle scene↔status; MENU opens the
|
||||
* Workbench shell; UP/DOWN step the backlight brightness.
|
||||
* Shell open: grid navigation per the original ui_amiga_shell semantics
|
||||
* (SELECT launches the tile — Statut / Scene / Auto / Lumiere; MENU closes).
|
||||
* Thread-safe (atomic request flags consumed by the display task; the
|
||||
* brightness path drives LEDC directly, which has its own locking).
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user