changeset 159:e05a792f6910

ui: make message less clever and provide spacing field
author David Demelier <markand@malikania.fr>
date Fri, 16 Oct 2020 18:44:05 +0200
parents e8d2740703df
children 22141b6b2e43
files examples/example-message.c libcore/core/font.c libcore/core/font.h librpg/rpg/message.c librpg/rpg/message.h libui/ui/label.c
diffstat 6 files changed, 100 insertions(+), 87 deletions(-) [+]
line wrap: on
line diff
--- a/examples/example-message.c	Fri Oct 16 17:45:39 2020 +0200
+++ b/examples/example-message.c	Fri Oct 16 18:44:05 2020 +0200
@@ -112,13 +112,15 @@
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.h = MH,
+		.spacing = 12,
 		.text = {
 			"This is a basic message.",
-			"You need to press <Enter> to close it."
+			"Vertical spacing is automatically computed.",
+			"You need to press <Enter> to close it.",
 		},
 	};
 
+	message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -129,7 +131,6 @@
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.h = MH,
 		.timeout = MESSAGE_TIMEOUT_DEFAULT,
 		.text = {
 			"This is a an automatic message.",
@@ -139,6 +140,7 @@
 		.flags = MESSAGE_FLAGS_AUTOMATIC
 	};
 
+	message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -149,7 +151,6 @@
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.h = MH,
 		.delay = MESSAGE_DELAY_DEFAULT,
 		.text = {
 			"This message will fade in."
@@ -157,6 +158,7 @@
 		.flags = MESSAGE_FLAGS_FADEIN
 	};
 
+	message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -167,7 +169,6 @@
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.h = MH,
 		.delay = MESSAGE_DELAY_DEFAULT,
 		.text = {
 			"This message will fade out."
@@ -175,6 +176,7 @@
 		.flags = MESSAGE_FLAGS_FADEOUT
 	};
 
+	message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -185,7 +187,6 @@
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.h = MH,
 		.delay = MESSAGE_DELAY_DEFAULT,
 		.text = {
 			"This message will fade in and out."
@@ -193,6 +194,7 @@
 		.flags = MESSAGE_FLAGS_FADEIN | MESSAGE_FLAGS_FADEOUT
 	};
 
+	message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -203,7 +205,6 @@
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.h = MH,
 		.text = {
 			"Okay, I've understood.",
 			"Nevermind, I'll do it again."
@@ -211,6 +212,7 @@
 		.flags = MESSAGE_FLAGS_QUESTION
 	};
 
+	message_query(&msg, NULL, &msg.h);
 	run(&msg);
 }
 
@@ -244,9 +246,10 @@
 		.x = MX,
 		.y = MY,
 		.w = MW,
-		.h = 50,
+		.h = 40,
 		.text = {
 			"This one is too small in height and will emit a warning.",
+			"Because this line will be incomplete."
 		},
 	};
 
@@ -293,23 +296,6 @@
 	run(&msg);
 }
 
-static void
-large(void)
-{
-	struct message msg = {
-		.x = MX,
-		.y = MY,
-		.w = 500,
-		.h = 500,
-		.text = {
-			"And this one is terribly large.",
-			"So lines will have large spacing."
-		},
-	};
-
-	run(&msg);
-}
-
 int
 main(int argc, char **argv)
 {
@@ -327,6 +313,5 @@
 	toosmallh();
 	toosmallw();
 	custom();
-	large();
 	quit();
 }
--- a/libcore/core/font.c	Fri Oct 16 17:45:39 2020 +0200
+++ b/libcore/core/font.c	Fri Oct 16 18:44:05 2020 +0200
@@ -111,7 +111,7 @@
 }
 
 bool
-font_box(const struct font *font, const char *text, unsigned int *w, unsigned int *h)
+font_query(const struct font *font, const char *text, unsigned int *w, unsigned int *h)
 {
 	assert(font_ok(font));
 	assert(text);
--- a/libcore/core/font.h	Fri Oct 16 17:45:39 2020 +0200
+++ b/libcore/core/font.h	Fri Oct 16 18:44:05 2020 +0200
@@ -140,7 +140,7 @@
  * \return false in case of error and pointers to w and h are left unmodified
  */
 bool
-font_box(const struct font *font, const char *text, unsigned int *w, unsigned int *h);
+font_query(const struct font *font, const char *text, unsigned int *w, unsigned int *h);
 
 /**
  * Close the font.
--- a/librpg/rpg/message.c	Fri Oct 16 17:45:39 2020 +0200
+++ b/librpg/rpg/message.c	Fri Oct 16 18:44:05 2020 +0200
@@ -79,63 +79,61 @@
 }
 
 static inline unsigned int
-spacing(const struct message *msg, const struct theme *t, unsigned int fh)
+min_width(const struct message *msg)
 {
 	assert(msg);
-	assert(t);
+
+	unsigned int maxw = 0, w = 0;
 
-	/* No vertical spacing if only one message. */
-	if (MESSAGE_LINES_MAX <= 1)
-		return 0;
+	for (size_t i = 0; i < MESSAGE_LINES_MAX; ++i) {
+		if (!msg->text[i])
+			continue;
+		if (!font_query(THEME(msg)->fonts[THEME_FONT_INTERFACE], msg->text[i], &w, NULL))
+			panic();
+		if (w > maxw)
+			maxw = w;
+	}
 
-	return ((msg->h - t->padding * 2) - (fh * MESSAGE_LINES_MAX)) / (MESSAGE_LINES_MAX - 1);
+	return (THEME(msg)->padding * 2) + maxw;
 }
 
 static inline unsigned int
-height(const struct theme *t, unsigned int lvreq)
+min_height(const struct message *msg)
 {
-	assert(t);
+	assert(msg);
 
-	return lvreq * MESSAGE_LINES_MAX;
+	const struct theme *th = THEME(msg);
+	const unsigned int lh  = font_height(th->fonts[THEME_FONT_INTERFACE]);
+
+	return (th->padding * 2) + (MESSAGE_LINES_MAX * lh) + ((MESSAGE_LINES_MAX - 1) * msg->spacing);
 }
 
 static void
 draw_lines(const struct message *msg)
 {
 	struct theme theme;
-	unsigned int lvreq, vreq, vspace = 0;
+	struct label label;
+	unsigned int lw, lh;
 
 	/* Shallow copy theme to modify colors. */
 	theme_shallow(&theme, msg->theme);
 
-	/*
-	 * Compute the maximum line height for the given font.
-	 *
-	 * Then, compute the minimum height required to draw the lines (without
-	 * their vertical spacing).
-	 */
-	lvreq = font_height(theme.fonts[THEME_FONT_INTERFACE]);
-	vreq = height(&theme, lvreq);
-
-	/* Now if enough space, compute the spacing between lines. */
-	if (vreq > msg->h)
-		trace("message height is too small: %u < %u", msg->h, vreq);
-	else
-		vspace = spacing(msg, &theme, lvreq);
-
 	for (int i = 0; i < MESSAGE_LINES_MAX; ++i) {
 		if (!msg->text[i])
 			continue;
+		if (!font_query(theme.fonts[THEME_FONT_INTERFACE], msg->text[i], &lw, &lh))
+			panic();
 
-		struct label label = {
-			.x = theme.padding,
-			.y = theme.padding + (i * (lvreq + vspace)),
-			.w = msg->w,
-			.h = msg->h,
-			.theme = &theme,
-			.text = msg->text[i],
-			.flags = LABEL_FLAGS_SHADOW
-		};
+		label.theme = &theme;
+		label.x = theme.padding;
+		label.y = theme.padding + (i * (lh + msg->spacing));
+		label.text = msg->text[i];
+		label.flags = LABEL_FLAGS_SHADOW;
+
+		if (label.x + lw > msg->w)
+			trace("message width too small: %u < %u", msg->w, min_width(msg));
+		if (label.y + lh > msg->h)
+			trace("message height too small: %u < %u", msg->h, min_height(msg));
 
 		/*
 		 * The function label_draw will use THEME_COLOR_NORMAL to draw
@@ -143,14 +141,9 @@
 		 * we need to cheat the normal color.
 		 */
 		if (msg->flags & MESSAGE_FLAGS_QUESTION && msg->index == (unsigned int)i)
-			theme.colors[THEME_COLOR_NORMAL] = theme.colors[THEME_COLOR_SELECTED];
+			theme.colors[THEME_COLOR_NORMAL] = THEME(msg)->colors[THEME_COLOR_SELECTED];
 		else
-			theme.colors[THEME_COLOR_NORMAL] = theme.colors[THEME_COLOR_NORMAL];
-
-		label_query(&label);
-
-		if (label.w > msg->w)
-			trace("message width is too small: %u < %u", msg->w, label.w);
+			theme.colors[THEME_COLOR_NORMAL] = THEME(msg)->colors[THEME_COLOR_NORMAL];
 
 		label_draw(&label);
 	}
@@ -175,6 +168,17 @@
 }
 
 void
+message_query(const struct message *msg, unsigned int *w, unsigned int *h)
+{
+	assert(msg);
+
+	if (w)
+		*w = min_width(msg);
+	if (h)
+		*h = min_height(msg);
+}
+
+void
 message_handle(struct message *msg, const union event *ev)
 {
 	assert(msg);
@@ -265,6 +269,11 @@
 	int x, y;
 	unsigned int w, h;
 
+	if (msg->w == 0 || msg->h == 0) {
+		trace("message has null dimensions");
+		return;
+	}
+
 	if (!texture_new(&tex, msg->w, msg->h))
 		panic();
 
--- a/librpg/rpg/message.h	Fri Oct 16 17:45:39 2020 +0200
+++ b/librpg/rpg/message.h	Fri Oct 16 18:44:05 2020 +0200
@@ -26,11 +26,8 @@
  * \ingroup drawing
  *
  * This module's purpose is to show a dialog box into the screen to show text
- * and optionally ask the user a question.
- *
- * By itself, it is very low level and does not prevent other parts of the game
- * to use the input so you probably need to inhibit input if your dialog is
- * meant to be displayed on a map.
+ * and optionally ask the user a question. It is similar to what you're used to
+ * see in many RPGs.
  *
  * To use it use the following procedure:
  *
@@ -50,17 +47,31 @@
  * 	.text = {
  * 		"Hello, what's up?"
  * 	},
- * 	// This image will be shown on the left as user face.
- * 	.avatar = mysuperavatar,
- * 	// This should point to a image that is used as background.
- * 	.frame = mysuperframe,
- * 	// The first color is normal text, the second is for selected text
- * 	// in case of question.
- * 	.colors = { 0xffffffff, 0x0000ffff },
  * 	// This indicates this message is a question.
- * 	.flags = MESSAGE_QUESTION
+ * 	.flags = MESSAGE_FLAGS_QUESTION
  * };
  * \endcode
+ *
+ * For performance reasons, flexibility and simplicity, the message box does
+ * not try to be clever about positions of lines. It will simply create an
+ * animation for opening the box, drawing the lines to the position according
+ * to the theme padding and spacing and interact with user. For convenience
+ * though, the \ref message_query can be used to determine dimensions required
+ * for a better final result.
+ *
+ * ## Example, computing the dimensions:
+ *
+ * \code
+ * // We create a message that we put on the center of the screen.
+ * struct message msg = {
+ *     .text = {
+ *         "Hi, have you tried turning it off and on again?"
+ *     }
+ * };
+ *
+ * message_query(&msg, &msg.w, &msg.h);
+ * align(ALIGN_CENTER, &msg.x, &msg.y, msg.w, msg.h, 0, 0, window.w, window.h);
+ * \endcode
  */
 
 #include <stdbool.h>
@@ -110,19 +121,16 @@
 
 /**
  * \brief Message object.
- *
- * This structure is used to display a message into the screen. It does not own
- * any user properties and therefore must exist while using it.
  */
 struct message {
 	int x;                                  /*!< (+) Position in x. */
 	int y;                                  /*!< (+) Position in y. */
 	unsigned int w;                         /*!< (+) Width. */
 	unsigned int h;                         /*!< (+) Height. */
+	unsigned int spacing;                   /*!< (+) Spacing between lines. */
 	unsigned int delay;                     /*!< (+) Delay for animations. */
 	unsigned int timeout;                   /*!< (+) Timeout in milliseconds. */
 	const char *text[MESSAGE_LINES_MAX];    /*!< (+) Lines of text to show. */
-	struct texture *avatar;                 /*!< (+&?) Avatar face. */
 	unsigned int index;                     /*!< (+) Line selected */
 	enum message_flags flags;               /*!< (+) Message flags */
 	enum message_state state;               /*!< (-) Current state */
@@ -144,6 +152,17 @@
 message_start(struct message *msg);
 
 /**
+ * Compute the minimal message dimensions required.
+ *
+ * \pre msg != NULL
+ * \param msg the message to query
+ * \param w the pointer to width (may be NULL)
+ * \param h the pointer to height (may be NULL)
+ */
+void
+message_query(const struct message *msg, unsigned int *w, unsigned int *h);
+
+/**
  * Handle input events.
  *
  * This function will alter state of the message and change its selection in
--- a/libui/ui/label.c	Fri Oct 16 17:45:39 2020 +0200
+++ b/libui/ui/label.c	Fri Oct 16 18:44:05 2020 +0200
@@ -73,7 +73,7 @@
 
 	struct theme *t = label->theme ? label->theme : theme_default();
 
-	if (!font_box(t->fonts[THEME_FONT_INTERFACE], label->text, &label->w, &label->h))
+	if (!font_query(t->fonts[THEME_FONT_INTERFACE], label->text, &label->w, &label->h))
 		panic();
 }