changeset 507:d49a05e7a5b5

ui: separate delegate/style Now UI elements do have different styling properties: - _delegate: functions used to update, draw or perform specific actions on the UI element. - _style: basic properties that the delegate should support if possible.
author David Demelier <markand@malikania.fr>
date Thu, 02 Mar 2023 21:36:43 +0100
parents e205625015ba
children 7f7602bae0bd
files examples/example-animation/example-animation.c examples/example-debug/example-debug.c examples/example-gridmenu/example-gridmenu.c examples/example-label/example-label.c examples/example-ui/CMakeLists.txt examples/example-ui/button-glower.c examples/example-ui/button-glower.h examples/example-ui/button-style-glow.c examples/example-ui/button-style-glow.h examples/example-ui/example-ui.c libmlk-example/mlk/example/glower.c libmlk-example/mlk/example/glower.h libmlk-ui/CMakeLists.txt libmlk-ui/mlk/ui/align.c libmlk-ui/mlk/ui/align.h libmlk-ui/mlk/ui/button.c libmlk-ui/mlk/ui/button.h libmlk-ui/mlk/ui/checkbox.c libmlk-ui/mlk/ui/checkbox.h libmlk-ui/mlk/ui/debug.c libmlk-ui/mlk/ui/debug.h libmlk-ui/mlk/ui/frame.c libmlk-ui/mlk/ui/frame.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 libmlk-ui/mlk/ui/notify.c libmlk-ui/mlk/ui/notify.h libmlk-ui/mlk/ui/ui.c libmlk-ui/mlk/ui/ui.h libmlk-ui/mlk/ui/ui_p.h
diffstat 32 files changed, 689 insertions(+), 534 deletions(-) [+]
line wrap: on
line diff
--- a/examples/example-animation/example-animation.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/examples/example-animation/example-animation.c	Thu Mar 02 21:36:43 2023 +0100
@@ -39,7 +39,6 @@
 	.text = "Keys: <Space> start or reset the animation.",
 	.x = 10,
 	.y = 10,
-	.flags = MLK_LABEL_FLAGS_SHADOW
 };
 
 static struct mlk_state *states[1];
--- a/examples/example-debug/example-debug.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/examples/example-debug/example-debug.c	Thu Mar 02 21:36:43 2023 +0100
@@ -27,7 +27,6 @@
 #include <mlk/core/util.h>
 
 #include <mlk/ui/debug.h>
-#include <mlk/ui/theme.h>
 #include <mlk/ui/ui.h>
 
 #include <mlk/example/example.h>
@@ -72,7 +71,7 @@
 
 	struct mlk_debug_report report = {0};
 
-	mlk_painter_set_color(0x4f8fbaff);
+	mlk_painter_set_color(MLK_EXAMPLE_BG);
 	mlk_painter_clear();
 	mlk_debugf(&report, "Game running.");
 	mlk_debugf(&report, "mouse: %d, %d", mouse_x, mouse_y);
--- a/examples/example-gridmenu/example-gridmenu.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/examples/example-gridmenu/example-gridmenu.c	Thu Mar 02 21:36:43 2023 +0100
@@ -38,6 +38,8 @@
 #include <mlk/example/glower.h>
 #include <mlk/example/registry.h>
 
+static void menu_update(struct mlk_gridmenu_delegate *, struct mlk_gridmenu *, unsigned int);
+
 static struct mlk_state *states[8];
 
 static const char * const items[] = {
@@ -64,25 +66,35 @@
 	.w = 300,
 	.h = 100
 };
-static struct mlk_gridmenu_style menu_style = {
-	.padding = 10,
-	.text_color = 0x222323ff
+static struct mlk_gridmenu_style menu_style = {0};
+static struct mlk_gridmenu_delegate menu_delegate = {
+	.update = menu_update
 };
 static struct mlk_gridmenu menu = {
 	.nrows = 3,
 	.ncols = 2,
 	.items = items,
 	.itemsz = MLK_UTIL_SIZE(items),
-	.style = &menu_style
+	.style = &menu_style,
+	.delegate = &menu_delegate
 };
 static struct mlk_glower menu_glower = {
-	.color = &menu_style.text_selected_color,
 	.start = 0x00bfa3ff,
 	.end = 0x006b6dff,
 	.delay = 20
 };
 
 static void
+menu_update(struct mlk_gridmenu_delegate *delegate, struct mlk_gridmenu *menu, unsigned int ticks)
+{
+	(void)delegate;
+	(void)menu;
+
+	mlk_glower_update(&menu_glower, ticks);
+	menu_style.selected_color = menu_glower.color;
+}
+
+static void
 init(void)
 {
 	int err;
@@ -90,6 +102,7 @@
 	if ((err = mlk_example_init("example-gridmenu")) < 0)
 		mlk_panicf("mlk_example_init: %s", mlk_err_string(err));
 
+	menu_style = mlk_gridmenu_style;
 	mlk_glower_init(&menu_glower);
 }
 
@@ -139,7 +152,6 @@
 		.draw = draw,
 	};
 
-	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);
--- a/examples/example-label/example-label.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/examples/example-label/example-label.c	Thu Mar 02 21:36:43 2023 +0100
@@ -31,77 +31,115 @@
 
 #include <mlk/ui/align.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>
 
-struct {
+static struct mlk_state *states[8];
+
+static struct mlk_label_style style = {
+	.color = 0x005162ff
+};
+
+/*
+ * Add a glower effect to the main label in the middle.
+ */
+static void main_update(struct mlk_label_delegate *, struct mlk_label *, unsigned int);
+
+static struct mlk_glower main_glower = {
+	.start  = 0xffce7fff,
+	.end    = 0xd58d6bff,
+	.delay  = 22
+};
+static struct mlk_label_delegate main_delegate = {
+	.update = main_update
+};
+static struct mlk_label_style main_style;
+
+static struct {
 	enum mlk_align align;
 	struct mlk_label label;
 } table[] = {
 	{
+		.align = MLK_ALIGN_CENTER,
+		.label = {
+			.text = "The world is Malikania.",
+			.style = &main_style,
+			.delegate = &main_delegate
+		}
+	},
+	{
 		.align = MLK_ALIGN_TOP_LEFT,
 		.label = {
-			.text = "Top left"
+			.text = "Top left",
+			.style = &style
 		}
 	},
 	{
 		.align = MLK_ALIGN_TOP,
 		.label = {
 			.text = "Top",
+			.style = &style
 		}
 	},
 	{
 		.align = MLK_ALIGN_TOP_RIGHT,
 		.label = {
 			.text = "Top right",
+			.style = &style
 		}
 	},
 	{
 		.align = MLK_ALIGN_RIGHT,
 		.label = {
 			.text = "Right",
+			.style = &style
 		}
 	},
 	{
 		.align = MLK_ALIGN_BOTTOM_RIGHT,
 		.label = {
 			.text = "Bottom right",
+			.style = &style
 		}
 	},
 	{
 		.align = MLK_ALIGN_BOTTOM,
 		.label = {
 			.text = "Bottom",
+			.style = &style
 		}
 	},
 	{
 		.align = MLK_ALIGN_BOTTOM_LEFT,
 		.label = {
 			.text = "Bottom left",
+			.style = &style
 		}
 	},
 	{
 		.align = MLK_ALIGN_LEFT,
 		.label = {
 			.text = "Left",
-		}
-	},
-	{
-		.align = MLK_ALIGN_CENTER,
-		.label = {
-			.text = "The world is Malikania.",
-			.flags = MLK_LABEL_FLAGS_SHADOW
+			.style = &style
 		}
 	}
 };
 
-static struct mlk_label mlabel = {
-	.text = "This one follows your mouse and is not aligned."
+static struct mlk_label mouse_label = {
+	.text = "This one follows your mouse and is not aligned.",
+	.style = &style
 };
 
-static struct mlk_state *states[1];
+static void
+main_update(struct mlk_label_delegate *delegate, struct mlk_label *label, unsigned int ticks)
+{
+	(void)delegate;
+
+	mlk_glower_update(&main_glower, ticks);
+	label->style->color = main_glower.color;
+}
 
 static void
 init(void)
@@ -115,10 +153,11 @@
 
 	for (size_t i = 0; i < MLK_UTIL_SIZE(table); ++i) {
 		l = &table[i].label;
-
 		mlk_label_query(l, &w, &h);
 		mlk_align(table[i].align, &l->x, &l->y, w, h, 0, 0, mlk_window.w, mlk_window.h);
 	}
+
+	mlk_glower_init(&main_glower);
 }
 
 static void
@@ -128,8 +167,8 @@
 
 	switch (ev->type) {
 	case MLK_EVENT_MOUSE:
-		mlabel.x = ev->mouse.x;
-		mlabel.y = ev->mouse.y;
+		mouse_label.x = ev->mouse.x;
+		mouse_label.y = ev->mouse.y;
 		break;
 	case MLK_EVENT_QUIT:
 		mlk_game_quit();
@@ -140,17 +179,28 @@
 }
 
 static void
+update(struct mlk_state *st, unsigned int ticks)
+{
+	(void)st;
+
+	for (size_t i = 0; i < MLK_UTIL_SIZE(table); ++i)
+		mlk_label_update(&table[i].label, ticks);
+
+	mlk_label_update(&mouse_label, ticks);
+}
+
+static void
 draw(struct mlk_state *st)
 {
 	(void)st;
 
-	mlk_painter_set_color(0x4f8fbaff);
+	mlk_painter_set_color(MLK_EXAMPLE_BG);
 	mlk_painter_clear();
 
 	for (size_t i = 0; i < MLK_UTIL_SIZE(table); ++i)
 		mlk_label_draw(&table[i].label);
 
-	mlk_label_draw(&mlabel);
+	mlk_label_draw(&mouse_label);
 	mlk_painter_present();
 }
 
@@ -159,6 +209,7 @@
 {
 	struct mlk_state state = {
 		.handle = handle,
+		.update = update,
 		.draw = draw
 	};
 
--- a/examples/example-ui/CMakeLists.txt	Thu Mar 02 08:54:00 2023 +0100
+++ b/examples/example-ui/CMakeLists.txt	Thu Mar 02 21:36:43 2023 +0100
@@ -20,8 +20,8 @@
 
 set(
 	SOURCES
-	${example-ui_SOURCE_DIR}/button-style-glow.c
-	${example-ui_SOURCE_DIR}/button-style-glow.h
+	${example-ui_SOURCE_DIR}/button-glower.c
+	${example-ui_SOURCE_DIR}/button-glower.h
 	${example-ui_SOURCE_DIR}/example-ui.c
 )
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-ui/button-glower.c	Thu Mar 02 21:36:43 2023 +0100
@@ -0,0 +1,48 @@
+/*
+ * button-style-glow.c -- example of glowing button
+ *
+ * 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 "button-glower.h"
+
+static void
+update(struct mlk_button_delegate *delegate, struct mlk_button *button, unsigned int ticks)
+{
+	(void)button;
+
+	struct button_glower *glower = delegate->data;
+
+	mlk_glower_update(&glower->glower, ticks);
+	glower->style.bg_color = glower->glower.color;
+}
+
+void
+button_glower_init(struct button_glower *glower, struct mlk_button *button)
+{
+	assert(glower);
+
+	glower->style.bg_color = glower->glower.start;
+	glower->delegate.data = glower;
+	glower->delegate.update = update;
+
+	/* Link this style and delegate to the button. */
+	button->style = &glower->style;
+	button->delegate = &glower->delegate;
+
+	mlk_glower_init(&glower->glower);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-ui/button-glower.h	Thu Mar 02 21:36:43 2023 +0100
@@ -0,0 +1,39 @@
+/*
+ * button-style-glow.h -- example of glowing button
+ *
+ * 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_UI_BUTTON_GLOWER_H
+#define MLK_EXAMPLE_UI_BUTTON_GLOWER_H
+
+#include <mlk/ui/button.h>
+
+#include <mlk/example/glower.h>
+
+#define BUTTON_STYLE_GLOW_COLOR_1       0x7da42dff
+#define BUTTON_STYLE_GLOW_COLOR_2       0xa6cc34ff
+#define BUTTON_STYLE_GLOW_DELAY         20
+
+struct button_glower {
+	struct mlk_glower glower;
+	struct mlk_button_style style;
+	struct mlk_button_delegate delegate;
+};
+
+void
+button_glower_init(struct button_glower *glower, struct mlk_button *button);
+
+#endif /* !MLK_EXAMPLE_UI_BUTTON_GLOWER_H */
--- a/examples/example-ui/button-style-glow.c	Thu Mar 02 08:54:00 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/*
- * button-style-glow.c -- example of glowing button
- *
- * 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 <mlk/example/glower.h>
-
-#include "button-style-glow.h"
-
-/*
- * This button style illuminates the background color by periodically changing
- * its red, green, blue components to the target color and then go back to the
- * original transition.
- *
- * Its sole purpose is to demonstrates how style works. Because the style needs
- * a state it is implemented through a parent button_style_glow structure which
- * holds an underlying style.
- *
- * This style does not override the glow function because we just change the
- * bg_color property that will be reused.
- */
-
-static void
-init(struct mlk_button_style *style, struct mlk_button *button)
-{
-	(void)button;
-
-	struct button_style_glow *styler = style->data;
-
-	styler->glow->color = &style->bg_color;
-
-	mlk_glower_init(styler->glow);
-}
-
-static void
-update(struct mlk_button_style *style, struct mlk_button *button, unsigned int ticks)
-{
-	(void)button;
-
-	struct button_style_glow *styler = style->data;
-
-	mlk_glower_update(styler->glow, ticks);
-}
-
-void
-button_style_glow_init(struct button_style_glow *styler)
-{
-	assert(styler);
-	assert(styler->glow);
-
-	styler->style.data = styler;
-	styler->style.init = init;
-	styler->style.update = update;
-}
--- a/examples/example-ui/button-style-glow.h	Thu Mar 02 08:54:00 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-/*
- * button-style-glow.h -- example of glowing button
- *
- * 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_BUTTON_STYLE_H
-#define MLK_EXAMPLE_BUTTON_STYLE_H
-
-#include <mlk/ui/button.h>
-
-#define BUTTON_STYLE_GLOW_COLOR_1       0x7da42dff
-#define BUTTON_STYLE_GLOW_COLOR_2       0xa6cc34ff
-#define BUTTON_STYLE_GLOW_DELAY         20
-
-struct mlk_glower;
-
-struct button_style_glow {
-	struct mlk_glower *glow;
-	struct mlk_button_style style;
-};
-
-void
-button_style_glow_init(struct button_style_glow *);
-
-#endif /* !MLK_EXAMPLE_BUTTON_GLOW_STYLE_H */
--- a/examples/example-ui/example-ui.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/examples/example-ui/example-ui.c	Thu Mar 02 21:36:43 2023 +0100
@@ -41,7 +41,7 @@
 #include <mlk/example/glower.h>
 #include <mlk/example/registry.h>
 
-#include "button-style-glow.h"
+#include "button-glower.h"
 
 #define FRAME_ORIGIN_X  (10)
 #define FRAME_ORIGIN_Y  (10)
@@ -54,6 +54,8 @@
 
 #define PADDING         (10)
 
+static struct mlk_state *states[8];
+
 /*
  * We design a basic UI like this.
  *
@@ -92,15 +94,13 @@
 		struct mlk_button hello;
 
 		/* [Quit] with custom style color. */
-		struct mlk_label_style quit_text_style;
 		struct mlk_button_style quit_style;
 		struct mlk_button quit;
 
 		/*
-		 * [Download free RAM] with custom style drawing.
+		 * [Download free RAM] with custom style using a delegate.
 		 */
-		struct mlk_glower download_glow;
-		struct button_style_glow download_style;
+		struct button_glower download_glower;
 		struct mlk_button download;
 	} buttons;
 } ui = {
@@ -135,40 +135,33 @@
 		},
 		.quit_style = {
 			.bg_color = 0x24aed6ff,
-			.border_color = 0x328ca7ff
-		},
-		.quit_text_style = {
-			.text_color = 0xf5f7faff
+			.text_color = 0xffffffff
 		},
 		.quit = {
 			.text = "Quit",
 			.h = ELEMENT_HEIGHT,
-			.style = &ui.buttons.quit_style,
-			.text_style = &ui.buttons.quit_text_style
+			.style = &ui.buttons.quit_style
 		},
-		.download_glow = {
-			.start = BUTTON_STYLE_GLOW_COLOR_1,
-			.end = BUTTON_STYLE_GLOW_COLOR_2,
-			.delay = BUTTON_STYLE_GLOW_DELAY
-		},
-		.download_style = {
-			.glow = &ui.buttons.download_glow,
+		.download_glower = {
+			.glower = {
+				.start = BUTTON_STYLE_GLOW_COLOR_1,
+				.end = BUTTON_STYLE_GLOW_COLOR_2,
+				.delay = BUTTON_STYLE_GLOW_DELAY
+			},
 			.style = {
-				.border_color = BUTTON_STYLE_GLOW_COLOR_1
+				.text_color = 0xffffffff,
+				.border_color = BUTTON_STYLE_GLOW_COLOR_1,
+				.border_size = 2
 			}
 		},
 		.download = {
 			.w = 180,
 			.h = 32,
 			.text = "!! Download free RAM !!",
-			.style = &ui.buttons.download_style.style,
-			.text_style = &ui.buttons.quit_text_style
 		}
 	}
 };
 
-static struct mlk_state *states[1];
-
 static void
 resize_header(void)
 {
@@ -333,11 +326,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_style);
-
-	mlk_button_init(&ui.buttons.hello);
-	mlk_button_init(&ui.buttons.quit);
-	mlk_button_init(&ui.buttons.download);
+	button_glower_init(&ui.buttons.download_glower, &ui.buttons.download);
 }
 
 static void
--- a/libmlk-example/mlk/example/glower.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-example/mlk/example/glower.c	Thu Mar 02 21:36:43 2023 +0100
@@ -37,9 +37,8 @@
 mlk_glower_init(struct mlk_glower *glow)
 {
 	assert(glow);
-	assert(glow->color);
 
-	*glow->color = glow->start;
+	glow->color = glow->start;
 	glow->target = glow->end;
 }
 
@@ -56,14 +55,14 @@
 		glow->elapsed = 0;
 
 		/* Color target reached, invert target color. */
-		if (*glow->color == glow->target)
+		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));
+			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);
+			glow->color = MLK_COLOR_HEX(r, g, b, 0xff);
 		}
 	}
 }
--- a/libmlk-example/mlk/example/glower.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-example/mlk/example/glower.h	Thu Mar 02 21:36:43 2023 +0100
@@ -23,7 +23,7 @@
 	/* public */
 	unsigned long start;
 	unsigned long end;
-	unsigned long *color;
+	unsigned long color;
 	unsigned int delay;
 
 	/* private */
--- a/libmlk-ui/CMakeLists.txt	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/CMakeLists.txt	Thu Mar 02 21:36:43 2023 +0100
@@ -38,6 +38,7 @@
 	${libmlk-ui_SOURCE_DIR}/mlk/ui/notify.h
 	${libmlk-ui_SOURCE_DIR}/mlk/ui/ui.c
 	${libmlk-ui_SOURCE_DIR}/mlk/ui/ui.h
+	${libmlk-ui_SOURCE_DIR}/mlk/ui/ui_p.h
 )
 
 set(
--- a/libmlk-ui/mlk/ui/align.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/align.c	Thu Mar 02 21:36:43 2023 +0100
@@ -23,14 +23,14 @@
 
 void
 mlk_align(enum mlk_align align,
-      int *x,
-      int *y,
-      unsigned int w,
-      unsigned int h,
-      int px,
-      int py,
-      unsigned int pw,
-      unsigned int ph)
+          int *x,
+          int *y,
+          unsigned int w,
+          unsigned int h,
+          int px,
+          int py,
+          unsigned int pw,
+          unsigned int ph)
 {
 	switch (align) {
 	case MLK_ALIGN_CENTER:
@@ -70,6 +70,8 @@
 		SET(x, px);
 		break;
 	default:
+		*x = px;
+		*y = py;
 		break;
 	}
 }
--- a/libmlk-ui/mlk/ui/align.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/align.h	Thu Mar 02 21:36:43 2023 +0100
@@ -22,6 +22,7 @@
 #include <mlk/core/core.h>
 
 enum mlk_align {
+	MLK_ALIGN_NONE,
 	MLK_ALIGN_CENTER,
 	MLK_ALIGN_TOP_LEFT,
 	MLK_ALIGN_TOP,
@@ -37,14 +38,14 @@
 
 void
 mlk_align(enum mlk_align,
-      int *,
-      int *,
-      unsigned int,
-      unsigned int,
-      int,
-      int,
-      unsigned int,
-      unsigned int);
+          int *,
+          int *,
+          unsigned int,
+          unsigned int,
+          int,
+          int,
+          unsigned int,
+          unsigned int);
 
 MLK_CORE_END_DECLS
 
--- a/libmlk-ui/mlk/ui/button.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/button.c	Thu Mar 02 21:36:43 2023 +0100
@@ -26,41 +26,8 @@
 
 #include "align.h"
 #include "button.h"
-#include "label.h"
-
-#define STYLE_INVOKE(s, f, ...)                                                 \
-do {                                                                            \
-        if (s && s->f)                                                          \
-                s->f(s, __VA_ARGS__);                                           \
-        else if (mlk_button_style.f)                                            \
-                mlk_button_style.f(s ? s : &mlk_button_style, __VA_ARGS__);     \
-} while (0)
-
-static void
-draw(struct mlk_button_style *style, const struct mlk_button *button)
-{
-	struct mlk_label label = {
-		.text = button->text,
-		.style = button->text_style
-	};
-	unsigned int lw, lh;
-
-	mlk_label_query(&label, &lw, &lh);
-
-	if (lw > button->w)
-		mlk_tracef("button width is too small for text: %u < %u", button->w, lw);
-	if (lh > button->h)
-		mlk_tracef("button height is too small for text: %u < %u", button->h, lh);
-
-	mlk_align(MLK_ALIGN_CENTER, &label.x, &label.y, lw, lh,
-	    button->x, button->y, button->w, button->h);
-
-	mlk_painter_set_color(style->border_color);
-	mlk_painter_draw_rectangle(button->x, button->y, button->w, button->h);
-	mlk_painter_set_color(style->bg_color);
-	mlk_painter_draw_rectangle(button->x + 1, button->y + 1, button->w - 2, button->h - 2);
-	mlk_label_draw(&label);
-}
+#include "ui.h"
+#include "ui_p.h"
 
 static inline int
 is_boxed(const struct mlk_button *button, const struct mlk_event_click *click)
@@ -73,18 +40,75 @@
 	    click->x, click->y);
 }
 
+static inline struct mlk_font *
+style_font(const struct mlk_button *button)
+{
+	const struct mlk_button_style *style = MLK__STYLE(button, mlk_button_style);
+
+	if (style->text_font)
+		return style->text_font;
+
+	return mlk_ui_fonts[MLK_UI_FONT_INTERFACE];
+}
+
+static void
+delegate_draw_frame(struct mlk_button_delegate *delegate, const struct mlk_button *button)
+{
+	(void)delegate;
+
+	const struct mlk_button_style *style = MLK__STYLE(button, mlk_button_style);
+
+	mlk_painter_set_color(style->border_color);
+	mlk_painter_draw_rectangle(button->x, button->y, button->w, button->h);
+	mlk_painter_set_color(style->bg_color);
+	mlk_painter_draw_rectangle(
+		button->x + style->border_size,
+		button->y + style->border_size,
+		button->w - (style->border_size * 2),
+		button->h - (style->border_size * 2)
+	);
+}
+
+static void
+delegate_draw_text(struct mlk_button_delegate *delegate, const struct mlk_button *button)
+{
+	(void)delegate;
+
+	const struct mlk_button_style *style = MLK__STYLE(button, mlk_button_style);
+
+	mlk_ui_draw_text(
+		MLK_ALIGN_CENTER,
+		style_font(button),
+		style->text_color,
+		button->text,
+		button->x,
+		button->y,
+		button->w,
+		button->h
+	);
+}
+
 struct mlk_button_style mlk_button_style = {
-	.bg_color       = 0xebf0f6ff,
-	.border_color   = 0xbac7dbff,
-	.draw           = draw
+	.bg_color       = MLK_UI_COLOR_BG,
+	.text_color     = MLK_UI_COLOR_TEXT,
+	.border_color   = MLK_UI_COLOR_BORDER,
+	.border_size    = 1
 };
 
-void
-mlk_button_init(struct mlk_button *button)
+struct mlk_button_delegate mlk_button_delegate = {
+	.draw_frame     = delegate_draw_frame,
+	.draw_text      = delegate_draw_text
+};
+
+int
+mlk_button_ok(const struct mlk_button *button)
 {
-	assert(button);
+	if (!button)
+		return 0;
+	if (button->text && strlen(button->text) == 0)
+		return 0;
 
-	STYLE_INVOKE(button->style, init, button);
+	return 1;
 }
 
 int
@@ -123,7 +147,7 @@
 {
 	assert(button);
 
-	STYLE_INVOKE(button->style, update, button, ticks);
+	MLK__DELEGATE_INVOKE(button->delegate, mlk_button_delegate, update, button, ticks);
 }
 
 void
@@ -131,13 +155,8 @@
 {
 	assert(button);
 
-	STYLE_INVOKE(button->style, draw, button);
-}
+	MLK__DELEGATE_INVOKE(button->delegate, mlk_button_delegate, draw_frame, button);
 
-void
-mlk_button_finish(struct mlk_button *button)
-{
-	assert(button);
-
-	STYLE_INVOKE(button->style, finish, button);
+	if (button->text)
+		MLK__DELEGATE_INVOKE(button->delegate, mlk_button_delegate, draw_text, button);
 }
--- a/libmlk-ui/mlk/ui/button.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/button.h	Thu Mar 02 21:36:43 2023 +0100
@@ -24,34 +24,39 @@
 union mlk_event;
 
 struct mlk_button;
-struct mlk_label_style;
-struct mlk_theme;
+struct mlk_font;
 
 struct mlk_button_style {
-	void *data;
 	unsigned long bg_color;
 	unsigned long border_color;
-	void (*init)(struct mlk_button_style *, struct mlk_button *);
-	void (*update)(struct mlk_button_style *, struct mlk_button *, unsigned int);
-	void (*draw)(struct mlk_button_style *, const struct mlk_button *);
-	void (*finish)(struct mlk_button_style *, struct mlk_button *);
+	unsigned long border_size;
+	unsigned long text_color;
+	struct mlk_font *text_font;
+};
+
+struct mlk_button_delegate {
+	void *data;
+	void (*update)(struct mlk_button_delegate *delegate, struct mlk_button *button, unsigned int ticks);
+	void (*draw_frame)(struct mlk_button_delegate *delegate, const struct mlk_button *button);
+	void (*draw_text)(struct mlk_button_delegate *delegate, const struct mlk_button *button);
 };
 
 struct mlk_button {
 	int x, y;
 	unsigned int w, h;
 	const char *text;
+	int pressed;
 	struct mlk_button_style *style;
-	struct mlk_label_style *text_style;
-	int pressed;
+	struct mlk_button_delegate *delegate;
 };
 
 extern struct mlk_button_style mlk_button_style;
+extern struct mlk_button_delegate mlk_button_delegate;
 
 MLK_CORE_BEGIN_DECLS
 
-void
-mlk_button_init(struct mlk_button *);
+int
+mlk_button_ok(const struct mlk_button *);
 
 int
 mlk_button_handle(struct mlk_button *, const union mlk_event *);
@@ -62,9 +67,6 @@
 void
 mlk_button_draw(const struct mlk_button *);
 
-void
-mlk_button_finish(struct mlk_button *);
-
 MLK_CORE_END_DECLS
 
 #endif /* !MLK_UI_BUTTON_H */
--- a/libmlk-ui/mlk/ui/checkbox.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/checkbox.c	Thu Mar 02 21:36:43 2023 +0100
@@ -23,16 +23,9 @@
 #include <mlk/core/maths.h>
 #include <mlk/core/painter.h>
 
-#include "label.h"
 #include "checkbox.h"
-
-#define STYLE_INVOKE(s, f, ...)                                                 \
-do {                                                                            \
-        if (s && s->f)                                                          \
-                s->f(s, __VA_ARGS__);                                           \
-        else if (mlk_checkbox_style.f)                                          \
-                mlk_checkbox_style.f(s ? s : &mlk_checkbox_style, __VA_ARGS__); \
-} while (0)
+#include "ui.h"
+#include "ui_p.h"
 
 static int
 is_boxed(const struct mlk_checkbox *cb, const struct mlk_event_click *click)
@@ -44,12 +37,26 @@
 }
 
 static void
-draw(struct mlk_checkbox_style *style, const struct mlk_checkbox *cb)
+draw(struct mlk_checkbox_delegate *delegate, const struct mlk_checkbox *cb)
 {
-	mlk_painter_set_color(style->border_color);
-	mlk_painter_draw_rectangle(cb->x, cb->y, cb->w, cb->h);
-	mlk_painter_set_color(style->bg_color);
-	mlk_painter_draw_rectangle(cb->x + 1, cb->y + 1, cb->w - 2, cb->h - 2);
+	(void)delegate;
+
+	const struct mlk_checkbox_style *style = MLK__STYLE(cb, mlk_checkbox_style);
+
+	if (!style->border_size) {
+		mlk_painter_set_color(style->bg_color);
+		mlk_painter_draw_rectangle(cb->x, cb->y, cb->w, cb->h);
+	} else {
+		mlk_painter_set_color(style->border_color);
+		mlk_painter_draw_rectangle(cb->x, cb->y, cb->w, cb->h);
+		mlk_painter_set_color(style->bg_color);
+		mlk_painter_draw_rectangle(
+			cb->x + style->border_size,
+			cb->y + style->border_size,
+			cb->w - (style->border_size * 2),
+			cb->h - (style->border_size * 2)
+		);
+	}
 
 	if (cb->checked) {
 		mlk_painter_set_color(style->check_color);
@@ -58,24 +65,26 @@
 }
 
 struct mlk_checkbox_style mlk_checkbox_style = {
-	.bg_color       = 0xebf0f6ff,
-	.border_color   = 0xbac7dbff,
-	.check_color    = 0x848795ff,
-	.draw           = draw
+	.bg_color = MLK_UI_COLOR_BG,
+	.check_color = MLK_UI_COLOR_TEXT,
+	.border_color = MLK_UI_COLOR_BORDER,
+	.border_size = 1
 };
 
-void
-mlk_checkbox_init(struct mlk_checkbox *cb)
+struct mlk_checkbox_delegate mlk_checkbox_delegate = {
+	.draw = draw
+};
+
+int
+mlk_checkbox_ok(const struct mlk_checkbox *cb)
 {
-	assert(cb);
-
-	STYLE_INVOKE(cb->style, init, cb);
+	return cb != NULL;
 }
 
 int
 mlk_checkbox_handle(struct mlk_checkbox *cb, const union mlk_event *ev)
 {
-	assert(cb);
+	assert(mlk_checkbox_ok(cb));
 	assert(ev);
 
 	switch (ev->type) {
@@ -91,18 +100,17 @@
 }
 
 void
-mlk_checkbox_draw(const struct mlk_checkbox *cb)
+mlk_checkbox_update(struct mlk_checkbox *cb, unsigned int ticks)
 {
-	assert(cb);
+	assert(mlk_checkbox_ok(cb));
 
-	STYLE_INVOKE(cb->style, draw, cb);
+	MLK__DELEGATE_INVOKE(cb->delegate, mlk_checkbox_delegate, update, cb, ticks);
 }
 
 void
-mlk_checkbox_finish(struct mlk_checkbox *cb)
+mlk_checkbox_draw(const struct mlk_checkbox *cb)
 {
-	assert(cb);
+	assert(mlk_checkbox_ok(cb));
 
-	STYLE_INVOKE(cb->style, finish, cb);
-
+	MLK__DELEGATE_INVOKE(cb->delegate, mlk_checkbox_delegate, draw, cb);
 }
--- a/libmlk-ui/mlk/ui/checkbox.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/checkbox.h	Thu Mar 02 21:36:43 2023 +0100
@@ -26,40 +26,42 @@
 struct mlk_checkbox;
 
 struct mlk_checkbox_style {
-	void *data;
 	unsigned long bg_color;
-	unsigned long border_color;
 	unsigned long check_color;
-	void (*init)(struct mlk_checkbox_style *, struct mlk_checkbox *);
-	void (*update)(struct mlk_checkbox_style *, struct mlk_checkbox *, unsigned int);
-	void (*draw)(struct mlk_checkbox_style *, const struct mlk_checkbox *);
-	void (*finish)(struct mlk_checkbox_style *, struct mlk_checkbox *);
+	unsigned long border_color;
+	unsigned int border_size;
+};
+
+struct mlk_checkbox_delegate {
+	void *data;
+	void (*update)(struct mlk_checkbox_delegate *, struct mlk_checkbox *, unsigned int);
+	void (*draw)(struct mlk_checkbox_delegate *, const struct mlk_checkbox *);
 };
 
 struct mlk_checkbox {
-	int x;
-	int y;
-	unsigned int w;
-	unsigned int h;
+	int x, y;
+	unsigned int w, h;
 	int checked;
+	struct mlk_checkbox_delegate *delegate;
 	struct mlk_checkbox_style *style;
 };
 
 extern struct mlk_checkbox_style mlk_checkbox_style;
+extern struct mlk_checkbox_delegate mlk_checkbox_delegate;
 
 MLK_CORE_BEGIN_DECLS
 
-void
-mlk_checkbox_init(struct mlk_checkbox *);
+int
+mlk_checkbox_ok(const struct mlk_checkbox *cb);
 
 int
 mlk_checkbox_handle(struct mlk_checkbox *, const union mlk_event *);
 
 void
-mlk_checkbox_draw(const struct mlk_checkbox *);
+mlk_checkbox_update(struct mlk_checkbox *cb, unsigned int ticks);
 
 void
-mlk_checkbox_finish(struct mlk_checkbox *);
+mlk_checkbox_draw(const struct mlk_checkbox *);
 
 MLK_CORE_END_DECLS
 
--- a/libmlk-ui/mlk/ui/debug.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/debug.c	Thu Mar 02 21:36:43 2023 +0100
@@ -22,6 +22,7 @@
 #include <mlk/core/texture.h>
 
 #include "debug.h"
+#include "ui.h"
 
 struct mlk_debug_options mlk_debug_options = {
 #if !defined(NDEBUG)
@@ -48,32 +49,30 @@
 void
 mlk_debugva(struct mlk_debug_report *report, const char *fmt, va_list ap)
 {
-#if 0
 	assert(report);
 	assert(fmt);
 
-	if (!mlk_debug_options.enable)
-		return;
-
 	char line[MLK_DEBUG_LINE_MAX];
-	const struct mlk_theme *theme;
 	struct mlk_font *font;
 	struct mlk_texture tex;
 	int x, y;
 
+	if (!mlk_debug_options.enable)
+		return;
+
 	vsnprintf(line, sizeof (line), fmt, ap);
 
-	theme = &mlk_theme;
-	font = theme->fonts[MLK_THEME_FONT_DEBUG];
+	// TODO: add style support.
+	font = mlk_ui_fonts[MLK_UI_FONT_INTERFACE];
 
-	if (mlk_font_render(font, &tex, line, MLK_THEME_COLOR_DEBUG) < 0)
+	if (mlk_font_render(font, &tex, line, MLK_UI_COLOR_DEBUG) < 0)
 		return;
 
-	x = theme->padding;
-	y = (theme->padding * (report->count + 1)) + (tex.h * (report->count));
+	// TODO: same here.
+	x = 10;
+	y = (10 * (report->count + 1)) + (tex.h * (report->count));
 	report->count++;
 
 	mlk_texture_draw(&tex, x, y);
 	mlk_texture_finish(&tex);
-#endif
 }
--- a/libmlk-ui/mlk/ui/debug.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/debug.h	Thu Mar 02 21:36:43 2023 +0100
@@ -33,7 +33,6 @@
 };
 
 struct mlk_debug_report {
-	const struct mlk_theme *theme;
 	unsigned int count;
 };
 
@@ -44,7 +43,6 @@
 void
 mlk_debugf(struct mlk_debug_report *, const char *, ...);
 
-
 void
 mlk_debugva(struct mlk_debug_report *, const char *, va_list);
 
--- a/libmlk-ui/mlk/ui/frame.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/frame.c	Thu Mar 02 21:36:43 2023 +0100
@@ -17,40 +17,54 @@
  */
 
 #include <assert.h>
+#include <stddef.h>
 
 #include <mlk/core/painter.h>
 
 #include "frame.h"
-
-#define STYLE_INVOKE(s, f, ...)                                                 \
-do {                                                                            \
-        if (s && s->f)                                                          \
-                s->f(s, __VA_ARGS__);                                           \
-        else if (mlk_frame_style.f)                                             \
-                mlk_frame_style.f(s ? s : &mlk_frame_style, __VA_ARGS__);       \
-} while (0)
+#include "ui.h"
+#include "ui_p.h"
 
 static void
-draw(struct mlk_frame_style *style, const struct mlk_frame *frame)
+delegate_draw(struct mlk_frame_delegate *delegate, const struct mlk_frame *frame)
 {
+	(void)delegate;
+
+	const struct mlk_frame_style *style = MLK__STYLE(frame, mlk_frame_style);
+
 	mlk_painter_set_color(style->border_color);
 	mlk_painter_draw_rectangle(frame->x, frame->y, frame->w, frame->h);
 	mlk_painter_set_color(style->bg_color);
-	mlk_painter_draw_rectangle(frame->x + 1, frame->y + 1, frame->w - 2, frame->h - 2);
+	mlk_painter_draw_rectangle(
+		frame->x + style->border_size,
+		frame->y + style->border_size,
+		frame->w - (style->border_size * 2),
+		frame->h - (style->border_size * 2)
+	);
 }
 
 struct mlk_frame_style mlk_frame_style = {
-	.bg_color       = 0xf5f7faff,
-	.border_color   = 0xcdd2daff,
-	.draw           = draw
+	.bg_color       = MLK_UI_COLOR_BG,
+	.border_color   = MLK_UI_COLOR_BORDER,
+	.border_size    = 2
+};
+
+struct mlk_frame_delegate mlk_frame_delegate = {
+	.draw           = delegate_draw
 };
 
+int
+mlk_frame_ok(const struct mlk_frame *frame)
+{
+	return frame != NULL;
+}
+
 void
-mlk_frame_init(struct mlk_frame *frame)
+mlk_frame_update(struct mlk_frame *frame, unsigned int ticks)
 {
 	assert(frame);
 
-	STYLE_INVOKE(frame->style, init, frame);
+	MLK__DELEGATE_INVOKE(frame->delegate, mlk_frame_delegate, update, frame, ticks);
 }
 
 void
@@ -58,13 +72,5 @@
 {
 	assert(frame);
 
-	STYLE_INVOKE(frame->style, draw, frame);
+	MLK__DELEGATE_INVOKE(frame->delegate, mlk_frame_delegate, draw, frame);
 }
-
-void
-mlk_frame_finish(struct mlk_frame *frame)
-{
-	assert(frame);
-
-	STYLE_INVOKE(frame->style, finish, frame);
-}
--- a/libmlk-ui/mlk/ui/frame.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/frame.h	Thu Mar 02 21:36:43 2023 +0100
@@ -26,30 +26,35 @@
 struct mlk_frame_style {
 	unsigned long bg_color;
 	unsigned long border_color;
-	void (*init)(struct mlk_frame_style *, struct mlk_frame *);
-	void (*update)(struct mlk_frame_style *, struct mlk_frame *, unsigned int);
-	void (*draw)(struct mlk_frame_style *, const struct mlk_frame *);
-	void (*finish)(struct mlk_frame_style *, struct mlk_frame *);
+	unsigned long border_size;
+};
+
+struct mlk_frame_delegate {
+	void *data;
+	void (*update)(struct mlk_frame_delegate *, struct mlk_frame *, unsigned int);
+	void (*draw)(struct mlk_frame_delegate *, const struct mlk_frame *);
 };
 
 struct mlk_frame {
 	int x, y;
 	unsigned int w, h;
 	struct mlk_frame_style *style;
+	struct mlk_frame_delegate *delegate;
 };
 
 extern struct mlk_frame_style mlk_frame_style;
+extern struct mlk_frame_delegate mlk_frame_delegate;
 
 MLK_CORE_BEGIN_DECLS
 
-void
-mlk_frame_init(struct mlk_frame *);
+int
+mlk_frame_ok(const struct mlk_frame *frame);
 
 void
-mlk_frame_draw(const struct mlk_frame *);
+mlk_frame_update(struct mlk_frame *frame, unsigned int ticks);
 
 void
-mlk_frame_finish(struct mlk_frame *);
+mlk_frame_draw(const struct mlk_frame *frame);
 
 MLK_CORE_END_DECLS
 
--- a/libmlk-ui/mlk/ui/gridmenu.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/gridmenu.c	Thu Mar 02 21:36:43 2023 +0100
@@ -30,16 +30,7 @@
 
 #include "gridmenu.h"
 #include "ui.h"
-
-#define STYLE_INVOKE(s, f, ...)                                                 \
-do {                                                                            \
-        if (s && s->f)                                                          \
-                s->f(s, __VA_ARGS__);                                           \
-        else if (mlk_gridmenu_style.f)                                          \
-                mlk_gridmenu_style.f(s ? s : &mlk_gridmenu_style, __VA_ARGS__); \
-} while (0)
-
-#define STYLE_GET(s, p) (s ? s->p : mlk_gridmenu_style.p)
+#include "ui_p.h"
 
 struct index {
 	unsigned int row;
@@ -67,12 +58,15 @@
 static void
 geometry(struct mlk_gridmenu *menu)
 {
+	const struct mlk_gridmenu_style *style;
 	unsigned int reqw = 0, reqh = 0, lw, lh;
 
 	/* Compute which item has the bigger width/height to create a spacing. */
 	menu->eltw = menu->elth = 0;
 	menu->spacew = menu->spaceh = 0;
 
+	style = MLK__STYLE(menu, mlk_gridmenu_style);
+
 	for (size_t i = 0; i < menu->itemsz; ++i) {
 		if (!(menu->items[i]))
 			continue;
@@ -84,8 +78,8 @@
 	}
 
 	/* Total texture size required to draw items. */
-	reqw = (STYLE_GET(menu->style, padding) * 3) + (menu->eltw * menu->ncols);
-	reqh = (STYLE_GET(menu->style, padding) * 3) + (menu->elth * menu->nrows);
+	reqw = (style->padding * 3) + (menu->eltw * menu->ncols);
+	reqh = (style->padding * 3) + (menu->elth * menu->nrows);
 
 	/*
 	 * Compute spacing between elements. We remove the padding because it
@@ -95,7 +89,7 @@
 		mlk_tracef("gridmenu width is too small: %u < %u", menu->w, reqw);
 		menu->spacew = 1;
 	} else if (menu->ncols > 1) {
-		reqw -= STYLE_GET(menu->style, padding) * 2;
+		reqw -= style->padding * 2;
 		menu->spacew = (menu->w - reqw) / menu->ncols;
 	}
 
@@ -103,7 +97,7 @@
 		mlk_tracef("gridmenu height is too small: %u < %u", menu->h, reqh);
 		menu->spaceh = 1;
 	} else if (menu->nrows > 1) {
-		reqh -= STYLE_GET(menu->style, padding) * 2;
+		reqh -= style->padding * 2;
 		menu->spaceh = (menu->h - reqh) / menu->nrows;
 	}
 }
@@ -150,15 +144,17 @@
 {
 	assert(click->type == MLK_EVENT_CLICKDOWN);
 
+	const struct mlk_gridmenu_style *style;
 	size_t pagesz, pagenr, selected, c = 0, r = 0;
 	int x, y;
 
+	style  = MLK__STYLE(menu, mlk_gridmenu_style);
 	pagesz = menu->nrows * menu->ncols;
 	pagenr = menu->selected / pagesz;
 
 	for (size_t i = 0; i < pagesz; ++i) {
-		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));
+		x = (int)(menu->x + style->padding + (c * menu->eltw) + (c * menu->spacew));
+		y = (int)(menu->y + 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;
@@ -180,9 +176,10 @@
 }
 
 static void
-draw(struct mlk_gridmenu_style *style, const struct mlk_gridmenu *menu)
+draw(struct mlk_gridmenu_delegate *delegate, const struct mlk_gridmenu *menu)
 {
 	size_t pagesz, pagenr, item, c = 0, r = 0;
+	const struct mlk_gridmenu_style *style;
 	struct mlk_texture tex;
 	struct mlk_font *font;
 	unsigned long color;
@@ -195,6 +192,7 @@
 	pagesz = menu->nrows * menu->ncols;
 	pagenr = menu->selected / pagesz;
 
+	style = MLK__STYLE(menu, mlk_gridmenu_style);
 	font = style_font(menu->style);
 
 	for (size_t i = 0; i < pagesz; ++i) {
@@ -203,13 +201,13 @@
 		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));
+		x = (int)(menu->x + style->padding + (c * menu->eltw) + (c * menu->spacew));
+		y = (int)(menu->y + style->padding + (r * menu->elth) + (r * menu->spaceh));
 
 		if (i == menu->selected % pagesz)
-			color = STYLE_GET(menu->style, text_selected_color);
+			color = style->selected_color;
 		else
-			color = STYLE_GET(menu->style, text_color);
+			color = 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));
@@ -228,18 +226,13 @@
 
 struct mlk_gridmenu_style mlk_gridmenu_style = {
 	.padding                = 10,
-	.text_color             = 0x000000ff,
-	.text_selected_color    = 0x328ca7ff,
-	.draw                   = draw
+	.text_color             = MLK_UI_COLOR_TEXT,
+	.selected_color         = MLK_UI_COLOR_SELECTED
 };
 
-void
-mlk_gridmenu_init(struct mlk_gridmenu *menu)
-{
-	assert(menu);
-
-	STYLE_INVOKE(menu->style, init, menu);
-}
+struct mlk_gridmenu_delegate mlk_gridmenu_delegate = {
+	.draw                   = draw
+};
 
 int
 mlk_gridmenu_ok(const struct mlk_gridmenu *menu)
@@ -285,7 +278,7 @@
 {
 	assert(mlk_gridmenu_ok(menu));
 
-	STYLE_INVOKE(menu->style, update, menu, ticks);
+	MLK__DELEGATE_INVOKE(menu->delegate, mlk_gridmenu_delegate, update, menu, ticks);
 }
 
 void
@@ -293,13 +286,5 @@
 {
 	assert(menu);
 
-	STYLE_INVOKE(menu->style, draw, menu);
+	MLK__DELEGATE_INVOKE(menu->delegate, mlk_gridmenu_delegate, draw, menu);
 }
-
-void
-mlk_gridmenu_finish(struct mlk_gridmenu *menu)
-{
-	assert(menu);
-
-	STYLE_INVOKE(menu->style, finish, menu);
-}
--- a/libmlk-ui/mlk/ui/gridmenu.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/gridmenu.h	Thu Mar 02 21:36:43 2023 +0100
@@ -31,15 +31,16 @@
 struct mlk_gridmenu;
 
 struct mlk_gridmenu_style {
-	void *data;
 	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 *);
-	void (*finish)(struct mlk_gridmenu_style *, struct mlk_gridmenu *);
+	unsigned long selected_color;
+};
+
+struct mlk_gridmenu_delegate {
+	void *data;
+	void (*update)(struct mlk_gridmenu_delegate *, struct mlk_gridmenu *, unsigned int);
+	void (*draw)(struct mlk_gridmenu_delegate *, const struct mlk_gridmenu *);
 };
 
 struct mlk_gridmenu {
@@ -52,6 +53,7 @@
 	unsigned int nrows;
 	unsigned int ncols;
 	struct mlk_gridmenu_style *style;
+	struct mlk_gridmenu_delegate *delegate;
 
 	/* private */
 	unsigned int eltw;      /* maximum entry label width */
@@ -61,12 +63,10 @@
 };
 
 extern struct mlk_gridmenu_style mlk_gridmenu_style;
+extern struct mlk_gridmenu_delegate mlk_gridmenu_delegate;
 
 MLK_CORE_BEGIN_DECLS
 
-void
-mlk_gridmenu_init(struct mlk_gridmenu *);
-
 int
 mlk_gridmenu_ok(const struct mlk_gridmenu *);
 
@@ -82,9 +82,6 @@
 void
 mlk_gridmenu_draw(const struct mlk_gridmenu *);
 
-void
-mlk_gridmenu_finish(struct mlk_gridmenu *);
-
 MLK_CORE_END_DECLS
 
 #endif /* !MLK_UI_GRIDMENU_H */
--- a/libmlk-ui/mlk/ui/label.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/label.c	Thu Mar 02 21:36:43 2023 +0100
@@ -19,58 +19,68 @@
 #include <assert.h>
 #include <string.h>
 
+#include <mlk/core/err.h>
 #include <mlk/core/font.h>
-#include <mlk/core/panic.h>
 #include <mlk/core/texture.h>
+#include <mlk/core/trace.h>
 
+#include "align.h"
 #include "label.h"
 #include "ui.h"
-
-#define STYLE_INVOKE(s, f, ...)                                                 \
-do {                                                                            \
-        if (s && s->f)                                                          \
-                s->f(s, __VA_ARGS__);                                           \
-        else if (mlk_label_style.f)                                             \
-                mlk_label_style.f(s ? s : &mlk_label_style, __VA_ARGS__);       \
-} while (0)
+#include "ui_p.h"
 
 static inline struct mlk_font *
-style_font(struct mlk_label_style *style)
+style_font(const struct mlk_label *label)
 {
-	if (style && style->text_font)
-		return style->text_font;
+	const struct mlk_label_style *style = MLK__STYLE(label, mlk_label_style);
+
+	if (style->font)
+		return style->font;
 
 	return mlk_ui_fonts[MLK_UI_FONT_INTERFACE];
 }
 
 static void
-draw(struct mlk_label_style *style, const struct mlk_label *label)
+delegate_query(struct mlk_label_delegate *delegate, const struct mlk_label *label, unsigned int *w, unsigned *h)
 {
+	(void)delegate;
+
 	struct mlk_font *font;
-	struct mlk_texture tex;
 	int err;
 
-	font = style_font(style);
+	font = style_font(label);
+
+	if ((err = mlk_font_query(font, label->text, w, h)) < 0)
+		mlk_tracef("mlk_font_query: %s", mlk_err_string(err));
+}
+
+static void
+delegate_draw(struct mlk_label_delegate *delegate, const struct mlk_label *label)
+{
+	(void)delegate;
 
-	if ((err = mlk_font_render(font, &tex, label->text, style->text_color)) < 0)
-		mlk_panic(err);
+	const struct mlk_label_style *style = MLK__STYLE(label, mlk_label_style);
 
-	mlk_texture_draw(&tex, label->x, label->y);
-	mlk_texture_finish(&tex);
+	mlk_ui_draw_text(
+		MLK_ALIGN_NONE,
+		style_font(label),
+		style->color,
+		label->text,
+		label->x,
+		label->y,
+		0,
+		0
+	);
 }
 
 struct mlk_label_style mlk_label_style = {
-	.text_color     = 0x000000ff,
-	.draw           = draw
+	.color  = MLK_UI_COLOR_TEXT
 };
 
-void
-mlk_label_init(struct mlk_label *label)
-{
-	assert(mlk_label_ok(label));
-
-	STYLE_INVOKE(label->style, init, label);
-}
+struct mlk_label_delegate mlk_label_delegate = {
+	.query  = delegate_query,
+	.draw   = delegate_draw
+};
 
 int
 mlk_label_ok(const struct mlk_label *label)
@@ -83,15 +93,15 @@
 {
 	assert(mlk_label_ok(label));
 
-	struct mlk_font *font;
-	int err;
-
-	font = style_font(label->style);
+	MLK__DELEGATE_INVOKE(label->delegate, mlk_label_delegate, query, label, w, h);
+}
 
-	if ((err = mlk_font_query(font, label->text, w, h)) < 0)
-		return err;
+void
+mlk_label_update(struct mlk_label *label, unsigned int ticks)
+{
+	assert(mlk_label_ok(label));
 
-	return err;
+	MLK__DELEGATE_INVOKE(label->delegate, mlk_label_delegate, update, label, ticks);
 }
 
 void
@@ -99,13 +109,5 @@
 {
 	assert(mlk_label_ok(label));
 
-	STYLE_INVOKE(label->style, draw, label);
+	MLK__DELEGATE_INVOKE(label->delegate, mlk_label_delegate, draw, label);
 }
-
-void
-mlk_label_finish(struct mlk_label *label)
-{
-	assert(mlk_label_ok(label));
-
-	STYLE_INVOKE(label->style, finish, label);
-}
--- a/libmlk-ui/mlk/ui/label.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/label.h	Thu Mar 02 21:36:43 2023 +0100
@@ -25,28 +25,29 @@
 struct mlk_label;
 
 struct mlk_label_style {
+	unsigned long color;
+	struct mlk_font *font;
+};
+
+struct mlk_label_delegate {
 	void *data;
-	unsigned long text_color;
-	struct mlk_font *text_font;
-	void (*init)(struct mlk_label_style *, struct mlk_label *);
-	void (*update)(struct mlk_label_style *, struct mlk_label *, unsigned int);
-	void (*draw)(struct mlk_label_style *, const struct mlk_label *);
-	void (*finish)(struct mlk_label_style *, struct mlk_label *);
+	void (*query)(struct mlk_label_delegate *, const struct mlk_label *, unsigned int *, unsigned *);
+	void (*update)(struct mlk_label_delegate *, struct mlk_label *, unsigned int);
+	void (*draw)(struct mlk_label_delegate *, const struct mlk_label *);
 };
 
 struct mlk_label {
 	int x, y;
 	const char *text;
 	struct mlk_label_style *style;
+	struct mlk_label_delegate *delegate;
 };
 
 extern struct mlk_label_style mlk_label_style;
+extern struct mlk_label_delegate mlk_label_delegate;
 
 MLK_CORE_BEGIN_DECLS
 
-void
-mlk_label_init(struct mlk_label *);
-
 int
 mlk_label_ok(const struct mlk_label *);
 
@@ -54,10 +55,10 @@
 mlk_label_query(const struct mlk_label *, unsigned int *, unsigned int *);
 
 void
-mlk_label_draw(const struct mlk_label *);
+mlk_label_update(struct mlk_label *, unsigned int ticks);
 
 void
-mlk_label_finish(struct mlk_label *);
+mlk_label_draw(const struct mlk_label *);
 
 MLK_CORE_END_DECLS
 
--- a/libmlk-ui/mlk/ui/notify.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/notify.c	Thu Mar 02 21:36:43 2023 +0100
@@ -21,17 +21,20 @@
 #include <string.h>
 
 #include <mlk/core/font.h>
+#include <mlk/core/painter.h>
 #include <mlk/core/texture.h>
 #include <mlk/core/trace.h>
+#include <mlk/core/util.h>
 #include <mlk/core/window.h>
 
 #include "align.h"
-#include "frame.h"
-#include "label.h"
 #include "notify.h"
+#include "ui.h"
 
-#define WIDTH   (mlk_window.w / 3)
+#define WIDTH   (mlk_window.w / 2.5)
 #define HEIGHT  (mlk_window.h / 10)
+#define MAX     (4)
+#define FONT    (mlk_notify_style.text_font ? mlk_notify_style.text_font : mlk_ui_fonts[MLK_UI_FONT_INTERFACE])
 
 struct geo {
 	const struct mlk_theme *theme;
@@ -47,31 +50,23 @@
 	int body_y;
 };
 
-static void draw(const struct mlk_notify *, size_t);
-
-static const struct mlk_notify_system default_system = {
-	.draw = draw
-};
-static const struct mlk_notify_system *system = &default_system;
-static struct mlk_notify stack[MLK_NOTIFY_MAX];
-static size_t stacksz;
+static struct mlk_notify stack[MAX];
 
 static void
 geometry(struct geo *geo, const struct mlk_notify *n, size_t index)
 {
-#if 0
+	struct mlk_font *font;
 	int x, y;
 
-	/* Determine theme. */
-	geo->theme = system->theme ? system->theme : &mlk_theme;
-
 	/* Determine notification position. */
-	x  = mlk_window.w - geo->theme->padding;
+	x  = mlk_window.w - mlk_notify_style.padding;
 	x -= WIDTH;
 
-	y  = geo->theme->padding * (index + 1);;
+	y  = mlk_notify_style.padding * (index + 1);
 	y += HEIGHT * index;
 
+	font = mlk_ui_fonts[MLK_UI_FONT_INTERFACE];
+
 	/* Content frame. */
 	geo->frame_x = x;
 	geo->frame_y = y;
@@ -81,36 +76,36 @@
 	/* Align icon at the left center. */
 	if (n->icon->h >= HEIGHT) {
 		mlk_tracef("notification icon is too large: %u > %u", n->icon->h, HEIGHT);
-		geo->icon_x = x + geo->theme->padding;
-		geo->icon_y = y + geo->theme->padding;
+		geo->icon_x = x + mlk_notify_style.padding;
+		geo->icon_y = y + mlk_notify_style.padding;
 	} else {
 		mlk_align(MLK_ALIGN_LEFT, &geo->icon_x, &geo->icon_y, n->icon->w, n->icon->h, x, y, WIDTH, HEIGHT);
 		geo->icon_x += geo->icon_y - y;
 	}
 
 	/* Align title to the right of the icon at the same y coordinate. */
-	geo->title_x  = geo->icon_x + n->icon->w + geo->theme->padding;
-	geo->title_y  = geo->icon_y;
-	geo->title_y -= mlk_font_height(geo->theme->fonts[MLK_THEME_FONT_INTERFACE]) / 2;
+	geo->title_x  = geo->icon_x + n->icon->w + mlk_notify_style.padding;
+	geo->title_y  = geo->icon_y + (mlk_notify_style.padding / 2);
+	geo->title_y -= mlk_font_height(font) / 2;
 
 	/* Align body so it ends at the end of the icon. */
 	geo->body_x  = geo->title_x;
-	geo->body_y  = geo->icon_y + n->icon->h;
-	geo->body_y -= mlk_font_height(geo->theme->fonts[MLK_THEME_FONT_INTERFACE]) / 2;
-#endif
+	geo->body_y  = geo->icon_y + n->icon->h - (mlk_notify_style.padding / 2);
+	geo->body_y -= mlk_font_height(font) / 2;
 }
 
 static void
 draw_frame(const struct geo *geo)
 {
-	const struct mlk_frame f = {
-		.x = geo->frame_x,
-		.y = geo->frame_y,
-		.w = geo->frame_w,
-		.h = geo->frame_h
-	};
-
-	mlk_frame_draw(&f);
+	mlk_painter_set_color(mlk_notify_style.border_color);
+	mlk_painter_draw_rectangle(geo->frame_x, geo->frame_y, geo->frame_w, geo->frame_h);
+	mlk_painter_set_color(mlk_notify_style.bg_color);
+	mlk_painter_draw_rectangle(
+		geo->frame_x +  mlk_notify_style.border_size,
+		geo->frame_y +  mlk_notify_style.border_size,
+		geo->frame_w - (mlk_notify_style.border_size * 2),
+		geo->frame_h - (mlk_notify_style.border_size * 2)
+	);
 }
 
 static void
@@ -122,35 +117,35 @@
 static void
 draw_title(const struct geo *geo, const struct mlk_notify *n)
 {
-#if 0
-	const struct mlk_label l = {
-		.x = geo->title_x,
-		.y = geo->title_y,
-		.text = n->title,
-		.flags = MLK_LABEL_FLAGS_SHADOW
-	};
-
-	mlk_label_draw(&l);
-#endif
+	mlk_ui_draw_text(
+		MLK_ALIGN_NONE,
+		FONT,
+		mlk_notify_style.text_color,
+		n->title,
+		geo->title_x,
+		geo->title_y,
+		0,
+		0
+	);
 }
 
 static void
 draw_body(const struct geo *geo, const struct mlk_notify *n)
 {
-#if 0
-	const struct mlk_label l = {
-		.x = geo->body_x,
-		.y = geo->body_y,
-		.text = n->body,
-		.flags = MLK_LABEL_FLAGS_SHADOW
-	};
-
-	mlk_label_draw(&l);
-#endif
+	mlk_ui_draw_text(
+		MLK_ALIGN_NONE,
+		FONT,
+		mlk_notify_style.text_color,
+		n->body,
+		geo->body_x,
+		geo->body_y,
+		0,
+		0
+	);
 }
 
 static void
-draw(const struct mlk_notify *n, size_t index)
+delegate_draw(const struct mlk_notify *n, size_t index)
 {
 	struct geo geo;
 
@@ -163,6 +158,20 @@
 	draw_body(&geo, n);
 }
 
+struct mlk_notify_style mlk_notify_style = {
+	.bg_color       = MLK_UI_COLOR_BG,
+	.border_color   = MLK_UI_COLOR_BORDER,
+	.border_size    = 2,
+	.delay          = 5000,
+	.padding        = 10
+};
+
+struct mlk_notify_delegate mlk_notify_delegate = {
+	.stack          = stack,
+	.stacksz        = MLK_UTIL_SIZE(stack),
+	.draw           = delegate_draw
+};
+
 void
 mlk_notify(const struct mlk_texture *icon, const char *title, const char *body)
 {
@@ -172,11 +181,13 @@
 
 	struct mlk_notify *n;
 
-	if (stacksz >= MLK_NOTIFY_MAX) {
-		memmove(&stack[0], &stack[1], sizeof (stack[0]) - MLK_NOTIFY_MAX - 1);
-		n = &stack[MLK_NOTIFY_MAX - 1];
+	if (mlk_notify_delegate.length >= mlk_notify_delegate.stacksz) {
+		memmove(&mlk_notify_delegate.stack[0],
+			&mlk_notify_delegate.stack[1],
+			sizeof (mlk_notify_delegate.stack[0]) - mlk_notify_delegate.stacksz - 1);
+		n = &mlk_notify_delegate.stack[mlk_notify_delegate.length - 1];
 	} else
-		n = &stack[stacksz++];
+		n = &mlk_notify_delegate.stack[mlk_notify_delegate.length++];
 
 	memset(n, 0, sizeof (*n));
 	n->icon = icon;
@@ -189,24 +200,19 @@
 {
 	struct mlk_notify *n;
 
-	for (size_t i = 0; i < stacksz; ++i) {
-		n = &stack[i];
+	for (size_t i = 0; i < mlk_notify_delegate.length; ++i) {
+		n = &mlk_notify_delegate.stack[i];
 		n->elapsed += ticks;
 
-		if (n->elapsed >= MLK_NOTIFY_TIMEOUT_DEFAULT)
-			memmove(n, n + 1, sizeof (*n) * (--stacksz - i));
+		if (n->elapsed >= mlk_notify_style.delay)
+			memmove(n, n + 1, sizeof (*n) * (--mlk_notify_delegate.length - i));
 	}
 }
 
 void
 mlk_notify_draw(void)
 {
-	for (size_t i = 0; i < stacksz; ++i)
-		system->draw(&stack[i], i);
+	for (size_t i = 0; i < mlk_notify_delegate.length; ++i)
+		if (mlk_notify_delegate.draw)
+			mlk_notify_delegate.draw(&mlk_notify_delegate.stack[i], i);
 }
-
-void
-mlk_notify_set_system(const struct mlk_notify_system *sys)
-{
-	system = sys ? sys : &default_system;
-}
--- a/libmlk-ui/mlk/ui/notify.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/notify.h	Thu Mar 02 21:36:43 2023 +0100
@@ -23,12 +23,8 @@
 
 #include <mlk/core/core.h>
 
-/* TODO: make this configurable at runtime. */
-#define MLK_NOTIFY_MAX                  (4)
-#define MLK_NOTIFY_TIMEOUT_DEFAULT      (5000)
-
+struct mlk_font;
 struct mlk_texture;
-struct mlk_theme;
 
 struct mlk_notify {
 	const struct mlk_texture *icon;
@@ -37,11 +33,27 @@
 	unsigned int elapsed;
 };
 
-struct mlk_notify_system {
-	struct mlk_theme *theme;
-	void (*draw)(const struct mlk_notify *, size_t);
+struct mlk_notify_style {
+	unsigned long bg_color;
+	unsigned long text_color;
+	struct mlk_font *text_font;
+	unsigned long border_color;
+	unsigned int border_size;
+	unsigned int delay;
+	unsigned int padding;
 };
 
+struct mlk_notify_delegate {
+	struct mlk_notify *stack;
+	size_t stacksz;
+	size_t length;
+	void (*update)(unsigned int ticks);
+	void (*draw)(const struct mlk_notify *notif, size_t index);
+};
+
+extern struct mlk_notify_style mlk_notify_style;
+extern struct mlk_notify_delegate mlk_notify_delegate;
+
 MLK_CORE_BEGIN_DECLS
 
 void
@@ -53,9 +65,6 @@
 void
 mlk_notify_draw(void);
 
-void
-mlk_notify_set_system(const struct mlk_notify_system *);
-
 MLK_CORE_END_DECLS
 
 #endif /*! MLK_UI_NOTIFY_H */
--- a/libmlk-ui/mlk/ui/ui.c	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/ui.c	Thu Mar 02 21:36:43 2023 +0100
@@ -16,13 +16,19 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <assert.h>
 #include <stddef.h>
+#include <string.h>
 
+#include <mlk/core/err.h>
 #include <mlk/core/font.h>
+#include <mlk/core/texture.h>
+#include <mlk/core/trace.h>
 #include <mlk/core/util.h>
 
 #include <assets/fonts/opensans-regular.h>
 
+#include "align.h"
 #include "ui.h"
 
 /* Default font catalog. */
@@ -72,6 +78,31 @@
 }
 
 void
+mlk_ui_draw_text(enum mlk_align align,
+                 struct mlk_font *font,
+                 unsigned long color,
+                 const char *text,
+                 int px,
+                 int py,
+                 unsigned int pw,
+                 unsigned int ph)
+{
+	assert(font);
+	assert(text && strlen(text) > 0);
+
+	struct mlk_texture texture;
+	int x, y, err;
+
+	if ((err = mlk_font_render(font, &texture, text, color)) < 0)
+		mlk_tracef("mlk_font_render: %s", mlk_err_string(err));
+	else {
+		mlk_align(align, &x, &y, texture.w, texture.h, px, py, pw, ph);
+		mlk_texture_draw(&texture, x, y);
+		mlk_texture_finish(&texture);
+	}
+}
+
+void
 mlk_ui_finish(void)
 {
 }
--- a/libmlk-ui/mlk/ui/ui.h	Thu Mar 02 08:54:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/ui.h	Thu Mar 02 21:36:43 2023 +0100
@@ -23,6 +23,16 @@
 
 MLK_CORE_BEGIN_DECLS
 
+/* TODO: make this a global variable to allow modification of default theme. */
+/* https://lospec.com/palette-list/duel */
+#define MLK_UI_COLOR_TEXT       0x222323ff
+#define MLK_UI_COLOR_SELECTED   0x55b67dff
+#define MLK_UI_COLOR_BG         0xf5f7faff
+#define MLK_UI_COLOR_BORDER     0xcdd2daff
+#define MLK_UI_COLOR_DEBUG      0xe45c5fff
+
+enum mlk_align;
+
 struct mlk_font;
 
 enum mlk_ui_font {
@@ -35,6 +45,17 @@
 int
 mlk_ui_init(void);
 
+/* TODO: probably better somewhere else? */
+void
+mlk_ui_draw_text(enum mlk_align align,
+                 struct mlk_font *font,
+                 unsigned long color,
+                 const char *text,
+                 int x,
+                 int y,
+                 unsigned int w,
+                 unsigned int h);
+
 void
 mlk_ui_finish(void);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-ui/mlk/ui/ui_p.h	Thu Mar 02 21:36:43 2023 +0100
@@ -0,0 +1,33 @@
+/*
+ * ui_p.h -- private helpers for libmlk-ui
+ *
+ * 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_UI_P_H
+#define MLK_UI_P_H
+
+#define MLK__DELEGATE_INVOKE(d, def, f, ...)    \
+do {                                            \
+        if (d && d->f)                          \
+                d->f(d, __VA_ARGS__);           \
+        else if (def.f)                         \
+                def.f(&def, __VA_ARGS__);       \
+} while (0)
+
+#define MLK__STYLE(w, d)                        \
+        (w->style ? w->style : &d)
+
+#endif /* !MLK_UI_P_H */