feat(box): activity carousel for home menu

The 7 mode tiles overflowed the grid and scrolled. Replace the wrapping tile
grid with the same Lunii-style horizontal carousel as Histoires: one big
activity tile per full-width slide, snap-scroll swipe, tap arrows, a dot per
activity. Header (date/time/weather) unchanged. Drop the now-unused make_tile.
This commit is contained in:
Clément Saillant
2026-06-15 09:13:33 +02:00
parent 3f97e8c4bf
commit 7c8630abbe
+118 -30
View File
@@ -50,35 +50,93 @@ static void tile_cb(lv_event_t *e)
lisael_ui_goto(mode);
}
static lv_obj_t *make_tile(lv_obj_t *parent, const tile_def_t *def)
// --- activity carousel (Lunii/Merlin style launcher) -------------------------
// One big activity tile per full-width slide; swipe (or arrows) to move one at a
// time, tap the tile to launch. Dots show the position.
#define HOME_SLIDE_W 320
static lv_obj_t *s_track;
static lv_obj_t *s_adots[8];
static int s_acur;
static int s_n_acts;
static void adots_update(int idx)
{
lv_obj_t *tile = lv_button_create(parent);
for (int i = 0; i < s_n_acts; i++) {
if (s_adots[i]) {
lv_obj_set_style_bg_opa(s_adots[i], i == idx ? LV_OPA_COVER : LV_OPA_30, 0);
}
}
}
static void home_scroll_cb(lv_event_t *e)
{
lv_obj_t *t = lv_event_get_target(e);
int idx = (lv_obj_get_scroll_x(t) + HOME_SLIDE_W / 2) / HOME_SLIDE_W;
if (idx < 0) idx = 0;
if (idx >= s_n_acts) idx = s_n_acts - 1;
s_acur = idx;
adots_update(idx);
}
static void home_arrow_cb(lv_event_t *e)
{
int idx = s_acur + (int)(intptr_t)lv_event_get_user_data(e);
if (idx < 0) idx = 0;
if (idx >= s_n_acts) idx = s_n_acts - 1;
s_acur = idx;
lv_obj_scroll_to_x(s_track, idx * HOME_SLIDE_W, LV_ANIM_ON);
adots_update(idx);
}
static void home_arrow(lv_obj_t *parent, const char *sym, lv_align_t al, int dir)
{
lv_obj_t *b = lv_button_create(parent);
lv_obj_remove_style_all(b);
lv_obj_set_size(b, 38, 60);
lv_obj_set_style_radius(b, 19, 0);
lv_obj_set_style_bg_opa(b, LV_OPA_40, 0);
lv_obj_set_style_bg_color(b, lv_color_white(), 0);
lv_obj_align(b, al, (al == LV_ALIGN_LEFT_MID) ? 2 : -2, 28);
lv_obj_add_event_cb(b, home_arrow_cb, LV_EVENT_CLICKED, (void *)(intptr_t)dir);
lv_obj_t *l = lv_label_create(b);
lv_label_set_text(l, sym);
lv_obj_set_style_text_color(l, lv_color_hex(0x495057), 0);
lv_obj_center(l);
}
static void add_act_slide(lv_obj_t *track, const tile_def_t *def)
{
lv_obj_t *slide = lv_obj_create(track);
lv_obj_remove_style_all(slide);
lv_obj_set_size(slide, HOME_SLIDE_W, LV_PCT(100));
lv_obj_clear_flag(slide, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_flex_flow(slide, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(slide, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_t *tile = lv_button_create(slide);
lv_obj_add_style(tile, lisael_ui_style_tile(), 0);
// Glossy 3D look: lighter at the top fading to the tile colour (the shared
// style sets LV_GRAD_DIR_VER + the soft drop shadow).
lv_obj_set_style_bg_color(tile, lv_color_lighten(def->color, 90), 0);
lv_obj_set_style_bg_grad_color(tile, def->color, 0);
// Press feedback: the tile sinks a little.
lv_obj_set_style_translate_y(tile, 4, LV_STATE_PRESSED);
lv_obj_set_style_shadow_ofs_y(tile, 3, LV_STATE_PRESSED);
lv_obj_set_size(tile, 94, 72);
lv_obj_add_event_cb(tile, tile_cb, LV_EVENT_CLICKED,
(void *)(intptr_t)def->mode);
lv_obj_t *col = lv_obj_create(tile);
lv_obj_remove_style_all(col);
lv_obj_set_size(col, LV_PCT(100), LV_PCT(100));
lv_obj_set_flex_flow(col, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(col, LV_FLEX_ALIGN_CENTER,
lv_obj_set_size(tile, 176, 122);
lv_obj_set_flex_flow(tile, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(tile, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_clear_flag(col, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_style_pad_row(tile, 6, 0);
lv_obj_clear_flag(tile, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_event_cb(tile, tile_cb, LV_EVENT_CLICKED, (void *)(intptr_t)def->mode);
lv_obj_t *icon = lv_image_create(col);
lv_obj_t *icon = lv_image_create(tile);
lv_image_set_src(icon, def->icon);
lv_obj_set_size(icon, 64, 64); // scale-to-fit by size (reliable, unlike set_scale)
lv_image_set_inner_align(icon, LV_IMAGE_ALIGN_CONTAIN);
lv_obj_t *txt = lv_label_create(col);
lv_obj_t *txt = lv_label_create(tile);
lv_label_set_text(txt, def->label);
return tile;
lv_obj_set_style_text_font(txt, &lisael_font_24, 0);
}
// --- internal temperature sensor (graceful fallback) ------------------------
@@ -135,16 +193,7 @@ lv_obj_t *lisael_screen_home(void)
lv_label_set_text(s_sensor_lbl, "");
lv_obj_align(s_sensor_lbl, LV_ALIGN_TOP_RIGHT, -12, 58);
// --- mode tiles ----------------------------------------------------------
lv_obj_t *grid = lv_obj_create(s_root);
lv_obj_remove_style_all(grid);
lv_obj_set_size(grid, LV_PCT(100), 160);
lv_obj_align(grid, LV_ALIGN_BOTTOM_MID, 0, -2);
lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_flex_align(grid, LV_FLEX_ALIGN_SPACE_EVENLY,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY);
lv_obj_set_style_pad_row(grid, 8, 0);
// --- activities: horizontal carousel (one big tile per slide) -----------
static const tile_def_t tiles[] = {
{ &lisael_ic_histoires, "Histoires", LISAEL_MODE_HISTOIRES, {0} },
{ &lisael_ic_book, "Lire", LISAEL_MODE_LIRE, {0} },
@@ -157,12 +206,51 @@ lv_obj_t *lisael_screen_home(void)
static const uint32_t colors[] = {
0xFFE08A, 0xFFD8A8, 0xB2F2BB, 0xA5D8FF, 0xD0BFFF, 0xFFC9C9, 0xCED4DA,
};
for (size_t i = 0; i < sizeof(tiles) / sizeof(tiles[0]); i++) {
s_n_acts = (int)(sizeof(tiles) / sizeof(tiles[0]));
s_track = lv_obj_create(s_root);
lv_obj_remove_style_all(s_track);
lv_obj_set_size(s_track, HOME_SLIDE_W, 134);
lv_obj_align(s_track, LV_ALIGN_TOP_MID, 0, 80);
lv_obj_set_flex_flow(s_track, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(s_track, 0, 0);
lv_obj_set_style_pad_column(s_track, 0, 0);
lv_obj_set_scroll_dir(s_track, LV_DIR_HOR);
lv_obj_set_scroll_snap_x(s_track, LV_SCROLL_SNAP_CENTER);
lv_obj_set_scrollbar_mode(s_track, LV_SCROLLBAR_MODE_OFF);
lv_obj_add_event_cb(s_track, home_scroll_cb, LV_EVENT_SCROLL_END, NULL);
for (int i = 0; i < s_n_acts; i++) {
tile_def_t t = tiles[i];
t.color = lv_color_hex(colors[i]);
make_tile(grid, &t);
add_act_slide(s_track, &t);
}
lv_obj_t *dotrow = lv_obj_create(s_root);
lv_obj_remove_style_all(dotrow);
lv_obj_set_size(dotrow, LV_PCT(90), 16);
lv_obj_align(dotrow, LV_ALIGN_BOTTOM_MID, 0, -6);
lv_obj_set_flex_flow(dotrow, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(dotrow, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_clear_flag(dotrow, LV_OBJ_FLAG_SCROLLABLE);
for (int i = 0; i < s_n_acts; i++) {
lv_obj_t *d = lv_obj_create(dotrow);
lv_obj_remove_style_all(d);
lv_obj_set_size(d, 9, 9);
lv_obj_set_style_radius(d, 5, 0);
lv_obj_set_style_margin_left(d, 3, 0);
lv_obj_set_style_margin_right(d, 3, 0);
lv_obj_set_style_bg_color(d, lv_color_hex(0x1971C2), 0);
lv_obj_set_style_bg_opa(d, i == 0 ? LV_OPA_COVER : LV_OPA_30, 0);
s_adots[i] = d;
}
home_arrow(s_root, LV_SYMBOL_LEFT, LV_ALIGN_LEFT_MID, -1);
home_arrow(s_root, LV_SYMBOL_RIGHT, LV_ALIGN_RIGHT_MID, +1);
s_acur = 0;
adots_update(0);
// NB: the live values are filled in shortly by the tick/net tasks via
// lisael_home_update_*(). We do NOT call them here: this builder runs while
// the caller already holds the display lock, and those helpers take the