changeset 415:a5b98db4fd87

misc: fix .hgignore too greedy
author David Demelier <markand@malikania.fr>
date Sun, 09 Oct 2022 13:53:33 +0200
parents 6947c1fefe5c
children 2862526f1fc9
files .hgignore examples/example-action/example-action.c examples/example-animation/example-animation.c examples/example-audio/example-audio.c examples/example-cursor/example-cursor.c examples/example-debug/example-debug.c examples/example-drawable/example-drawable.c examples/example-font/example-font.c examples/example-gridmenu/example-gridmenu.c examples/example-label/example-label.c examples/example-message/example-message.c examples/example-notify/example-notify.c examples/example-sprite/example-sprite.c examples/example-trace/example-trace.c examples/example-ui/example-ui.c src/tools/bcc/mlk-bcc.c
diffstat 16 files changed, 2929 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sun Oct 09 13:51:03 2022 +0200
+++ b/.hgignore	Sun Oct 09 13:53:33 2022 +0200
@@ -29,44 +29,44 @@
 ^src/libmlk-data/maps/.*\.tileset$
 
 # Examples.
-examples/example-action/example-action
-examples/example-animation/example-animation
-examples/example-audio/example-audio
-examples/example-battle/main
-examples/example-cursor/example-cursor
-examples/example-debug/example-debug
-examples/example-drawable/example-drawable
-examples/example-font/example-font
-examples/example-gridmenu/example-gridmenu
-examples/example-label/example-label
-examples/example-message/example-message
-examples/example-notify/example-notify
-examples/example-sprite/example-sprite
-examples/example-trace/example-trace
-examples/example-ui/example-ui
-examples/example/character-john
-examples/example/registry
-examples/example/spell-fire
-examples/example/trace_hud
+^examples/example-action/example-action$
+^examples/example-animation/example-animation$
+^examples/example-audio/example-audio$
+^examples/example-battle/main$
+^examples/example-cursor/example-cursor$
+^examples/example-debug/example-debug$
+^examples/example-drawable/example-drawable$
+^examples/example-font/example-font$
+^examples/example-gridmenu/example-gridmenu$
+^examples/example-label/example-label$
+^examples/example-message/example-message$
+^examples/example-notify/example-notify$
+^examples/example-sprite/example-sprite$
+^examples/example-trace/example-trace$
+^examples/example-ui/example-ui$
+^examples/example/character-john$
+^examples/example/registry$
+^examples/example/spell-fire$
+^examples/example/trace_hud$
 
 # Tests.
-tests/test-action-script
-tests/test-action
-tests/test-alloc
-tests/test-character
-tests/test-color
-tests/test-drawable
-tests/test-error
-tests/test-map
-tests/test-save-quest
-tests/test-save
-tests/test-state
-tests/test-tileset
-tests/test-util
-tests/test-vfs-directory
-tests/test-vfs-zip
+^tests/test-action-script$
+^tests/test-action$
+^tests/test-alloc$
+^tests/test-character$
+^tests/test-color$
+^tests/test-drawable$
+^tests/test-error$
+^tests/test-map$
+^tests/test-save-quest$
+^tests/test-save$
+^tests/test-state$
+^tests/test-tileset$
+^tests/test-util$
+^tests/test-vfs-directory$
+^tests/test-vfs-zip$
 
 # Tools.
-src/tools/bcc/mlk-bcc
-src/tools/bcc/mlk-map
-src/tools/bcc/mlk-tileset
+^src/tools/bcc/mlk-bcc$
+^src/tools/bcc/mlk-map$
+^src/tools/bcc/mlk-tileset$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-action/example-action.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,425 @@
+/*
+ * example-action.c -- example on how to use automatic actions
+ *
+ * Copyright (c) 2020-2022 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 <core/action.h>
+#include <core/action-stack.h>
+#include <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/image.h>
+#include <core/maths.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/script.h>
+#include <core/sprite.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/texture.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#include <rpg/message.h>
+#include <rpg/rpg.h>
+
+#include <assets/sprites/chest.h>
+#include <assets/sprites/people.h>
+
+#define W       1280
+#define H       720
+
+#define MW      (W * 0.75)
+#define MX      ((W / 2) - (MW / 2))
+#define MY      (100)
+
+/* This is a stack of "parallel" events. */
+static struct action *events_actions[32];
+static struct action_stack events;
+
+/* This is a stack of modal events. */
+static struct action *modal_actions[32];
+static struct action_stack modal;
+
+/* Maximum number of states. */
+static struct state *states[1];
+
+/*
+ * Event object for the chest to click on.
+ */
+static struct {
+	const char *text[2];
+	struct message msg;
+	struct action msg_act;
+	int x;
+	int y;
+	int opened;
+	struct texture image;
+	struct sprite sprite;
+	struct action event;
+} chest = {
+	.text = {
+		"100000 pièces.",
+		"Te voilà riche sale file de crevette."
+	},
+	.msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.lines = chest.text,
+		.linesz = 2
+	}
+};
+
+/*
+ * Event object for the guide who ask dialog with you.
+ *
+ * It is a script spawned upon click like this.
+ *
+ * [guide.event]
+ *       |           spawn
+ *   (on click)------------------->[message 1]
+ *                                      v
+ *                                 [message 2]
+ *                                      v
+ *                                 [question 3]
+ *                   spawn              v
+ * [response]<---------------------[check result]
+ *     v
+ * [message 4/5]
+ */
+static struct {
+	struct {
+		const char *text[6];
+		struct message msg;
+		struct action act;
+	} msgs[5];
+
+	/* This is the sprite and action to click on. */
+	int x;
+	int y;
+	struct texture image;
+	struct sprite sprite;
+	struct action event;
+
+	/* This is the event for the response. */
+	struct action response;
+
+	struct script script;
+	struct action script_act;
+} guide = {
+	.msgs = {
+		{
+			.text = {
+				"Bienvenue dans ce monde Molko."
+			},
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.delay = MESSAGE_DELAY_DEFAULT,
+				.flags = MESSAGE_FLAGS_FADEIN,
+				.lines = guide.msgs[0].text,
+				.linesz = 1
+			},
+		},
+		{
+			.text = {
+				"Penses tu vraiment pouvoir me battre ?"
+			},
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.lines = guide.msgs[1].text,
+				.linesz = 1
+			}
+		},
+		{
+			.text = {
+				"Non j'ai la trouille.",
+				"Bien sûr, je suis la légende."
+			},
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.flags = MESSAGE_FLAGS_QUESTION,
+				.lines = guide.msgs[2].text,
+				.linesz = 2
+			}
+		},
+
+		/* In case of NO. */
+		{
+			.text = {
+				"Poule mouillée."
+			},
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.lines = guide.msgs[3].text,
+				.linesz = 1
+			}
+		},
+
+		/* In case of YES. */
+		{
+			.text = {
+				"Prépare toi à souffrir."
+			},
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.lines = guide.msgs[4].text,
+				.linesz = 1
+			}
+		}
+	}
+};
+
+static int
+guide_response_update(struct action *act, unsigned int ticks)
+{
+	/* Immediately return to get access to end. */
+	(void)act;
+	(void)ticks;
+
+	return 1;
+}
+
+static void
+guide_response_end(struct action *act)
+{
+	(void)act;
+
+	/* We add a final response depending on the result. */
+	const int index = guide.msgs[2].msg.index + 3;
+
+	message_action(&guide.msgs[index].msg, &guide.msgs[index].act);
+	message_query(&guide.msgs[index].msg, NULL, &guide.msgs[index].msg.h);
+	action_stack_add(&modal, &guide.msgs[index].act);
+}
+
+static void
+guide_popup(void)
+{
+	/* Prepare the script with first 3 messages. */
+	for (size_t i = 0; i < 3; ++i) {
+		message_action(&guide.msgs[i].msg, &guide.msgs[i].act);
+		message_query(&guide.msgs[i].msg, NULL, &guide.msgs[i].msg.h);
+		script_append(&guide.script, &guide.msgs[i].act);
+	}
+
+	/* Reset the message index. */
+	guide.msgs[2].msg.index = 0;
+
+	/* This is final action that analyze user input. */
+	guide.response.update = guide_response_update;
+	guide.response.end = guide_response_end;
+	script_append(&guide.script, &guide.response);
+
+	script_action(&guide.script, &guide.script_act);
+	action_stack_add(&modal, &guide.script_act);
+}
+
+static void
+guide_handle(struct action *act, const union event *ev)
+{
+	(void)act;
+
+	if (!action_stack_completed(&modal))
+		return;
+
+	switch (ev->type) {
+	case EVENT_CLICKDOWN:
+		if (maths_is_boxed(guide.x, guide.y,
+		    guide.sprite.cellw, guide.sprite.cellh,
+		    ev->click.x, ev->click.y))
+			guide_popup();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+guide_draw(struct action *act)
+{
+	(void)act;
+
+	sprite_draw(&guide.sprite, 0, 0, guide.x, guide.y);
+}
+
+static void
+guide_init(void)
+{
+	if (image_openmem(&guide.image, assets_people, sizeof (assets_people)) < 0)
+		panic();
+
+	sprite_init(&guide.sprite, &guide.image, 48, 48);
+
+	/* This is the global guide action. */
+	guide.event.handle = guide_handle;
+	guide.event.draw = guide_draw;
+	guide.x = 200;
+	guide.y = 200;
+}
+
+static void
+chest_handle(struct action *act, const union event *ev)
+{
+	(void)act;
+
+	if (chest.opened || !action_stack_completed(&modal))
+		return;
+
+	switch (ev->type) {
+	case EVENT_CLICKDOWN:
+		if (maths_is_boxed(chest.x, chest.y, chest.sprite.cellw, chest.sprite.cellh,
+		    ev->click.x, ev->click.y)) {
+			chest.opened = 1;
+			message_action(&chest.msg, &chest.msg_act);
+			message_query(&chest.msg, NULL, &chest.msg.h);
+			action_stack_add(&modal, &chest.msg_act);
+		}
+	default:
+		break;
+	}
+}
+
+static void
+chest_draw(struct action *act)
+{
+	(void)act;
+
+	const int row = chest.opened ? chest.sprite.nrows - 1 : 0;
+	const int col = chest.opened ? chest.sprite.ncols - 1 : 0;
+
+	sprite_draw(&chest.sprite, row, col, chest.x, chest.y);
+}
+
+static void
+chest_init(void)
+{
+	if (image_openmem(&chest.image, assets_chest, sizeof (assets_chest)) < 0)
+		panic();
+
+	sprite_init(&chest.sprite, &chest.image, 32, 32);
+
+	chest.x = 100;
+	chest.y = 100;
+	chest.event.handle = chest_handle;
+	chest.event.draw = chest_draw;
+}
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-action") < 0 || ui_init() < 0 || rpg_init() < 0)
+		panic();
+	if (window_open("Example - Action", W, H) < 0)
+		panic();
+
+	guide_init();
+	chest_init();
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		action_stack_handle(&events, ev);
+		action_stack_handle(&modal, ev);
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	action_stack_update(&events, ticks);
+	action_stack_update(&modal, ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+	action_stack_draw(&events);
+	action_stack_draw(&modal);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
+
+	action_stack_init(&modal, modal_actions, UTIL_SIZE(modal_actions));
+
+	action_stack_init(&events, events_actions, UTIL_SIZE(events_actions));
+	action_stack_add(&events, &chest.event);
+	action_stack_add(&events, &guide.event);
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	rpg_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-animation/example-animation.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,148 @@
+/*
+ * example-animation.c -- example on how to use animations
+ *
+ * Copyright (c) 2020-2022 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 <core/animation.h>
+#include <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/image.h>
+#include <core/sys.h>
+#include <core/window.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/sprite.h>
+#include <core/state.h>
+#include <core/texture.h>
+#include <core/util.h>
+
+#include <ui/label.h>
+#include <ui/ui.h>
+
+#include <assets/sprites/numbers.h>
+
+#define W       1280
+#define H       720
+
+static struct label label = {
+	.text = "Keys: <Space> start or reset the animation.",
+	.x = 10,
+	.y = 10,
+	.flags = LABEL_FLAGS_SHADOW
+};
+
+static struct state *states[1];
+static struct texture numbers;
+static struct animation animation;
+static struct sprite sprite;
+static int completed = 1;
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-animation") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Animation", W, H) < 0)
+		panic();
+	if (image_openmem(&numbers, assets_numbers, sizeof (assets_numbers)) < 0)
+		panic();
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_SPACE:
+			animation_start(&animation);
+			completed = animation_completed(&animation);
+			break;
+		default:
+			break;
+		}
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	if (!completed)
+		completed = animation_update(&animation, ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	label_draw(&label);
+
+	if (!completed)
+		animation_draw(&animation, (window.w - sprite.cellw) / 2, (window.h - sprite.cellh) / 2);
+
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
+
+	sprite_init(&sprite, &numbers, 48, 48);
+	animation_init(&animation, &sprite, 1000);
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-audio/example-audio.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,155 @@
+/*
+ * example-audio.c -- show how to use sounds and music
+ *
+ * Copyright (c) 2020-2022 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 <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/music.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/sound.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/label.h>
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#include <assets/music/vabsounds-romance.h>
+#include <assets/sounds/fire.h>
+
+#define W       1280
+#define H       720
+
+static struct state *states[1];
+static struct music music;
+static struct sound sound;
+
+static struct label label_music = {
+	.text = "Music: <Space> play, <p> pause, <r> resume, <q> stop, <l> loop.",
+	.x = 10,
+	.y = 10,
+	.flags = LABEL_FLAGS_SHADOW
+};
+
+static struct label label_sound = {
+	.text = "Sound: click anywhere to pop a sound.",
+	.x = 10,
+	.y = 30,
+	.flags = LABEL_FLAGS_SHADOW
+};
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-audio") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Audio", W, H) < 0)
+		panic();
+	if (music_openmem(&music, assets_vabsounds_romance, sizeof (assets_vabsounds_romance)) < 0 ||
+	    sound_openmem(&sound, assets_fire, sizeof (assets_fire)) < 0)
+		panic();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_CLICKDOWN:
+		if (sound_play(&sound) < 0)
+			panic();
+		break;
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_p:
+			music_pause(&music);
+			break;
+		case KEY_r:
+			music_resume(&music);
+			break;
+		case KEY_q:
+			music_stop(&music);
+			break;
+		case KEY_l:
+			music_play(&music, MUSIC_LOOP);
+			break;
+		case KEY_SPACE:
+			music_play(&music, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0x006554ff);
+	painter_clear();
+	label_draw(&label_music);
+	label_draw(&label_sound);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+
+	music_finish(&music);
+	sound_finish(&sound);
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-cursor/example-cursor.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,148 @@
+/*
+ * example-cursor.c -- example on how to change cursor
+ *
+ * Copyright (c) 2020-2022 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 <stdio.h>
+
+#include <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/key.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/label.h>
+#include <ui/ui.h>
+
+#define W 1280
+#define H 720
+
+static struct state *states[1];
+static char help_text[128];
+static enum window_cursor cursor = WINDOW_CURSOR_ARROW;
+
+static struct label help = {
+	.x = 10,
+	.y = 10,
+	.text = help_text,
+	.flags = LABEL_FLAGS_SHADOW
+};
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-cursor") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Cursor", W, H) < 0)
+		panic();
+}
+
+static void
+change(enum window_cursor cursor)
+{
+	static const char *names[] = {
+		[WINDOW_CURSOR_ARROW]           = "WINDOW_CURSOR_ARROW",
+		[WINDOW_CURSOR_EDIT]            = "WINDOW_CURSOR_EDIT",
+		[WINDOW_CURSOR_WAIT]            = "WINDOW_CURSOR_WAIT",
+		[WINDOW_CURSOR_CROSSHAIR]       = "WINDOW_CURSOR_CROSSHAIR",
+		[WINDOW_CURSOR_SIZE]            = "WINDOW_CURSOR_SIZE",
+		[WINDOW_CURSOR_NO]              = "WINDOW_CURSOR_NO",
+		[WINDOW_CURSOR_HAND]            = "WINDOW_CURSOR_HAND"
+	};
+
+	snprintf(help_text, sizeof (help_text), "Keys: <Left>/<Right> to change cursor. Current: %s", names[cursor]);
+	window_set_cursor(cursor);
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_LEFT:
+			if (cursor > 0)
+				change(--cursor);
+			break;
+		case KEY_RIGHT:
+			if (cursor + 1 < WINDOW_CURSOR_LAST)
+				change(++cursor);
+			break;
+		default:
+			break;
+		}
+
+
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xebede9ff);
+	painter_clear();
+	label_draw(&help);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	change(cursor);
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-debug/example-debug.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,113 @@
+/*
+ * example-debug.c -- example on how to use debug interface
+ *
+ * Copyright (c) 2020-2022 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 <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/window.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/util.h>
+
+#include <ui/debug.h>
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#define W 1280
+#define H 720
+
+static struct state *states[1];
+static int mouse_x;
+static int mouse_y;
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-debug") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Debug", W, H) < 0)
+		panic();
+
+	debug_options.enable = 1;
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_MOUSE:
+		mouse_x = ev->mouse.x;
+		mouse_y = ev->mouse.y;
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	struct debug_report report = {0};
+
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	debugf(&report, "Game running.");
+	debugf(&report, "mouse: %d, %d", mouse_x, mouse_y);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-drawable/example-drawable.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,208 @@
+/*
+ * example-drawable.c -- example on how to use automatic drawables
+ *
+ * Copyright (c) 2020-2022 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 <stdio.h>
+#include <stdlib.h>
+
+#include <core/alloc.h>
+#include <core/animation.h>
+#include <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/drawable.h>
+#include <core/drawable-stack.h>
+#include <core/key.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/sys.h>
+#include <core/image.h>
+#include <core/sprite.h>
+#include <core/state.h>
+#include <core/texture.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/label.h>
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#include <assets/sprites/explosion.h>
+
+#define W       1280
+#define H       720
+
+static struct label help = {
+	.x = 10,
+	.y = 10,
+	.text = "Keys: <Esc> to reset. Click anywhere to spawn a drawable.",
+	.flags = LABEL_FLAGS_SHADOW
+};
+
+static struct state *states[1];
+static struct drawable *drawables[64];
+static struct drawable_stack stack;
+
+/*
+ * List of drawables for this example.
+ * -----------------------------------------------------------------------------
+ */
+
+/* 0: Explosion animation. */
+static struct texture explosion_tex;
+static struct sprite explosion_sprite;
+
+struct explosion {
+	struct animation anim;
+	struct drawable dw;
+};
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-drawable") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Drawable", W, H) < 0)
+		panic();
+
+	/* 0: Explosion animation. */
+	if (image_openmem(&explosion_tex, assets_explosion, sizeof (assets_explosion)) < 0)
+		panic();
+
+	sprite_init(&explosion_sprite, &explosion_tex, 256, 256);
+}
+
+static int
+explosion_update(struct drawable *dw, unsigned int ticks)
+{
+	struct explosion *ex = dw->data;
+
+	return animation_update(&ex->anim, ticks);
+}
+
+static void
+explosion_draw(struct drawable *dw)
+{
+	struct explosion *ex = dw->data;
+
+	animation_draw(&ex->anim, ex->dw.x, ex->dw.y);
+}
+
+static void
+explosion_finish(struct drawable *dw)
+{
+	free(dw->data);
+}
+
+static void
+spawn(int x, int y)
+{
+	struct explosion *ex = alloc_new0(sizeof (*ex));
+
+	animation_init(&ex->anim, &explosion_sprite, 15);
+
+	ex->dw.data = ex;
+	ex->dw.x = x - (int)(explosion_sprite.cellw / 2);
+	ex->dw.y = y - (int)(explosion_sprite.cellh / 2);
+	ex->dw.update = explosion_update;
+	ex->dw.draw = explosion_draw;
+	ex->dw.finish = explosion_finish;
+
+	drawable_stack_add(&stack, &ex->dw);
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_ESCAPE:
+			drawable_stack_finish(&stack);
+			break;
+		default:
+			break;
+		}
+		break;
+	case EVENT_CLICKDOWN:
+		spawn(ev->click.x, ev->click.y);
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	drawable_stack_update(&stack, ticks);
+
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xebede9ff);
+	painter_clear();
+	label_draw(&help);
+	drawable_stack_draw(&stack);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
+
+	drawable_stack_init(&stack, drawables, UTIL_SIZE(drawables));
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-font/example-font.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,148 @@
+/*
+ * example-font.c -- show how to use fonts
+ *
+ * Copyright (c) 2020-2022 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 <core/core.h>
+#include <core/event.h>
+#include <core/font.h>
+#include <core/game.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/texture.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#define W       (1280)
+#define H       (720)
+
+/* Friendly taken from: https://lospec.com/palette-list/apollo */
+static const unsigned long colors[] = {
+	0x3c5e8bff,     /* Blue. */
+	0x468232ff,     /* Green. */
+	0xad7757ff,     /* Light brown. */
+	0x884b2bff,     /* Brown. */
+	0x752438ff,     /* Red. */
+	0x7a367bff,     /* Magenta. */
+	0x151d28ff,     /* Dark */
+	0xc7cfccff,     /* Christian Grey. */
+};
+
+static struct state *states[1];
+static int ci = 0;
+static enum font_style style = FONT_STYLE_ANTIALIASED;
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-font") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Font", W, H) < 0)
+		panic();
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_LEFT:
+			if (ci > 0)
+				ci--;
+			break;
+		case KEY_RIGHT:
+			if ((size_t)ci < UTIL_SIZE(colors))
+				ci++;
+			break;
+		case KEY_SPACE:
+			if (style == FONT_STYLE_ANTIALIASED)
+				style = FONT_STYLE_NONE;
+			else
+				style = FONT_STYLE_ANTIALIASED;
+
+			theme_default()->fonts[THEME_FONT_INTERFACE]->style = style;
+		default:
+			break;
+		}
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	struct font *font = theme_default()->fonts[THEME_FONT_INTERFACE];
+	struct texture tex;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+
+	if (font_render(font, &tex, "Example of text. Use <Left>/<Right> to change color and <Space> to toggle antialiasing.", colors[ci]) < 0)
+		panic();
+
+	texture_draw(&tex, 10, 10);
+	painter_present();
+	texture_finish(&tex);
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-gridmenu/example-gridmenu.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,132 @@
+/*
+ * example-gridmenu.c -- show how to use grid menu
+ *
+ * Copyright (c) 2020-2022 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 <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/trace.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/align.h>
+#include <ui/gridmenu.h>
+#include <ui/label.h>
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#define W       (1280)
+#define H       (720)
+
+static struct state *states[1];
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-gridmenu") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Grid menu", W, H) < 0)
+		panic();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	struct gridmenu *menu = st->data;
+
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		if (gridmenu_handle(st->data, ev))
+			tracef("selected index: %zu (%s)", menu->selected, menu->items[menu->selected]);
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	gridmenu_draw(st->data);
+	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 gridmenu menu = {0};
+	struct state state = {
+		.data = &menu,
+		.handle = handle,
+		.draw = draw,
+	};
+
+	gridmenu_init(&menu, 3, 2, items, UTIL_SIZE(items));
+	gridmenu_resize(&menu, 0, 0, 300, 100);
+
+	align(ALIGN_CENTER, &menu.x, &menu.y, menu.w, menu.h, 0, 0, W, H);
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-label/example-label.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,186 @@
+/*
+ * example-label.c -- show how to use label and alignments
+ *
+ * Copyright (c) 2020-2022 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 <stddef.h>
+
+#include <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/align.h>
+#include <ui/label.h>
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#define W       (1280)
+#define H       (720)
+
+struct {
+	enum align align;
+	struct label label;
+} table[] = {
+	{
+		.align = ALIGN_TOP_LEFT,
+		.label = {
+			.text = "Top left"
+		}
+	},
+	{
+		.align = ALIGN_TOP,
+		.label = {
+			.text = "Top",
+		}
+	},
+	{
+		.align = ALIGN_TOP_RIGHT,
+		.label = {
+			.text = "Top right",
+		}
+	},
+	{
+		.align = ALIGN_RIGHT,
+		.label = {
+			.text = "Right",
+		}
+	},
+	{
+		.align = ALIGN_BOTTOM_RIGHT,
+		.label = {
+			.text = "Bottom right",
+		}
+	},
+	{
+		.align = ALIGN_BOTTOM,
+		.label = {
+			.text = "Bottom",
+		}
+	},
+	{
+		.align = ALIGN_BOTTOM_LEFT,
+		.label = {
+			.text = "Bottom left",
+		}
+	},
+	{
+		.align = ALIGN_LEFT,
+		.label = {
+			.text = "Left",
+		}
+	},
+	{
+		.align = ALIGN_CENTER,
+		.label = {
+			.text = "The world is Malikania.",
+			.flags = LABEL_FLAGS_SHADOW
+		}
+	}
+};
+
+static struct label mlabel = {
+	.text = "This one follows your mouse and is not aligned."
+};
+
+static struct state *states[1];
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-label") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Label", W, H) < 0)
+		panic();
+
+	for (size_t i = 0; i < UTIL_SIZE(table); ++i) {
+		struct label *l = &table[i].label;
+		unsigned int w, h;
+
+		label_query(l, &w, &h);
+		align(table[i].align, &l->x, &l->y, w, h, 0, 0, W, H);
+	}
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_MOUSE:
+		mlabel.x = ev->mouse.x;
+		mlabel.y = ev->mouse.y;
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+
+	for (size_t i = 0; i < UTIL_SIZE(table); ++i)
+		label_draw(&table[i].label);
+
+	label_draw(&mlabel);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-message/example-message.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,350 @@
+/*
+ * example-message.c -- show how to use messages
+ *
+ * Copyright (c) 2020-2022 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 <stddef.h>
+
+#include <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/frame.h>
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#include <rpg/message.h>
+#include <rpg/rpg.h>
+
+#define W       (1280)
+#define H       (720)
+
+#define MW      (W * 0.75)
+#define MH      (H * 0.120)
+#define MX      ((W / 2) - (MW / 2))
+#define MY      (100)
+
+static struct state *states[1];
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-message") < 0 || ui_init() < 0 || rpg_init() < 0)
+		panic();
+	if (window_open("Example - Message", W, H) < 0)
+		panic();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	rpg_finish();
+	ui_finish();
+	core_finish();
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		message_handle(st->data, ev);
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	if (message_update(st->data, ticks))
+		game_quit();
+}
+
+static void
+draw(struct state *st)
+{
+	painter_set_color(0xffffffff);
+	painter_clear();
+	message_draw(st->data);
+	painter_present();
+}
+
+static void
+run(struct message *msg)
+{
+	struct state state = {
+		.data = msg,
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
+
+	message_start(msg);
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+my_draw_frame(const struct theme *th, const struct frame *f)
+{
+	(void)th;
+
+	painter_set_color(0xff0000ff);
+	painter_draw_rectangle(f->x, f->y, f->w, f->h);
+}
+
+static void
+basic(void)
+{
+	const char * const text[] = {
+		"This is a basic message.",
+		"Vertical spacing is automatically computed.",
+		"You need to press <Enter> to close it.",
+	};
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.spacing = 12,
+		.lines = text,
+		.linesz = 3
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+automatic(void)
+{
+	const char * const text[] = {
+		"This is a an automatic message.",
+		"It will disappear in a few seconds.",
+		"You can still press <Enter> to close it quicker."
+	};
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.timeout = MESSAGE_TIMEOUT_DEFAULT,
+		.lines = text,
+		.linesz = 3,
+		.flags = MESSAGE_FLAGS_AUTOMATIC
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+fadein(void)
+{
+	const char * const text[] = {
+		"This message will fade in."
+	};
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.delay = MESSAGE_DELAY_DEFAULT,
+		.lines = text,
+		.linesz = 1,
+		.flags = MESSAGE_FLAGS_FADEIN
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+fadeout(void)
+{
+	const char * const text[] = {
+		"This message will fade out."
+	};
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.delay = MESSAGE_DELAY_DEFAULT,
+		.lines = text,
+		.linesz = 1,
+		.flags = MESSAGE_FLAGS_FADEOUT
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+fade(void)
+{
+	const char * const text[] = {
+		"This message will fade in and out."
+	};
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.delay = MESSAGE_DELAY_DEFAULT,
+		.lines = text,
+		.linesz = 1,
+		.flags = MESSAGE_FLAGS_FADEIN | MESSAGE_FLAGS_FADEOUT
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+question(void)
+{
+	const char * const text[] = {
+		"Okay, I've understood.",
+		"Nevermind, I'll do it again."
+	};
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.lines = text,
+		.linesz = 2,
+		.flags = MESSAGE_FLAGS_QUESTION
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+smallbottom(void)
+{
+	const unsigned int w = window.w / 4;
+	const unsigned int h = MH;
+	const int x = (window.w / 2) - (w / 2);
+	const int y = (window.h - h - 10);
+	const char * const text[] = {
+		"This one is small here."
+	};
+
+	struct message msg = {
+		.x = x,
+		.y = y,
+		.w = w,
+		.h = h,
+		.delay = MESSAGE_DELAY_DEFAULT,
+		.flags = MESSAGE_FLAGS_FADEIN | MESSAGE_FLAGS_FADEOUT,
+		.lines = text,
+		.linesz = 1
+	};
+
+	run(&msg);
+}
+
+static void
+toosmallh(void)
+{
+	const char * const text[] = {
+		"This one is too small in height and will emit a warning.",
+		"Because this line will be incomplete."
+	};
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.h = 40,
+		.lines = text,
+		.linesz = 2
+	};
+
+	run(&msg);
+}
+
+static void
+toosmallw(void)
+{
+	const char * const text[] = {
+		"This one is too small in width."
+	};
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = 160,
+		.h = MH,
+		.lines = text,
+		.linesz = 1
+	};
+
+	run(&msg);
+}
+
+static void
+custom(void)
+{
+	const char * const text[] = {
+		"This one will destroy your eyes.",
+		"Because it use a terrible custom theme."
+	};
+	struct theme theme;
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.h = MH,
+		.lines = text,
+		.linesz = 2,
+		.theme = &theme
+	};
+
+	/* Borrow default theme and change its frame drawing. */
+	theme_shallow(&theme, NULL);
+	theme.draw_frame = my_draw_frame;
+	theme.colors[THEME_COLOR_NORMAL] = 0x0000ffff;
+
+	run(&msg);
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	basic();
+	fadein();
+	fadeout();
+	fade();
+	automatic();
+	question();
+	smallbottom();
+	toosmallh();
+	toosmallw();
+	custom();
+	quit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-notify/example-notify.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,133 @@
+/*
+ * example-notify -- show how notifications work
+ *
+ * Copyright (c) 2020-2022 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 <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/image.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/texture.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/notify.h>
+#include <ui/label.h>
+#include <ui/ui.h>
+
+/* Sword by Icongeek26 (https://www.flaticon.com). */
+#include <assets/images/sword.h>
+
+#define W       1280
+#define H       720
+
+static struct label help = {
+	.text = "Keys: <Space> to generate a notification.",
+	.x = 10,
+	.y = 10,
+	.flags = LABEL_FLAGS_SHADOW
+};
+static struct texture icon;
+static struct state *states[1];
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-notify") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Notify", W, H) < 0)
+		panic();
+	if (image_openmem(&icon, assets_sword, sizeof (assets_sword)) < 0)
+		panic();
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	case EVENT_KEYDOWN:
+		if (ev->key.key == KEY_SPACE)
+			notify(&icon, "Quest", "Quest finished.");
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	notify_update(ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+	label_draw(&help);
+	notify_draw();
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-sprite/example-sprite.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,164 @@
+/*
+ * example-sprite.c -- example on how to use sprites
+ *
+ * Copyright (c) 2020-2022 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 <stdio.h>
+
+#include <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/image.h>
+#include <core/key.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/image.h>
+#include <core/sprite.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/texture.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/align.h>
+#include <ui/label.h>
+#include <ui/ui.h>
+
+#include <assets/sprites/people.h>
+
+#define W       1280
+#define H       720
+#define HEADER "Keys: <Left>/<Right> and <Up/Down> to select a column/row. Current: %u, %u (total %u/%u)"
+
+static char msg[512];
+static struct texture texture;
+static struct sprite sprite;
+static unsigned int row, column;
+static struct state *states[1];
+
+static struct label help = {
+	.x = 10,
+	.y = 10,
+	.text = msg,
+	.flags = LABEL_FLAGS_SHADOW
+};
+
+static void
+changed(void)
+{
+	snprintf(msg, sizeof (msg), HEADER, column, row, sprite.ncols, sprite.nrows);
+}
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-sprite") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Sprite", W, H) < 0)
+		panic();
+	if (image_openmem(&texture, assets_people, sizeof (assets_people)) < 0)
+		panic();
+
+	sprite_init(&sprite, &texture, 48, 48);
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_LEFT:
+			if (column > 0)
+				column--;
+			break;
+		case KEY_RIGHT:
+			if (column + 1 < sprite.ncols)
+				column++;
+			break;
+		case KEY_UP:
+			if (row > 0)
+				row--;
+			break;
+		case KEY_DOWN:
+			if (row + 1 < sprite.nrows)
+				row++;
+			break;
+		default:
+			break;
+		}
+
+		changed();
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	int x, y;
+
+	painter_set_color(0xebede9ff);
+	painter_clear();
+	align(ALIGN_CENTER, &x, &y, sprite.cellw, sprite.cellh, 0, 0, W, H);
+	sprite_draw(&sprite, row, column, x, y);
+	label_draw(&help);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	changed();
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	texture_finish(&texture);
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-trace/example-trace.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,129 @@
+/*
+ * example-trace.c -- example on how to use custom trace handlers
+ *
+ * Copyright (c) 2020-2022 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 <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/sys.h>
+#include <core/window.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/trace.h>
+#include <core/util.h>
+
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#include "trace_hud.h"
+
+#define W 1280
+#define H 720
+
+static struct state *states[1];
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-trace") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Trace", W, H) < 0)
+		panic();
+
+	trace_handler = trace_hud_handler;
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_ESCAPE:
+			trace_hud_clear();
+			break;
+		default:
+			tracef("keydown pressed: %d", ev->key.key);
+			break;
+		}
+		break;
+	case EVENT_CLICKDOWN:
+		tracef("click at %d,%d", ev->click.x, ev->click.y);
+		break;
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	trace_hud_update(ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	trace_hud_draw();
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-ui/example-ui.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,288 @@
+/*
+ * example-action.c -- example on how to use automatic actions
+ *
+ * Copyright (c) 2020-2022 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 <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/maths.h>
+#include <core/panic.h>
+#include <core/painter.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/align.h>
+#include <ui/button.h>
+#include <ui/checkbox.h>
+#include <ui/frame.h>
+#include <ui/label.h>
+#include <ui/theme.h>
+#include <ui/ui.h>
+
+#define W               (1280)
+#define H               (720)
+
+#define FRAME_ORIGIN_X  (10)
+#define FRAME_ORIGIN_Y  (10)
+#define FRAME_WIDTH     (400)
+#define FRAME_HEIGHT    (200)
+
+#define HEADER_HEIGHT   (32)
+
+#define ELEMENT_HEIGHT  (20)
+
+/*
+ * We design a basic UI like this.
+ *
+ *                    FRAME_WIDTH
+ * +---------------------------------------------+--
+ * | Title                                       | | HEADER_HEIGHT
+ * +---------------------------------------------+--
+ * | [x] Auto save                               |
+ * |                                             |
+ * |                                             |
+ * |                                    [ Quit ] |
+ * +---------------------------------------------+
+ */
+static struct {
+	struct {
+		int active;
+		int x;
+		int y;
+	} motion;
+
+	struct {
+		struct frame frame;
+	} panel;
+
+	struct {
+		struct label label;
+	} header;
+
+	struct {
+		struct checkbox cb;
+		struct label label;
+	} autosave;
+
+	struct {
+		struct button button;
+	} quit;
+} ui = {
+	.panel = {
+		.frame = {
+			.x = FRAME_ORIGIN_X,
+			.y = FRAME_ORIGIN_Y,
+			.w = FRAME_WIDTH,
+			.h = FRAME_HEIGHT
+		}
+	},
+	.header = {
+		.label = {
+			.text = "Preferences",
+			.x = FRAME_ORIGIN_X,
+			.y = FRAME_ORIGIN_Y,
+			.flags = LABEL_FLAGS_SHADOW,
+		}
+	},
+	.autosave = {
+		.cb = {
+			.w = ELEMENT_HEIGHT,
+			.h = ELEMENT_HEIGHT
+		},
+		.label = {
+			.text = "Auto save game",
+			.flags = LABEL_FLAGS_SHADOW,
+		}
+	},
+	.quit = {
+		.button = {
+			.text = "Quit",
+			.h = ELEMENT_HEIGHT
+		}
+	}
+};
+
+static struct state *states[1];
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "example-ui") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - UI", W, H) < 0)
+		panic();
+}
+
+static void
+resize_header(void)
+{
+	struct frame *f = &ui.panel.frame;
+	struct label *l = &ui.header.label;
+	unsigned int w, h;
+
+	/* Header. */
+	label_query(l, &w, &h);
+	align(ALIGN_LEFT, &l->x, &l->y, w, h, f->x, f->y, f->w, HEADER_HEIGHT);
+
+	l->x += theme_default()->padding;
+}
+
+static void
+resize_autosave(void)
+{
+	unsigned int padding = theme_default()->padding;
+	struct frame *f = &ui.panel.frame;
+	struct checkbox *c = &ui.autosave.cb;
+	struct label *l = &ui.autosave.label;
+
+	c->x = f->x + padding;
+	c->y = f->y + HEADER_HEIGHT + padding;
+
+	l->x = c->x + c->w + padding;
+	l->y = c->y;
+}
+
+static void
+resize_button(void)
+{
+	unsigned int padding = theme_default()->padding;
+	struct frame *f = &ui.panel.frame;
+	struct button *b = &ui.quit.button;
+
+	/* Button. */
+	b->w = f->w / 4;
+
+	align(ALIGN_BOTTOM_RIGHT, &b->x, &b->y, b->w, b->h,
+	    f->x, f->y, f->w, f->h);
+
+	b->x -= padding;
+	b->y -= padding;
+}
+
+static void
+resize(void)
+{
+	resize_header();
+	resize_autosave();
+	resize_button();
+}
+
+static int
+headerclick(int x, int y)
+{
+	return maths_is_boxed(
+	    ui.panel.frame.x,
+	    ui.panel.frame.y,
+	    ui.panel.frame.w,
+	    HEADER_HEIGHT,
+	    x,
+	    y
+	);
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	case EVENT_MOUSE:
+		if (ui.motion.active) {
+			ui.panel.frame.x += ev->mouse.x - ui.motion.x;
+			ui.panel.frame.y += ev->mouse.y - ui.motion.y;
+			ui.motion.x = ev->mouse.x;
+			ui.motion.y = ev->mouse.y;
+			resize();
+		}
+		break;
+	case EVENT_CLICKDOWN:
+		if (headerclick(ev->click.x, ev->click.y)) {
+			ui.motion.active = 1;
+			ui.motion.x = ev->click.x;
+			ui.motion.y = ev->click.y;
+			window_set_cursor(WINDOW_CURSOR_SIZE);
+		}
+		break;
+	case EVENT_CLICKUP:
+		ui.motion.active = 0;
+		window_set_cursor(WINDOW_CURSOR_ARROW);
+		break;
+	default:
+		break;
+	}
+
+	checkbox_handle(&ui.autosave.cb, ev);
+
+	if (button_handle(&ui.quit.button, ev))
+		game_quit();
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+	frame_draw(&ui.panel.frame);
+	label_draw(&ui.header.label);
+	checkbox_draw(&ui.autosave.cb);
+	label_draw(&ui.autosave.label);
+	button_draw(&ui.quit.button);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	resize();
+
+	game_init(states, UTIL_SIZE(states));
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tools/bcc/mlk-bcc.c	Sun Oct 09 13:53:33 2022 +0200
@@ -0,0 +1,165 @@
+/*
+ * bcc.c -- binary to C/C++ arrays converter
+ *
+ * Copyright (c) 2020-2022 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 <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "arg.h"
+
+char *argv0;
+
+static const char *charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
+static char findentchar = '\t';
+static int findent = 1, fconst, fnull, fstatic;
+
+static void
+usage(void)
+{
+	fprintf(stderr, "usage: bcc [-0cs] [-I tab-indent] [-i space-indent] input variable\n");
+	exit(1);
+}
+
+static void
+die(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	fputs("abort: ", stderr);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	exit(1);
+}
+
+static char *
+mangle(char *variable)
+{
+	char *p;
+	size_t pos;
+
+	/* Remove extension. */
+	if ((p = strrchr(variable, '.')))
+		*p = '\0';
+
+	/* Remove disallowed characters. */
+	while ((pos = strspn(variable, charset)) != strlen(variable))
+		variable[pos] = '_';
+
+	return variable;
+}
+
+static void
+indent(void)
+{
+	for (int i = 0; i < findent; ++i)
+		putchar(findentchar);
+}
+
+static void
+put(int ch)
+{
+	printf("0x%02hhx", (unsigned char)ch);
+}
+
+static void
+process(const char *input, const char *variable)
+{
+	FILE *fp;
+	int ch, col = 0;
+
+	if (strcmp(input, "-") == 0)
+		fp = stdin;
+	else if (!(fp = fopen(input, "rb")))
+		die("%s: %s\n", input, strerror(errno));
+
+	if (fstatic)
+		printf("static ");
+	if (fconst)
+		printf("const ");
+
+	printf("unsigned char %s[] = {\n", variable);
+
+	for (ch = fgetc(fp); ch != EOF; ) {
+		if (col == 0)
+			indent();
+
+		put(ch);
+
+		if ((ch = fgetc(fp)) != EOF || fnull)
+			printf(",%s", col < 3 ? " " : "");
+
+		if (++col == 4) {
+			col = 0;
+			putchar('\n');
+		}
+
+		/* Add final '\0' if required. */
+		if (ch == EOF && fnull) {
+			if (col++ == 0)
+				indent();
+
+			put(0);
+		}
+	}
+
+	if (col != 0)
+		printf("\n");
+
+	puts("};");
+	fclose(fp);
+}
+
+int
+main(int argc, char *argv[])
+{
+#if defined(__OpenBSD__)
+	if (pledge("rpath stdio", NULL) < 0)
+		die("abort: %s\n", strerror(errno));
+#endif
+
+	ARGBEGIN {
+	case '0':
+		fnull = 1;
+		break;
+	case 'c':
+		fconst = 1;
+		break;
+	case 'I':
+		findentchar = '\t';
+		findent = atoi(EARGF(usage()));
+		break;
+	case 'i':
+		findentchar = ' ';
+		findent = atoi(EARGF(usage()));
+		break;
+	case 's':
+		fstatic = 1;
+		break;
+	default:
+		usage();
+		break;
+	} ARGEND
+
+	if (argc < 2)
+		usage();
+
+	process(argv[0], mangle(argv[1]));
+}