changeset 209:23a844fdc911

examples: move all into subdirectories, closes #2513
author David Demelier <markand@malikania.fr>
date Wed, 11 Nov 2020 17:10:40 +0100
parents c0e0d4accae8
children 70e6ed74940d
files cmake/MolkoDefineLibrary.cmake examples/CMakeLists.txt examples/assets/music/vabsounds-romance.ogg examples/assets/musics/vabsounds-romance.ogg examples/battle/character-john.c examples/battle/character-john.h examples/battle/registry.c examples/battle/registry.h examples/battle/spell-fire.c examples/battle/spell-fire.h examples/example-action.c examples/example-action/CMakeLists.txt examples/example-action/main.c examples/example-animation.c examples/example-animation/CMakeLists.txt examples/example-animation/main.c examples/example-audio.c examples/example-audio/CMakeLists.txt examples/example-audio/main.c examples/example-battle.c examples/example-battle/CMakeLists.txt examples/example-battle/character-john.c examples/example-battle/character-john.h examples/example-battle/main.c examples/example-battle/registry.c examples/example-battle/registry.h examples/example-battle/spell-fire.c examples/example-battle/spell-fire.h examples/example-cursor.c examples/example-cursor/CMakeLists.txt examples/example-cursor/main.c examples/example-debug.c examples/example-debug/CMakeLists.txt examples/example-debug/main.c examples/example-drawable.c examples/example-drawable/CMakeLists.txt examples/example-drawable/main.c examples/example-font.c examples/example-font/CMakeLists.txt examples/example-font/main.c examples/example-gridmenu.c examples/example-gridmenu/CMakeLists.txt examples/example-gridmenu/main.c examples/example-inventory.c examples/example-inventory/main.c examples/example-label.c examples/example-label/CMakeLists.txt examples/example-label/main.c examples/example-message.c examples/example-message/CMakeLists.txt examples/example-message/main.c examples/example-sprite.c examples/example-sprite/CMakeLists.txt examples/example-sprite/main.c examples/example-trace.c examples/example-trace/CMakeLists.txt examples/example-trace/main.c examples/example-ui.c examples/example-ui/CMakeLists.txt examples/example-ui/main.c librpg/CMakeLists.txt librpg/rpg/inventory.c librpg/rpg/inventory.h tests/CMakeLists.txt tests/test-inventory.c
diffstat 65 files changed, 3830 insertions(+), 4119 deletions(-) [+]
line wrap: on
line diff
--- a/cmake/MolkoDefineLibrary.cmake	Wed Nov 11 16:31:55 2020 +0100
+++ b/cmake/MolkoDefineLibrary.cmake	Wed Nov 11 17:10:40 2020 +0100
@@ -75,7 +75,7 @@
 
 	if (${LIB_TYPE} MATCHES "INTERFACE")
 		add_library(${LIB_TARGET} INTERFACE)
-		target_sources(${LIB_TARGET} INTERFACE ${LIB_SOURCES})
+		target_sources(${LIB_TARGET} INTERFACE ${LIB_SOURCES} ${OUTPUTS})
 		target_include_directories(
 			${LIB_TARGET}
 			INTERFACE
--- a/examples/CMakeLists.txt	Wed Nov 11 16:31:55 2020 +0100
+++ b/examples/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -18,123 +18,42 @@
 
 project(examples)
 
-molko_define_executable(
-	TARGET example-battle
-	SOURCES
-		${examples_SOURCE_DIR}/example-battle.c
-		${examples_SOURCE_DIR}/battle/spell-fire.c
-		${examples_SOURCE_DIR}/battle/spell-fire.h
-		${examples_SOURCE_DIR}/battle/registry.c
-		${examples_SOURCE_DIR}/battle/registry.h
-	FOLDER examples
-	ASSETS
-		${examples_SOURCE_DIR}/assets/images/haunted-wood.png
-		${examples_SOURCE_DIR}/assets/images/black-cat.png
-		${examples_SOURCE_DIR}/assets/sprites/cursor.png
-		${examples_SOURCE_DIR}/assets/sprites/explosion.png
-		${examples_SOURCE_DIR}/assets/sounds/fire.wav
-	LIBRARIES librpg libadventure
-)
-
-molko_define_executable(
-	TARGET example-action
-	SOURCES example-action.c
-	FOLDER examples
+set(
 	ASSETS
-		${examples_SOURCE_DIR}/assets/sprites/chest.png
-		${examples_SOURCE_DIR}/assets/sprites/people.png
-	LIBRARIES librpg
-)
-
-molko_define_executable(
-	TARGET example-animation
-	SOURCES example-animation.c
-	FOLDER examples
-	ASSETS
-		${examples_SOURCE_DIR}/assets/sprites/numbers.png
-	LIBRARIES libui
-)
-
-molko_define_executable(
-	TARGET example-audio
-	SOURCES example-audio.c
-	FOLDER examples
-	ASSETS
-		${examples_SOURCE_DIR}/assets/musics/vabsounds-romance.ogg
-		${examples_SOURCE_DIR}/assets/sounds/fire.wav
-	LIBRARIES libui
-)
-
-molko_define_executable(
-	TARGET example-cursor
-	SOURCES example-cursor.c
-	FOLDER examples
-	LIBRARIES libui
+	${examples_SOURCE_DIR}/assets/images/black-cat.png
+	${examples_SOURCE_DIR}/assets/images/haunted-wood.png
+	${examples_SOURCE_DIR}/assets/music/vabsounds-romance.ogg
+	${examples_SOURCE_DIR}/assets/sounds/fire.wav
+	${examples_SOURCE_DIR}/assets/sprites/chest.png
+	${examples_SOURCE_DIR}/assets/sprites/cursor.png
+	${examples_SOURCE_DIR}/assets/sprites/explosion.png
+	${examples_SOURCE_DIR}/assets/sprites/numbers.png
+	${examples_SOURCE_DIR}/assets/sprites/people.png
 )
 
-molko_define_executable(
-	TARGET example-debug
-	SOURCES example-debug.c
-	FOLDER examples
-	LIBRARIES libui
-)
+# Can't use an interface library as examples live in subdirectories.
+file(WRITE ${examples_BINARY_DIR}/none.c "void molko() {}")
 
-molko_define_executable(
-	TARGET example-font
-	SOURCES example-font.c
-	LIBRARIES libui
+molko_define_library(
+	TARGET libexamples
 	FOLDER examples
-)
-
-molko_define_executable(
-	TARGET example-label
-	SOURCES example-label.c
-	LIBRARIES libui
-	FOLDER examples
-)
-
-molko_define_executable(
-	TARGET example-message
-	SOURCES example-message.c
-	FOLDER examples
-	LIBRARIES librpg
+	SOURCES ${examples_BINARY_DIR}/none.c ${ASSETS}
+	ASSETS ${ASSETS}
 )
 
-molko_define_executable(
-	TARGET example-sprite
-	SOURCES example-sprite.c
-	FOLDER examples
-	ASSETS
-		${examples_SOURCE_DIR}/assets/sprites/people.png
-	LIBRARIES libui
-)
-
-molko_define_executable(
-	TARGET example-drawable
-	SOURCES example-drawable.c
-	FOLDER examples
-	LIBRARIES libui
-	ASSETS
-		${examples_SOURCE_DIR}/assets/sprites/explosion.png
-)
+add_subdirectory(example-action)
+add_subdirectory(example-animation)
+add_subdirectory(example-audio)
+add_subdirectory(example-battle)
+add_subdirectory(example-cursor)
+add_subdirectory(example-debug)
+add_subdirectory(example-drawable)
+add_subdirectory(example-font)
+add_subdirectory(example-gridmenu)
+add_subdirectory(example-label)
+add_subdirectory(example-message)
+add_subdirectory(example-sprite)
+add_subdirectory(example-trace)
+add_subdirectory(example-ui)
 
-molko_define_executable(
-	TARGET example-gridmenu
-	SOURCES example-gridmenu.c
-	FOLDER examples
-	LIBRARIES libui
-)
-
-molko_define_executable(
-	TARGET example-trace
-	SOURCES example-trace.c
-	FOLDER examples
-	LIBRARIES libui libadventure
-)
-
-molko_define_executable(
-	TARGET example-ui
-	SOURCES example-ui.c
-	FOLDER examples
-	LIBRARIES libui
-)
+source_group("assets" FILES ${ASSETS})
Binary file examples/assets/music/vabsounds-romance.ogg has changed
Binary file examples/assets/musics/vabsounds-romance.ogg has changed
--- a/examples/battle/character-john.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/*
- * character-john.c -- john character
- *
- * Copyright (c) 2020 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 <rpg/character.h>
-
-#include "character-john.h"
-#include "spell-fire.h"
-#include "registry.h"
-
-static void
-adventurer_reset(struct character *ch)
-{
-	/* TODO: this function should compute the attack thanks to the level. */
-	ch->hpmax = 120;
-	ch->mpmax = 50;
-	ch->atk = 50;
-	ch->def = 50;
-	ch->agt = 50;
-	ch->luck = 50;
-}
-
-const struct character character_john = {
-	.name = "John ",
-	.type = "Adventurer",
-	.level = 1,
-	.hp = 120,
-	.mp = 50,
-	.reset = adventurer_reset,
-	.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
-	.spells = {
-		&spell_fire
-	}
-};
--- a/examples/battle/character-john.h	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-/*
- * character-john.h -- john character
- *
- * Copyright (c) 2020 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 EXAMPLES_BATTLE_CHARACTER_JOHN_H
-#define EXAMPLES_BATTLE_CHARACTER_JOHN_H
-
-const struct character character_john;
-
-#endif /* !EXAMPLES_BATTLE_CHARACTER_JOHN_H */
--- a/examples/battle/registry.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/*
- * registry.h -- registry of resources
- *
- * Copyright (c) 2020 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/image.h>
-#include <core/panic.h>
-#include <core/util.h>
-
-#include <adventure/assets/sprites/john.h>
-
-#include <assets/images/haunted-wood.h>
-#include <assets/images/black-cat.h>
-
-#include <assets/sprites/cursor.h>
-#include <assets/sprites/explosion.h>
-
-#include <assets/sounds/fire.h>
-
-#include "registry.h"
-
-struct texture registry_textures[REGISTRY_TEXTURE_NUM];
-struct sprite registry_sprites[REGISTRY_TEXTURE_NUM];
-struct sound registry_sounds[REGISTRY_SOUND_NUM];
-
-#define REGISTRY_TEXTURE(s, ptr, cw, ch) \
-	{ (s), (ptr), sizeof ((ptr)), (cw), (ch) }
-
-static const struct {
-	enum registry_texture index;
-	const void *data;
-	size_t datasz;
-	unsigned int cellw;
-	unsigned int cellh;
-} textures[] = {
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_CURSOR, sprites_cursor, 24, 24),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_EXPLOSION, sprites_explosion, 256, 256),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_JOHN, sprites_john, 48, 48),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_HAUNTED_WOOD, images_haunted_wood, 0, 0),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_BLACK_CAT, images_black_cat, 0, 0)
-};
-
-#define REGISTRY_SOUND(s, ptr) \
-	{ (s), (ptr), sizeof ((ptr)) }
-
-static const struct {
-	enum registry_sound index;
-	const void *data;
-	size_t datasz;
-} sounds[] = {
-	REGISTRY_SOUND(REGISTRY_SOUND_FIRE, sounds_fire)
-};
-
-static void
-load_textures_and_sprites(void)
-{
-	for (size_t i = 0; i < NELEM(textures); ++i) {
-		struct texture *texture = &registry_textures[textures[i].index];
-		struct sprite *sprite = &registry_sprites[textures[i].index];
-
-		if (!image_openmem(texture, textures[i].data, textures[i].datasz))
-			panic();
-
-		if (textures[i].cellw == 0 || textures[i].cellh == 0)
-			sprite_init(sprite, texture, texture->w, texture->h);
-		else
-			sprite_init(sprite, texture, textures[i].cellw, textures[i].cellh);
-	}
-}
-
-static void
-load_sounds(void)
-{
-	for (size_t i = 0; i < NELEM(sounds); ++i) {
-		struct sound *sound = &registry_sounds[sounds[i].index];
-
-		if (!sound_openmem(sound, sounds[i].data, sounds[i].datasz))
-			panic();
-	}
-}
-
-void
-registry_init(void)
-{
-	load_textures_and_sprites();
-	load_sounds();
-}
-
-void
-registry_finish(void)
-{
-	for (size_t i = 0; i < NELEM(registry_textures); ++i)
-		texture_finish(&registry_textures[i]);
-	for (size_t i = 0; i < NELEM(registry_sounds); ++i)
-		sound_finish(&registry_sounds[i]);
-}
--- a/examples/battle/registry.h	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * registry.h -- registry of resources
- *
- * Copyright (c) 2020 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 EXAMPLES_BATTLE_REGISTRY_H
-#define EXAMPLES_BATTLE_REGISTRY_H
-
-#include <core/sound.h>
-#include <core/sprite.h>
-#include <core/texture.h>
-
-enum registry_texture {
-	/* UI. */
-	REGISTRY_TEXTURE_CURSOR,
-
-	/* Animations. */
-	REGISTRY_TEXTURE_EXPLOSION,
-
-	/* Characters. */
-	REGISTRY_TEXTURE_JOHN,
-
-	/* Enemies. */
-	REGISTRY_TEXTURE_HAUNTED_WOOD,
-	REGISTRY_TEXTURE_BLACK_CAT,
-
-	/* Unused.*/
-	REGISTRY_TEXTURE_NUM
-};
-
-enum registry_sound {
-	REGISTRY_SOUND_FIRE,
-	REGISTRY_SOUND_NUM
-};
-
-extern struct texture registry_textures[REGISTRY_TEXTURE_NUM];
-extern struct sprite registry_sprites[REGISTRY_TEXTURE_NUM];
-extern struct sound registry_sounds[REGISTRY_SOUND_NUM];
-
-void
-registry_init(void);
-
-void
-registry_finish(void);
-
-#endif /* !EXAMPLES_BATTLE_REGISTRY_H */
--- a/examples/battle/spell-fire.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/*
- * spell-fire.c -- example of spell: fire
- *
- * Copyright (c) 2020 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 <stdlib.h>
-
-#include <core/action.h>
-#include <core/animation.h>
-#include <core/alloc.h>
-
-#include <ui/align.h>
-
-#include <rpg/battle.h>
-#include <rpg/character.h>
-#include <rpg/spell.h>
-
-#include "registry.h"
-#include "spell-fire.h"
-
-struct data {
-	struct battle *battle;
-	struct animation animation;
-	struct action action;
-	unsigned int selection;
-};
-
-static bool
-update(struct action *act, unsigned int ticks)
-{
-	struct data *data = act->data;
-
-	return animation_update(&data->animation, ticks);
-}
-
-static void
-draw(struct action *act)
-{
-	const struct data *data = act->data;
-	const struct battle_entity *et = &data->battle->enemies[data->selection];
-	int x, y;
-
-	align(ALIGN_CENTER,
-	    &x, &y, data->animation.sprite->cellw, data->animation.sprite->cellh,
-	    et->x, et->y, et->ch->sprite->cellw, et->ch->sprite->cellh);
-
-	animation_draw(&data->animation, x, y);
-}
-
-static void
-end(struct action *act)
-{
-	struct data *data = act->data;
-	struct character *ch = data->battle->enemies[data->selection].ch;
-
-	/* TODO: compute damage. */
-	const unsigned int damage = 100;
-	if ((unsigned int)ch->hp < damage)
-		ch->hp = 0;
-	else
-		ch->hp -= damage;
-
-	battle_indicator_hp(data->battle, data->battle->enemies[data->selection].ch, 100);
-}
-
-static void
-finish(struct action *act)
-{
-	free(act->data);
-}
-
-static void
-fire_action(struct battle *bt, struct character *owner, unsigned int selection)
-{
-	struct data *data;
-
-	(void)owner;
-
-	data = alloc_zero(1, sizeof (*data));
-	data->battle = bt;
-	data->selection = selection;
-	data->action.data = data;
-	data->action.update = update;
-	data->action.draw = draw;
-	data->action.finish = finish;
-	data->action.end = end;
-
-	animation_init(&data->animation, &registry_sprites[REGISTRY_TEXTURE_EXPLOSION], 12);
-	animation_start(&data->animation);
-
-	sound_play(&registry_sounds[REGISTRY_SOUND_FIRE], -1, 0);
-
-	action_stack_add(&bt->actions[0], &data->action);
-}
-
-const struct spell spell_fire = {
-	.name = "Fire",
-	.description = "A delicate fire.",
-	.mp = 5,
-	.type = SPELL_TYPE_FIRE,
-	.selection = SELECTION_ENEMY_ONE,
-	.action = fire_action
-};
--- a/examples/battle/spell-fire.h	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-/*
- * spell-fire.c -- example of spell: fire
- *
- * Copyright (c) 2020 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 EXAMPLES_BATTLE_SPELL_FIRE_H
-#define EXAMPLES_BATTLE_SPELL_FIRE_H
-
-extern const struct spell spell_fire;
-
-#endif /* !EXAMPLES_BATTLE_SPELL_FIRE_H */
--- a/examples/example-action.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,400 +0,0 @@
-/*
- * example-action.c -- example on how to use automatic actions
- *
- * Copyright (c) 2020 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/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_stack events;
-
-/* This is a stack of modal events. */
-static struct action_stack modal;
-
-/*
- * Event object for the chest to click on.
- */
-static struct {
-	struct message msg;
-	struct action msg_act;
-	int x;
-	int y;
-	bool opened;
-	struct texture image;
-	struct sprite sprite;
-	struct action event;
-} chest = {
-	.msg = {
-		.x = MX,
-		.y = MY,
-		.w = MW,
-		.text = {
-			"100000 pièces.",
-			"Te voilà riche sale file de crevette."
-		}
-	}
-};
-
-/*
- * 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 {
-		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 = {
-		{
-			.msg = {
-				.x = MX,
-				.y = MY,
-				.w = MW,
-				.delay = MESSAGE_DELAY_DEFAULT,
-				.flags = MESSAGE_FLAGS_FADEIN,
-				.text = {
-					"Bienvenue dans ce monde Molko."
-				}
-			},
-		},
-		{
-			.msg = {
-				.x = MX,
-				.y = MY,
-				.w = MW,
-				.text = {
-					"Penses tu vraiment pouvoir me battre ?"
-				}
-			}
-		},
-		{
-			.msg = {
-				.x = MX,
-				.y = MY,
-				.w = MW,
-				.flags = MESSAGE_FLAGS_QUESTION,
-				.text = {
-					"Non j'ai la trouille.",
-					"Bien sûr, je suis la légende."
-				}
-			}
-		},
-
-		/* In case of NO. */
-		{
-			.msg = {
-				.x = MX,
-				.y = MY,
-				.w = MW,
-				.text = {
-					"Poule mouillée."
-				}
-			}
-		},
-
-		/* In case of YES. */
-		{
-			.msg = {
-				.x = MX,
-				.y = MY,
-				.w = MW,
-				.text = {
-					"Prépare toi à souffrir."
-				}
-			}
-		}
-	}
-};
-
-static bool
-guide_response_update(struct action *act, unsigned int ticks)
-{
-	/* Immediately return to get access to end. */
-	(void)act;
-	(void)ticks;
-
-	return true;
-}
-
-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, sprites_people, sizeof (sprites_people)))
-		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 = true;
-			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 ? 3 : 0;
-
-	sprite_draw(&chest.sprite, row, 0, chest.x, chest.y);
-}
-
-static void
-chest_init(void)
-{
-	if (!image_openmem(&chest.image, sprites_chest, sizeof (sprites_chest)))
-		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() || !ui_init() || !rpg_init())
-		panic();
-	if (!window_open("Example - Action", W, H))
-		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_add(&events, &chest.event);
-	action_stack_add(&events, &guide.event);
-
-	game_switch(&state, true);
-	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-action/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-action)
+
+molko_define_executable(
+	TARGET example-action
+	SOURCES ${example-action_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES librpg libexamples
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-action/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,400 @@
+/*
+ * example-action.c -- example on how to use automatic actions
+ *
+ * Copyright (c) 2020 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/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_stack events;
+
+/* This is a stack of modal events. */
+static struct action_stack modal;
+
+/*
+ * Event object for the chest to click on.
+ */
+static struct {
+	struct message msg;
+	struct action msg_act;
+	int x;
+	int y;
+	bool opened;
+	struct texture image;
+	struct sprite sprite;
+	struct action event;
+} chest = {
+	.msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.text = {
+			"100000 pièces.",
+			"Te voilà riche sale file de crevette."
+		}
+	}
+};
+
+/*
+ * 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 {
+		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 = {
+		{
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.delay = MESSAGE_DELAY_DEFAULT,
+				.flags = MESSAGE_FLAGS_FADEIN,
+				.text = {
+					"Bienvenue dans ce monde Molko."
+				}
+			},
+		},
+		{
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.text = {
+					"Penses tu vraiment pouvoir me battre ?"
+				}
+			}
+		},
+		{
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.flags = MESSAGE_FLAGS_QUESTION,
+				.text = {
+					"Non j'ai la trouille.",
+					"Bien sûr, je suis la légende."
+				}
+			}
+		},
+
+		/* In case of NO. */
+		{
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.text = {
+					"Poule mouillée."
+				}
+			}
+		},
+
+		/* In case of YES. */
+		{
+			.msg = {
+				.x = MX,
+				.y = MY,
+				.w = MW,
+				.text = {
+					"Prépare toi à souffrir."
+				}
+			}
+		}
+	}
+};
+
+static bool
+guide_response_update(struct action *act, unsigned int ticks)
+{
+	/* Immediately return to get access to end. */
+	(void)act;
+	(void)ticks;
+
+	return true;
+}
+
+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, sprites_people, sizeof (sprites_people)))
+		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 = true;
+			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 ? 3 : 0;
+
+	sprite_draw(&chest.sprite, row, 0, chest.x, chest.y);
+}
+
+static void
+chest_init(void)
+{
+	if (!image_openmem(&chest.image, sprites_chest, sizeof (sprites_chest)))
+		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() || !ui_init() || !rpg_init())
+		panic();
+	if (!window_open("Example - Action", W, H))
+		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_add(&events, &chest.event);
+	action_stack_add(&events, &guide.event);
+
+	game_switch(&state, true);
+	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;
+}
--- a/examples/example-animation.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/*
- * example-animation.c -- example on how to use animations
- *
- * Copyright (c) 2020 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 texture numbers;
-static struct animation animation;
-static struct sprite sprite;
-static bool completed = true;
-
-static void
-init(void)
-{
-	if (!core_init() || !ui_init())
-		panic();
-	if (!window_open("Example - Animation", W, H))
-		panic();
-	if (!image_openmem(&numbers, sprites_numbers, sizeof (sprites_numbers)))
-		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_switch(&state, true);
-	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-animation/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-animation)
+
+molko_define_executable(
+	TARGET example-animation
+	SOURCES ${example-animation_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES libui libexamples
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-animation/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,146 @@
+/*
+ * example-animation.c -- example on how to use animations
+ *
+ * Copyright (c) 2020 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 texture numbers;
+static struct animation animation;
+static struct sprite sprite;
+static bool completed = true;
+
+static void
+init(void)
+{
+	if (!core_init() || !ui_init())
+		panic();
+	if (!window_open("Example - Animation", W, H))
+		panic();
+	if (!image_openmem(&numbers, sprites_numbers, sizeof (sprites_numbers)))
+		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_switch(&state, true);
+	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();
+}
+
--- a/examples/example-audio.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-/*
- * example-audio.c -- show how to use sounds and music
- *
- * Copyright (c) 2020 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>
-
-/* https://freesound.org/people/VABsounds/sounds/423658 */
-#include <assets/musics/vabsounds-romance.h>
-#include <assets/sounds/fire.h>
-
-#define W 1280
-#define H 720
-
-static struct music music;
-static struct sound sound;
-
-static struct label label_music = {
-	.text = "Music: <Space> play, <f> fade in, <s> fade out, <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() || !ui_init())
-		panic();
-	if (!window_open("Example - Audio", W, H))
-		panic();
-	if (!music_openmem(&music, musics_vabsounds_romance, sizeof (musics_vabsounds_romance)) ||
-	    !sound_openmem(&sound, sounds_fire, sizeof (sounds_fire)))
-		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, -1, 0))
-			panic();
-		break;
-	case EVENT_KEYDOWN:
-		switch (ev->key.key) {
-		case KEY_f:
-			music_play(&music, 0, 500);
-			break;
-		case KEY_s:
-			music_stop(500);
-			break;
-		case KEY_p:
-			music_pause();
-			break;
-		case KEY_r:
-			music_resume();
-			break;
-		case KEY_q:
-			music_stop(0);
-			break;
-		case KEY_l:
-			music_play(&music, MUSIC_LOOP, 0);
-			break;
-		case KEY_SPACE:
-			music_play(&music, 0, 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_switch(&state, true);
-	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-audio/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-audio)
+
+molko_define_executable(
+	TARGET example-audio
+	SOURCES ${example-audio_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES libui libexamples
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-audio/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,160 @@
+/*
+ * example-audio.c -- show how to use sounds and music
+ *
+ * Copyright (c) 2020 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>
+
+/* https://freesound.org/people/VABsounds/sounds/423658 */
+#include <assets/music/vabsounds-romance.h>
+#include <assets/sounds/fire.h>
+
+#define W 1280
+#define H 720
+
+static struct music music;
+static struct sound sound;
+
+static struct label label_music = {
+	.text = "Music: <Space> play, <f> fade in, <s> fade out, <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() || !ui_init())
+		panic();
+	if (!window_open("Example - Audio", W, H))
+		panic();
+	if (!music_openmem(&music, music_vabsounds_romance, sizeof (music_vabsounds_romance)) ||
+	    !sound_openmem(&sound, sounds_fire, sizeof (sounds_fire)))
+		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, -1, 0))
+			panic();
+		break;
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_f:
+			music_play(&music, 0, 500);
+			break;
+		case KEY_s:
+			music_stop(500);
+			break;
+		case KEY_p:
+			music_pause();
+			break;
+		case KEY_r:
+			music_resume();
+			break;
+		case KEY_q:
+			music_stop(0);
+			break;
+		case KEY_l:
+			music_play(&music, MUSIC_LOOP, 0);
+			break;
+		case KEY_SPACE:
+			music_play(&music, 0, 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_switch(&state, true);
+	game_loop();
+
+	music_finish(&music);
+	sound_finish(&sound);
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- a/examples/example-battle.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,299 +0,0 @@
-/*
- * example-battle.c -- show how to use battle
- *
- * Copyright (c) 2020 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <core/alloc.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/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/theme.h>
-#include <ui/ui.h>
-
-#include <rpg/character.h>
-#include <rpg/battle.h>
-#include <rpg/rpg.h>
-#include <rpg/spell.h>
-
-#include "battle/registry.h"
-#include "battle/spell-fire.h"
-
-#define W 1280
-#define H 720
-
-static void
-adventurer_reset(struct character *ch)
-{
-	/* TODO: this function should compute the attack thanks to the level. */
-	ch->hpmax = 120;
-	ch->mpmax = 50;
-	ch->atk = 50;
-	ch->def = 50;
-	ch->agt = 50;
-	ch->luck = 50;
-}
-
-static void
-haunted_wood_reset(struct character *ch)
-{
-	ch->hpmax = ch->hp = 2000;
-	ch->mpmax = ch->mp = 250;
-	ch->atk = 178;
-	ch->def = 80;
-	ch->agt = 80;
-	ch->luck = 100;
-}
-
-static void
-black_cat_reset(struct character *ch)
-{
-	ch->hpmax = ch->hp = 126;
-	ch->mpmax = ch->mp = 38;
-	ch->atk = 22;
-	ch->def = 19;
-	ch->agt = 21;
-	ch->luck = 14;
-}
-
-static struct character team[] = {
-	{
-		.name = "Molko",
-		.type = "Adventurer",
-		.level = 1,
-		.hp = 120,
-		.mp = 50,
-		.reset = adventurer_reset,
-		.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
-		.spells = {
-			&spell_fire
-		}
-	},
-	{
-		.name = "Fake Molko",
-		.type = "Adventurer",
-		.level = 1,
-		.hp = 120,
-		.mp = 50,
-		.reset = adventurer_reset,
-		.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
-		.spells = {
-			&spell_fire
-		}
-	}
-};
-
-static void
-haunted_wood_strat(struct character *ch, struct battle *bt)
-{
-	(void)ch;
-
-	/* TODO: Select randomly. */
-	battle_attack(bt, bt->order_cur->ch, bt->team[0].ch);
-}
-
-static void
-black_cat_strat(struct character *ch, struct battle *bt)
-{
-	(void)ch;
-
-	/* TODO: Select randomly. */
-	battle_attack(bt, bt->order_cur->ch, bt->team[0].ch);
-}
-
-static struct character haunted_wood = {
-	.name = "Haunted Wood",
-	.type = "Wood",
-	.level = 30,
-	.reset = haunted_wood_reset,
-	.sprite = &registry_sprites[REGISTRY_TEXTURE_HAUNTED_WOOD],
-	.exec = haunted_wood_strat
-};
-
-static struct character black_cat = {
-	.name = "Black Cat",
-	.type = "Cat",
-	.level = 6,
-	.reset = black_cat_reset,
-	.sprite = &registry_sprites[REGISTRY_TEXTURE_BLACK_CAT],
-	.exec = black_cat_strat
-};
-
-static void
-init(void)
-{
-	if (!core_init() || !ui_init() || !rpg_init())
-		panic();
-	if (!window_open("Example - Battle", W, H))
-		panic();
-
-	registry_init();
-
-	/* Set cursor in default theme. */
-	theme_default()->sprites[THEME_SPRITE_CURSOR] = &registry_sprites[REGISTRY_TEXTURE_CURSOR];
-}
-
-static struct state fight_state;
-
-static void
-prepare_to_fight(void)
-{
-	struct battle *bt = alloc_zero(1, sizeof (*bt));
-
-//	bt->enemies[0].ch = &haunted_wood;
-	bt->team[0].ch = &team[0];
-	bt->team[1].ch = &team[1];
-
-	/* Positionate the single ennemy to the left. */
-	align(ALIGN_LEFT,
-	    &bt->enemies[0].x, &bt->enemies[0].y, haunted_wood.sprite->cellw, haunted_wood.sprite->cellh,
-	    0, 0, window.w, window.h);
-
-	/* Black cat is near the previous monster. */
-	bt->enemies[1].ch = &black_cat;
-	bt->enemies[1].x = 500;
-	bt->enemies[1].y = 100;
-
-	battle_start(bt);
-
-	fight_state.data = bt;
-	game_switch(&fight_state, false);
-}
-
-
-static void
-empty_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)
-			prepare_to_fight();
-		break;
-	default:
-		break;
-	}
-}
-
-static void
-empty_draw(struct state *st)
-{
-	(void)st;
-
-	static const struct label info = {
-		.text = "Press <Space> to start a battle.",
-		.x = 10,
-		.y = 10,
-		.flags = LABEL_FLAGS_SHADOW
-	};
-
-	painter_set_color(0x4f8fbaff);
-	painter_clear();
-	label_draw(&info);
-	painter_present();
-}
-
-static struct state empty_state = {
-	.handle = empty_handle,
-	.draw = empty_draw
-};
-
-static void
-fight_handle(struct state *st, const union event *ev)
-{
-	battle_handle(st->data, ev);
-}
-
-static void
-fight_update(struct state *st, unsigned int ticks)
-{
-	struct battle *bt = st->data;
-
-	if (battle_update(bt, ticks))
-		game_switch(&empty_state, false);
-}
-
-static void
-fight_draw(struct state *st)
-{
-	painter_set_color(0x000000ff);
-	painter_clear();
-	battle_draw(st->data);
-	painter_present();
-}
-
-static void
-fight_finish(struct state *st)
-{
-	battle_finish(st->data);
-	free(st->data);
-}
-
-static struct state fight_state = {
-	.handle = fight_handle,
-	.update = fight_update,
-	.draw = fight_draw,
-	.finish = fight_finish,
-};
-
-static void
-run(void)
-{
-	game_switch(&empty_state, true);
-	game_loop();
-}
-
-static void
-quit(void)
-{
-	registry_finish();
-	theme_finish();
-	window_finish();
-	sys_finish();
-}
-
-int
-main(int argc, char **argv)
-{
-	--argc;
-	++argv;
-
-	init();
-	run();
-	quit();
-
-	return 0;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-battle/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,31 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-battle)
+
+molko_define_executable(
+	TARGET example-battle
+	SOURCES
+		${example-battle_SOURCE_DIR}/main.c
+		${example-battle_SOURCE_DIR}/spell-fire.c
+		${example-battle_SOURCE_DIR}/spell-fire.h
+		${example-battle_SOURCE_DIR}/registry.c
+		${example-battle_SOURCE_DIR}/registry.h
+	FOLDER examples
+	LIBRARIES librpg libadventure libexamples
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-battle/character-john.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,48 @@
+/*
+ * character-john.c -- john character
+ *
+ * Copyright (c) 2020 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 <rpg/character.h>
+
+#include "character-john.h"
+#include "spell-fire.h"
+#include "registry.h"
+
+static void
+adventurer_reset(struct character *ch)
+{
+	/* TODO: this function should compute the attack thanks to the level. */
+	ch->hpmax = 120;
+	ch->mpmax = 50;
+	ch->atk = 50;
+	ch->def = 50;
+	ch->agt = 50;
+	ch->luck = 50;
+}
+
+const struct character character_john = {
+	.name = "John ",
+	.type = "Adventurer",
+	.level = 1,
+	.hp = 120,
+	.mp = 50,
+	.reset = adventurer_reset,
+	.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
+	.spells = {
+		&spell_fire
+	}
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-battle/character-john.h	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,24 @@
+/*
+ * character-john.h -- john character
+ *
+ * Copyright (c) 2020 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 EXAMPLES_BATTLE_CHARACTER_JOHN_H
+#define EXAMPLES_BATTLE_CHARACTER_JOHN_H
+
+const struct character character_john;
+
+#endif /* !EXAMPLES_BATTLE_CHARACTER_JOHN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-battle/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,299 @@
+/*
+ * example-battle.c -- show how to use battle
+ *
+ * Copyright (c) 2020 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <core/alloc.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/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/theme.h>
+#include <ui/ui.h>
+
+#include <rpg/character.h>
+#include <rpg/battle.h>
+#include <rpg/rpg.h>
+#include <rpg/spell.h>
+
+#include "registry.h"
+#include "spell-fire.h"
+
+#define W 1280
+#define H 720
+
+static void
+adventurer_reset(struct character *ch)
+{
+	/* TODO: this function should compute the attack thanks to the level. */
+	ch->hpmax = 120;
+	ch->mpmax = 50;
+	ch->atk = 50;
+	ch->def = 50;
+	ch->agt = 50;
+	ch->luck = 50;
+}
+
+static void
+haunted_wood_reset(struct character *ch)
+{
+	ch->hpmax = ch->hp = 2000;
+	ch->mpmax = ch->mp = 250;
+	ch->atk = 178;
+	ch->def = 80;
+	ch->agt = 80;
+	ch->luck = 100;
+}
+
+static void
+black_cat_reset(struct character *ch)
+{
+	ch->hpmax = ch->hp = 126;
+	ch->mpmax = ch->mp = 38;
+	ch->atk = 22;
+	ch->def = 19;
+	ch->agt = 21;
+	ch->luck = 14;
+}
+
+static struct character team[] = {
+	{
+		.name = "Molko",
+		.type = "Adventurer",
+		.level = 1,
+		.hp = 120,
+		.mp = 50,
+		.reset = adventurer_reset,
+		.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
+		.spells = {
+			&spell_fire
+		}
+	},
+	{
+		.name = "Fake Molko",
+		.type = "Adventurer",
+		.level = 1,
+		.hp = 120,
+		.mp = 50,
+		.reset = adventurer_reset,
+		.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
+		.spells = {
+			&spell_fire
+		}
+	}
+};
+
+static void
+haunted_wood_strat(struct character *ch, struct battle *bt)
+{
+	(void)ch;
+
+	/* TODO: Select randomly. */
+	battle_attack(bt, bt->order_cur->ch, bt->team[0].ch);
+}
+
+static void
+black_cat_strat(struct character *ch, struct battle *bt)
+{
+	(void)ch;
+
+	/* TODO: Select randomly. */
+	battle_attack(bt, bt->order_cur->ch, bt->team[0].ch);
+}
+
+static struct character haunted_wood = {
+	.name = "Haunted Wood",
+	.type = "Wood",
+	.level = 30,
+	.reset = haunted_wood_reset,
+	.sprite = &registry_sprites[REGISTRY_TEXTURE_HAUNTED_WOOD],
+	.exec = haunted_wood_strat
+};
+
+static struct character black_cat = {
+	.name = "Black Cat",
+	.type = "Cat",
+	.level = 6,
+	.reset = black_cat_reset,
+	.sprite = &registry_sprites[REGISTRY_TEXTURE_BLACK_CAT],
+	.exec = black_cat_strat
+};
+
+static void
+init(void)
+{
+	if (!core_init() || !ui_init() || !rpg_init())
+		panic();
+	if (!window_open("Example - Battle", W, H))
+		panic();
+
+	registry_init();
+
+	/* Set cursor in default theme. */
+	theme_default()->sprites[THEME_SPRITE_CURSOR] = &registry_sprites[REGISTRY_TEXTURE_CURSOR];
+}
+
+static struct state fight_state;
+
+static void
+prepare_to_fight(void)
+{
+	struct battle *bt = alloc_zero(1, sizeof (*bt));
+
+//	bt->enemies[0].ch = &haunted_wood;
+	bt->team[0].ch = &team[0];
+	bt->team[1].ch = &team[1];
+
+	/* Positionate the single ennemy to the left. */
+	align(ALIGN_LEFT,
+	    &bt->enemies[0].x, &bt->enemies[0].y, haunted_wood.sprite->cellw, haunted_wood.sprite->cellh,
+	    0, 0, window.w, window.h);
+
+	/* Black cat is near the previous monster. */
+	bt->enemies[1].ch = &black_cat;
+	bt->enemies[1].x = 500;
+	bt->enemies[1].y = 100;
+
+	battle_start(bt);
+
+	fight_state.data = bt;
+	game_switch(&fight_state, false);
+}
+
+
+static void
+empty_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)
+			prepare_to_fight();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+empty_draw(struct state *st)
+{
+	(void)st;
+
+	static const struct label info = {
+		.text = "Press <Space> to start a battle.",
+		.x = 10,
+		.y = 10,
+		.flags = LABEL_FLAGS_SHADOW
+	};
+
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	label_draw(&info);
+	painter_present();
+}
+
+static struct state empty_state = {
+	.handle = empty_handle,
+	.draw = empty_draw
+};
+
+static void
+fight_handle(struct state *st, const union event *ev)
+{
+	battle_handle(st->data, ev);
+}
+
+static void
+fight_update(struct state *st, unsigned int ticks)
+{
+	struct battle *bt = st->data;
+
+	if (battle_update(bt, ticks))
+		game_switch(&empty_state, false);
+}
+
+static void
+fight_draw(struct state *st)
+{
+	painter_set_color(0x000000ff);
+	painter_clear();
+	battle_draw(st->data);
+	painter_present();
+}
+
+static void
+fight_finish(struct state *st)
+{
+	battle_finish(st->data);
+	free(st->data);
+}
+
+static struct state fight_state = {
+	.handle = fight_handle,
+	.update = fight_update,
+	.draw = fight_draw,
+	.finish = fight_finish,
+};
+
+static void
+run(void)
+{
+	game_switch(&empty_state, true);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	registry_finish();
+	theme_finish();
+	window_finish();
+	sys_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	--argc;
+	++argv;
+
+	init();
+	run();
+	quit();
+
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-battle/registry.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,111 @@
+/*
+ * registry.h -- registry of resources
+ *
+ * Copyright (c) 2020 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/image.h>
+#include <core/panic.h>
+#include <core/util.h>
+
+#include <adventure/assets/sprites/john.h>
+
+#include <assets/images/haunted-wood.h>
+#include <assets/images/black-cat.h>
+
+#include <assets/sprites/cursor.h>
+#include <assets/sprites/explosion.h>
+
+#include <assets/sounds/fire.h>
+
+#include "registry.h"
+
+struct texture registry_textures[REGISTRY_TEXTURE_NUM];
+struct sprite registry_sprites[REGISTRY_TEXTURE_NUM];
+struct sound registry_sounds[REGISTRY_SOUND_NUM];
+
+#define REGISTRY_TEXTURE(s, ptr, cw, ch) \
+	{ (s), (ptr), sizeof ((ptr)), (cw), (ch) }
+
+static const struct {
+	enum registry_texture index;
+	const void *data;
+	size_t datasz;
+	unsigned int cellw;
+	unsigned int cellh;
+} textures[] = {
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_CURSOR, sprites_cursor, 24, 24),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_EXPLOSION, sprites_explosion, 256, 256),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_JOHN, sprites_john, 48, 48),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_HAUNTED_WOOD, images_haunted_wood, 0, 0),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_BLACK_CAT, images_black_cat, 0, 0)
+};
+
+#define REGISTRY_SOUND(s, ptr) \
+	{ (s), (ptr), sizeof ((ptr)) }
+
+static const struct {
+	enum registry_sound index;
+	const void *data;
+	size_t datasz;
+} sounds[] = {
+	REGISTRY_SOUND(REGISTRY_SOUND_FIRE, sounds_fire)
+};
+
+static void
+load_textures_and_sprites(void)
+{
+	for (size_t i = 0; i < NELEM(textures); ++i) {
+		struct texture *texture = &registry_textures[textures[i].index];
+		struct sprite *sprite = &registry_sprites[textures[i].index];
+
+		if (!image_openmem(texture, textures[i].data, textures[i].datasz))
+			panic();
+
+		if (textures[i].cellw == 0 || textures[i].cellh == 0)
+			sprite_init(sprite, texture, texture->w, texture->h);
+		else
+			sprite_init(sprite, texture, textures[i].cellw, textures[i].cellh);
+	}
+}
+
+static void
+load_sounds(void)
+{
+	for (size_t i = 0; i < NELEM(sounds); ++i) {
+		struct sound *sound = &registry_sounds[sounds[i].index];
+
+		if (!sound_openmem(sound, sounds[i].data, sounds[i].datasz))
+			panic();
+	}
+}
+
+void
+registry_init(void)
+{
+	load_textures_and_sprites();
+	load_sounds();
+}
+
+void
+registry_finish(void)
+{
+	for (size_t i = 0; i < NELEM(registry_textures); ++i)
+		texture_finish(&registry_textures[i]);
+	for (size_t i = 0; i < NELEM(registry_sounds); ++i)
+		sound_finish(&registry_sounds[i]);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-battle/registry.h	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,59 @@
+/*
+ * registry.h -- registry of resources
+ *
+ * Copyright (c) 2020 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 EXAMPLES_BATTLE_REGISTRY_H
+#define EXAMPLES_BATTLE_REGISTRY_H
+
+#include <core/sound.h>
+#include <core/sprite.h>
+#include <core/texture.h>
+
+enum registry_texture {
+	/* UI. */
+	REGISTRY_TEXTURE_CURSOR,
+
+	/* Animations. */
+	REGISTRY_TEXTURE_EXPLOSION,
+
+	/* Characters. */
+	REGISTRY_TEXTURE_JOHN,
+
+	/* Enemies. */
+	REGISTRY_TEXTURE_HAUNTED_WOOD,
+	REGISTRY_TEXTURE_BLACK_CAT,
+
+	/* Unused.*/
+	REGISTRY_TEXTURE_NUM
+};
+
+enum registry_sound {
+	REGISTRY_SOUND_FIRE,
+	REGISTRY_SOUND_NUM
+};
+
+extern struct texture registry_textures[REGISTRY_TEXTURE_NUM];
+extern struct sprite registry_sprites[REGISTRY_TEXTURE_NUM];
+extern struct sound registry_sounds[REGISTRY_SOUND_NUM];
+
+void
+registry_init(void);
+
+void
+registry_finish(void);
+
+#endif /* !EXAMPLES_BATTLE_REGISTRY_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-battle/spell-fire.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,116 @@
+/*
+ * spell-fire.c -- example of spell: fire
+ *
+ * Copyright (c) 2020 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 <stdlib.h>
+
+#include <core/action.h>
+#include <core/animation.h>
+#include <core/alloc.h>
+
+#include <ui/align.h>
+
+#include <rpg/battle.h>
+#include <rpg/character.h>
+#include <rpg/spell.h>
+
+#include "registry.h"
+#include "spell-fire.h"
+
+struct data {
+	struct battle *battle;
+	struct animation animation;
+	struct action action;
+	unsigned int selection;
+};
+
+static bool
+update(struct action *act, unsigned int ticks)
+{
+	struct data *data = act->data;
+
+	return animation_update(&data->animation, ticks);
+}
+
+static void
+draw(struct action *act)
+{
+	const struct data *data = act->data;
+	const struct battle_entity *et = &data->battle->enemies[data->selection];
+	int x, y;
+
+	align(ALIGN_CENTER,
+	    &x, &y, data->animation.sprite->cellw, data->animation.sprite->cellh,
+	    et->x, et->y, et->ch->sprite->cellw, et->ch->sprite->cellh);
+
+	animation_draw(&data->animation, x, y);
+}
+
+static void
+end(struct action *act)
+{
+	struct data *data = act->data;
+	struct character *ch = data->battle->enemies[data->selection].ch;
+
+	/* TODO: compute damage. */
+	const unsigned int damage = 100;
+	if ((unsigned int)ch->hp < damage)
+		ch->hp = 0;
+	else
+		ch->hp -= damage;
+
+	battle_indicator_hp(data->battle, data->battle->enemies[data->selection].ch, 100);
+}
+
+static void
+finish(struct action *act)
+{
+	free(act->data);
+}
+
+static void
+fire_action(struct battle *bt, struct character *owner, unsigned int selection)
+{
+	struct data *data;
+
+	(void)owner;
+
+	data = alloc_zero(1, sizeof (*data));
+	data->battle = bt;
+	data->selection = selection;
+	data->action.data = data;
+	data->action.update = update;
+	data->action.draw = draw;
+	data->action.finish = finish;
+	data->action.end = end;
+
+	animation_init(&data->animation, &registry_sprites[REGISTRY_TEXTURE_EXPLOSION], 12);
+	animation_start(&data->animation);
+
+	sound_play(&registry_sounds[REGISTRY_SOUND_FIRE], -1, 0);
+
+	action_stack_add(&bt->actions[0], &data->action);
+}
+
+const struct spell spell_fire = {
+	.name = "Fire",
+	.description = "A delicate fire.",
+	.mp = 5,
+	.type = SPELL_TYPE_FIRE,
+	.selection = SELECTION_ENEMY_ONE,
+	.action = fire_action
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-battle/spell-fire.h	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,24 @@
+/*
+ * spell-fire.c -- example of spell: fire
+ *
+ * Copyright (c) 2020 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 EXAMPLES_BATTLE_SPELL_FIRE_H
+#define EXAMPLES_BATTLE_SPELL_FIRE_H
+
+extern const struct spell spell_fire;
+
+#endif /* !EXAMPLES_BATTLE_SPELL_FIRE_H */
--- a/examples/example-cursor.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/*
- * example-cursor.c -- example on how to change cursor
- *
- * Copyright (c) 2020 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 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() || !ui_init())
-		panic();
-	if (!window_open("Example - Cursor", W, H))
-		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_switch(&state, true);
-	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-cursor/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-cursor)
+
+molko_define_executable(
+	TARGET example-cursor
+	SOURCES ${example-cursor_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES libui
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-cursor/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,146 @@
+/*
+ * example-cursor.c -- example on how to change cursor
+ *
+ * Copyright (c) 2020 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 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() || !ui_init())
+		panic();
+	if (!window_open("Example - Cursor", W, H))
+		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_switch(&state, true);
+	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();
+}
--- a/examples/example-debug.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/*
- * example-debug.c -- example on how to use debug interface
- *
- * Copyright (c) 2020 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 int mouse_x;
-static int mouse_y;
-
-static void
-init(void)
-{
-	if (!core_init() || !ui_init())
-		panic();
-	if (!window_open("Example - Debug", W, H))
-		panic();
-
-	debug_options.enable = true;
-}
-
-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_switch(&state, true);
-	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/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-debug)
+
+molko_define_executable(
+	TARGET example-debug
+	SOURCES ${example-debug_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES libui
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-debug/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,111 @@
+/*
+ * example-debug.c -- example on how to use debug interface
+ *
+ * Copyright (c) 2020 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 int mouse_x;
+static int mouse_y;
+
+static void
+init(void)
+{
+	if (!core_init() || !ui_init())
+		panic();
+	if (!window_open("Example - Debug", W, H))
+		panic();
+
+	debug_options.enable = true;
+}
+
+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_switch(&state, true);
+	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();
+}
+
--- a/examples/example-drawable.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +0,0 @@
-/*
- * example-drawable.c -- example on how to use automatic drawables
- *
- * Copyright (c) 2020 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/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 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() || !ui_init())
-		panic();
-	if (!window_open("Example - Drawable", W, H))
-		panic();
-
-	/* 0: Explosion animation. */
-	if (!image_openmem(&explosion_tex, sprites_explosion, sizeof (sprites_explosion)))
-		panic();
-
-	sprite_init(&explosion_sprite, &explosion_tex, 256, 256);
-}
-
-static bool
-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_zero(1, 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
-	};
-
-	game_switch(&state, true);
-	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/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-drawable)
+
+molko_define_executable(
+	TARGET example-drawable
+	SOURCES ${example-drawable_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES libui libexamples
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-drawable/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,202 @@
+/*
+ * example-drawable.c -- example on how to use automatic drawables
+ *
+ * Copyright (c) 2020 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/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 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() || !ui_init())
+		panic();
+	if (!window_open("Example - Drawable", W, H))
+		panic();
+
+	/* 0: Explosion animation. */
+	if (!image_openmem(&explosion_tex, sprites_explosion, sizeof (sprites_explosion)))
+		panic();
+
+	sprite_init(&explosion_sprite, &explosion_tex, 256, 256);
+}
+
+static bool
+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_zero(1, 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
+	};
+
+	game_switch(&state, true);
+	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();
+}
--- a/examples/example-font.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/*
- * example-font.c -- show how to use fonts
- *
- * Copyright (c) 2020 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 int ci = 0;
-static enum font_style style = FONT_STYLE_ANTIALIASED;
-
-static void
-init(void)
-{
-	if (!core_init() || !ui_init())
-		panic();
-	if (!window_open("Example - Font", W, H))
-		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 < NELEM(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]))
-		panic();
-
-	texture_draw(&tex, 10, 10);
-	painter_present();
-	texture_finish(&tex);
-}
-
-static void
-run(void)
-{
-	struct state state = {
-		.handle = handle,
-		.draw = draw
-	};
-
-	game_switch(&state, true);
-	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-font/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-font)
+
+molko_define_executable(
+	TARGET example-font
+	SOURCES ${example-font_SOURCE_DIR}/main.c
+	LIBRARIES libui
+	FOLDER examples
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-font/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,146 @@
+/*
+ * example-font.c -- show how to use fonts
+ *
+ * Copyright (c) 2020 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 int ci = 0;
+static enum font_style style = FONT_STYLE_ANTIALIASED;
+
+static void
+init(void)
+{
+	if (!core_init() || !ui_init())
+		panic();
+	if (!window_open("Example - Font", W, H))
+		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 < NELEM(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]))
+		panic();
+
+	texture_draw(&tex, 10, 10);
+	painter_present();
+	texture_finish(&tex);
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	game_switch(&state, true);
+	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;
+}
--- a/examples/example-gridmenu.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-/*
- * example-gridmenu.c -- show how to use grid menu
- *
- * Copyright (c) 2020 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 void
-init(void)
-{
-	if (!core_init() || !ui_init())
-		panic();
-	if (!window_open("Example - Grid menu", W, H))
-		panic();
-}
-
-static void
-quit(void)
-{
-	window_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:
-		gridmenu_handle(st->data, ev);
-		break;
-	}
-}
-
-static void
-update(struct state *st, unsigned int ticks)
-{
-	(void)ticks;
-
-	struct gridmenu *menu = st->data;
-
-	if (menu->state == GRIDMENU_STATE_ACTIVATED) {
-		tracef("selected index: %u", (unsigned int)menu->selected);
-		gridmenu_reset(menu);
-	}
-}
-
-static void
-draw(struct state *st)
-{
-	painter_set_color(0x4f8fbaff);
-	painter_clear();
-	gridmenu_draw(st->data);
-	painter_present();
-}
-
-static void
-run(void)
-{
-	struct gridmenu menu = {
-		.menu = {
-			"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",
-		},
-		.w = 300,
-		.h = 100,
-		.nrows = 3,
-		.ncols = 2
-	};
-	struct state state = {
-		.data = &menu,
-		.handle = handle,
-		.update = update,
-		.draw = draw,
-	};
-
-	align(ALIGN_CENTER, &menu.x, &menu.y, menu.w, menu.h, 0, 0, W, H);
-
-	/* Need to repaint at least once. */
-	gridmenu_repaint(&menu);
-
-	game_switch(&state, true);
-	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-gridmenu/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-gridmenu)
+
+molko_define_executable(
+	TARGET example-gridmenu
+	SOURCES ${example-gridmenu_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES libui
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-gridmenu/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,139 @@
+/*
+ * example-gridmenu.c -- show how to use grid menu
+ *
+ * Copyright (c) 2020 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 void
+init(void)
+{
+	if (!core_init() || !ui_init())
+		panic();
+	if (!window_open("Example - Grid menu", W, H))
+		panic();
+}
+
+static void
+quit(void)
+{
+	window_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:
+		gridmenu_handle(st->data, ev);
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)ticks;
+
+	struct gridmenu *menu = st->data;
+
+	if (menu->state == GRIDMENU_STATE_ACTIVATED) {
+		tracef("selected index: %u", (unsigned int)menu->selected);
+		gridmenu_reset(menu);
+	}
+}
+
+static void
+draw(struct state *st)
+{
+	painter_set_color(0x4f8fbaff);
+	painter_clear();
+	gridmenu_draw(st->data);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct gridmenu menu = {
+		.menu = {
+			"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",
+		},
+		.w = 300,
+		.h = 100,
+		.nrows = 3,
+		.ncols = 2
+	};
+	struct state state = {
+		.data = &menu,
+		.handle = handle,
+		.update = update,
+		.draw = draw,
+	};
+
+	align(ALIGN_CENTER, &menu.x, &menu.y, menu.w, menu.h, 0, 0, W, H);
+
+	/* Need to repaint at least once. */
+	gridmenu_repaint(&menu);
+
+	game_switch(&state, true);
+	game_loop();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- a/examples/example-inventory.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,180 +0,0 @@
-/*
- * example-inventory.c -- show how to use inventory dialog
- *
- * Copyright (c) 2020 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/clock.h>
-#include <core/core.h>
-#include <core/event.h>
-#include <core/image.h>
-#include <core/painter.h>
-#include <core/panic.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/item.h>
-#include <rpg/inventory.h>
-#include <rpg/inventory_dialog.h>
-#include <rpg/rpg.h>
-
-/* https://shikashiassets.itch.io/shikashis-fantasy-icons-pack */
-#include <assets/images/fish.h>
-#include <assets/images/potion.h>
-#include <assets/images/sword.h>
-
-#define W 1280
-#define H 720
-
-static struct {
-	const unsigned char *data;
-	const size_t datasz;
-	struct texture icon;
-	struct item item;
-} items[] = {
-	{
-		.data = images_potion,
-		.datasz = sizeof (images_potion),
-		.item = {
-			.name = "Potion",
-			.summary = "Recover 100 HP.",
-			.stackable = ITEM_STACK_MAX
-		}
-	},
-	{
-		.data = images_fish,
-		.datasz = sizeof (images_fish),
-		.item = {
-			.name = "Fish",
-			.summary = "Recover 1000 HP.",
-			.stackable = ITEM_STACK_MAX
-		}
-	},
-	{
-		.data = images_sword,
-		.datasz = sizeof (images_sword),
-		.item = {
-			.name = "Sword",
-			.summary = "A very basic sword.",
-			.stackable = 1
-		}
-	},
-};
-
-static void
-init(void)
-{
-	if (!core_init() || !ui_init() || !rpg_init())
-		panic();
-	if (!window_open("Example - Inventory", W, H))
-		panic();
-
-	for (size_t i = 0; i < NELEM(items); ++i) {
-		if (!image_openmem(&items[i].icon, items[i].data, items[i].datasz))
-			panic();
-
-		items[i].item.icon = &items[i].icon;
-	}
-}
-
-static void
-quit(void)
-{
-	window_finish();
-	rpg_finish();
-	ui_finish();
-	core_finish();
-}
-
-static void
-run(struct inventory_dialog *dlg)
-{
-	struct clock clock = {0};
-
-	clock_start(&clock);
-	inventory_dialog_open(dlg);
-
-	while (dlg->state) {
-		union event ev;
-		unsigned int elapsed = clock_elapsed(&clock);
-
-		clock_start(&clock);
-
-		while (event_poll(&ev)) {
-			switch (ev.type) {
-			case EVENT_QUIT:
-				inventory_dialog_finish(dlg);
-				break;
-			default:
-				inventory_dialog_handle(dlg, &ev);
-				break;
-			}
-		}
-
-		inventory_dialog_update(dlg, elapsed);
-		painter_set_color(0xffffffff);
-		painter_clear();
-		inventory_dialog_draw(dlg);
-		painter_present();
-
-		if ((elapsed = clock_elapsed(&clock)) < 20)
-			delay(20 - elapsed);
-	}
-}
-
-static void
-basic(void)
-{
-	struct inventory inv = { 0 };
-	struct inventory_dialog dlg = {
-		.inv = &inv,
-		.x = 60,
-		.y = 60
-	};
-
-	/* Add items manually to be able to sort. */
-	inv.items[1][5].item = &items[0].item;
-	inv.items[1][5].amount = 12;
-
-	inv.items[1][2].item = &items[0].item;
-	inv.items[1][2].amount = 9;
-
-	inv.items[2][7].item = &items[1].item;
-	inv.items[2][7].amount = 9;
-
-	inv.items[2][8].item = &items[1].item;
-	inv.items[2][8].amount = 3;
-
-	inv.items[2][4].item = &items[2].item;
-	inv.items[2][4].amount = 1;
-
-	run(&dlg);
-}
-
-int
-main(int argc, char **argv)
-{
-	(void)argc;
-	(void)argv;
-
-	init();
-	basic();
-	quit();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-inventory/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,180 @@
+/*
+ * example-inventory.c -- show how to use inventory dialog
+ *
+ * Copyright (c) 2020 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/clock.h>
+#include <core/core.h>
+#include <core/event.h>
+#include <core/image.h>
+#include <core/painter.h>
+#include <core/panic.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/item.h>
+#include <rpg/inventory.h>
+#include <rpg/inventory_dialog.h>
+#include <rpg/rpg.h>
+
+/* https://shikashiassets.itch.io/shikashis-fantasy-icons-pack */
+#include <assets/images/fish.h>
+#include <assets/images/potion.h>
+#include <assets/images/sword.h>
+
+#define W 1280
+#define H 720
+
+static struct {
+	const unsigned char *data;
+	const size_t datasz;
+	struct texture icon;
+	struct item item;
+} items[] = {
+	{
+		.data = images_potion,
+		.datasz = sizeof (images_potion),
+		.item = {
+			.name = "Potion",
+			.summary = "Recover 100 HP.",
+			.stackable = ITEM_STACK_MAX
+		}
+	},
+	{
+		.data = images_fish,
+		.datasz = sizeof (images_fish),
+		.item = {
+			.name = "Fish",
+			.summary = "Recover 1000 HP.",
+			.stackable = ITEM_STACK_MAX
+		}
+	},
+	{
+		.data = images_sword,
+		.datasz = sizeof (images_sword),
+		.item = {
+			.name = "Sword",
+			.summary = "A very basic sword.",
+			.stackable = 1
+		}
+	},
+};
+
+static void
+init(void)
+{
+	if (!core_init() || !ui_init() || !rpg_init())
+		panic();
+	if (!window_open("Example - Inventory", W, H))
+		panic();
+
+	for (size_t i = 0; i < NELEM(items); ++i) {
+		if (!image_openmem(&items[i].icon, items[i].data, items[i].datasz))
+			panic();
+
+		items[i].item.icon = &items[i].icon;
+	}
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	rpg_finish();
+	ui_finish();
+	core_finish();
+}
+
+static void
+run(struct inventory_dialog *dlg)
+{
+	struct clock clock = {0};
+
+	clock_start(&clock);
+	inventory_dialog_open(dlg);
+
+	while (dlg->state) {
+		union event ev;
+		unsigned int elapsed = clock_elapsed(&clock);
+
+		clock_start(&clock);
+
+		while (event_poll(&ev)) {
+			switch (ev.type) {
+			case EVENT_QUIT:
+				inventory_dialog_finish(dlg);
+				break;
+			default:
+				inventory_dialog_handle(dlg, &ev);
+				break;
+			}
+		}
+
+		inventory_dialog_update(dlg, elapsed);
+		painter_set_color(0xffffffff);
+		painter_clear();
+		inventory_dialog_draw(dlg);
+		painter_present();
+
+		if ((elapsed = clock_elapsed(&clock)) < 20)
+			delay(20 - elapsed);
+	}
+}
+
+static void
+basic(void)
+{
+	struct inventory inv = { 0 };
+	struct inventory_dialog dlg = {
+		.inv = &inv,
+		.x = 60,
+		.y = 60
+	};
+
+	/* Add items manually to be able to sort. */
+	inv.items[1][5].item = &items[0].item;
+	inv.items[1][5].amount = 12;
+
+	inv.items[1][2].item = &items[0].item;
+	inv.items[1][2].amount = 9;
+
+	inv.items[2][7].item = &items[1].item;
+	inv.items[2][7].amount = 9;
+
+	inv.items[2][8].item = &items[1].item;
+	inv.items[2][8].amount = 3;
+
+	inv.items[2][4].item = &items[2].item;
+	inv.items[2][4].amount = 1;
+
+	run(&dlg);
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	basic();
+	quit();
+}
--- a/examples/example-label.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,182 +0,0 @@
-/*
- * example-label.c -- show how to use label and alignments
- *
- * Copyright (c) 2020 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/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 void
-init(void)
-{
-	if (!core_init() || !ui_init())
-		panic();
-	if (!window_open("Example - Label", W, H))
-		panic();
-
-	for (size_t i = 0; i < NELEM(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 < NELEM(table); ++i)
-		label_draw(&table[i].label);
-
-	label_draw(&mlabel);
-	painter_present();
-}
-
-static void
-run(void)
-{
-	struct state state = {
-		.handle = handle,
-		.draw = draw
-	};
-
-	game_switch(&state, true);
-	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/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-label)
+
+molko_define_executable(
+	TARGET example-label
+	SOURCES ${example-label_SOURCE_DIR}/main.c
+	LIBRARIES libui
+	FOLDER examples
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-label/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,182 @@
+/*
+ * example-label.c -- show how to use label and alignments
+ *
+ * Copyright (c) 2020 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/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 void
+init(void)
+{
+	if (!core_init() || !ui_init())
+		panic();
+	if (!window_open("Example - Label", W, H))
+		panic();
+
+	for (size_t i = 0; i < NELEM(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 < NELEM(table); ++i)
+		label_draw(&table[i].label);
+
+	label_draw(&mlabel);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	game_switch(&state, true);
+	game_loop();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
+
--- a/examples/example-message.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,325 +0,0 @@
-/*
- * example-message.c -- show how to use messages
- *
- * Copyright (c) 2020 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/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 void
-init(void)
-{
-	if (!core_init() || !ui_init() || !rpg_init())
-		panic();
-	if (!window_open("Example - Message", W, H))
-		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_switch(&state, true);
-	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)
-{
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = MW,
-		.spacing = 12,
-		.text = {
-			"This is a basic message.",
-			"Vertical spacing is automatically computed.",
-			"You need to press <Enter> to close it.",
-		},
-	};
-
-	message_query(&msg, NULL, &msg.h);
-	run(&msg);
-}
-
-static void
-automatic(void)
-{
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = MW,
-		.timeout = MESSAGE_TIMEOUT_DEFAULT,
-		.text = {
-			"This is a an automatic message.",
-			"It will disappear in a few seconds.",
-			"You can still press <Enter> to close it quicker."
-		},
-		.flags = MESSAGE_FLAGS_AUTOMATIC
-	};
-
-	message_query(&msg, NULL, &msg.h);
-	run(&msg);
-}
-
-static void
-fadein(void)
-{
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = MW,
-		.delay = MESSAGE_DELAY_DEFAULT,
-		.text = {
-			"This message will fade in."
-		},
-		.flags = MESSAGE_FLAGS_FADEIN
-	};
-
-	message_query(&msg, NULL, &msg.h);
-	run(&msg);
-}
-
-static void
-fadeout(void)
-{
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = MW,
-		.delay = MESSAGE_DELAY_DEFAULT,
-		.text = {
-			"This message will fade out."
-		},
-		.flags = MESSAGE_FLAGS_FADEOUT
-	};
-
-	message_query(&msg, NULL, &msg.h);
-	run(&msg);
-}
-
-static void
-fade(void)
-{
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = MW,
-		.delay = MESSAGE_DELAY_DEFAULT,
-		.text = {
-			"This message will fade in and out."
-		},
-		.flags = MESSAGE_FLAGS_FADEIN | MESSAGE_FLAGS_FADEOUT
-	};
-
-	message_query(&msg, NULL, &msg.h);
-	run(&msg);
-}
-
-static void
-question(void)
-{
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = MW,
-		.text = {
-			"Okay, I've understood.",
-			"Nevermind, I'll do it again."
-		},
-		.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);
-
-	struct message msg = {
-		.x = x,
-		.y = y,
-		.w = w,
-		.h = h,
-		.delay = MESSAGE_DELAY_DEFAULT,
-		.flags = MESSAGE_FLAGS_FADEIN | MESSAGE_FLAGS_FADEOUT,
-		.text = {
-			"This one is small here."
-		}
-	};
-
-	run(&msg);
-}
-
-static void
-toosmallh(void)
-{
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = MW,
-		.h = 40,
-		.text = {
-			"This one is too small in height and will emit a warning.",
-			"Because this line will be incomplete."
-		},
-	};
-
-	run(&msg);
-}
-
-static void
-toosmallw(void)
-{
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = 160,
-		.h = MH,
-		.text = {
-			"This one is too small in width."
-		},
-	};
-
-	run(&msg);
-}
-
-static void
-custom(void)
-{
-	struct theme theme;
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = MW,
-		.h = MH,
-		.text = {
-			"This one will destroy your eyes.",
-			"Because it use a terrible custom theme."
-		},
-		.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-message/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-message)
+
+molko_define_executable(
+	TARGET example-message
+	SOURCES ${example-message_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES librpg
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-message/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,325 @@
+/*
+ * example-message.c -- show how to use messages
+ *
+ * Copyright (c) 2020 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/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 void
+init(void)
+{
+	if (!core_init() || !ui_init() || !rpg_init())
+		panic();
+	if (!window_open("Example - Message", W, H))
+		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_switch(&state, true);
+	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)
+{
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.spacing = 12,
+		.text = {
+			"This is a basic message.",
+			"Vertical spacing is automatically computed.",
+			"You need to press <Enter> to close it.",
+		},
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+automatic(void)
+{
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.timeout = MESSAGE_TIMEOUT_DEFAULT,
+		.text = {
+			"This is a an automatic message.",
+			"It will disappear in a few seconds.",
+			"You can still press <Enter> to close it quicker."
+		},
+		.flags = MESSAGE_FLAGS_AUTOMATIC
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+fadein(void)
+{
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.delay = MESSAGE_DELAY_DEFAULT,
+		.text = {
+			"This message will fade in."
+		},
+		.flags = MESSAGE_FLAGS_FADEIN
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+fadeout(void)
+{
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.delay = MESSAGE_DELAY_DEFAULT,
+		.text = {
+			"This message will fade out."
+		},
+		.flags = MESSAGE_FLAGS_FADEOUT
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+fade(void)
+{
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.delay = MESSAGE_DELAY_DEFAULT,
+		.text = {
+			"This message will fade in and out."
+		},
+		.flags = MESSAGE_FLAGS_FADEIN | MESSAGE_FLAGS_FADEOUT
+	};
+
+	message_query(&msg, NULL, &msg.h);
+	run(&msg);
+}
+
+static void
+question(void)
+{
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.text = {
+			"Okay, I've understood.",
+			"Nevermind, I'll do it again."
+		},
+		.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);
+
+	struct message msg = {
+		.x = x,
+		.y = y,
+		.w = w,
+		.h = h,
+		.delay = MESSAGE_DELAY_DEFAULT,
+		.flags = MESSAGE_FLAGS_FADEIN | MESSAGE_FLAGS_FADEOUT,
+		.text = {
+			"This one is small here."
+		}
+	};
+
+	run(&msg);
+}
+
+static void
+toosmallh(void)
+{
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.h = 40,
+		.text = {
+			"This one is too small in height and will emit a warning.",
+			"Because this line will be incomplete."
+		},
+	};
+
+	run(&msg);
+}
+
+static void
+toosmallw(void)
+{
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = 160,
+		.h = MH,
+		.text = {
+			"This one is too small in width."
+		},
+	};
+
+	run(&msg);
+}
+
+static void
+custom(void)
+{
+	struct theme theme;
+	struct message msg = {
+		.x = MX,
+		.y = MY,
+		.w = MW,
+		.h = MH,
+		.text = {
+			"This one will destroy your eyes.",
+			"Because it use a terrible custom theme."
+		},
+		.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();
+}
--- a/examples/example-sprite.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,159 +0,0 @@
-/*
- * example-sprite.c -- example on how to use sprites
- *
- * Copyright (c) 2020 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/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 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() || !ui_init())
-		panic();
-	if (!window_open("Example - Sprite", W, H))
-		panic();
-	if (!image_openmem(&texture, sprites_people, sizeof (sprites_people)))
-		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
-	};
-
-	game_switch(&state, true);
-	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-sprite/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-sprite)
+
+molko_define_executable(
+	TARGET example-sprite
+	SOURCES ${example-sprite_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES libui libexamples
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-sprite/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,161 @@
+/*
+ * example-sprite.c -- example on how to use sprites
+ *
+ * Copyright (c) 2020 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/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 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() || !ui_init())
+		panic();
+	if (!window_open("Example - Sprite", W, H))
+		panic();
+	if (!image_openmem(&texture, sprites_people, sizeof (sprites_people)))
+		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_switch(&state, true);
+	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();
+}
--- a/examples/example-trace.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-/*
- * example-trace.c -- example on how to use custom trace handlers
- *
- * Copyright (c) 2020 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 <adventure/trace_hud.h>
-
-#define W 1280
-#define H 720
-
-static void
-init(void)
-{
-	if (!core_init() || !ui_init())
-		panic();
-	if (!window_open("Example - Trace", W, H))
-		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_switch(&state, true);
-	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-trace/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-trace)
+
+molko_define_executable(
+	TARGET example-trace
+	SOURCES ${example-trace_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES libui libadventure
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-trace/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,126 @@
+/*
+ * example-trace.c -- example on how to use custom trace handlers
+ *
+ * Copyright (c) 2020 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 <adventure/trace_hud.h>
+
+#define W 1280
+#define H 720
+
+static void
+init(void)
+{
+	if (!core_init() || !ui_init())
+		panic();
+	if (!window_open("Example - Trace", W, H))
+		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_switch(&state, true);
+	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();
+}
+
--- a/examples/example-ui.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,324 +0,0 @@
-/*
- * example-action.c -- example on how to use automatic actions
- *
- * Copyright (c) 2020 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/action.h>
-#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 action_stack st;
-
-	struct {
-		bool active;
-		int x;
-		int y;
-	} motion;
-
-	struct {
-		struct frame frame;
-		struct action act;
-	} panel;
-
-	struct {
-		struct label label;
-		struct action act;
-	} header;
-
-	struct {
-		struct checkbox cb;
-		struct action cb_act;
-		struct label label;
-		struct action label_act;
-	} autosave;
-
-	struct {
-		struct button button;
-		struct action act;
-	} 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 void
-init(void)
-{
-	if (!core_init() || !ui_init())
-		panic();
-	if (!window_open("Example - UI", W, H))
-		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 void
-prepare(void)
-{
-	/* Frame. */
-	frame_action(&ui.panel.frame, &ui.panel.act);
-
-	/* Header title. */
-	label_action(&ui.header.label, &ui.header.act);
-
-	/* Button quit. */
-	button_action(&ui.quit.button, &ui.quit.act);
-
-	/* Autosave. */
-	checkbox_action(&ui.autosave.cb, &ui.autosave.cb_act);
-	label_action(&ui.autosave.label, &ui.autosave.label_act);
-
-	/* Add all UI elements. */
-	action_stack_add(&ui.st, &ui.panel.act);
-	action_stack_add(&ui.st, &ui.header.act);
-	action_stack_add(&ui.st, &ui.autosave.cb_act);
-	action_stack_add(&ui.st, &ui.autosave.label_act);
-	action_stack_add(&ui.st, &ui.quit.act);
-}
-
-static bool
-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 = true;
-			ui.motion.x = ev->click.x;
-			ui.motion.y = ev->click.y;
-			window_set_cursor(WINDOW_CURSOR_SIZE);
-		}
-		else
-			action_stack_handle(&ui.st, ev);
-		break;
-	case EVENT_CLICKUP:
-		ui.motion.active = false;
-		window_set_cursor(WINDOW_CURSOR_ARROW);
-		/* Fallthrough. */
-	default:
-		action_stack_handle(&ui.st, ev);
-		break;
-	}
-}
-
-static void
-update(struct state *st, unsigned int ticks)
-{
-	(void)st;
-
-	if (ui.quit.button.state == BUTTON_STATE_ACTIVATED)
-		game_quit();
-	else
-		action_stack_update(&ui.st, ticks);
-}
-
-static void
-draw(struct state *st)
-{
-	(void)st;
-
-	painter_set_color(0xffffffff);
-	painter_clear();
-	action_stack_draw(&ui.st);
-	painter_present();
-}
-
-static void
-run(void)
-{
-	struct state state = {
-		.handle = handle,
-		.update = update,
-		.draw = draw
-	};
-
-	prepare();
-	resize();
-
-	game_switch(&state, true);
-	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-ui/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,26 @@
+#
+# CMakeLists.txt -- CMake build system for molko
+#
+# Copyright (c) 2020 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.
+#
+
+project(example-ui)
+
+molko_define_executable(
+	TARGET example-ui
+	SOURCES ${example-ui_SOURCE_DIR}/main.c
+	FOLDER examples
+	LIBRARIES libui
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-ui/main.c	Wed Nov 11 17:10:40 2020 +0100
@@ -0,0 +1,324 @@
+/*
+ * example-action.c -- example on how to use automatic actions
+ *
+ * Copyright (c) 2020 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/action.h>
+#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 action_stack st;
+
+	struct {
+		bool active;
+		int x;
+		int y;
+	} motion;
+
+	struct {
+		struct frame frame;
+		struct action act;
+	} panel;
+
+	struct {
+		struct label label;
+		struct action act;
+	} header;
+
+	struct {
+		struct checkbox cb;
+		struct action cb_act;
+		struct label label;
+		struct action label_act;
+	} autosave;
+
+	struct {
+		struct button button;
+		struct action act;
+	} 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 void
+init(void)
+{
+	if (!core_init() || !ui_init())
+		panic();
+	if (!window_open("Example - UI", W, H))
+		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 void
+prepare(void)
+{
+	/* Frame. */
+	frame_action(&ui.panel.frame, &ui.panel.act);
+
+	/* Header title. */
+	label_action(&ui.header.label, &ui.header.act);
+
+	/* Button quit. */
+	button_action(&ui.quit.button, &ui.quit.act);
+
+	/* Autosave. */
+	checkbox_action(&ui.autosave.cb, &ui.autosave.cb_act);
+	label_action(&ui.autosave.label, &ui.autosave.label_act);
+
+	/* Add all UI elements. */
+	action_stack_add(&ui.st, &ui.panel.act);
+	action_stack_add(&ui.st, &ui.header.act);
+	action_stack_add(&ui.st, &ui.autosave.cb_act);
+	action_stack_add(&ui.st, &ui.autosave.label_act);
+	action_stack_add(&ui.st, &ui.quit.act);
+}
+
+static bool
+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 = true;
+			ui.motion.x = ev->click.x;
+			ui.motion.y = ev->click.y;
+			window_set_cursor(WINDOW_CURSOR_SIZE);
+		}
+		else
+			action_stack_handle(&ui.st, ev);
+		break;
+	case EVENT_CLICKUP:
+		ui.motion.active = false;
+		window_set_cursor(WINDOW_CURSOR_ARROW);
+		/* Fallthrough. */
+	default:
+		action_stack_handle(&ui.st, ev);
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	if (ui.quit.button.state == BUTTON_STATE_ACTIVATED)
+		game_quit();
+	else
+		action_stack_update(&ui.st, ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+	action_stack_draw(&ui.st);
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
+
+	prepare();
+	resize();
+
+	game_switch(&state, true);
+	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;
+}
--- a/librpg/CMakeLists.txt	Wed Nov 11 16:31:55 2020 +0100
+++ b/librpg/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -48,8 +48,6 @@
 	${librpg_SOURCE_DIR}/rpg/battle-state-victory.h
 	${librpg_SOURCE_DIR}/rpg/character.c
 	${librpg_SOURCE_DIR}/rpg/character.h
-	${librpg_SOURCE_DIR}/rpg/inventory.c
-	${librpg_SOURCE_DIR}/rpg/inventory.h
 	${librpg_SOURCE_DIR}/rpg/item.c
 	${librpg_SOURCE_DIR}/rpg/item.h
 	${librpg_SOURCE_DIR}/rpg/map.c
--- a/librpg/rpg/inventory.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-/*
- * inventory.c -- inventory of items
- *
- * Copyright (c) 2020 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.
- */
-
-#if 0
-
-#include <assert.h>
-#include <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "inventory.h"
-#include "item.h"
-
-#define INVENTORY_TOTAL (INVENTORY_ROWS_MAX * INVENTORY_COLS_MAX)
-
-static bool
-can_be_used(struct inventory_slot *slot, const struct item *item)
-{
-	assert(item);
-
-	/* Empty slot. */
-	if (!slot->item)
-		return false;
-
-	/* Not same object. */
-	if (strcmp(slot->item->name, item->name) != 0)
-		return false;
-
-	/* No space in this slot. */
-	if (slot->amount >= slot->item->stackable)
-		return false;
-
-	return true;
-}
-
-static struct inventory_slot *
-find(struct inventory *iv, const struct item *item)
-{
-	assert(iv);
-	assert(item);
-
-	/* First pass: find an entry with the same item. */
-	for (unsigned int r = 0; r < INVENTORY_ROWS_MAX; ++r)
-		for (unsigned int c = 0; c < INVENTORY_COLS_MAX; ++c)
-			if (can_be_used(&iv->items[r][c], item))
-				return &iv->items[r][c];
-
-	/* Second pass: try to find an empty slot. */
-	for (unsigned int r = 0; r < INVENTORY_ROWS_MAX; ++r)
-		for (unsigned int c = 0; c < INVENTORY_COLS_MAX; ++c)
-			if (!iv->items[r][c].item)
-				return &iv->items[r][c];
-
-	return NULL;
-}
-
-static unsigned
-provide(struct inventory_slot *slot, struct item *item, unsigned int amount)
-{
-	assert(slot);
-
-	unsigned int avail;
-
-	/* The slot may be empty, make sure it contains this item. */
-	slot->item = item;
-
-	/*
-	 * Example:
-	 *
-	 * The slot has already 10 items.
-	 * The slot item is stackble up to 64 items.
-	 *
-	 * When pushing:
-	 *
-	 * 80: 54 pushed, 26 left
-	 * 30: 30 pushed, 0 left.
-	 */
-	avail = slot->item->stackable - slot->amount;
-
-	if (amount > avail) {
-		slot->amount += avail;
-		amount -= avail;
-	} else {
-		slot->amount += amount;
-		amount = 0;
-	}
-
-	return amount;
-}
-
-static bool
-merge(struct inventory_slot *slot, struct inventory_slot *other)
-{
-	assert(slot);
-	assert(slot->item);
-	assert(other);
-
-	/* Not compatible, return false to let the sorting continue. */
-	if (slot->item != other->item)
-		return false;
-
-	while (slot->amount < slot->item->stackable && other->amount) {
-		slot->amount++;
-		other->amount--;
-	}
-
-	/* No more amount in the other slot, empty it. */
-	if (other->amount == 0U)
-		memset(other, 0, sizeof (*other));
-
-	return slot->amount >= slot->item->stackable;
-}
-
-static void
-sort(struct inventory *iv, struct inventory_slot *slot, int r, int c)
-{
-	assert(slot);
-	assert(slot->item);
-
-	/* Merge until the end of thiw row. */
-	for (c = c + 1; c < INVENTORY_COLS_MAX; ++c)
-		if (merge(slot, &iv->items[r][c]))
-			return;
-
-	/* Merge the next rows. */
-	for (r = r + 1; r < INVENTORY_ROWS_MAX; ++r)
-		for (c = 0; c < INVENTORY_COLS_MAX; ++c)
-			if (merge(slot, &iv->items[r][c]))
-				return;
-}
-
-unsigned int
-inventory_push(struct inventory *iv, struct item *item, unsigned int amount)
-{
-	assert(iv);
-	assert(item);
-
-	while (amount) {
-		struct inventory_slot *slot;
-
-		if (!(slot = find(iv, item)))
-			break;
-
-		/* Add as much as we can in this slot. */
-		amount = provide(slot, item, amount);
-	}
-
-	return amount;
-}
-
-static int
-compare_slot(const void *v1, const void *v2)
-{
-	const struct inventory_slot *slot1 = v1;
-	const struct inventory_slot *slot2 = v2;
-	int cmp;
-
-	/* Two null slots compare equal. */
-	if (!slot1->item && !slot2->item)
-		return 0;
-
-	/* Null left should be moved after. */
-	if (!slot1->item)
-		return 1;
-
-	/* Null right slots should be moved after. */
-	if (!slot2->item)
-		return -1;
-
-	/* If they are identical, use amount to sort. */
-	if ((cmp = strcmp(slot1->item->name, slot2->item->name)) == 0)
-		return (long long int)slot2->amount - (long long int)slot1->amount;
-
-	return cmp;
-}
-
-void
-inventory_sort(struct inventory *iv)
-{
-	assert(iv);
-
-	for (int r = 0; r < INVENTORY_ROWS_MAX; ++r)
-		for (int c = 0; c < INVENTORY_COLS_MAX; ++c)
-			if (iv->items[r][c].item)
-				sort(iv, &iv->items[r][c], r, c);
-
-	/* Sort by names AND by amount. */
-	qsort(iv->items, INVENTORY_TOTAL, sizeof (struct inventory_slot), compare_slot);
-}
-
-void
-inventory_clear(struct inventory *iv)
-{
-	assert(iv);
-
-	memset(iv, 0, sizeof (*iv));
-}
-
-#endif
--- a/librpg/rpg/inventory.h	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/*
- * inventory.h -- inventory of items
- *
- * Copyright (c) 2020 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 MOLKO_INVENTORY_H
-#define MOLKO_INVENTORY_H
-
-#if 0
-
-/**
- * \file inventory.h
- * \brief Inventory of items.
- */
-
-/**
- * \brief Maximum number of rows.
- */
-#define INVENTORY_ROWS_MAX      3
-
-/**
- * \brief Maximum number of columns.
- */
-#define INVENTORY_COLS_MAX      10
-
-struct item;
-
-/**
- * \brief Inventory slot.
- *
- * This structure describe a 'cell' into the inventory. It references a item
- * and has a given amount of it.
- */
-struct inventory_slot {
-	struct item *item;      /*!< (+&?) Pointer to the item. */
-	unsigned int amount;    /*!< (-) Number of items in this slot. */
-};
-
-/**
- * \brief Inventory structure.
- */
-struct inventory {
-	/**
-	 * (-) Grid of objects.
-	 */
-	struct inventory_slot items[INVENTORY_ROWS_MAX][INVENTORY_COLS_MAX];
-};
-
-/**
- * Try to push as much as possible the given item.
- *
- * \pre iv != NULL
- * \pre item != NULL
- * \param iv the inventory
- * \param item the item to reference
- * \param amount the desired amount
- * \return 0 if all items were pushed or the number left otherwise
- */
-unsigned int
-inventory_push(struct inventory *iv, struct item *item, unsigned int amount);
-
-/**
- * Sort the inventory.
- *
- * \pre iv != NULL
- * \pre item != NULL
- * \param iv the inventory
- */
-void
-inventory_sort(struct inventory *iv);
-
-/**
- * Clears the inventory.
- *
- * \pre iv != NULL
- * \param iv the inventory
- */
-void
-inventory_clear(struct inventory *iv);
-
-#endif
-
-#endif /* !MOLKO_INVENTORY_H */
--- a/tests/CMakeLists.txt	Wed Nov 11 16:31:55 2020 +0100
+++ b/tests/CMakeLists.txt	Wed Nov 11 17:10:40 2020 +0100
@@ -22,7 +22,6 @@
 molko_define_test(TARGET action-script SOURCES test-action-script.c)
 molko_define_test(TARGET color SOURCES test-color.c)
 molko_define_test(TARGET error SOURCES test-error.c)
-molko_define_test(TARGET inventory SOURCES test-inventory.c)
 molko_define_test(
 	TARGET map
 	SOURCES test-map.c
--- a/tests/test-inventory.c	Wed Nov 11 16:31:55 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,266 +0,0 @@
-/*
- * test-inventory.c -- test inventory functions
- *
- * Copyright (c) 2020 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.
- */
-
-#define GREATEST_USE_ABBREVS 0
-#include <greatest.h>
-
-#if 0
-
-#include <rpg/item.h>
-#include <rpg/inventory.h>
-
-static struct item potion = {
-	.name = "Potion",
-	.stackable = 64
-};
-
-static struct item elixir = {
-	.name = "Elixir",
-	.stackable = 64
-};
-
-static struct item sword = {
-	.name = "Sword",
-	.stackable = 1
-};
-
-#define SET(iv, r, c, i, a)                                             \
-do {                                                                    \
-        (iv)->items[(r)][(c)].item = (i);                               \
-        (iv)->items[(r)][(c)].amount = (a);                             \
-} while (0)                                                             \
-
-GREATEST_TEST
-push_simple(void)
-{
-	struct inventory inv = { 0 };
-	unsigned int left;
-
-	/*
-	 * [0][0] should contain 50 potions.
-	 */
-	left = inventory_push(&inv, &potion, 50U);
-	GREATEST_ASSERT_EQ(left, 0U);
-	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 50U);
-	inventory_clear(&inv);
-
-	/*
-	 * [0][1] should contain 64 potions.
-	 * [0][2] should contain 10 potions.
-	 */
-	left = inventory_push(&inv, &potion, 74U);
-	GREATEST_ASSERT_EQ(left, 0U);
-	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 64U);
-	GREATEST_ASSERT_EQ(inv.items[0][1].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][1].amount, 10U);
-	inventory_clear(&inv);
-
-	/*
-	 * 1. We push 10 potions
-	 * 2. We push 10 more potions.
-	 *
-	 * [0][0] should contain 20 potions.
-	 */
-	left = inventory_push(&inv, &potion, 10U);
-	GREATEST_ASSERT_EQ(left, 0U);
-	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 10U);
-	left = inventory_push(&inv, &potion, 10U);
-	GREATEST_ASSERT_EQ(left, 0U);
-	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 20U);
-	inventory_clear(&inv);
-
-	GREATEST_PASS();
-}
-
-GREATEST_TEST
-push_full(void)
-{
-	struct inventory inv = { 0 };
-	unsigned int left;
-
-	/*
-	 * Fill the inventory with
-	 *
-	 * - 29 x 64 potions
-	 * - Last slot will have already 50 potions.
-	 *
-	 * This means the maximum potions to push is only 14.
-	 */
-	for (int i = 0; i < 29; ++i) {
-		left = inventory_push(&inv, &potion, 64U);
-		GREATEST_ASSERT_EQ(left, 0U);
-	}
-
-	left = inventory_push(&inv, &potion, 50U);
-	GREATEST_ASSERT_EQ(left, 0U);
-
-	/*
-	 * Try to add 20 potions, only 14 should be pushed.
-	 */
-	left = inventory_push(&inv, &potion, 20U);
-	GREATEST_ASSERT_EQ(left, 6U);
-	GREATEST_ASSERT_EQ(inv.items[2][9].amount, 64U);
-
-	GREATEST_PASS();
-}
-
-GREATEST_TEST
-push_other(void)
-{
-	struct inventory inv = { 0 };
-	unsigned int left;
-
-	/*
-	 * [0][0] should contain 30 potions.
-	 */
-	left = inventory_push(&inv, &potion, 30U);
-	GREATEST_ASSERT_EQ(left, 0U);
-	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 30U);
-
-	/*
-	 * Since elixir is not a potion, it should be pushed into an other
-	 * cell.
-	 *
-	 * [0][0] should be untouched
-	 * [0][1] should contain 30 elixir.
-	 */
-	left = inventory_push(&inv, &elixir, 30U);
-	GREATEST_ASSERT_EQ(left, 0U);
-	GREATEST_ASSERT_EQ(inv.items[0][0].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 30U);
-	GREATEST_ASSERT_EQ(inv.items[0][1].item, &elixir);
-	GREATEST_ASSERT_EQ(inv.items[0][1].amount, 30U);
-
-	GREATEST_PASS();
-}
-
-GREATEST_SUITE(push)
-{
-	GREATEST_RUN_TEST(push_simple);
-	GREATEST_RUN_TEST(push_full);
-	GREATEST_RUN_TEST(push_other);
-}
-
-GREATEST_TEST
-sort_simple(void)
-{
-	struct inventory inv = { 0 };
-
-	/*
-	 * Create this inventory:
-	 *
-	 *   0      1      2      3      4      5      6      7      8      9
-	 * 0 [    ] [P:10] [    ] [    ] [S: 1] [    ] [    ] [E:24] [    ] [    ]
-	 * 1 [    ] [    ] [P:12] [    ] [    ] [    ] [    ] [    ] [    ] [    ]
-	 * 2 [    ] [    ] [    ] [    ] [E: 8] [    ] [    ] [    ] [P: 5] [    ]
-	 */
-	SET(&inv, 0, 1, &potion, 10);
-	SET(&inv, 0, 4, &sword, 1);
-	SET(&inv, 0, 7, &elixir, 24);
-	SET(&inv, 1, 2, &potion, 12);
-	SET(&inv, 2, 4, &elixir, 8);
-	SET(&inv, 2, 8, &potion, 5);
-
-	/*
-	 * Should look like this:
-	 *
-	 *   0      1      2      3      4      5      6      7      8      9
-	 * 0 [E:32] [P:27] [S: 1] [    ] [    ] [    ] [    ] [    ] [    ] [    ]
-	 * 1 [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ]
-	 * 2 [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ]
-	 */
-	inventory_sort(&inv);
-	GREATEST_ASSERT_EQ(inv.items[0][0].item, &elixir);
-	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 32U);
-	GREATEST_ASSERT_EQ(inv.items[0][1].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][1].amount, 27U);
-	GREATEST_ASSERT_EQ(inv.items[0][2].item, &sword);
-	GREATEST_ASSERT_EQ(inv.items[0][2].amount, 1U);
-
-	GREATEST_PASS();
-}
-
-GREATEST_TEST
-sort_with_full(void)
-{
-	struct inventory inv = { 0 };
-
-	/*
-	 * Create this inventory:
-	 *
-	 *   0      1      2      3      4      5      6      7      8      9
-	 * 0 [    ] [P:62] [    ] [    ] [S: 1] [    ] [    ] [E:64] [    ] [    ]
-	 * 1 [    ] [    ] [P:12] [    ] [    ] [    ] [    ] [    ] [    ] [    ]
-	 * 2 [    ] [    ] [    ] [    ] [E: 8] [    ] [    ] [    ] [P: 5] [    ]
-	 */
-	SET(&inv, 0, 1, &potion, 62);
-	SET(&inv, 0, 4, &sword, 1);
-	SET(&inv, 0, 7, &elixir, 64);
-	SET(&inv, 1, 2, &potion, 12);
-	SET(&inv, 2, 4, &elixir, 8);
-	SET(&inv, 2, 8, &potion, 5);
-
-	/*
-	 * Should look like this:
-	 *
-	 *   0      1      2      3      4      5      6      7      8      9
-	 * 0 [E:64] [E: 8] [P:64] [P:10] [S: 1] [    ] [    ] [    ] [    ] [    ]
-	 * 1 [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ]
-	 * 2 [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ] [    ]
-	 */
-	inventory_sort(&inv);
-	GREATEST_ASSERT_EQ(inv.items[0][0].item, &elixir);
-	GREATEST_ASSERT_EQ(inv.items[0][0].amount, 64U);
-	GREATEST_ASSERT_EQ(inv.items[0][1].item, &elixir);
-	GREATEST_ASSERT_EQ(inv.items[0][1].amount, 8U);
-	GREATEST_ASSERT_EQ(inv.items[0][2].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][2].amount, 64U);
-	GREATEST_ASSERT_EQ(inv.items[0][3].item, &potion);
-	GREATEST_ASSERT_EQ(inv.items[0][3].amount, 15U);
-	GREATEST_ASSERT_EQ(inv.items[0][4].item, &sword);
-	GREATEST_ASSERT_EQ(inv.items[0][4].amount, 1U);
-
-	GREATEST_PASS();
-}
-
-GREATEST_SUITE(sort)
-{
-	GREATEST_RUN_TEST(sort_simple);
-	GREATEST_RUN_TEST(sort_with_full);
-}
-
-GREATEST_MAIN_DEFS();
-
-#endif
-
-int
-main(int argc, char **argv)
-{
-#if 0
-	GREATEST_MAIN_BEGIN();
-	GREATEST_RUN_SUITE(push);
-	GREATEST_RUN_SUITE(sort);
-	GREATEST_MAIN_END();
-#endif
-	return 0;
-}