changeset 327:42a6710629f5

ui: implement notifications
author David Demelier <markand@malikania.fr>
date Sun, 03 Oct 2021 10:31:45 +0200
parents 06782f7888f3
children 60a4f904da21
files cmake/MlkBcc.cmake cmake/MlkExecutable.cmake examples/CMakeLists.txt examples/example-notify/CMakeLists.txt examples/example-notify/assets/images/sword.png examples/example-notify/main.c src/libmlk-ui/CMakeLists.txt src/libmlk-ui/assets/fonts/opensans-medium.ttf src/libmlk-ui/ui/label.c src/libmlk-ui/ui/label.h src/libmlk-ui/ui/notify.c src/libmlk-ui/ui/notify.h src/libmlk-ui/ui/theme.c src/libmlk-ui/ui/theme.h
diffstat 14 files changed, 465 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/cmake/MlkBcc.cmake	Sat Oct 02 18:09:15 2021 +0200
+++ b/cmake/MlkBcc.cmake	Sun Oct 03 10:31:45 2021 +0200
@@ -55,7 +55,7 @@
 			COMMAND
 				$<TARGET_FILE:mlk-bcc> ${args} ${a} ${outputname} > ${outputfile}
 			COMMENT "Generating ${output}"
-			DEPENDS $<TARGET_FILE:mlk-bcc>
+			DEPENDS $<TARGET_FILE:mlk-bcc> ${a}
 		)
 
 		list(APPEND ${_bcc_OUTPUTS_VAR} ${CMAKE_CURRENT_BINARY_DIR}/${output})
--- a/cmake/MlkExecutable.cmake	Sat Oct 02 18:09:15 2021 +0200
+++ b/cmake/MlkExecutable.cmake	Sun Oct 03 10:31:45 2021 +0200
@@ -19,7 +19,7 @@
 function(mlk_executable)
 	set(options "INSTALL")
 	set(oneValueArgs "NAME;FOLDER")
-	set(multiValueArgs "SOURCES;LIBRARIES;INCLUDES;FLAGS")
+	set(multiValueArgs "ASSETS;SOURCES;LIBRARIES;INCLUDES;FLAGS")
 
 	cmake_parse_arguments(EXE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
@@ -27,7 +27,12 @@
 		message(FATAL_ERROR "Missing NAME")
 	endif ()
 
-	add_executable(${EXE_NAME} ${EXE_SOURCES})
+	if (EXE_ASSETS)
+		mlk_bcc(ASSETS ${EXE_ASSETS} OUTPUTS_VAR HEADERS)
+		source_group(build/assets FILES ${HEADERS})
+	endif ()
+
+	add_executable(${EXE_NAME} ${EXE_SOURCES} ${HEADERS})
 	set_target_properties(${EXE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
 
 	foreach (cfg ${CMAKE_CONFIGURATION_TYPES})
--- a/examples/CMakeLists.txt	Sat Oct 02 18:09:15 2021 +0200
+++ b/examples/CMakeLists.txt	Sun Oct 03 10:31:45 2021 +0200
@@ -31,6 +31,7 @@
 	example-gridmenu
 	example-label
 	example-message
+	example-notify
 	example-sprite
 	example-trace
 	example-ui
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-notify/CMakeLists.txt	Sun Oct 03 10:31:45 2021 +0200
@@ -0,0 +1,40 @@
+#
+# CMakeLists.txt -- CMake build system for Molko's Adventure
+#
+# Copyright (c) 2020-2021 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-notify)
+
+set(
+	SOURCES
+	${example-notify_SOURCE_DIR}/main.c
+)
+
+set(
+	ASSETS
+	${example-notify_SOURCE_DIR}/assets/images/sword.png
+)
+
+mlk_executable(
+	NAME example-notify
+	FOLDER examples
+	LIBRARIES libmlk-rpg
+	ASSETS ${ASSETS}
+	SOURCES ${SOURCES}
+	INCLUDES PRIVATE ${example-notify_BINARY_DIR}
+)
+
+source_group("" FILES ${SOURCES} ${ASSETS})
Binary file examples/example-notify/assets/images/sword.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-notify/main.c	Sun Oct 03 10:31:45 2021 +0200
@@ -0,0 +1,131 @@
+/*
+ * example-notify -- show how notifications work
+ *
+ * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include <core/core.h>
+#include <core/event.h>
+#include <core/game.h>
+#include <core/image.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/texture.h>
+#include <core/window.h>
+
+#include <ui/notify.h>
+#include <ui/label.h>
+#include <ui/ui.h>
+
+/* Sword by Icongeek26 (https://www.flaticon.com). */
+#include <assets/images/sword.h>
+
+#define W       1280
+#define H       720
+#define PATH(r) util_pathf("%s/%s", sys_dir(SYS_DIR_DATA), r)
+
+static struct label help = {
+	.text = "Keys: <Space> to generate a notification.",
+	.x = 10,
+	.y = 10,
+	.flags = LABEL_FLAGS_SHADOW
+};
+static struct texture icon;
+
+static void
+init(void)
+{
+	if (core_init("fr.malikania", "mlk-adventure") < 0 || ui_init() < 0)
+		panic();
+	if (window_open("Example - Notify", W, H) < 0)
+		panic();
+	if (image_openmem(&icon, assets_images_sword, sizeof (assets_images_sword)) < 0)
+		panic();
+}
+
+static void
+handle(struct state *st, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_QUIT:
+		game_quit();
+		break;
+	case EVENT_KEYDOWN:
+		if (ev->key.key == KEY_SPACE)
+			notify(&icon, "Quest", "Quest finished.");
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+update(struct state *st, unsigned int ticks)
+{
+	(void)st;
+
+	notify_update(ticks);
+}
+
+static void
+draw(struct state *st)
+{
+	(void)st;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+	label_draw(&help);
+	notify_draw();
+	painter_present();
+}
+
+static void
+run(void)
+{
+	struct state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
+
+	game_push(&state);
+	game_loop();
+}
+
+static void
+quit(void)
+{
+	window_finish();
+	ui_finish();
+	core_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+
+	return 0;
+}
--- a/src/libmlk-ui/CMakeLists.txt	Sat Oct 02 18:09:15 2021 +0200
+++ b/src/libmlk-ui/CMakeLists.txt	Sun Oct 03 10:31:45 2021 +0200
@@ -34,6 +34,8 @@
 	${libmlk-ui_SOURCE_DIR}/ui/gridmenu.h
 	${libmlk-ui_SOURCE_DIR}/ui/label.c
 	${libmlk-ui_SOURCE_DIR}/ui/label.h
+	${libmlk-ui_SOURCE_DIR}/ui/notify.c
+	${libmlk-ui_SOURCE_DIR}/ui/notify.h
 	${libmlk-ui_SOURCE_DIR}/ui/theme.c
 	${libmlk-ui_SOURCE_DIR}/ui/theme.h
 	${libmlk-ui_SOURCE_DIR}/ui/ui.c
@@ -44,6 +46,7 @@
 set(
 	ASSETS
 	${libmlk-ui_SOURCE_DIR}/assets/fonts/opensans-light.ttf
+	${libmlk-ui_SOURCE_DIR}/assets/fonts/opensans-medium.ttf
 	${libmlk-ui_SOURCE_DIR}/assets/fonts/opensans-regular.ttf
 )
 
Binary file src/libmlk-ui/assets/fonts/opensans-medium.ttf has changed
--- a/src/libmlk-ui/ui/label.c	Sat Oct 02 18:09:15 2021 +0200
+++ b/src/libmlk-ui/ui/label.c	Sun Oct 03 10:31:45 2021 +0200
@@ -42,7 +42,9 @@
 	struct font *font;
 	struct texture tex;
 
-	font = t->fonts[THEME_FONT_INTERFACE];
+	font = label->flags & LABEL_FLAGS_IMPORTANT
+		? t->fonts[THEME_FONT_IMPORTANT]
+		: t->fonts[THEME_FONT_INTERFACE];
 
 	/* Shadow text, only if enabled. */
 	if (label->flags & LABEL_FLAGS_SHADOW) {
@@ -74,8 +76,11 @@
 	assert(label->text);
 
 	const struct theme *t = label->theme ? label->theme : theme_default();
+	const struct font *f = label->flags & LABEL_FLAGS_IMPORTANT
+		? t->fonts[THEME_FONT_IMPORTANT]
+		: t->fonts[THEME_FONT_INTERFACE];
 
-	if (font_query(t->fonts[THEME_FONT_INTERFACE], label->text, w, h) < 0)
+	if (font_query(f, label->text, w, h) < 0)
 		panic();
 }
 
--- a/src/libmlk-ui/ui/label.h	Sat Oct 02 18:09:15 2021 +0200
+++ b/src/libmlk-ui/ui/label.h	Sun Oct 03 10:31:45 2021 +0200
@@ -27,6 +27,7 @@
 enum label_flags {
 	LABEL_FLAGS_NONE,
 	LABEL_FLAGS_SHADOW      = (1 << 0),
+	LABEL_FLAGS_IMPORTANT   = (1 << 1)
 };
 
 struct label {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-ui/ui/notify.c	Sun Oct 03 10:31:45 2021 +0200
@@ -0,0 +1,210 @@
+/*
+ * notify.c -- in game notifications
+ *
+ * Copyright (c) 2020-2021 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 <stddef.h>
+#include <string.h>
+
+#include <core/font.h>
+#include <core/texture.h>
+#include <core/trace.h>
+#include <core/window.h>
+
+#include "align.h"
+#include "frame.h"
+#include "label.h"
+#include "notify.h"
+#include "theme.h"
+#include "ui_p.h"
+
+#define WIDTH   (window.w / 3)
+#define HEIGHT  (window.h / 10)
+
+struct geo {
+	const struct theme *theme;
+	int frame_x;
+	int frame_y;
+	unsigned int frame_w;
+	unsigned int frame_h;
+	int icon_x;
+	int icon_y;
+	int title_x;
+	int title_y;
+	int body_x;
+	int body_y;
+};
+
+static void draw(const struct notify *, size_t);
+
+static const struct notify_system default_system = {
+	.draw = draw
+};
+static const struct notify_system *system = &default_system;
+static struct notify stack[NOTIFY_MAX];
+static size_t stacksz;
+
+static void
+geometry(struct geo *geo, const struct notify *n, size_t index)
+{
+	int x, y;
+
+	/* Determine theme. */
+	geo->theme = system->theme ? system->theme : theme_default();
+
+	/* Determine notification position. */
+	x  = window.w - geo->theme->padding;
+	x -= WIDTH;
+
+	y  = geo->theme->padding * (index + 1);;
+	y += HEIGHT * index;
+
+	/* Content frame. */
+	geo->frame_x = x;
+	geo->frame_y = y;
+	geo->frame_w = WIDTH;
+	geo->frame_h = HEIGHT;
+
+	/* Align icon at the left center. */
+	if (n->icon->h >= HEIGHT) {
+		tracef(_("notification icon is too large: %u > %u"), n->icon->h, HEIGHT);
+		geo->icon_x = x + geo->theme->padding;
+		geo->icon_y = y + geo->theme->padding;
+	} else {
+		align(ALIGN_LEFT, &geo->icon_x, &geo->icon_y, n->icon->w, n->icon->h, x, y, WIDTH, HEIGHT);
+		geo->icon_x += geo->icon_y - y;
+	}
+
+	/* Align title to the right of the icon at the same y coordinate. */
+	geo->title_x  = geo->icon_x + n->icon->w + geo->theme->padding;
+	geo->title_y  = geo->icon_y;
+	geo->title_y -= font_height(geo->theme->fonts[THEME_FONT_IMPORTANT]) / 2;
+
+	/* Align body so it ends at the end of the icon. */
+	geo->body_x  = geo->title_x;
+	geo->body_y  = geo->icon_y + n->icon->h;
+	geo->body_y -= font_height(geo->theme->fonts[THEME_FONT_INTERFACE]) / 2;
+}
+
+static void
+draw_frame(const struct geo *geo)
+{
+	const struct frame f = {
+		.x = geo->frame_x,
+		.y = geo->frame_y,
+		.w = geo->frame_w,
+		.h = geo->frame_h
+	};
+
+	frame_draw(&f);
+}
+
+static void
+draw_icon(const struct geo *geo, const struct notify *n)
+{
+	texture_draw(n->icon, geo->icon_x, geo->icon_y);
+}
+
+#include <stdio.h>
+
+static void
+draw_title(const struct geo *geo, const struct notify *n)
+{
+	const struct label l = {
+		.x = geo->title_x,
+		.y = geo->title_y,
+		.text = n->title,
+		.flags = LABEL_FLAGS_SHADOW | LABEL_FLAGS_IMPORTANT
+	};
+
+	label_draw(&l);
+}
+
+static void
+draw_body(const struct geo *geo, const struct notify *n)
+{
+	const struct label l = {
+		.x = geo->body_x,
+		.y = geo->body_y,
+		.text = n->body,
+		.flags = LABEL_FLAGS_SHADOW
+	};
+
+	label_draw(&l);
+}
+
+static void
+draw(const struct notify *n, size_t index)
+{
+	struct geo geo;
+
+	/* Compute notification size and widgets. */
+	geometry(&geo, n, index);
+
+	draw_frame(&geo);
+	draw_icon(&geo, n);
+	draw_title(&geo, n);
+	draw_body(&geo, n);
+}
+
+void
+notify(const struct texture *icon, const char *title, const char *body)
+{
+	assert(icon);
+	assert(title);
+	assert(body);
+
+	struct notify *n;
+
+	if (stacksz >= NOTIFY_MAX) {
+		memmove(&stack[0], &stack[1], sizeof (stack[0]) - NOTIFY_MAX - 1);
+		n = &stack[NOTIFY_MAX - 1];
+	} else
+		n = &stack[stacksz++];
+
+	memset(n, 0, sizeof (*n));
+	n->icon = icon;
+	n->title = title;
+	n->body = body;
+}
+
+void
+notify_update(unsigned int ticks)
+{
+	struct notify *n;
+
+	for (size_t i = 0; i < stacksz; ++i) {
+		n = &stack[i];
+		n->elapsed += ticks;
+
+		if (n->elapsed >= NOTIFY_TIMEOUT_DEFAULT)
+			memmove(n, n + 1, sizeof (*n) * (--stacksz - i));
+	}
+}
+
+void
+notify_draw(void)
+{
+	for (size_t i = 0; i < stacksz; ++i)
+		system->draw(&stack[i], i);
+}
+
+void
+notify_set_system(const struct notify_system *sys)
+{
+	system = sys ? sys : &default_system;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-ui/ui/notify.h	Sun Oct 03 10:31:45 2021 +0200
@@ -0,0 +1,60 @@
+/*
+ * notify.h -- in game notifications
+ *
+ * Copyright (c) 2020-2021 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_UI_NOTIFY_H
+#define MOLKO_UI_NOTIFY_H
+
+#include <stddef.h>
+
+#include <core/core.h>
+
+#define NOTIFY_MAX              (4)
+#define NOTIFY_TIMEOUT_DEFAULT  (5000)
+
+struct texture;
+struct theme;
+
+struct notify {
+	const struct texture *icon;
+	const char *title;
+	const char *body;
+	unsigned int elapsed;
+};
+
+struct notify_system {
+	struct theme *theme;
+	void (*draw)(const struct notify *, size_t);
+};
+
+CORE_BEGIN_DECLS
+
+void
+notify(const struct texture *, const char *, const char *);
+
+void
+notify_update(unsigned int ticks);
+
+void
+notify_draw(void);
+
+void
+notify_set_system(const struct notify_system *);
+
+CORE_END_DECLS
+
+#endif /*! MOLKO_UI_NOTIFY_H */
--- a/src/libmlk-ui/ui/theme.c	Sat Oct 02 18:09:15 2021 +0200
+++ b/src/libmlk-ui/ui/theme.c	Sun Oct 03 10:31:45 2021 +0200
@@ -28,6 +28,7 @@
 #include <core/util.h>
 
 #include <assets/fonts/opensans-light.h>
+#include <assets/fonts/opensans-medium.h>
 #include <assets/fonts/opensans-regular.h>
 
 #include "align.h"
@@ -66,7 +67,8 @@
 	struct font font;
 } default_fonts[] = {
 	FONT(assets_fonts_opensans_light, 12, THEME_FONT_DEBUG),
-	FONT(assets_fonts_opensans_regular, 14, THEME_FONT_INTERFACE)
+	FONT(assets_fonts_opensans_regular, 14, THEME_FONT_INTERFACE),
+	FONT(assets_fonts_opensans_medium, 14, THEME_FONT_IMPORTANT)
 };
 
 int
--- a/src/libmlk-ui/ui/theme.h	Sat Oct 02 18:09:15 2021 +0200
+++ b/src/libmlk-ui/ui/theme.h	Sun Oct 03 10:31:45 2021 +0200
@@ -31,6 +31,7 @@
 enum theme_font {
 	THEME_FONT_DEBUG,
 	THEME_FONT_INTERFACE,
+	THEME_FONT_IMPORTANT,
 	THEME_FONT_NUM
 };