feat(games): tile carousel like home menu

Replace vertical button list with a horizontal snap-scroll
carousel: one 176x122 tile per slide, 64px CONTAIN icon,
lisael_font_24 label, left/right arrows, dot page indicator.
Mirrors home.c pattern exactly (own s_gtrack/s_gdots statics).
All game callbacks and sub-screens unchanged.
This commit is contained in:
L'électron rare
2026-06-21 10:23:50 +02:00
parent a4d00827c7
commit 24d9e25381
+157 -27
View File
@@ -314,6 +314,103 @@ static void balloons_cb(lv_event_t *e)
250, 0, false);
}
// ---------------------------------------------------------------------------
// Games menu: horizontal tile carousel (mirrors home.c pattern).
// ---------------------------------------------------------------------------
#define GAMES_SLIDE_W 320
#define GAMES_N_TILES 4
typedef struct {
const lv_image_dsc_t *icon;
const char *label;
lv_color_t color;
lv_event_cb_t cb;
} game_tile_def_t;
static lv_obj_t *s_gtrack;
static lv_obj_t *s_gdots[GAMES_N_TILES];
static int s_gcur;
static void gdots_update(int idx)
{
for (int i = 0; i < GAMES_N_TILES; i++) {
if (s_gdots[i]) {
lv_obj_set_style_bg_opa(s_gdots[i],
i == idx ? LV_OPA_COVER : LV_OPA_30, 0);
}
}
}
static void games_scroll_cb(lv_event_t *e)
{
lv_obj_t *t = lv_event_get_target(e);
int idx = (lv_obj_get_scroll_x(t) + GAMES_SLIDE_W / 2) / GAMES_SLIDE_W;
if (idx < 0) idx = 0;
if (idx >= GAMES_N_TILES) idx = GAMES_N_TILES - 1;
s_gcur = idx;
gdots_update(idx);
}
static void games_arrow_cb(lv_event_t *e)
{
int idx = s_gcur + (int)(intptr_t)lv_event_get_user_data(e);
if (idx < 0) idx = 0;
if (idx >= GAMES_N_TILES) idx = GAMES_N_TILES - 1;
s_gcur = idx;
lv_obj_scroll_to_x(s_gtrack, idx * GAMES_SLIDE_W, LV_ANIM_ON);
gdots_update(idx);
}
static void games_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, games_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_game_slide(lv_obj_t *track, const game_tile_def_t *def)
{
lv_obj_t *slide = lv_obj_create(track);
lv_obj_remove_style_all(slide);
lv_obj_set_size(slide, GAMES_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);
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);
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, 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_set_style_pad_row(tile, 6, 0);
lv_obj_clear_flag(tile, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_event_cb(tile, def->cb, LV_EVENT_CLICKED, NULL);
lv_obj_t *icon = lv_image_create(tile);
lv_image_set_src(icon, def->icon);
lv_obj_set_size(icon, 64, 64);
lv_image_set_inner_align(icon, LV_IMAGE_ALIGN_CONTAIN);
lv_obj_t *txt = lv_label_create(tile);
lv_label_set_text(txt, def->label);
lv_obj_set_style_text_font(txt, &lisael_font_24, 0);
}
lv_obj_t *lisael_screen_games(void)
{
if (s_menu_root) {
@@ -321,6 +418,8 @@ lv_obj_t *lisael_screen_games(void)
}
s_menu_root = lv_obj_create(NULL);
lv_obj_set_style_bg_color(s_menu_root, lv_color_hex(0xF3F0FF), 0);
lv_obj_set_style_bg_grad_color(s_menu_root, lv_color_hex(0xFAF5FF), 0);
lv_obj_set_style_bg_grad_dir(s_menu_root, LV_GRAD_DIR_VER, 0);
lisael_ui_add_back_button(s_menu_root);
@@ -329,34 +428,65 @@ lv_obj_t *lisael_screen_games(void)
lv_obj_set_style_text_font(title, &lisael_font_24, 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 16);
lv_obj_t *list = lv_obj_create(s_menu_root);
lv_obj_remove_style_all(list);
lv_obj_set_size(list, LV_PCT(100), 150);
lv_obj_align(list, LV_ALIGN_CENTER, 0, 20);
lv_obj_set_flex_flow(list, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(list, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_row(list, 10, 0);
struct { const char *txt; const lv_image_dsc_t *icon; lv_event_cb_t cb; } games[] = {
{ "Memory", &lisael_ic_cat, memory_cb },
{ "Compter", &lisael_ic_apple, count_cb },
{ "Calme ta colère", &lisael_ic_balloon, balloons_cb },
{ "Coloriage", &lisael_ic_palette, color_cb },
// Tile definitions (icon, label, background color, click callback).
static const game_tile_def_t tiles[GAMES_N_TILES] = {
{ &lisael_ic_cat, "Memory", {0}, memory_cb },
{ &lisael_ic_apple, "Compter", {0}, count_cb },
{ &lisael_ic_balloon, "Calme ta col\xc3\xa8re", {0}, balloons_cb },
{ &lisael_ic_palette, "Coloriage", {0}, color_cb },
};
for (size_t i = 0; i < sizeof(games) / sizeof(games[0]); i++) {
lv_obj_t *b = lv_button_create(list);
lv_obj_add_style(b, lisael_ui_style_button(), 0);
lv_obj_set_width(b, LV_PCT(82));
lv_obj_set_flex_flow(b, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(b, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_column(b, 12, 0);
lv_obj_add_event_cb(b, games[i].cb, LV_EVENT_CLICKED, NULL);
lv_obj_t *ic = lv_image_create(b);
lv_image_set_src(ic, games[i].icon);
lv_obj_t *l = lv_label_create(b);
lv_label_set_text(l, games[i].txt);
static const uint32_t colors[GAMES_N_TILES] = {
0xFFC9C9, // pink — Memory
0xB2F2BB, // green — Compter
0xA5D8FF, // blue — Calme ta colère
0xFFD8A8, // orange — Coloriage
};
// Scrollable track (horizontal, snap-center, no scrollbar).
s_gtrack = lv_obj_create(s_menu_root);
lv_obj_remove_style_all(s_gtrack);
lv_obj_set_size(s_gtrack, GAMES_SLIDE_W, 134);
lv_obj_align(s_gtrack, LV_ALIGN_TOP_MID, 0, 56);
lv_obj_set_flex_flow(s_gtrack, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(s_gtrack, 0, 0);
lv_obj_set_style_pad_column(s_gtrack, 0, 0);
lv_obj_set_scroll_dir(s_gtrack, LV_DIR_HOR);
lv_obj_set_scroll_snap_x(s_gtrack, LV_SCROLL_SNAP_CENTER);
lv_obj_set_scrollbar_mode(s_gtrack, LV_SCROLLBAR_MODE_OFF);
lv_obj_add_event_cb(s_gtrack, games_scroll_cb, LV_EVENT_SCROLL_END, NULL);
for (int i = 0; i < GAMES_N_TILES; i++) {
game_tile_def_t t = tiles[i];
t.color = lv_color_hex(colors[i]);
add_game_slide(s_gtrack, &t);
}
// Dot row (page indicator).
lv_obj_t *dotrow = lv_obj_create(s_menu_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 < GAMES_N_TILES; 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(0x6741D9), 0);
lv_obj_set_style_bg_opa(d, i == 0 ? LV_OPA_COVER : LV_OPA_30, 0);
s_gdots[i] = d;
}
// Left/right arrow buttons.
games_arrow(s_menu_root, LV_SYMBOL_LEFT, LV_ALIGN_LEFT_MID, -1);
games_arrow(s_menu_root, LV_SYMBOL_RIGHT, LV_ALIGN_RIGHT_MID, +1);
s_gcur = 0;
gdots_update(0);
return s_menu_root;
}