changeset 506:e205625015ba

ui: gridmenu is stylable
author David Demelier <markand@malikania.fr>
date Thu, 02 Mar 2023 08:54:00 +0100
parents 6100c643dba0
children d49a05e7a5b5
files examples/example-gridmenu/example-gridmenu.c examples/example-ui/button-style-glow.c examples/example-ui/button-style-glow.h examples/example-ui/example-ui.c libmlk-example/CMakeLists.txt libmlk-example/mlk/example/example.h libmlk-example/mlk/example/glower.c libmlk-example/mlk/example/glower.h libmlk-ui/mlk/ui/gridmenu.c libmlk-ui/mlk/ui/gridmenu.h libmlk-ui/mlk/ui/label.c libmlk-ui/mlk/ui/label.h
diffstat 12 files changed, 312 insertions(+), 194 deletions(-) [+]
line wrap: on
line diff
--- a/examples/example-gridmenu/example-gridmenu.c	Wed Mar 01 16:24:07 2023 +0100
+++ b/examples/example-gridmenu/example-gridmenu.c	Thu Mar 02 08:54:00 2023 +0100
@@ -29,15 +29,58 @@
 #include <mlk/core/window.h>
 
 #include <mlk/ui/align.h>
+#include <mlk/ui/frame.h>
 #include <mlk/ui/gridmenu.h>
 #include <mlk/ui/label.h>
-#include <mlk/ui/theme.h>
 #include <mlk/ui/ui.h>
 
 #include <mlk/example/example.h>
+#include <mlk/example/glower.h>
 #include <mlk/example/registry.h>
 
-static struct mlk_state *states[1];
+static struct mlk_state *states[8];
+
+static const char * const items[] = {
+	"Feu mineur",
+	"Feu majeur",
+	"Feu septième",
+	"Glace mineure",
+	"Glace majeure",
+	"Glace septième",
+	"Foudre mineure",
+	"Foudre majeure",
+	"Foudre septième",
+	"Choc mineur",
+	"Choc majeur",
+	"Choc septième",
+	"Portée",
+	"Escapade",
+	"Destruction",
+	"Résurrection",
+	"Double tour"
+};
+
+static struct mlk_frame frame = {
+	.w = 300,
+	.h = 100
+};
+static struct mlk_gridmenu_style menu_style = {
+	.padding = 10,
+	.text_color = 0x222323ff
+};
+static struct mlk_gridmenu menu = {
+	.nrows = 3,
+	.ncols = 2,
+	.items = items,
+	.itemsz = MLK_UTIL_SIZE(items),
+	.style = &menu_style
+};
+static struct mlk_glower menu_glower = {
+	.color = &menu_style.text_selected_color,
+	.start = 0x00bfa3ff,
+	.end = 0x006b6dff,
+	.delay = 20
+};
 
 static void
 init(void)
@@ -46,67 +89,62 @@
 
 	if ((err = mlk_example_init("example-gridmenu")) < 0)
 		mlk_panicf("mlk_example_init: %s", mlk_err_string(err));
+
+	mlk_glower_init(&menu_glower);
 }
 
 static void
 handle(struct mlk_state *st, const union mlk_event *ev)
 {
-	struct mlk_gridmenu *menu = st->data;
+	(void)st;
 
 	switch (ev->type) {
 	case MLK_EVENT_QUIT:
 		mlk_game_quit();
 		break;
 	default:
-		if (mlk_gridmenu_handle(st->data, ev))
-			mlk_tracef("selected index: %zu (%s)", menu->selected, menu->items[menu->selected]);
+		if (mlk_gridmenu_handle(&menu, ev))
+			mlk_tracef("selected index: %zu (%s)", menu.selected, menu.items[menu.selected]);
 		break;
 	}
 }
 
 static void
+update(struct mlk_state *st, unsigned int ticks)
+{
+	(void)st;
+
+	mlk_glower_update(&menu_glower, ticks);
+	mlk_gridmenu_update(&menu, ticks);
+}
+
+static void
 draw(struct mlk_state *st)
 {
-	mlk_painter_set_color(0x4f8fbaff);
+	(void)st;
+
+	mlk_painter_set_color(MLK_EXAMPLE_BG);
 	mlk_painter_clear();
-	mlk_gridmenu_draw(st->data);
+	mlk_frame_draw(&frame);
+	mlk_gridmenu_draw(&menu);
 	mlk_painter_present();
 }
 
 static void
 run(void)
 {
-	const char * const items[] = {
-	    "Feu mineur",
-	    "Feu majeur",
-	    "Feu septième",
-	    "Glace mineure",
-	    "Glace majeure",
-	    "Glace septième",
-	    "Foudre mineure",
-	    "Foudre majeure",
-	    "Foudre septième",
-	    "Choc mineur",
-	    "Choc majeur",
-	    "Choc septième",
-	    "Portée",
-	    "Escapade",
-	    "Destruction",
-	    "Résurrection",
-	    "Double tour"
-	};
-
-	struct mlk_gridmenu menu = {0};
 	struct mlk_state state = {
-		.data = &menu,
 		.handle = handle,
+		.update = update,
 		.draw = draw,
 	};
 
-	mlk_gridmenu_init(&menu, 3, 2, items, MLK_UTIL_SIZE(items));
+	mlk_gridmenu_init(&menu);
 	mlk_gridmenu_resize(&menu, 0, 0, 300, 100);
 
 	mlk_align(MLK_ALIGN_CENTER, &menu.x, &menu.y, menu.w, menu.h, 0, 0, mlk_window.w, mlk_window.h);
+	frame.x = menu.x;
+	frame.y = menu.y;
 
 	mlk_game_init(states, MLK_UTIL_SIZE(states));
 	mlk_game_push(&state);
--- a/examples/example-ui/button-style-glow.c	Wed Mar 01 16:24:07 2023 +0100
+++ b/examples/example-ui/button-style-glow.c	Thu Mar 02 08:54:00 2023 +0100
@@ -20,6 +20,8 @@
 
 #include <mlk/core/color.h>
 
+#include <mlk/example/glower.h>
+
 #include "button-style-glow.h"
 
 /*
@@ -35,32 +37,16 @@
  * bg_color property that will be reused.
  */
 
-static inline unsigned int
-increment(unsigned long ccmp, unsigned long ctgt)
-{
-	if (ctgt > ccmp)
-		return ccmp + 2 > ctgt ? ctgt : ccmp + 2;
-	if (ctgt < ccmp)
-		return ccmp - 2 < ctgt ? ctgt : ccmp - 2;
-
-	return ccmp;
-}
-
 static void
 init(struct mlk_button_style *style, struct mlk_button *button)
 {
 	(void)button;
 
-	struct button_style_glow *glow = style->data;
+	struct button_style_glow *styler = style->data;
 
-	/*
-	 * We start at colors[0] and reach colors[1], colors[2] will be the
-	 * destination color that will swap over time.
-	 */
-	glow->colors[2] = glow->colors[1];
-	glow->elapsed = 0;
+	styler->glow->color = &style->bg_color;
 
-	style->bg_color = glow->colors[0];
+	mlk_glower_init(styler->glow);
 }
 
 static void
@@ -68,45 +54,18 @@
 {
 	(void)button;
 
-	struct button_style_glow *glow = style->data;
-	unsigned int red, green, blue;
-
-	glow->elapsed += ticks;
-
-	if (glow->elapsed >= glow->delay) {
-		glow->elapsed = 0;
+	struct button_style_glow *styler = style->data;
 
-		/* Color target reached, invert logic. */
-		if (style->bg_color == glow->colors[2]) {
-			if (glow->colors[2] == glow->colors[0])
-				glow->colors[2] = glow->colors[1];
-			else
-				glow->colors[2] = glow->colors[0];
-		} else {
-			red = increment(
-				MLK_COLOR_R(style->bg_color),
-				MLK_COLOR_R(glow->colors[2])
-			);
-			green = increment(
-				MLK_COLOR_G(style->bg_color),
-				MLK_COLOR_G(glow->colors[2])
-			);
-			blue = increment(
-				MLK_COLOR_B(style->bg_color),
-				MLK_COLOR_B(glow->colors[2])
-			);
-
-			style->bg_color = MLK_COLOR_HEX(red, green, blue, 0xff);
-		}
-	}
+	mlk_glower_update(styler->glow, ticks);
 }
 
 void
-button_style_glow_init(struct button_style_glow *glow)
+button_style_glow_init(struct button_style_glow *styler)
 {
-	assert(glow);
+	assert(styler);
+	assert(styler->glow);
 
-	glow->style.data = glow;
-	glow->style.init = init;
-	glow->style.update = update;
+	styler->style.data = styler;
+	styler->style.init = init;
+	styler->style.update = update;
 }
--- a/examples/example-ui/button-style-glow.h	Wed Mar 01 16:24:07 2023 +0100
+++ b/examples/example-ui/button-style-glow.h	Thu Mar 02 08:54:00 2023 +0100
@@ -25,10 +25,10 @@
 #define BUTTON_STYLE_GLOW_COLOR_2       0xa6cc34ff
 #define BUTTON_STYLE_GLOW_DELAY         20
 
+struct mlk_glower;
+
 struct button_style_glow {
-	unsigned long colors[3];
-	unsigned int delay;
-	unsigned int elapsed;
+	struct mlk_glower *glow;
 	struct mlk_button_style style;
 };
 
--- a/examples/example-ui/example-ui.c	Wed Mar 01 16:24:07 2023 +0100
+++ b/examples/example-ui/example-ui.c	Thu Mar 02 08:54:00 2023 +0100
@@ -38,6 +38,7 @@
 #include <mlk/ui/ui.h>
 
 #include <mlk/example/example.h>
+#include <mlk/example/glower.h>
 #include <mlk/example/registry.h>
 
 #include "button-style-glow.h"
@@ -98,7 +99,8 @@
 		/*
 		 * [Download free RAM] with custom style drawing.
 		 */
-		struct button_style_glow download_glow;
+		struct mlk_glower download_glow;
+		struct button_style_glow download_style;
 		struct mlk_button download;
 	} buttons;
 } ui = {
@@ -145,20 +147,21 @@
 			.text_style = &ui.buttons.quit_text_style
 		},
 		.download_glow = {
-			.colors = {
-				BUTTON_STYLE_GLOW_COLOR_1,
-				BUTTON_STYLE_GLOW_COLOR_2
-			},
+			.start = BUTTON_STYLE_GLOW_COLOR_1,
+			.end = BUTTON_STYLE_GLOW_COLOR_2,
+			.delay = BUTTON_STYLE_GLOW_DELAY
+		},
+		.download_style = {
+			.glow = &ui.buttons.download_glow,
 			.style = {
 				.border_color = BUTTON_STYLE_GLOW_COLOR_1
-			},
-			.delay = BUTTON_STYLE_GLOW_DELAY
+			}
 		},
 		.download = {
 			.w = 180,
 			.h = 32,
 			.text = "!! Download free RAM !!",
-			.style = &ui.buttons.download_glow.style,
+			.style = &ui.buttons.download_style.style,
 			.text_style = &ui.buttons.quit_text_style
 		}
 	}
@@ -309,7 +312,7 @@
 {
 	(void)st;
 
-	mlk_painter_set_color(0x88d6ffff);
+	mlk_painter_set_color(MLK_EXAMPLE_BG);
 	mlk_painter_clear();
 	mlk_frame_draw(&ui.panel.frame);
 	mlk_label_draw(&ui.header.label);
@@ -330,7 +333,7 @@
 	if ((err = mlk_example_init("example-ui")) < 0)
 		mlk_panicf("mlk_example_init: %s", mlk_err_string(err));
 
-	button_style_glow_init(&ui.buttons.download_glow);
+	button_style_glow_init(&ui.buttons.download_style);
 
 	mlk_button_init(&ui.buttons.hello);
 	mlk_button_init(&ui.buttons.quit);
--- a/libmlk-example/CMakeLists.txt	Wed Mar 01 16:24:07 2023 +0100
+++ b/libmlk-example/CMakeLists.txt	Thu Mar 02 08:54:00 2023 +0100
@@ -24,6 +24,8 @@
 	${libmlk-example_SOURCE_DIR}/mlk/example/character-john.h
 	${libmlk-example_SOURCE_DIR}/mlk/example/example.c
 	${libmlk-example_SOURCE_DIR}/mlk/example/example.h
+	${libmlk-example_SOURCE_DIR}/mlk/example/glower.c
+	${libmlk-example_SOURCE_DIR}/mlk/example/glower.h
 	${libmlk-example_SOURCE_DIR}/mlk/example/registry.c
 	${libmlk-example_SOURCE_DIR}/mlk/example/registry.h
 	${libmlk-example_SOURCE_DIR}/mlk/example/spell-fire.c
--- a/libmlk-example/mlk/example/example.h	Wed Mar 01 16:24:07 2023 +0100
+++ b/libmlk-example/mlk/example/example.h	Thu Mar 02 08:54:00 2023 +0100
@@ -19,8 +19,9 @@
 #ifndef MLK_EXAMPLE_EXAMPLE_H
 #define MLK_EXAMPLE_EXAMPLE_H
 
-#define MLK_EXAMPLE_W 960
-#define MLK_EXAMPLE_H 540
+#define MLK_EXAMPLE_W   960
+#define MLK_EXAMPLE_H   540
+#define MLK_EXAMPLE_BG	0xc6ecffff
 
 int
 mlk_example_init(const char *);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-example/mlk/example/glower.c	Thu Mar 02 08:54:00 2023 +0100
@@ -0,0 +1,69 @@
+/*
+ * glower.c -- simple color animation
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include <mlk/core/color.h>
+
+#include "glower.h"
+
+static inline unsigned int
+increment(unsigned long ccmp, unsigned long ctgt)
+{
+	if (ctgt > ccmp)
+		return (unsigned int)(ccmp + 2 > ctgt ? ctgt : ccmp + 2);
+	if (ctgt < ccmp)
+		return (unsigned int)(ccmp - 2 < ctgt ? ctgt : ccmp - 2);
+
+	return (unsigned int)ccmp;
+}
+
+void
+mlk_glower_init(struct mlk_glower *glow)
+{
+	assert(glow);
+	assert(glow->color);
+
+	*glow->color = glow->start;
+	glow->target = glow->end;
+}
+
+void
+mlk_glower_update(struct mlk_glower *glow, unsigned int ticks)
+{
+	assert(glow);
+
+	unsigned int r, g, b;
+
+	glow->elapsed += ticks;
+
+	if (glow->elapsed >= glow->delay) {
+		glow->elapsed = 0;
+
+		/* Color target reached, invert target color. */
+		if (*glow->color == glow->target)
+			glow->target = glow->target == glow->start ? glow->end : glow->start;
+		else {
+			r = increment(MLK_COLOR_R(*glow->color), MLK_COLOR_R(glow->target));
+			g = increment(MLK_COLOR_G(*glow->color), MLK_COLOR_G(glow->target));
+			b = increment(MLK_COLOR_B(*glow->color), MLK_COLOR_B(glow->target));
+
+			*glow->color = MLK_COLOR_HEX(r, g, b, 0xff);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-example/mlk/example/glower.h	Thu Mar 02 08:54:00 2023 +0100
@@ -0,0 +1,40 @@
+/*
+ * glower.h -- simple color animation
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MLK_EXAMPLE_GLOWER_H
+#define MLK_EXAMPLE_GLOWER_H
+
+struct mlk_glower {
+	/* public */
+	unsigned long start;
+	unsigned long end;
+	unsigned long *color;
+	unsigned int delay;
+
+	/* private */
+	unsigned long target;
+	unsigned int elapsed;
+};
+
+void
+mlk_glower_init(struct mlk_glower *);
+
+void
+mlk_glower_update(struct mlk_glower *, unsigned int);
+
+#endif /* !MLK_EXAMPLE_GLOWER_H */
--- a/libmlk-ui/mlk/ui/gridmenu.c	Wed Mar 01 16:24:07 2023 +0100
+++ b/libmlk-ui/mlk/ui/gridmenu.c	Thu Mar 02 08:54:00 2023 +0100
@@ -21,15 +21,15 @@
 #include <string.h>
 
 #include <mlk/core/event.h>
+#include <mlk/core/font.h>
 #include <mlk/core/maths.h>
 #include <mlk/core/painter.h>
 #include <mlk/core/panic.h>
 #include <mlk/core/texture.h>
 #include <mlk/core/trace.h>
 
-#include "frame.h"
-#include "label.h"
 #include "gridmenu.h"
+#include "ui.h"
 
 #define STYLE_INVOKE(s, f, ...)                                                 \
 do {                                                                            \
@@ -46,21 +46,27 @@
 	unsigned int col;
 };
 
+static inline struct mlk_font *
+style_font(struct mlk_gridmenu_style *style)
+{
+	if (style && style->text_font)
+		return style->text_font;
+
+	return mlk_ui_fonts[MLK_UI_FONT_INTERFACE];
+}
+
 static struct index
 get_index(const struct mlk_gridmenu *menu)
 {
 	return (struct index) {
-		.row = menu->selected / menu->ncols,
-		.col = menu->selected % menu->ncols
+		.row = (unsigned int)(menu->selected / menu->ncols),
+		.col = (unsigned int)(menu->selected % menu->ncols)
 	};
 }
 
 static void
 geometry(struct mlk_gridmenu *menu)
 {
-	struct mlk_label label = {
-		.style = menu->text_style
-	};
 	unsigned int reqw = 0, reqh = 0, lw, lh;
 
 	/* Compute which item has the bigger width/height to create a spacing. */
@@ -68,18 +74,18 @@
 	menu->spacew = menu->spaceh = 0;
 
 	for (size_t i = 0; i < menu->itemsz; ++i) {
-		if (!(label.text = menu->items[i]))
+		if (!(menu->items[i]))
 			continue;
 
-		mlk_label_query(&label, &lw, &lh);
+		mlk_font_query(style_font(menu->style), menu->items[i], &lw, &lh);
 
 		menu->eltw = fmax(menu->eltw, lw);
 		menu->elth = fmax(menu->elth, lh);
 	}
 
 	/* Total texture size required to draw items. */
-	reqw = (STYLE_GET(menu->style, padding) * 2) + (menu->eltw * menu->ncols);
-	reqh = (STYLE_GET(menu->style, padding) * 2) + (menu->elth * menu->nrows);
+	reqw = (STYLE_GET(menu->style, padding) * 3) + (menu->eltw * menu->ncols);
+	reqh = (STYLE_GET(menu->style, padding) * 3) + (menu->elth * menu->nrows);
 
 	/*
 	 * Compute spacing between elements. We remove the padding because it
@@ -102,58 +108,6 @@
 	}
 }
 
-static void
-draw_frame(const struct mlk_gridmenu *menu)
-{
-	const struct mlk_frame f = {
-		.x = menu->x,
-		.y = menu->y,
-		.w = menu->w,
-		.h = menu->h,
-	};
-
-	mlk_frame_draw(&f);
-}
-
-static void
-draw_labels(const struct mlk_gridmenu *menu)
-{
-	size_t pagesz, pagenr, item, c = 0, r = 0;
-	struct mlk_label label = {0};
-
-	/*
-	 * Select the first top-left column based on the current selection and
-	 * the number of rows/columns.
-	 */
-	pagesz = menu->nrows * menu->ncols;
-	pagenr = menu->selected / pagesz;
-
-	for (size_t i = 0; i < pagesz; ++i) {
-		item = i + pagenr * pagesz;
-
-		if (item >= menu->itemsz || !menu->items[item])
-			continue;
-
-		label.text = menu->items[item];
-		label.x = menu->x + STYLE_GET(menu->style, padding) + (c * menu->eltw) + (c * menu->spacew);
-		label.y = menu->y + STYLE_GET(menu->style, padding) + (r * menu->elth) + (r * menu->spaceh);
-
-		if (i == menu->selected % pagesz)
-			label.style = menu->text_selected_style
-			     ? menu->text_selected_style
-			     : &mlk_label_style_selected;
-		else
-			label.style = menu->text_style;
-
-		mlk_label_draw(&label);
-
-		if (++c >= menu->ncols) {
-			++r;
-			c = 0;
-		}
-	}
-}
-
 static int
 handle_keydown(struct mlk_gridmenu *menu, const struct mlk_event_key *key)
 {
@@ -203,8 +157,8 @@
 	pagenr = menu->selected / pagesz;
 
 	for (size_t i = 0; i < pagesz; ++i) {
-		x = menu->x + STYLE_GET(menu->style, padding) + (c * menu->eltw) + (c * menu->spacew);
-		y = menu->y + STYLE_GET(menu->style, padding) + (r * menu->elth) + (r * menu->spaceh);
+		x = (int)(menu->x + STYLE_GET(menu->style, padding) + (c * menu->eltw) + (c * menu->spacew));
+		y = (int)(menu->y + STYLE_GET(menu->style, padding) + (r * menu->elth) + (r * menu->spaceh));
 
 		if (mlk_maths_is_boxed(x, y, menu->eltw, menu->elth, click->x, click->y)) {
 			selected  = c + r * menu->ncols;
@@ -225,23 +179,72 @@
 	return 0;
 }
 
+static void
+draw(struct mlk_gridmenu_style *style, const struct mlk_gridmenu *menu)
+{
+	size_t pagesz, pagenr, item, c = 0, r = 0;
+	struct mlk_texture tex;
+	struct mlk_font *font;
+	unsigned long color;
+	int x, y, err;
+
+	/*
+	 * Select the first top-left column based on the current selection and
+	 * the number of rows/columns.
+	 */
+	pagesz = menu->nrows * menu->ncols;
+	pagenr = menu->selected / pagesz;
+
+	font = style_font(menu->style);
+
+	for (size_t i = 0; i < pagesz; ++i) {
+		item = i + pagenr * pagesz;
+
+		if (item >= menu->itemsz || !menu->items[item])
+			continue;
+
+		x = (int)(menu->x + STYLE_GET(menu->style, padding) + (c * menu->eltw) + (c * menu->spacew));
+		y = (int)(menu->y + STYLE_GET(menu->style, padding) + (r * menu->elth) + (r * menu->spaceh));
+
+		if (i == menu->selected % pagesz)
+			color = STYLE_GET(menu->style, text_selected_color);
+		else
+			color = STYLE_GET(menu->style, text_color);
+
+		if ((err = mlk_font_render(font, &tex, menu->items[item], color)) < 0) {
+			mlk_tracef("unable to render grid menu item: %s", mlk_err_string(err));
+			continue;
+		}
+
+		mlk_texture_draw(&tex, x, y);
+		mlk_texture_finish(&tex);
+
+		if (++c >= menu->ncols) {
+			++r;
+			c = 0;
+		}
+	}
+}
+
+struct mlk_gridmenu_style mlk_gridmenu_style = {
+	.padding                = 10,
+	.text_color             = 0x000000ff,
+	.text_selected_color    = 0x328ca7ff,
+	.draw                   = draw
+};
+
 void
-mlk_gridmenu_init(struct mlk_gridmenu *menu,
-              unsigned int nr,
-              unsigned int nc,
-              const char * const *items,
-              size_t itemsz)
+mlk_gridmenu_init(struct mlk_gridmenu *menu)
 {
 	assert(menu);
-	assert(nr);
-	assert(nc);
 
-	memset(menu, 0, sizeof (*menu));
+	STYLE_INVOKE(menu->style, init, menu);
+}
 
-	menu->nrows = nr;
-	menu->ncols = nc;
-	menu->items = items;
-	menu->itemsz = itemsz;
+int
+mlk_gridmenu_ok(const struct mlk_gridmenu *menu)
+{
+	return menu && menu->items && menu->itemsz;
 }
 
 void
@@ -278,12 +281,19 @@
 }
 
 void
+mlk_gridmenu_update(struct mlk_gridmenu *menu, unsigned int ticks)
+{
+	assert(mlk_gridmenu_ok(menu));
+
+	STYLE_INVOKE(menu->style, update, menu, ticks);
+}
+
+void
 mlk_gridmenu_draw(const struct mlk_gridmenu *menu)
 {
 	assert(menu);
 
-	draw_frame(menu);
-	draw_labels(menu);
+	STYLE_INVOKE(menu->style, draw, menu);
 }
 
 void
@@ -291,6 +301,5 @@
 {
 	assert(menu);
 
-	
-
+	STYLE_INVOKE(menu->style, finish, menu);
 }
--- a/libmlk-ui/mlk/ui/gridmenu.h	Wed Mar 01 16:24:07 2023 +0100
+++ b/libmlk-ui/mlk/ui/gridmenu.h	Thu Mar 02 08:54:00 2023 +0100
@@ -27,13 +27,15 @@
 
 union mlk_event;
 
+struct mlk_font;
 struct mlk_gridmenu;
 
 struct mlk_gridmenu_style {
 	void *data;
-	unsigned long bg_color;
-	unsigned long border_color;
 	unsigned int padding;
+	unsigned long text_color;
+	unsigned long text_selected_color;
+	struct mlk_font *text_font;
 	void (*init)(struct mlk_gridmenu_style *, struct mlk_gridmenu *);
 	void (*update)(struct mlk_gridmenu_style *, struct mlk_gridmenu *, unsigned int);
 	void (*draw)(struct mlk_gridmenu_style *, const struct mlk_gridmenu *);
@@ -44,17 +46,12 @@
 	/* public */
 	int x, y;
 	unsigned int w, h;
-
 	const char * const *items;
 	size_t itemsz;
 	size_t selected;
-
 	unsigned int nrows;
 	unsigned int ncols;
-
 	struct mlk_gridmenu_style *style;
-	struct mlk_label_style *text_style;
-	struct mlk_label_style *text_selected_style;
 
 	/* private */
 	unsigned int eltw;      /* maximum entry label width */
@@ -68,7 +65,10 @@
 MLK_CORE_BEGIN_DECLS
 
 void
-mlk_gridmenu_init(struct mlk_gridmenu *, unsigned int, unsigned int, const char * const *, size_t);
+mlk_gridmenu_init(struct mlk_gridmenu *);
+
+int
+mlk_gridmenu_ok(const struct mlk_gridmenu *);
 
 void
 mlk_gridmenu_resize(struct mlk_gridmenu *, int, int, unsigned int, unsigned int);
@@ -77,6 +77,9 @@
 mlk_gridmenu_handle(struct mlk_gridmenu *, const union mlk_event *);
 
 void
+mlk_gridmenu_update(struct mlk_gridmenu *, unsigned int);
+
+void
 mlk_gridmenu_draw(const struct mlk_gridmenu *);
 
 void
--- a/libmlk-ui/mlk/ui/label.c	Wed Mar 01 16:24:07 2023 +0100
+++ b/libmlk-ui/mlk/ui/label.c	Thu Mar 02 08:54:00 2023 +0100
@@ -64,11 +64,6 @@
 	.draw           = draw
 };
 
-struct mlk_label_style mlk_label_style_selected = {
-	.text_color     = 0x7da42dff,
-	.draw           = draw
-};
-
 void
 mlk_label_init(struct mlk_label *label)
 {
--- a/libmlk-ui/mlk/ui/label.h	Wed Mar 01 16:24:07 2023 +0100
+++ b/libmlk-ui/mlk/ui/label.h	Thu Mar 02 08:54:00 2023 +0100
@@ -41,7 +41,6 @@
 };
 
 extern struct mlk_label_style mlk_label_style;
-extern struct mlk_label_style mlk_label_style_selected;
 
 MLK_CORE_BEGIN_DECLS