Mercurial > molko
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})
--- /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 )
--- 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