21bfd2f7de
Add LISAEL_MODE_ROUTINE to the UI shell: enum entry, build_screen() case, home carousel tile (8th, broom icon U+1F9F9, periwinkle pastel), minimal navigable stub in modes/routine.c, CMakeLists srcs update, Twemoji icon generated (17/17 ok). s_adots[8] now at capacity.
146 lines
4.9 KiB
C
146 lines
4.9 KiB
C
// LVGL UI shell + mode router for Lisael Box (LVGL 9 API).
|
|
//
|
|
// Screens are built lazily and cached so switching back is instant. Mode
|
|
// transitions use lv_screen_load_anim() for a gentle slide. All LVGL access
|
|
// from background tasks goes through bsp_display_lock()/unlock(); the functions
|
|
// here are called either from app_main during init (lock held by caller) or
|
|
// from LVGL event callbacks (already on the LVGL task).
|
|
|
|
#include "ui/ui.h"
|
|
#include "ui/icons.h"
|
|
#include "ui/fonts/lisael_fonts.h"
|
|
#include "lisael_config.h"
|
|
#include "net/wifi.h"
|
|
#include "net/wifi_portal.h"
|
|
|
|
#include "esp_log.h"
|
|
#include "bsp/esp-box-3.h"
|
|
|
|
static const char *TAG = "ui";
|
|
|
|
static lv_obj_t *s_screens[LISAEL_MODE_COUNT];
|
|
static lisael_mode_t s_current = LISAEL_MODE_HOME;
|
|
|
|
static lv_style_t s_style_tile;
|
|
static lv_style_t s_style_button;
|
|
static bool s_styles_ready;
|
|
|
|
static void init_styles(void)
|
|
{
|
|
if (s_styles_ready) {
|
|
return;
|
|
}
|
|
// Big rounded glossy tiles for the home menu — soft drop shadow for depth,
|
|
// a vertical gloss gradient (set per tile in make_tile), rounded corners.
|
|
lv_style_init(&s_style_tile);
|
|
lv_style_set_radius(&s_style_tile, 22);
|
|
lv_style_set_bg_opa(&s_style_tile, LV_OPA_COVER);
|
|
lv_style_set_bg_grad_dir(&s_style_tile, LV_GRAD_DIR_VER); // colours per tile
|
|
lv_style_set_border_width(&s_style_tile, 0);
|
|
lv_style_set_pad_all(&s_style_tile, 6);
|
|
lv_style_set_shadow_width(&s_style_tile, 18);
|
|
lv_style_set_shadow_ofs_y(&s_style_tile, 8);
|
|
lv_style_set_shadow_spread(&s_style_tile, 1);
|
|
lv_style_set_shadow_color(&s_style_tile, lv_color_hex(0x5b6b8a));
|
|
lv_style_set_shadow_opa(&s_style_tile, LV_OPA_40);
|
|
|
|
// Generic large touch button.
|
|
lv_style_init(&s_style_button);
|
|
lv_style_set_radius(&s_style_button, 14);
|
|
lv_style_set_pad_all(&s_style_button, 10);
|
|
lv_style_set_min_height(&s_style_button, 56); // comfortable for small hands
|
|
|
|
s_styles_ready = true;
|
|
}
|
|
|
|
const lv_style_t *lisael_ui_style_tile(void)
|
|
{
|
|
init_styles();
|
|
return &s_style_tile;
|
|
}
|
|
|
|
const lv_style_t *lisael_ui_style_button(void)
|
|
{
|
|
init_styles();
|
|
return &s_style_button;
|
|
}
|
|
|
|
static lv_obj_t *build_screen(lisael_mode_t mode)
|
|
{
|
|
switch (mode) {
|
|
case LISAEL_MODE_HOME: return lisael_screen_home();
|
|
case LISAEL_MODE_RADIO: return lisael_screen_radio();
|
|
case LISAEL_MODE_CALM: return lisael_screen_calm();
|
|
case LISAEL_MODE_CLOCK: return lisael_screen_clock();
|
|
case LISAEL_MODE_GAMES: return lisael_screen_games();
|
|
case LISAEL_MODE_HISTOIRES:return lisael_screen_histoires();
|
|
case LISAEL_MODE_LIRE: return lisael_screen_lire();
|
|
case LISAEL_MODE_WIFI: return lisael_screen_wifi();
|
|
case LISAEL_MODE_ROUTINE: return lisael_screen_routine();
|
|
default: return lisael_screen_home();
|
|
}
|
|
}
|
|
|
|
void lisael_ui_goto(lisael_mode_t mode)
|
|
{
|
|
if ((unsigned)mode >= (unsigned)LISAEL_MODE_COUNT) {
|
|
return; // borne nette pour GCC 14 (-Werror=array-bounds)
|
|
}
|
|
if (mode == LISAEL_MODE_WIFI) {
|
|
// On-demand provisioning: bring the SoftAP + captive portal up so the
|
|
// Wi-Fi can be (re)configured from a phone. Torn down by the screen's
|
|
// "Fermer" button. Both calls are idempotent.
|
|
lisael_wifi_start_ap();
|
|
lisael_wifi_portal_start();
|
|
}
|
|
if (!s_screens[mode]) {
|
|
s_screens[mode] = build_screen(mode);
|
|
}
|
|
// Slide direction: going "deeper" (home->mode) slides left, back slides right.
|
|
lv_screen_load_anim_t anim = (mode == LISAEL_MODE_HOME)
|
|
? LV_SCR_LOAD_ANIM_MOVE_RIGHT
|
|
: LV_SCR_LOAD_ANIM_MOVE_LEFT;
|
|
lv_screen_load_anim(s_screens[mode], anim, 250, 0, false);
|
|
s_current = mode;
|
|
ESP_LOGI(TAG, "mode -> %d", mode);
|
|
}
|
|
|
|
static void back_cb(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
lisael_ui_goto(LISAEL_MODE_HOME);
|
|
}
|
|
|
|
lv_obj_t *lisael_ui_add_back_button(lv_obj_t *parent)
|
|
{
|
|
lv_obj_t *btn = lv_button_create(parent);
|
|
lv_obj_add_style(btn, lisael_ui_style_button(), 0);
|
|
lv_obj_set_size(btn, 64, 56);
|
|
lv_obj_align(btn, LV_ALIGN_TOP_LEFT, 6, 6);
|
|
lv_obj_add_event_cb(btn, back_cb, LV_EVENT_CLICKED, NULL);
|
|
|
|
lv_obj_t *lbl = lv_label_create(btn);
|
|
lv_label_set_text(lbl, LV_SYMBOL_LEFT); // monochrome arrow — always renders
|
|
lv_obj_center(lbl);
|
|
return btn;
|
|
}
|
|
|
|
void lisael_ui_init(void)
|
|
{
|
|
// Default font -> Fredoka (rounded, kid-friendly, with French accents).
|
|
// The default theme applies it to every label unless overridden.
|
|
lv_display_t *disp = lv_display_get_default();
|
|
lv_theme_t *th = lv_theme_default_init(
|
|
disp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_PINK),
|
|
false, &lisael_font_18);
|
|
if (th) {
|
|
lv_display_set_theme(disp, th);
|
|
}
|
|
|
|
init_styles();
|
|
s_screens[LISAEL_MODE_HOME] = lisael_screen_home();
|
|
lv_screen_load(s_screens[LISAEL_MODE_HOME]);
|
|
s_current = LISAEL_MODE_HOME;
|
|
ESP_LOGI(TAG, "UI initialised");
|
|
}
|