changeset 502:ab2816f9551c

ui: start reworking on styles While here, demonstrates how we can use a custom style on a button.
author David Demelier <markand@malikania.fr>
date Wed, 01 Mar 2023 10:34:12 +0100
parents 23559c7ccf47
children a55d0a29f466
files examples/example-ui/CMakeLists.txt examples/example-ui/button-style-glow.c examples/example-ui/button-style-glow.h examples/example-ui/example-ui.c libmlk-ui/mlk/ui/button.c libmlk-ui/mlk/ui/button.h libmlk-ui/mlk/ui/theme.c
diffstat 7 files changed, 334 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- a/examples/example-ui/CMakeLists.txt	Tue Feb 28 15:54:44 2023 +0100
+++ b/examples/example-ui/CMakeLists.txt	Wed Mar 01 10:34:12 2023 +0100
@@ -20,6 +20,8 @@
 
 set(
 	SOURCES
+	${example-ui_SOURCE_DIR}/button-style-glow.c
+	${example-ui_SOURCE_DIR}/button-style-glow.h
 	${example-ui_SOURCE_DIR}/example-ui.c
 )
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-ui/button-style-glow.c	Wed Mar 01 10:34:12 2023 +0100
@@ -0,0 +1,112 @@
+/*
+ * button-style-glow.c -- example of glowing button
+ *
+ * Copyright (c) 2020-2023 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 <mlk/core/color.h>
+
+#include "button-style-glow.h"
+
+/*
+ * This button style illuminates the background color by periodically changing
+ * its red, green, blue components to the target color and then go back to the
+ * original transition.
+ *
+ * Its sole purpose is to demonstrates how style works. Because the style needs
+ * a state it is implemented through a parent button_style_glow structure which
+ * holds an underlying style.
+ *
+ * This style does not override the glow function because we just change the
+ * bg_color property that will be reused.
+ */
+
+static inline unsigned int
+increment(unsigned long ccmp, unsigned long ctgt)
+{
+	if (ctgt > ccmp)
+		return ccmp + 2 > ctgt ? ctgt : ccmp + 2;
+	if (ctgt < ccmp)
+		return ccmp - 2 < ctgt ? ctgt : ccmp - 2;
+
+	return ccmp;
+}
+
+static void
+init(struct mlk_button_style *style, struct mlk_button *button)
+{
+	(void)button;
+
+	struct button_style_glow *glow = style->data;
+
+	/*
+	 * We start at colors[0] and reach colors[1], colors[2] will be the
+	 * destination color that will swap over time.
+	 */
+	glow->colors[2] = glow->colors[1];
+	glow->elapsed = 0;
+
+	style->bg_color = glow->colors[0];
+}
+
+static void
+update(struct mlk_button_style *style, struct mlk_button *button, unsigned int ticks)
+{
+	(void)button;
+
+	struct button_style_glow *glow = style->data;
+	unsigned int red, green, blue;
+
+	glow->elapsed += ticks;
+
+	if (glow->elapsed >= glow->delay) {
+		glow->elapsed = 0;
+
+		/* Color target reached, invert logic. */
+		if (style->bg_color == glow->colors[2]) {
+			if (glow->colors[2] == glow->colors[0])
+				glow->colors[2] = glow->colors[1];
+			else
+				glow->colors[2] = glow->colors[0];
+		} else {
+			red = increment(
+				MLK_COLOR_R(style->bg_color),
+				MLK_COLOR_R(glow->colors[2])
+			);
+			green = increment(
+				MLK_COLOR_G(style->bg_color),
+				MLK_COLOR_G(glow->colors[2])
+			);
+			blue = increment(
+				MLK_COLOR_B(style->bg_color),
+				MLK_COLOR_B(glow->colors[2])
+			);
+
+			style->bg_color = MLK_COLOR_HEX(red, green, blue, 0xff);
+		}
+	}
+}
+
+void
+button_style_glow_init(struct button_style_glow *glow)
+{
+	assert(glow);
+
+	glow->style.data = glow;
+	glow->style.init = init;
+	glow->style.update = update;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-ui/button-style-glow.h	Wed Mar 01 10:34:12 2023 +0100
@@ -0,0 +1,38 @@
+/*
+ * button-style-glow.h -- example of glowing button
+ *
+ * Copyright (c) 2020-2023 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 MLK_EXAMPLE_BUTTON_STYLE_H
+#define MLK_EXAMPLE_BUTTON_STYLE_H
+
+#include <mlk/ui/button.h>
+
+#define BUTTON_STYLE_GLOW_COLOR_1       0x235b7cff
+#define BUTTON_STYLE_GLOW_COLOR_2       0x2d80a6ff
+#define BUTTON_STYLE_GLOW_DELAY         20
+
+struct button_style_glow {
+	unsigned long colors[3];
+	unsigned int delay;
+	unsigned int elapsed;
+	struct mlk_button_style style;
+};
+
+void
+button_style_glow_init(struct button_style_glow *);
+
+#endif /* !MLK_EXAMPLE_BUTTON_GLOW_STYLE_H */
--- a/examples/example-ui/example-ui.c	Tue Feb 28 15:54:44 2023 +0100
+++ b/examples/example-ui/example-ui.c	Wed Mar 01 10:34:12 2023 +0100
@@ -16,15 +16,16 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <mlk/core/core.h>
+#include <mlk/core/clock.h>
 #include <mlk/core/err.h>
 #include <mlk/core/event.h>
 #include <mlk/core/game.h>
 #include <mlk/core/maths.h>
+#include <mlk/core/painter.h>
 #include <mlk/core/panic.h>
-#include <mlk/core/painter.h>
 #include <mlk/core/state.h>
 #include <mlk/core/sys.h>
+#include <mlk/core/trace.h>
 #include <mlk/core/util.h>
 #include <mlk/core/window.h>
 
@@ -33,10 +34,14 @@
 #include <mlk/ui/checkbox.h>
 #include <mlk/ui/frame.h>
 #include <mlk/ui/label.h>
+#include <mlk/ui/notify.h>
 #include <mlk/ui/theme.h>
 #include <mlk/ui/ui.h>
 
 #include <mlk/example/example.h>
+#include <mlk/example/registry.h>
+
+#include "button-style-glow.h"
 
 #define FRAME_ORIGIN_X  (10)
 #define FRAME_ORIGIN_Y  (10)
@@ -57,7 +62,7 @@
  * | [x] Auto save                               |
  * |                                             |
  * |                                             |
- * |                                    [ Quit ] |
+ * |                             [Hello][ Quit ] |
  * +---------------------------------------------+
  */
 static struct {
@@ -81,8 +86,19 @@
 	} autosave;
 
 	struct {
-		struct mlk_button button;
-	} quit;
+		/* [Hello] with default style*/
+		struct mlk_button hello;
+
+		/* [Quit] with custom style color. */
+		struct mlk_button_style quit_style;
+		struct mlk_button quit;
+
+		/*
+		 * [Download free RAM] with custom style drawing.
+		 */
+		struct button_style_glow download_glow;
+		struct mlk_button download;
+	} buttons;
 } ui = {
 	.panel = {
 		.frame = {
@@ -110,10 +126,31 @@
 			.flags = MLK_LABEL_FLAGS_SHADOW,
 		}
 	},
-	.quit = {
-		.button = {
+	.buttons = {
+		.hello = {
+			.text = "Hello",
+			.h = ELEMENT_HEIGHT
+		},
+		.quit_style = {
+			.bg_color = 0x235b7cff
+		},
+		.quit = {
 			.text = "Quit",
-			.h = ELEMENT_HEIGHT
+			.h = ELEMENT_HEIGHT,
+			.style = &ui.buttons.quit_style
+		},
+		.download_glow = {
+			.colors = {
+				BUTTON_STYLE_GLOW_COLOR_1,
+				BUTTON_STYLE_GLOW_COLOR_2
+			},
+			.delay = BUTTON_STYLE_GLOW_DELAY
+		},
+		.download = {
+			.w = 180,
+			.h = 32,
+			.text = "!! Download free RAM !!",
+			.style = &ui.buttons.download_glow.style
 		}
 	}
 };
@@ -121,15 +158,6 @@
 static struct mlk_state *states[1];
 
 static void
-init(void)
-{
-	int err;
-
-	if ((err = mlk_example_init("example-ui")) < 0)
-		mlk_panicf("mlk_example_init: %s", mlk_err_string(err));
-}
-
-static void
 resize_header(void)
 {
 	struct mlk_frame *f = &ui.panel.frame;
@@ -163,16 +191,25 @@
 {
 	unsigned int padding = mlk_theme_default()->padding;
 	struct mlk_frame *f = &ui.panel.frame;
-	struct mlk_button *b = &ui.quit.button;
+	struct mlk_button *quit = &ui.buttons.quit;
+	struct mlk_button *hello = &ui.buttons.hello;
+	struct mlk_button *dl = &ui.buttons.download;
 
-	/* Button. */
-	b->w = f->w / 4;
+	/* Buttons takes 1/4 of the frame width. */
+	quit->w = hello->w = f->w / 4;
 
-	mlk_align(MLK_ALIGN_BOTTOM_RIGHT, &b->x, &b->y, b->w, b->h,
+	mlk_align(MLK_ALIGN_BOTTOM_RIGHT, &quit->x, &quit->y, quit->w, quit->h,
 	    f->x, f->y, f->w, f->h);
 
-	b->x -= padding;
-	b->y -= padding;
+	quit->x -= padding;
+	quit->y -= padding;
+
+	/* Hello is immediately left. */
+	hello->x = quit->x - quit->w - padding;
+	hello->y = quit->y;
+
+	/* Download free ram is at the bottom center. */
+	mlk_align(MLK_ALIGN_BOTTOM, &dl->x, &dl->y, dl->w, dl->h, 0, 0, mlk_window.w, mlk_window.h - 10);
 }
 
 static void
@@ -232,8 +269,32 @@
 
 	mlk_checkbox_handle(&ui.autosave.cb, ev);
 
-	if (mlk_button_handle(&ui.quit.button, ev))
+	if (mlk_button_handle(&ui.buttons.quit, ev))
 		mlk_game_quit();
+	if (mlk_button_handle(&ui.buttons.hello, ev))
+		mlk_notify(
+		    &mlk_registry_textures[MLK_REGISTRY_TEXTURE_SWORD],
+		    "Hello",
+		    "Hello world!"
+		);
+	if (mlk_button_handle(&ui.buttons.download, ev))
+		mlk_notify(
+		    &mlk_registry_textures[MLK_REGISTRY_TEXTURE_SWORD],
+		    "Complete",
+		    "16GB of RAM successfully downloaded!"
+		);
+}
+
+static void
+update(struct mlk_state *st, unsigned int ticks)
+{
+	(void)st;
+
+	mlk_button_update(&ui.buttons.quit, ticks);
+	mlk_button_update(&ui.buttons.hello, ticks);
+	mlk_button_update(&ui.buttons.download, ticks);
+
+	mlk_notify_update(ticks);
 }
 
 static void
@@ -247,15 +308,34 @@
 	mlk_label_draw(&ui.header.label);
 	mlk_checkbox_draw(&ui.autosave.cb);
 	mlk_label_draw(&ui.autosave.label);
-	mlk_button_draw(&ui.quit.button);
+	mlk_button_draw(&ui.buttons.hello);
+	mlk_button_draw(&ui.buttons.quit);
+	mlk_button_draw(&ui.buttons.download);
+	mlk_notify_draw();
 	mlk_painter_present();
 }
 
 static void
+init(void)
+{
+	int err;
+
+	if ((err = mlk_example_init("example-ui")) < 0)
+		mlk_panicf("mlk_example_init: %s", mlk_err_string(err));
+
+	button_style_glow_init(&ui.buttons.download_glow);
+
+	mlk_button_init(&ui.buttons.hello);
+	mlk_button_init(&ui.buttons.quit);
+	mlk_button_init(&ui.buttons.download);
+}
+
+static void
 run(void)
 {
 	struct mlk_state state = {
 		.handle = handle,
+		.update = update,
 		.draw = draw
 	};
 
--- a/libmlk-ui/mlk/ui/button.c	Tue Feb 28 15:54:44 2023 +0100
+++ b/libmlk-ui/mlk/ui/button.c	Wed Mar 01 10:34:12 2023 +0100
@@ -29,30 +29,23 @@
 #include "label.h"
 #include "theme.h"
 
-static int
-is_boxed(const struct mlk_button *button, const struct mlk_event_click *click)
-{
-	assert(button);
-	assert(click);
-	assert(click->type == MLK_EVENT_CLICKDOWN || click->type == MLK_EVENT_CLICKUP);
+#define STYLE_INVOKE(s, f, ...)                                                 \
+do {                                                                            \
+        if (s && s->f)                                                          \
+                s->f(s, __VA_ARGS__);                                           \
+        else if (mlk_button_style.f)                                            \
+                mlk_button_style.f(s ? s : &mlk_button_style, __VA_ARGS__);     \
+} while (0)
 
-	return mlk_maths_is_boxed(button->x, button->y, button->w, button->h,
-	    click->x, click->y);
-}
-
-void
-mlk_button_draw_default(const struct mlk_theme *t, const struct mlk_button *button)
+static void
+draw(struct mlk_button_style *style, const struct mlk_button *button)
 {
-	assert(t);
-	assert(button);
-
-	(void)t;
-
 	struct mlk_label label = {
 		.text = button->text,
 	};
 	unsigned int lw, lh;
 
+	// TODO: once label has style, copy color.
 	mlk_label_query(&label, &lw, &lh);
 
 	if (lw > button->w)
@@ -63,22 +56,49 @@
 	mlk_align(MLK_ALIGN_CENTER, &label.x, &label.y, lw, lh,
 	    button->x, button->y, button->w, button->h);
 
-	mlk_painter_set_color(0x577277ff);
+	mlk_painter_set_color(style->bg_color);
 	mlk_painter_draw_rectangle(button->x, button->y, button->w, button->h);
 
 	mlk_label_draw(&label);
 }
 
+static inline int
+is_boxed(const struct mlk_button *button, const struct mlk_event_click *click)
+{
+	assert(button);
+	assert(click);
+	assert(click->type == MLK_EVENT_CLICKDOWN || click->type == MLK_EVENT_CLICKUP);
+
+	return mlk_maths_is_boxed(button->x, button->y, button->w, button->h,
+	    click->x, click->y);
+}
+
+struct mlk_button_style mlk_button_style = {
+	.bg_color = 0x577277ff,
+	.text_color = 0xffffffff,
+	.draw = draw
+};
+
+void
+mlk_button_init(struct mlk_button *button)
+{
+	assert(button);
+
+	STYLE_INVOKE(button->style, init, button);
+}
+
 int
 mlk_button_handle(struct mlk_button *button, const union mlk_event *ev)
 {
 	assert(button);
 	assert(ev);
 
+	int active = 0;
+
 	switch (ev->type) {
 	case MLK_EVENT_CLICKDOWN:
 		if (is_boxed(button, &ev->click))
-			button->state = MLK_BUTTON_STATE_PRESSED;
+			button->pressed = 1;
 		break;
 	case MLK_EVENT_CLICKUP:
 		/*
@@ -86,24 +106,24 @@
 		 * finally activated. This let the user to move the cursor
 		 * outside the button to "forget" the press.
 		 */
-		if (!is_boxed(button, &ev->click))
-			button->state = MLK_BUTTON_STATE_NONE;
-		else if (button->state == MLK_BUTTON_STATE_PRESSED)
-			button->state = MLK_BUTTON_STATE_ACTIVATED;
+		button->pressed = 0;
+
+		if (is_boxed(button, &ev->click))
+			active = 1;
 		break;
 	default:
 		break;
 	}
 
-	return button->state == MLK_BUTTON_STATE_ACTIVATED;
+	return active;
 }
 
 void
-mlk_button_reset(struct mlk_button *button)
+mlk_button_update(struct mlk_button *button, unsigned int ticks)
 {
 	assert(button);
 
-	button->state = MLK_BUTTON_STATE_NONE;
+	STYLE_INVOKE(button->style, update, button, ticks);
 }
 
 void
@@ -111,5 +131,13 @@
 {
 	assert(button);
 
-	mlk_theme_draw_button(button->theme, button);
+	STYLE_INVOKE(button->style, draw, button);
 }
+
+void
+mlk_button_finish(struct mlk_button *button)
+{
+	assert(button);
+
+	STYLE_INVOKE(button->style, finish, button);
+}
--- a/libmlk-ui/mlk/ui/button.h	Tue Feb 28 15:54:44 2023 +0100
+++ b/libmlk-ui/mlk/ui/button.h	Wed Mar 01 10:34:12 2023 +0100
@@ -24,37 +24,45 @@
 union mlk_event;
 
 struct mlk_theme;
+struct mlk_button;
 
-enum mlk_button_state {
-	MLK_BUTTON_STATE_NONE,
-	MLK_BUTTON_STATE_PRESSED,
-	MLK_BUTTON_STATE_ACTIVATED
+struct mlk_button_style {
+	void *data;
+	unsigned long bg_color;
+	unsigned long text_color;
+	void (*init)(struct mlk_button_style *, struct mlk_button *);
+	void (*update)(struct mlk_button_style *, struct mlk_button *, unsigned int);
+	void (*draw)(struct mlk_button_style *, const struct mlk_button *);
+	void (*finish)(struct mlk_button_style *, struct mlk_button *);
 };
 
 struct mlk_button {
-	int x;
-	int y;
-	unsigned int w;
-	unsigned int h;
+	int x, y;
+	unsigned int w, h;
 	const char *text;
-	enum mlk_button_state state;
-	const struct mlk_theme *theme;
+	struct mlk_button_style *style;
+	int pressed;
 };
 
+extern struct mlk_button_style mlk_button_style;
+
 MLK_CORE_BEGIN_DECLS
 
+void
+mlk_button_init(struct mlk_button *);
+
 int
 mlk_button_handle(struct mlk_button *, const union mlk_event *);
 
 void
-mlk_button_reset(struct mlk_button *);
-
-void
-mlk_button_draw_default(const struct mlk_theme *, const struct mlk_button *);
+mlk_button_update(struct mlk_button *, unsigned int);
 
 void
 mlk_button_draw(const struct mlk_button *);
 
+void
+mlk_button_finish(struct mlk_button *);
+
 MLK_CORE_END_DECLS
 
 #endif /* !MLK_UI_BUTTON_H */
--- a/libmlk-ui/mlk/ui/theme.c	Tue Feb 28 15:54:44 2023 +0100
+++ b/libmlk-ui/mlk/ui/theme.c	Wed Mar 01 10:34:12 2023 +0100
@@ -55,7 +55,6 @@
 	.padding = 10,
 	.draw_frame = mlk_frame_draw_default,
 	.draw_label = mlk_label_draw_default,
-	.draw_button = mlk_button_draw_default,
 	.draw_checkbox = mlk_checkbox_draw_default
 };