changeset 152:1008a796a9e7

ui: make UI widgets usable as actions While here add a example-ui program with move support.
author David Demelier <markand@malikania.fr>
date Thu, 15 Oct 2020 18:09:45 +0200
parents b19d076856d2
children aa6e70e330a1
files doxygen/groups.c examples/example-ui.c libui/ui/button.c libui/ui/button.h libui/ui/checkbox.c libui/ui/checkbox.h libui/ui/frame.c libui/ui/frame.h libui/ui/label.c libui/ui/label.h libui/ui/theme.h
diffstat 11 files changed, 376 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
--- a/doxygen/groups.c	Thu Oct 15 14:01:24 2020 +0200
+++ b/doxygen/groups.c	Thu Oct 15 18:09:45 2020 +0200
@@ -40,3 +40,8 @@
  * \defgroup input Input and events
  * \brief Input and event handling.
  */
+
+/**
+ * \defgroup ui UI
+ * \brief User interface elements.
+ */
--- a/examples/example-ui.c	Thu Oct 15 14:01:24 2020 +0200
+++ b/examples/example-ui.c	Thu Oct 15 18:09:45 2020 +0200
@@ -16,8 +16,10 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <core/action.h>
 #include <core/clock.h>
 #include <core/event.h>
+#include <core/maths.h>
 #include <core/panic.h>
 #include <core/painter.h>
 #include <core/sys.h>
@@ -30,53 +32,101 @@
 #include <ui/label.h>
 #include <ui/theme.h>
 
-#define W       (1280)
-#define H       (720)
+#define W               (1280)
+#define H               (720)
 
-static struct frame frame = {
-	.x = 10,
-	.y = 10,
-	.w = 400,
-	.h = 200
-};
+#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)
 
-static struct label title = {
-	.text = "Preferences",
-	.x = 10,
-	.y = 10,
-	.w = 400,
-	.h = 200,
-	.flags = LABEL_FLAGS_SHADOW,
-	.align = ALIGN_TOP_LEFT
-};
-
+/*
+ * We design a basic UI like this.
+ *
+ *                    FRAME_WIDTH
+ * +---------------------------------------------+--
+ * | Title                                       | | HEADER_HEIGHT
+ * +---------------------------------------------+--
+ * | [x] Auto save                               |
+ * |                                             |
+ * |                                             |
+ * |                                    [ Quit ] |
+ * +---------------------------------------------+
+ */
 static struct {
-	struct checkbox cb;
-	struct label label;
-} autosave = {
-	.cb = {
-		.x = 20,
-		.y = 60,
-		.w = 20,
-		.h = 20
+	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
+		}
 	},
-	.label = {
-		.text = "Auto save game",
-		.x = 20 + 20,
-		.y = 60,
-		.w = 200,
-		.h = 20,
-		.align = ALIGN_LEFT,
-		.flags = LABEL_FLAGS_SHADOW
+	.header = {
+		.label = {
+			.text = "Preferences",
+			.x = FRAME_ORIGIN_X,
+			.y = FRAME_ORIGIN_Y,
+			.w = FRAME_WIDTH,
+			.h = HEADER_HEIGHT,
+			.flags = LABEL_FLAGS_SHADOW,
+			.align = ALIGN_LEFT
+		}
+	},
+	.autosave = {
+		.cb = {
+			.w = ELEMENT_HEIGHT,
+			.h = ELEMENT_HEIGHT
+		},
+		.label = {
+			.text = "Auto save game",
+			.align = ALIGN_LEFT,
+			.flags = LABEL_FLAGS_SHADOW,
+			.h = ELEMENT_HEIGHT
+		}
+	},
+	.quit = {
+		.button = {
+			.text = "Quit",
+			.h = ELEMENT_HEIGHT
+		}
 	}
 };
 
-static struct button btquit = {
-	.text = "Quit",
-	.w = 50,
-	.h = 20
-};
-
 static void
 init(void)
 {
@@ -87,13 +137,85 @@
 }
 
 static void
+resize(void)
+{
+	const unsigned int padding = theme_default()->padding;
+
+	/* Header. */
+	ui.header.label.x = ui.panel.frame.x;
+	ui.header.label.y = ui.panel.frame.y;
+
+	/* Auto save. */
+	ui.autosave.cb.x = ui.panel.frame.x + padding;
+	ui.autosave.cb.y = ui.panel.frame.y + HEADER_HEIGHT + padding;
+	ui.autosave.label.w = ui.panel.frame.w - ui.autosave.cb.w - padding;
+	ui.autosave.label.x = ui.autosave.cb.x + ui.autosave.cb.w;
+	ui.autosave.label.y = ui.autosave.cb.y;
+
+	/* Button. */
+	ui.quit.button.w = ui.panel.frame.w / 4;
+
+	align(
+	    ALIGN_BOTTOM_RIGHT,
+	    &ui.quit.button.x,
+	    &ui.quit.button.y,
+	    ui.quit.button.w,
+	    ui.quit.button.h,
+	    ui.panel.frame.x + padding,
+	    ui.panel.frame.y + padding,
+	    ui.panel.frame.w - padding * 2,
+	    ui.panel.frame.h - padding * 2
+	);
+}
+
+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
 run(void)
 {
 	struct clock clock = {0};
 
 	clock_start(&clock);
 
-	for (;;) {
+	prepare();
+	resize();
+
+	while (ui.quit.button.state != BUTTON_STATE_ACTIVATED) {
 		unsigned int elapsed = clock_elapsed(&clock);
 
 		clock_start(&clock);
@@ -102,31 +224,37 @@
 			switch (ev.type) {
 			case EVENT_QUIT:
 				return;
+			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;
+				}
+				else
+					action_stack_handle(&ui.st, &ev);
+				break;
+			case EVENT_CLICKUP:
+				ui.motion.active = false;
+				/* Fallthrough. */
 			default:
-				checkbox_handle(&autosave.cb, &ev);
-				button_handle(&btquit, &ev);
-
-				if (btquit.state == BUTTON_STATE_ACTIVATED)
-					return;
-
+				action_stack_handle(&ui.st, &ev);
 				break;
 			}
 		}
 
-		/* Compute button position at runtime. */
-		align(ALIGN_BOTTOM_RIGHT, &btquit.x, &btquit.y, btquit.w, btquit.h,
-		    frame.x, frame.y, frame.w, frame.h);
-
-		btquit.x -= theme_default()->padding;
-		btquit.y -= theme_default()->padding;
-
 		painter_set_color(0xffffffff);
 		painter_clear();
-		frame_draw(&frame);
-		label_draw(&title);
-		checkbox_draw(&autosave.cb);
-		label_draw(&autosave.label);
-		button_draw(&btquit);
+		action_stack_update(&ui.st, elapsed);
+		action_stack_draw(&ui.st);
 		painter_present();
 
 		if ((elapsed = clock_elapsed(&clock)) < 20)
@@ -154,4 +282,3 @@
 
 	return 0;
 }
-
--- a/libui/ui/button.c	Thu Oct 15 14:01:24 2020 +0200
+++ b/libui/ui/button.c	Thu Oct 15 18:09:45 2020 +0200
@@ -17,11 +17,14 @@
  */
 
 #include <assert.h>
+#include <string.h>
 
+#include <core/action.h>
 #include <core/event.h>
 #include <core/maths.h>
 #include <core/painter.h>
 
+#include "align.h"
 #include "button.h"
 #include "label.h"
 #include "theme.h"
@@ -37,6 +40,18 @@
 	    click->x, click->y);
 }
 
+static void
+handle(struct action *act, const union event *ev)
+{
+	button_handle(act->data, ev);
+}
+
+static void
+draw(struct action *act)
+{
+	button_draw(act->data);
+}
+
 void
 button_draw_default(struct theme *t, const struct button *button)
 {
@@ -102,3 +117,15 @@
 
 	theme_draw_button(button->theme, button);
 }
+
+void
+button_action(struct button *button, struct action *act)
+{
+	assert(button);
+	assert(act);
+
+	memset(act, 0, sizeof (*act));
+	act->data = button;
+	act->handle = handle;
+	act->draw = draw;
+}
--- a/libui/ui/button.h	Thu Oct 15 14:01:24 2020 +0200
+++ b/libui/ui/button.h	Thu Oct 15 18:09:45 2020 +0200
@@ -22,10 +22,12 @@
 /**
  * \file button.h
  * \brief GUI button.
+ * \ingroup ui
  */
 
 union event;
 
+struct action;
 struct theme;
 
 /**
@@ -93,4 +95,26 @@
 void
 button_draw(struct button *button);
 
+/**
+ * Convert the button into an action.
+ *
+ * The following field will be set into the action:
+ *
+ * - act->data: points to button (reference),
+ * - act->handle: a wrapper to button_handle,
+ * - act->draw: a wrapper to button_draw.
+ *
+ * The button being an UI element is considered to never completes, as such
+ * you will need to handle this case or to use a custom update function.
+ *
+ * \note You will still need to check the button state and reset it at some
+ *       point.
+ * \pre button != NULL
+ * \pre act != NULL
+ * \param button the button to reference
+ * \param act the action to fill
+ */
+void
+button_action(struct button *button, struct action *act);
+
 #endif /* !MOLKO_BUTTON_H */
--- a/libui/ui/checkbox.c	Thu Oct 15 14:01:24 2020 +0200
+++ b/libui/ui/checkbox.c	Thu Oct 15 18:09:45 2020 +0200
@@ -17,7 +17,9 @@
  */
 
 #include <assert.h>
+#include <string.h>
 
+#include <core/action.h>
 #include <core/event.h>
 #include <core/maths.h>
 #include <core/painter.h>
@@ -35,6 +37,18 @@
 	return maths_is_boxed(cb->x, cb->y, cb->w, cb->h, click->x, click->y);
 }
 
+static void
+handle(struct action *act, const union event *ev)
+{
+	checkbox_handle(act->data, ev);
+}
+
+static void
+draw(struct action *act)
+{
+	checkbox_draw(act->data);
+}
+
 void
 checkbox_draw_default(struct theme *t, const struct checkbox *cb)
 {
@@ -73,3 +87,15 @@
 {
 	theme_draw_checkbox(cb->theme, cb);
 }
+
+void
+checkbox_action(struct checkbox *cb, struct action *act)
+{
+	assert(cb);
+	assert(act);
+
+	memset(act, 0, sizeof (*act));
+	act->data = cb;
+	act->handle = handle;
+	act->draw = draw;
+}
--- a/libui/ui/checkbox.h	Thu Oct 15 14:01:24 2020 +0200
+++ b/libui/ui/checkbox.h	Thu Oct 15 18:09:45 2020 +0200
@@ -22,12 +22,16 @@
 /**
  * \file checkbox.h
  * \brief GUI checkbox.
+ * \ingroup ui
  */
 
 #include <stdbool.h>
 
 union event;
 
+struct action;
+struct theme;
+
 /**
  * \brief GUI checkbox.
  */
@@ -71,4 +75,26 @@
 void
 checkbox_draw(const struct checkbox *cb);
 
+/**
+ * Convert the checkbox into an action.
+ *
+ * The following field will be set into the action:
+ *
+ * - act->data: points to cb (reference),
+ * - act->handle: a wrapper to checkbox_handle,
+ * - act->draw: a wrapper to checkbox_draw.
+ *
+ * The checkbox being an UI element is considered to never completes, as such
+ * you will need to handle this case or to use a custom update function.
+ *
+ * \note You will still need to check the checkbox state and reset it at some
+ *       point.
+ * \pre cb != NULL
+ * \pre act != NULL
+ * \param cb the checkbox to reference
+ * \param act the action to fill
+ */
+void
+checkbox_action(struct checkbox *cb, struct action *act);
+
 #endif /* !MOLKO_CHECKBOX_H */
--- a/libui/ui/frame.c	Thu Oct 15 14:01:24 2020 +0200
+++ b/libui/ui/frame.c	Thu Oct 15 18:09:45 2020 +0200
@@ -17,12 +17,20 @@
  */
 
 #include <assert.h>
+#include <string.h>
 
+#include <core/action.h>
 #include <core/painter.h>
 
 #include "frame.h"
 #include "theme.h"
 
+static void
+draw(struct action *act)
+{
+	frame_draw(act->data);
+}
+
 void
 frame_draw_default(struct theme *t, const struct frame *frame)
 {
@@ -46,3 +54,14 @@
 
 	theme_draw_frame(frame->theme, frame);
 }
+
+void
+frame_action(struct frame *frame, struct action *act)
+{
+	assert(frame);
+	assert(act);
+
+	memset(act, 0, sizeof (*act));
+	act->data = frame;
+	act->draw = draw;
+}
--- a/libui/ui/frame.h	Thu Oct 15 14:01:24 2020 +0200
+++ b/libui/ui/frame.h	Thu Oct 15 18:09:45 2020 +0200
@@ -22,8 +22,10 @@
 /**
  * \file frame.h
  * \brief GUI frame.
+ * \ingroup ui
  */
 
+struct action;
 struct theme;
 
 /**
@@ -66,4 +68,23 @@
 void
 frame_draw(const struct frame *frame);
 
+/**
+ * Convert the frame into an action.
+ *
+ * The following field will be set into the action:
+ *
+ * - act->data: points to frame (reference),
+ * - act->draw: a wrapper to checkbox_draw.
+ *
+ * The frame being an UI element is considered to never completes, as such
+ * you will need to handle this case or to use a custom update function.
+ *
+ * \pre frame != NULL
+ * \pre act != NULL
+ * \param frame the frame to reference
+ * \param act the action to fill
+ */
+void
+frame_action(struct frame *frame, struct action *act);
+
 #endif /* !MOLKO_FRAME_H */
--- a/libui/ui/label.c	Thu Oct 15 14:01:24 2020 +0200
+++ b/libui/ui/label.c	Thu Oct 15 18:09:45 2020 +0200
@@ -17,15 +17,22 @@
  */
 
 #include <assert.h>
+#include <string.h>
 
+#include <core/action.h>
 #include <core/font.h>
 #include <core/panic.h>
-#include <core/trace.h>
 #include <core/texture.h>
 
 #include "label.h"
 #include "theme.h"
 
+static void
+draw(struct action *act)
+{
+	label_draw(act->data);
+}
+
 void
 label_draw_default(struct theme *t, const struct label *label)
 {
@@ -83,8 +90,16 @@
 	assert(label);
 	assert(label->text);
 
-	if (label->align != ALIGN_NONE && (label->w == 0 || label->h == 0))
-		trace("label %p has alignment but null dimensions", label);
-
 	theme_draw_label(label->theme, label);
 }
+
+void
+label_action(struct label *label, struct action *act)
+{
+	assert(label);
+	assert(act);
+
+	memset(act, 0, sizeof (*act));
+	act->data = label;
+	act->draw = draw;
+}
--- a/libui/ui/label.h	Thu Oct 15 14:01:24 2020 +0200
+++ b/libui/ui/label.h	Thu Oct 15 18:09:45 2020 +0200
@@ -22,10 +22,12 @@
 /**
  * \file label.h
  * \brief GUI label.
+ * \ingroup ui
  */
 
 #include "align.h"
 
+struct action;
 struct theme;
 
 /**
@@ -74,4 +76,23 @@
 void
 label_draw(const struct label *label);
 
+/**
+ * Convert the label into an action.
+ *
+ * The following field will be set into the action:
+ *
+ * - act->data: points to label (reference),
+ * - act->draw: a wrapper to label_draw.
+ *
+ * The label being an UI element is considered to never completes, as such
+ * you will need to handle this case or to use a custom update function.
+ *
+ * \pre frame != NULL
+ * \pre act != NULL
+ * \param label the label to reference
+ * \param act the action to fill
+ */
+void
+label_action(struct label *label, struct action *act);
+
 #endif /* !MOLKO_LABEL_H */
--- a/libui/ui/theme.h	Thu Oct 15 14:01:24 2020 +0200
+++ b/libui/ui/theme.h	Thu Oct 15 18:09:45 2020 +0200
@@ -22,6 +22,7 @@
 /**
  * \file theme.h
  * \brief Abstract theming.
+ * \ingroup ui
  */
 
 #include <stdbool.h>