changeset 534:88e9bd420a28

rpg: add delegate/style support to mlk_message
author David Demelier <markand@malikania.fr>
date Sun, 05 Mar 2023 10:51:25 +0100
parents 79afc6d5cc7e
children 7d6a879901e0
files examples/example-action/dialog.c examples/example-action/dialog.h examples/example-action/example-action.c examples/example-message/example-message.c libmlk-rpg/mlk/rpg/message.c libmlk-rpg/mlk/rpg/message.h libmlk-ui/mlk/ui/label.h libmlk-ui/mlk/ui/ui.h
diffstat 8 files changed, 320 insertions(+), 228 deletions(-) [+]
line wrap: on
line diff
--- a/examples/example-action/dialog.c	Sat Mar 04 20:38:00 2023 +0100
+++ b/examples/example-action/dialog.c	Sun Mar 05 10:51:25 2023 +0100
@@ -38,7 +38,7 @@
 {
 	struct dialog *dlg = act->data;
 
-	message_handle(&dlg->msg, ev);
+	mlk_message_handle(&dlg->msg, ev);
 }
 
 static int
@@ -46,7 +46,7 @@
 {
 	struct dialog *dlg = act->data;
 
-	return message_update(&dlg->msg, ticks);
+	return mlk_message_update(&dlg->msg, ticks);
 }
 
 static void
@@ -54,7 +54,7 @@
 {
 	struct dialog *dlg = act->data;
 
-	message_draw(&dlg->msg);
+	mlk_message_draw(&dlg->msg);
 }
 
 static void
@@ -62,7 +62,7 @@
 {
 	struct dialog *dlg = act->data;
 
-	if ((dlg->msg.flags & MESSAGE_FLAGS_QUESTION) && dlg->response)
+	if ((dlg->msg.flags & MLK_MESSAGE_FLAGS_QUESTION) && dlg->response)
 		dlg->response(dlg, dlg->msg.index);
 }
 
@@ -77,8 +77,8 @@
 	dlg->msg.delay = QMD;
 	dlg->msg.spacing = QMS;
 
-	message_start(&dlg->msg);
-	message_query(&dlg->msg, NULL, &dlg->msg.h);
+	mlk_message_start(&dlg->msg);
+	mlk_message_query(&dlg->msg, NULL, &dlg->msg.h);
 
 	dlg->action.data = dlg;
 	dlg->action.handle = handle;
--- a/examples/example-action/dialog.h	Sat Mar 04 20:38:00 2023 +0100
+++ b/examples/example-action/dialog.h	Sun Mar 05 10:51:25 2023 +0100
@@ -27,7 +27,7 @@
 	/* public */
 	void *data;
 	void (*response)(struct dialog *, unsigned int);
-	struct message msg;
+	struct mlk_message msg;
 
 	/* private */
 	struct mlk_action action;
--- a/examples/example-action/example-action.c	Sat Mar 04 20:38:00 2023 +0100
+++ b/examples/example-action/example-action.c	Sun Mar 05 10:51:25 2023 +0100
@@ -79,7 +79,7 @@
 static struct dialog script_left[] = {
 	{
 		.msg = {
-			.flags = MESSAGE_FLAGS_FADEIN,
+			.flags = MLK_MESSAGE_FLAGS_FADEIN,
 			.linesz = 1,
 			.lines = (const char *[]) {
 				"Welcome to this game."
@@ -99,7 +99,7 @@
 	{
 		.response = script_left_response,
 		.msg = {
-			.flags = MESSAGE_FLAGS_QUESTION,
+			.flags = MLK_MESSAGE_FLAGS_QUESTION,
 			.linesz = 2,
 			.lines = (const char *[]) {
 				"Of course I am",
@@ -112,7 +112,7 @@
 static struct dialog script_left_responses[] = {
 	{
 		.msg = {
-			.flags = MESSAGE_FLAGS_FADEOUT,
+			.flags = MLK_MESSAGE_FLAGS_FADEOUT,
 			.linesz = 1,
 			.lines = (const char *[]) {
 				 "Don't be so confident"
@@ -121,7 +121,7 @@
 	},
 	{
 		.msg = {
-			.flags = MESSAGE_FLAGS_FADEOUT,
+			.flags = MLK_MESSAGE_FLAGS_FADEOUT,
 			.linesz = 1,
 			.lines = (const char *[]) {
 				 "Nevermind, I'll do it myself"
@@ -137,7 +137,7 @@
 static struct dialog script_right[] = {
 	{
 		.msg = {
-			.flags = MESSAGE_FLAGS_FADEIN,
+			.flags = MLK_MESSAGE_FLAGS_FADEIN,
 			.linesz = 1,
 			.lines = (const char *[]) {
 				"Why did you select this chest?"
@@ -147,7 +147,7 @@
 	{
 		.response = script_right_response,
 		.msg = {
-			.flags = MESSAGE_FLAGS_QUESTION,
+			.flags = MLK_MESSAGE_FLAGS_QUESTION,
 			.linesz = 2,
 			.lines = (const char *[]) {
 				"Because I think there was some gold",
@@ -160,7 +160,7 @@
 static struct dialog script_right_responses[] = {
 	{
 		.msg = {
-			.flags = MESSAGE_FLAGS_FADEOUT,
+			.flags = MLK_MESSAGE_FLAGS_FADEOUT,
 			.linesz = 1,
 			.lines = (const char *[]) {
 				"Go away!"
@@ -169,7 +169,7 @@
 	},
 	{
 		.msg = {
-			.flags = MESSAGE_FLAGS_FADEOUT,
+			.flags = MLK_MESSAGE_FLAGS_FADEOUT,
 			.linesz = 1,
 			.lines = (const char *[]) {
 				"Install OpenBSD then"
--- a/examples/example-message/example-message.c	Sat Mar 04 20:38:00 2023 +0100
+++ b/examples/example-message/example-message.c	Sun Mar 05 10:51:25 2023 +0100
@@ -58,7 +58,7 @@
 		mlk_game_quit();
 		break;
 	default:
-		message_handle(st->data, ev);
+		mlk_message_handle(st->data, ev);
 		break;
 	}
 }
@@ -66,7 +66,7 @@
 static void
 update(struct mlk_state *st, unsigned int ticks)
 {
-	if (message_update(st->data, ticks))
+	if (mlk_message_update(st->data, ticks))
 		mlk_game_quit();
 }
 
@@ -75,12 +75,12 @@
 {
 	mlk_painter_set_color(MLK_EXAMPLE_BG);
 	mlk_painter_clear();
-	message_draw(st->data);
+	mlk_message_draw(st->data);
 	mlk_painter_present();
 }
 
 static void
-run(struct message *msg)
+run(struct mlk_message *msg)
 {
 	struct mlk_state state = {
 		.data = msg,
@@ -89,7 +89,7 @@
 		.draw = draw
 	};
 
-	message_start(msg);
+	mlk_message_start(msg);
 
 	mlk_game_init();
 	mlk_game_push(&state);
@@ -115,16 +115,15 @@
 		"Vertical spacing is automatically computed.",
 		"You need to press <Enter> to close it.",
 	};
-	struct message msg = {
+	struct mlk_message msg = {
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.spacing = 12,
 		.lines = text,
 		.linesz = 3
 	};
 
-	message_query(&msg, NULL, &msg.h);
+	mlk_message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -136,17 +135,16 @@
 		"It will disappear in a few seconds.",
 		"You can still press <Enter> to close it quicker."
 	};
-	struct message msg = {
+	struct mlk_message msg = {
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.timeout = MESSAGE_TIMEOUT_DEFAULT,
 		.lines = text,
 		.linesz = 3,
-		.flags = MESSAGE_FLAGS_AUTOMATIC
+		.flags = MLK_MESSAGE_FLAGS_AUTOMATIC
 	};
 
-	message_query(&msg, NULL, &msg.h);
+	mlk_message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -156,17 +154,16 @@
 	const char * const text[] = {
 		"This message will fade in."
 	};
-	struct message msg = {
+	struct mlk_message msg = {
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.delay = MESSAGE_DELAY_DEFAULT,
 		.lines = text,
 		.linesz = 1,
-		.flags = MESSAGE_FLAGS_FADEIN
+		.flags = MLK_MESSAGE_FLAGS_FADEIN
 	};
 
-	message_query(&msg, NULL, &msg.h);
+	mlk_message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -176,17 +173,16 @@
 	const char * const text[] = {
 		"This message will fade out."
 	};
-	struct message msg = {
+	struct mlk_message msg = {
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.delay = MESSAGE_DELAY_DEFAULT,
 		.lines = text,
 		.linesz = 1,
-		.flags = MESSAGE_FLAGS_FADEOUT
+		.flags = MLK_MESSAGE_FLAGS_FADEOUT
 	};
 
-	message_query(&msg, NULL, &msg.h);
+	mlk_message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -196,17 +192,16 @@
 	const char * const text[] = {
 		"This message will fade in and out."
 	};
-	struct message msg = {
+	struct mlk_message msg = {
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.delay = MESSAGE_DELAY_DEFAULT,
 		.lines = text,
 		.linesz = 1,
-		.flags = MESSAGE_FLAGS_FADEIN | MESSAGE_FLAGS_FADEOUT
+		.flags = MLK_MESSAGE_FLAGS_FADEIN | MLK_MESSAGE_FLAGS_FADEOUT
 	};
 
-	message_query(&msg, NULL, &msg.h);
+	mlk_message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -217,16 +212,16 @@
 		"Okay, I've understood.",
 		"Nevermind, I'll do it again."
 	};
-	struct message msg = {
+	struct mlk_message msg = {
 		.x = MX,
 		.y = MY,
 		.w = MW,
 		.lines = text,
 		.linesz = 2,
-		.flags = MESSAGE_FLAGS_QUESTION
+		.flags = MLK_MESSAGE_FLAGS_QUESTION
 	};
 
-	message_query(&msg, NULL, &msg.h);
+	mlk_message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -241,13 +236,12 @@
 		"This one is small here."
 	};
 
-	struct message msg = {
+	struct mlk_message msg = {
 		.x = x,
 		.y = y,
 		.w = w,
 		.h = h,
-		.delay = MESSAGE_DELAY_DEFAULT,
-		.flags = MESSAGE_FLAGS_FADEIN | MESSAGE_FLAGS_FADEOUT,
+		.flags = MLK_MESSAGE_FLAGS_FADEIN | MLK_MESSAGE_FLAGS_FADEOUT,
 		.lines = text,
 		.linesz = 1
 	};
@@ -262,7 +256,7 @@
 		"This one is too small in height and will emit a warning.",
 		"Because this line will be incomplete."
 	};
-	struct message msg = {
+	struct mlk_message msg = {
 		.x = MX,
 		.y = MY,
 		.w = MW,
@@ -280,7 +274,7 @@
 	const char * const text[] = {
 		"This one is too small in width."
 	};
-	struct message msg = {
+	struct mlk_message msg = {
 		.x = MX,
 		.y = MY,
 		.w = 160,
@@ -295,28 +289,26 @@
 static void
 custom(void)
 {
-#if 0
 	const char * const text[] = {
 		"This one will destroy your eyes.",
 		"Because it use a terrible custom theme."
 	};
-	struct mlk_theme theme;
-	struct message msg = {
+	struct mlk_message_style style = mlk_message_style;
+	struct mlk_message msg = {
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.h = MH,
 		.lines = text,
 		.linesz = 2,
-		.theme = &theme
+		.style = &style
 	};
 
-	/* Borrow default theme and change its frame drawing. */
-	theme.draw_frame = my_draw_frame;
-	theme.colors[MLK_THEME_COLOR_NORMAL] = 0x0000ffff;
+	style.bg_color = 0xf85d80ff;
+	style.border_color = 0xd94a69ff;
+	style.text_color = 0xffffffff;
 
+	mlk_message_query(&msg, NULL, &msg.h);
 	run(&msg);
-#endif
 }
 
 static void
--- a/libmlk-rpg/mlk/rpg/message.c	Sat Mar 04 20:38:00 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/message.c	Sun Mar 05 10:51:25 2023 +0100
@@ -30,125 +30,123 @@
 #include <mlk/core/util.h>
 
 #include <mlk/ui/align.h>
-#include <mlk/ui/frame.h>
-#include <mlk/ui/label.h>
+#include <mlk/ui/ui.h>
+#include <mlk/ui/ui_p.h>
 
 #include "message.h"
 
-static void
-draw_frame(const struct message *msg)
+static inline struct mlk_font *
+style_font(const struct mlk_message *message)
+{
+	const struct mlk_message_style *style = MLK__STYLE(message, mlk_message_style);
+
+	if (style->text_font)
+		return style->text_font;
+
+	return mlk_ui_fonts[MLK_UI_FONT_INTERFACE];
+}
+
+static unsigned int
+min_width(const struct mlk_message *msg)
 {
 	assert(msg);
 
-	struct mlk_frame frame = {
-		.w = msg->w,
-		.h = msg->h
-	};
-
-	mlk_frame_draw(&frame);
-}
-
-static inline unsigned int
-min_width(const struct message *msg)
-{
-#if 0
-	assert(msg);
-
+	const struct mlk_message_style *style = MLK__STYLE(msg, mlk_message_style);
+	struct mlk_font *font;
 	unsigned int maxw = 0, w = 0;
 	int err;
 
+	font = style_font(msg);
+
 	for (size_t i = 0; i < msg->linesz; ++i) {
 		if (!msg->lines[i])
 			continue;
-		if ((err = mlk_font_query(THEME(msg)->fonts[MLK_THEME_FONT_INTERFACE], msg->lines[i], &w, NULL)) < 0)
-			mlk_panic(err);
+		if ((err = mlk_font_query(font, msg->lines[i], &w, NULL)) < 0)
+			return err;
 		if (w > maxw)
 			maxw = w;
 	}
 
-	return (THEME(msg)->padding * 2) + maxw;
-#endif
-	return 0;
+	return (style->padding * 2) + maxw;
 }
 
-static inline unsigned int
-min_height(const struct message *msg)
+static unsigned int
+min_height(const struct mlk_message *msg)
 {
 	assert(msg);
 
-#if 0
-	const struct mlk_theme *th = THEME(msg);
-	const unsigned int lh  = mlk_font_height(th->fonts[MLK_THEME_FONT_INTERFACE]);
+	const struct mlk_message_style *style = MLK__STYLE(msg, mlk_message_style);
+	struct mlk_font *font;
+	unsigned int lh;
 
-	return (th->padding * 2) + (msg->linesz * lh) + ((msg->linesz - 1) * msg->spacing);
-#endif
-	return 0;
+	font = style_font(msg);
+	lh = mlk_font_height(font);
+
+	return (style->padding * 2) + (msg->linesz * lh) + ((msg->linesz - 1) * style->padding);
 }
 
 static void
-draw_lines(const struct message *msg)
+draw_frame(const struct mlk_message *msg)
 {
-#if 0
-	const struct mlk_theme *theme = THEME(msg);
-	struct mlk_label label;
-	unsigned int lw, lh;
-	int err;
+	const struct mlk_message_style *style = MLK__STYLE(msg, mlk_message_style);
+
+	mlk_painter_set_color(style->border_color);
+	mlk_painter_draw_rectangle(0, 0, msg->w, msg->h);
+	mlk_painter_set_color(style->bg_color);
+	mlk_painter_draw_rectangle(
+		style->border_size,
+		style->border_size,
+		msg->w - (style->border_size * 2),
+		msg->h - (style->border_size * 2)
+	);
+}
+
+static void
+draw_lines(const struct mlk_message *msg)
+{
+	const struct mlk_message_style *style;
+	struct mlk_font *font;
+	struct mlk_texture texture;
+	unsigned long color;
+	int err, x, y;
+
+	style = MLK__STYLE(msg, mlk_message_style);
+	font = style_font(msg);
 
 	for (size_t i = 0; i < msg->linesz; ++i) {
 		if (!msg->lines[i])
 			continue;
-		if ((err = mlk_font_query(theme->fonts[MLK_THEME_FONT_INTERFACE], msg->lines[i], &lw, &lh)) < 0)
-			mlk_panic(err);
+
+		if ((msg->flags & MLK_MESSAGE_FLAGS_QUESTION) && msg->index == (unsigned int)i)
+			color = style->selected_color;
+		else
+			color = style->text_color;
 
-		label.x = theme->padding;
-		label.y = theme->padding + (i * (lh + msg->spacing));
-		label.text = msg->lines[i];
-		label.flags = MLK_LABEL_FLAGS_SHADOW;
+		if ((err = mlk_font_render(font, &texture, msg->lines[i], color)) < 0) {
+			mlk_tracef("%s", mlk_err_string(err));
+			continue;
+		}
 
-		if (label.x + lw > msg->w)
+		x = style->padding;
+		y = style->padding + (i * (texture.h + style->padding));
+
+		if (x + texture.w > msg->w)
 			mlk_tracef("message width too small: %u < %u", msg->w, min_width(msg));
-		if (label.y + lh > msg->h)
+		if (y + texture.h > msg->h)
 			mlk_tracef("message height too small: %u < %u", msg->h, min_height(msg));
 
-		/*
-		 * The function label_draw will use THEME_COLOR_NORMAL to draw
-		 * text and THEME_COLOR_SHADOW so if we have selected a line
-		 * we need to cheat the normal color.
-		 */
-#if 0
-		if ((msg->flags & MESSAGE_FLAGS_QUESTION) && msg->index == (unsigned int)i)
-			label.flags |= MLK_LABEL_FLAGS_SELECTED;
-		else
-			label.flags &= ~(MLK_LABEL_FLAGS_SELECTED);
-#endif
-
-		mlk_label_draw(&label);
+		mlk_texture_draw(&texture, x, y);
+		mlk_texture_finish(&texture);
 	}
-#endif
 }
 
-void
-message_start(struct message *msg)
+static int
+delegate_query(struct mlk_message_delegate *self,
+               const struct mlk_message *msg,
+               unsigned int *w,
+               unsigned int *h)
 {
-	assert(msg);
-
-	if (msg->flags & (MESSAGE_FLAGS_FADEIN|MESSAGE_FLAGS_FADEOUT))
-		assert(msg->delay > 0);
-
-	msg->elapsed = 0;
-	msg->scale = msg->flags & MESSAGE_FLAGS_FADEIN ? 0.0 : 1.0;
-	msg->state = msg->flags & MESSAGE_FLAGS_FADEIN
-	    ? MESSAGE_STATE_OPENING
-	    : MESSAGE_STATE_SHOWING;
-
-	if (msg->flags & MESSAGE_FLAGS_AUTOMATIC && msg->timeout == 0)
-		mlk_tracef("message is automatic but has zero timeout");
-}
-
-void
-message_query(const struct message *msg, unsigned int *w, unsigned int *h)
-{
-	assert(msg);
+	(void)self;
 
 	if (w)
 		*w = min_width(msg);
@@ -156,77 +154,47 @@
 		*h = min_height(msg);
 }
 
-void
-message_handle(struct message *msg, const union mlk_event *ev)
+static void
+delegate_update(struct mlk_message_delegate *self,
+                struct mlk_message *msg,
+                unsigned int ticks)
 {
-	assert(msg);
-	assert(ev);
-
-	/* Skip if the message animation hasn't complete. */
-	if (msg->state != MESSAGE_STATE_SHOWING)
-		return;
-
-	/* Only keyboard event are valid. */
-	if (ev->type != MLK_EVENT_KEYDOWN || msg->state == MESSAGE_STATE_NONE)
-		return;
+	(void)self;
 
-	switch (ev->key.key) {
-	case MLK_KEY_UP:
-		if (msg->index > 0)
-			msg->index--;
-		break;
-	case MLK_KEY_DOWN:
-		if (msg->index + 1 < msg->linesz && msg->lines[msg->index + 1])
-			msg->index++;
-		break;
-	case MLK_KEY_ENTER:
-		msg->state = msg->flags & MESSAGE_FLAGS_FADEOUT
-		    ? MESSAGE_STATE_HIDING
-		    : MESSAGE_STATE_NONE;
-		msg->elapsed = 0;
-		break;
-	default:
-		break;
-	}
-}
-
-int
-message_update(struct message *msg, unsigned int ticks)
-{
-	assert(msg);
+	const struct mlk_message_style *style = MLK__STYLE(msg, mlk_message_style);
 
 	msg->elapsed += ticks;
 
 	switch (msg->state) {
-	case MESSAGE_STATE_OPENING:
-		msg->scale = (double)msg->elapsed / (double)msg->delay;
+	case MLK_MESSAGE_STATE_OPENING:
+		msg->scale = (double)msg->elapsed / (double)style->delay;
 
 		if (msg->scale > 1)
 			msg->scale = 1;
 
-		if (msg->elapsed >= msg->delay) {
-			msg->state = MESSAGE_STATE_SHOWING;
+		if (msg->elapsed >= style->delay) {
+			msg->state = MLK_MESSAGE_STATE_SHOWING;
 			msg->elapsed = 0;
 		}
 
 		break;
-	case MESSAGE_STATE_SHOWING:
+	case MLK_MESSAGE_STATE_SHOWING:
 		/* Do automatically switch state if requested by the user. */
-		if (msg->flags & MESSAGE_FLAGS_AUTOMATIC && msg->elapsed >= msg->timeout) {
-			msg->state = msg->flags & MESSAGE_FLAGS_FADEOUT
-			    ? MESSAGE_STATE_HIDING
-			    : MESSAGE_STATE_NONE;
+		if (msg->flags & MLK_MESSAGE_FLAGS_AUTOMATIC && msg->elapsed >= style->duration) {
+			msg->state = msg->flags & MLK_MESSAGE_FLAGS_FADEOUT
+			    ? MLK_MESSAGE_STATE_HIDING
+			    : MLK_MESSAGE_STATE_NONE;
 			msg->elapsed = 0;
 		}
 
 		break;
-	case MESSAGE_STATE_HIDING:
-		msg->scale = 1 - (double)msg->elapsed / (double)msg->delay;
+	case MLK_MESSAGE_STATE_HIDING:
+		msg->scale = 1 - (double)msg->elapsed / (double)style->delay;
 
 		if (msg->scale < 0)
 			msg->scale = 0;
-		if (msg->elapsed >= msg->delay) {
-			msg->state = MESSAGE_STATE_NONE;
+		if (msg->elapsed >= style->delay) {
+			msg->state = MLK_MESSAGE_STATE_NONE;
 			msg->elapsed = 0;
 		}
 
@@ -235,13 +203,12 @@
 		break;
 	}
 
-	return msg->state == MESSAGE_STATE_NONE;
 }
 
-void
-message_draw(const struct message *msg)
+static void
+delegate_draw(struct mlk_message_delegate *self, const struct mlk_message *msg)
 {
-	assert(msg);
+	(void)self;
 
 	struct mlk_texture tex;
 	int x, y, err;
@@ -272,11 +239,114 @@
 	mlk_texture_finish(&tex);
 }
 
+struct mlk_message_style mlk_message_style = {
+	.padding        = MLK_UI_PADDING,
+	.delay          = MLK_MESSAGE_DELAY_DEFAULT,
+	.duration       = MLK_MESSAGE_DURATION_DEFAULT,
+	.bg_color       = MLK_UI_COLOR_BG,
+	.border_color   = MLK_UI_COLOR_BORDER,
+	.border_size    = MLK_UI_BORDER,
+	.text_color     = MLK_UI_COLOR_TEXT,
+	.selected_color = MLK_UI_COLOR_SELECTED
+};
+struct mlk_message_delegate mlk_message_delegate = {
+	.query          = delegate_query,
+	.update         = delegate_update,
+	.draw           = delegate_draw
+};
+
 void
-message_hide(struct message *msg)
+mlk_message_start(struct mlk_message *msg)
+{
+	assert(msg);
+
+	const struct mlk_message_style *style = MLK__STYLE(msg, mlk_message_style);
+
+	if ((msg->flags & (MLK_MESSAGE_FLAGS_FADEIN | MLK_MESSAGE_FLAGS_FADEOUT)) && style->delay == 0)
+		mlk_tracef("message has animation but zero delay");
+
+	msg->elapsed = 0;
+	msg->scale = msg->flags & MLK_MESSAGE_FLAGS_FADEIN ? 0.0 : 1.0;
+	msg->state = msg->flags & MLK_MESSAGE_FLAGS_FADEIN
+	    ? MLK_MESSAGE_STATE_OPENING
+	    : MLK_MESSAGE_STATE_SHOWING;
+
+	if (msg->flags & MLK_MESSAGE_FLAGS_AUTOMATIC && style->duration == 0)
+		mlk_tracef("message is automatic but has zero duration");
+}
+
+int
+mlk_message_query(const struct mlk_message *msg, unsigned int *w, unsigned int *h)
 {
 	assert(msg);
 
-	msg->state = MESSAGE_STATE_HIDING;
+	MLK__DELEGATE_INVOKE_RET(msg->delegate, mlk_message_delegate, query, msg, w, h);
+
+	if (w)
+		*w = 0;
+	if (h)
+		*h = 0;
+
+	return MLK_ERR_NO_SUPPORT;
+}
+
+void
+mlk_message_handle(struct mlk_message *msg, const union mlk_event *ev)
+{
+	assert(msg);
+	assert(ev);
+
+	/* Skip if the message animation hasn't complete. */
+	if (msg->state != MLK_MESSAGE_STATE_SHOWING)
+		return;
+
+	/* Only keyboard event are valid. */
+	if (ev->type != MLK_EVENT_KEYDOWN || msg->state == MLK_MESSAGE_STATE_NONE)
+		return;
+
+	switch (ev->key.key) {
+	case MLK_KEY_UP:
+		if (msg->index > 0)
+			msg->index--;
+		break;
+	case MLK_KEY_DOWN:
+		if (msg->index + 1 < msg->linesz && msg->lines[msg->index + 1])
+			msg->index++;
+		break;
+	case MLK_KEY_ENTER:
+		msg->state = msg->flags & MLK_MESSAGE_FLAGS_FADEOUT
+		    ? MLK_MESSAGE_STATE_HIDING
+		    : MLK_MESSAGE_STATE_NONE;
+		msg->elapsed = 0;
+		break;
+	default:
+		break;
+	}
+}
+
+int
+mlk_message_update(struct mlk_message *msg, unsigned int ticks)
+{
+	assert(msg);
+
+	MLK__DELEGATE_INVOKE(msg->delegate, mlk_message_delegate, update, msg, ticks);
+
+	return msg->state == MLK_MESSAGE_STATE_NONE;
+}
+
+void
+mlk_message_draw(const struct mlk_message *msg)
+{
+	assert(msg);
+
+	MLK__DELEGATE_INVOKE(msg->delegate, mlk_message_delegate, draw, msg);
+}
+
+void
+mlk_message_hide(struct mlk_message *msg)
+{
+	assert(msg);
+
+	msg->state = MLK_MESSAGE_STATE_HIDING;
 	msg->elapsed = 0;
 }
--- a/libmlk-rpg/mlk/rpg/message.h	Sat Mar 04 20:38:00 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/message.h	Sun Mar 05 10:51:25 2023 +0100
@@ -23,67 +23,94 @@
 
 #include <mlk/core/texture.h>
 
+#define MLK_MESSAGE_DELAY_DEFAULT       (150)
+#define MLK_MESSAGE_DURATION_DEFAULT    (5000)
+
 struct mlk_font;
-struct mlk_theme;
+struct mlk_message;
 
 union mlk_event;
 
-#define MESSAGE_DELAY_DEFAULT           (150)
-#define MESSAGE_TIMEOUT_DEFAULT         (5000)
+enum mlk_message_flags {
+	MLK_MESSAGE_FLAGS_AUTOMATIC     = (1 << 0),
+	MLK_MESSAGE_FLAGS_QUESTION      = (1 << 1),
+	MLK_MESSAGE_FLAGS_FADEIN        = (1 << 2),
+	MLK_MESSAGE_FLAGS_FADEOUT       = (1 << 3)
+};
 
-enum message_flags {
-	MESSAGE_FLAGS_AUTOMATIC         = (1 << 0),
-	MESSAGE_FLAGS_QUESTION          = (1 << 1),
-	MESSAGE_FLAGS_FADEIN            = (1 << 2),
-	MESSAGE_FLAGS_FADEOUT           = (1 << 3)
+enum mlk_message_state {
+	MLK_MESSAGE_STATE_NONE,
+	MLK_MESSAGE_STATE_OPENING,
+	MLK_MESSAGE_STATE_SHOWING,
+	MLK_MESSAGE_STATE_HIDING
 };
 
-enum message_state {
-	MESSAGE_STATE_NONE,
-	MESSAGE_STATE_OPENING,
-	MESSAGE_STATE_SHOWING,
-	MESSAGE_STATE_HIDING
+struct mlk_message_style {
+	unsigned int padding;
+	unsigned int delay;
+	unsigned int duration;
+	unsigned long bg_color;
+	unsigned long border_color;
+	unsigned long border_size;
+	unsigned long text_color;
+	unsigned long selected_color;
+	struct mlk_font *text_font;
 };
 
-struct message {
-	int x;
-	int y;
-	unsigned int w;
-	unsigned int h;
-	unsigned int spacing;
-	unsigned int delay;
-	unsigned int timeout;
+struct mlk_message_delegate {
+	void *data;
+
+	int (*query)(struct mlk_message_delegate *self,
+	             const struct mlk_message *message,
+	             unsigned int *w,
+	             unsigned int *h);
+
+	void (*update)(struct mlk_message_delegate *self,
+	               struct mlk_message *message,
+	               unsigned int ticks);
+
+	void (*draw)(struct mlk_message_delegate *self,
+	             const struct mlk_message *message);
+};
+
+struct mlk_message {
+	int x, y;
+	unsigned int w, h;
 	const char * const *lines;
 	size_t linesz;
 	unsigned int index;
-	enum message_flags flags;
-	enum message_state state;
-	const struct mlk_theme *theme;
+	enum mlk_message_flags flags;
+	enum mlk_message_state state;
+	struct mlk_message_style *style;
+	struct mlk_message_delegate *delegate;
 	unsigned int elapsed;
 	double scale;
 };
 
+extern struct mlk_message_style mlk_message_style;
+extern struct mlk_message_delegate mlk_message_delegate;
+
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
 void
-message_start(struct message *msg);
+mlk_message_start(struct mlk_message *msg);
 
-void
-message_query(const struct message *msg, unsigned int *w, unsigned int *h);
+int
+mlk_message_query(const struct mlk_message *msg, unsigned int *w, unsigned int *h);
 
 void
-message_handle(struct message *msg, const union mlk_event *ev);
+mlk_message_handle(struct mlk_message *msg, const union mlk_event *ev);
 
 int
-message_update(struct message *msg, unsigned int ticks);
+mlk_message_update(struct mlk_message *msg, unsigned int ticks);
 
 void
-message_draw(const struct message *msg);
+mlk_message_draw(const struct mlk_message *msg);
 
 void
-message_hide(struct message *msg);
+mlk_message_hide(struct mlk_message *msg);
 
 #if defined(__cplusplus)
 }
--- a/libmlk-ui/mlk/ui/label.h	Sat Mar 04 20:38:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/label.h	Sun Mar 05 10:51:25 2023 +0100
@@ -29,7 +29,7 @@
 
 struct mlk_label_delegate {
 	void *data;
-	int (*query)(struct mlk_label_delegate *, const struct mlk_label *, unsigned int *, unsigned *);
+	int (*query)(struct mlk_label_delegate *, const struct mlk_label *, unsigned int *, unsigned int *);
 	void (*update)(struct mlk_label_delegate *, struct mlk_label *, unsigned int);
 	void (*draw)(struct mlk_label_delegate *, const struct mlk_label *);
 };
--- a/libmlk-ui/mlk/ui/ui.h	Sat Mar 04 20:38:00 2023 +0100
+++ b/libmlk-ui/mlk/ui/ui.h	Sun Mar 05 10:51:25 2023 +0100
@@ -23,11 +23,14 @@
 
 /* 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
+#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)
+
+#define MLK_UI_PADDING          (10)
+#define MLK_UI_BORDER           (2)
 
 enum mlk_align;