commit 791c8bda54d3acfcffabba04ff1be38852c38435
parent 829de8bbcb77ea2607f1ec22113222ac755256b8
Author: lumidify <nobody@lumidify.org>
Date:   Sat, 16 Jan 2021 21:37:25 +0100
Improve resizing; misc. changes
Diffstat:
| M | box.c |  |  | 119 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------- | 
| M | button.c |  |  | 4 | ++-- | 
| M | grid.c |  |  | 74 | ++++++++++++++++++++++++++++++++++++++++++++++++++------------------------ | 
| M | label.c |  |  | 4 | ++-- | 
| M | ltk.h |  |  | 8 | ++++++-- | 
| M | ltkd.c |  |  | 15 | +++++++++------ | 
| M | testbox.gui |  |  | 2 | ++ | 
7 files changed, 161 insertions(+), 65 deletions(-)
diff --git a/box.c b/box.c
@@ -38,6 +38,7 @@ static void ltk_box_draw(ltk_box *box);
 static ltk_box *ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient);
 static void ltk_box_destroy(ltk_box *box, int shallow);
 static void ltk_recalculate_box(ltk_box *box);
+static void ltk_box_child_size_change(ltk_box *box, ltk_widget *widget);
 /* FIXME: Why is sticky unsigned short? */
 static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short sticky, char **errstr);
 static int ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_box *box, char **errstr);
@@ -91,6 +92,7 @@ ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient) {
 	box->widget.mouse_release = <k_box_mouse_release;
 	box->widget.motion_notify = <k_box_motion_notify;
 	box->widget.resize = <k_recalculate_box;
+	box->widget.child_size_change = <k_box_child_size_change;
 	box->widget.remove_child = <k_box_remove;
 
 	box->sc = NULL;
@@ -117,7 +119,6 @@ ltk_box_destroy(ltk_box *box, int shallow) {
 	free(box);
 }
 
-/* FIXME: Allow no sticky value to be centered in middle */
 /* FIXME: Need some sort of "visible rect" so widgets don't draw outside */
 /* FIXME: Make this function name more consistent */
 static void
@@ -128,26 +129,70 @@ ltk_recalculate_box(ltk_box *box) {
 		ptr = box->widgets[i];
 		if (box->orient == LTK_HORIZONTAL) {
 			ptr->rect.x = cur_pos;
-			cur_pos += ptr->rect.w;
 			if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM)
 				ptr->rect.h = box->widget.rect.h;
 			if (ptr->sticky & LTK_STICKY_TOP)
 				ptr->rect.y = box->widget.rect.y;
 			else if (ptr->sticky & LTK_STICKY_BOTTOM)
 				ptr->rect.y = box->widget.rect.y + box->widget.rect.h - ptr->rect.h;
+			else
+				ptr->rect.y = box->widget.rect.y + (box->widget.rect.h - ptr->rect.h) / 2;
+			if (ptr->resize)
+				ptr->resize(ptr);
+			cur_pos += ptr->rect.w;
 		} else {
 			ptr->rect.y = cur_pos;
-			cur_pos += ptr->rect.h;
 			if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT)
 				ptr->rect.w = box->widget.rect.w;
 			if (ptr->sticky & LTK_STICKY_LEFT)
 				ptr->rect.x = box->widget.rect.x;
 			else if (ptr->sticky & LTK_STICKY_RIGHT)
 				ptr->rect.x = box->widget.rect.x + box->widget.rect.w - ptr->rect.w;
+			else
+				ptr->rect.x = box->widget.rect.x + (box->widget.rect.w - ptr->rect.w) / 2;
+			if (ptr->resize)
+				ptr->resize(ptr);
+			cur_pos += ptr->rect.h;
 		}
 	}
 }
 
+/* FIXME: This entire resizing thing is a bit weird. For instance, if a label
+   in a vertical box increases its height because its width has been decreased
+   and it is forced to wrap, should that just change the rect or also the
+   ideal size? Ideal size wouldn't really make sense here, but then the box
+   might be forced to add a scrollbar even though the parent widget would
+   actually give it more space if it knew that it needed it. */
+
+static void
+ltk_box_child_size_change(ltk_box *box, ltk_widget *widget) {
+	short size_changed = 0;
+	/* This is always reset here - if it needs to be changed,
+	   the resize function called by the last child_size_change
+	   function will fix it */
+	/* Note: This seems a bit weird, but if each widget set its rect itself,
+	   that would also lead to weird things. For instance, if a butten is
+	   added to after a box after being ungridded, and its rect was changed
+	   by the grid (e.g. because of a column weight), who should reset the
+	   rect if it doesn't have sticky set? Of course, the resize function
+	   could also set all widgets even if they don't have any sticky
+	   settings, but there'd probably be some catch as well. */
+	widget->rect.w = widget->ideal_w;
+	widget->rect.h = widget->ideal_h;
+	if (box->orient == LTK_HORIZONTAL && widget->ideal_h > box->widget.ideal_h) {
+		box->widget.ideal_h = widget->ideal_h;
+		size_changed = 1;
+	} else if (box->orient == LTK_VERTICAL && widget->ideal_w > box->widget.ideal_h) {
+		box->widget.ideal_w = widget->ideal_w;
+		size_changed = 1;
+	}
+
+	if (size_changed && box->widget.parent && box->widget.parent->child_size_change)
+		box->widget.parent->child_size_change(box->widget.parent, box);
+	else
+		ltk_recalculate_box(box);
+}
+
 static int
 ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short sticky, char **errstr) {
 	if (widget->parent) {
@@ -164,9 +209,13 @@ ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short
 	}
 
 	box->widgets[box->num_widgets++] = widget;
+	if (box->orient == LTK_HORIZONTAL)
+		box->widget.ideal_w += widget->ideal_w;
+	else
+		box->widget.ideal_h += widget->ideal_h;
 	widget->parent = box;
 	widget->sticky = sticky;
-	ltk_recalculate_box(box);
+	ltk_box_child_size_change(box, widget);
 	ltk_window_invalidate_rect(window, box->widget.rect);
 
 	return 0;
@@ -186,6 +235,25 @@ ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_box *box, char **errs
 				    (box->num_widgets - i - 1) * sizeof(ltk_widget *));
 			box->num_widgets--;
 			ltk_window_invalidate_rect(window, box->widget.rect);
+			/* search for new ideal width/height */
+			/* FIXME: make this all a bit nicer and break the lines better */
+			if (box->orient == LTK_HORIZONTAL && widget->ideal_h == box->widget.ideal_h) {
+				box->widget.ideal_h = 0;
+				for (size_t j = 0; j < box->num_widgets; j++) {
+					if (box->widgets[j]->ideal_h > box->widget.ideal_h)
+						box->widget.ideal_h = box->widgets[j]->ideal_h;
+				}
+				if (box->widget.parent && box->widget.parent->resize)
+					box->widget.parent->resize(box->widget.parent);
+			} else if (box->orient == LTK_VERTICAL && widget->ideal_w == box->widget.ideal_w) {
+				box->widget.ideal_w = 0;
+				for (size_t j = 0; j < box->num_widgets; j++) {
+					if (box->widgets[j]->ideal_w > box->widget.ideal_w)
+						box->widget.ideal_w = box->widgets[j]->ideal_w;
+				}
+				if (box->widget.parent && box->widget.parent->resize)
+					box->widget.parent->resize(box->widget.parent);
+			}
 			return 0;
 		}
 	}
@@ -204,18 +272,10 @@ ltk_box_mouse_event(ltk_box *box, XEvent event, void (*handler)(ltk_widget *, XE
 		handler(box->sc, event);
 	*/
 
+	/* FIXME: When scrolling is implemented, check only the currently visible items */
 	for (size_t i = 0; i < box->num_widgets; i++) {
 		widget = box->widgets[i];
-		if (box->orient == LTK_HORIZONTAL) {
-			pos = event.xbutton.x;
-			start = widget->rect.x;
-			size = widget->rect.w;
-		} else {
-			pos = event.xbutton.y;
-			start = widget->rect.y;
-			size = widget->rect.h;
-		}
-		if (pos >= start && pos <= start + size) {
+		if (ltk_collide_rect(widget->rect, event.xbutton.x, event.xbutton.y)) {
 			handler(widget, event);
 			return;
 		}
@@ -243,7 +303,7 @@ ltk_box_motion_notify(ltk_box *box, XEvent event) {
 	ltk_box_mouse_event(box, event, <k_widget_motion_notify_event);
 }
 
-/* box <box id> add <widget id> <sticky> */
+/* box <box id> add <widget id> [sticky] */
 static int
 ltk_box_cmd_add(
     ltk_window *window,
@@ -255,7 +315,7 @@ ltk_box_cmd_add(
 	ltk_widget *widget;
 
 	int sticky = 0;
-	if (num_tokens != 5) {
+	if (num_tokens != 4 && num_tokens != 5) {
 		*errstr = "Invalid number of arguments.\n";
 		return 1;
 	}
@@ -266,19 +326,20 @@ ltk_box_cmd_add(
 		return 1;
 	}
 
-	/* FIXME: Allow default value (just no sticky at all) */
-	for (c = tokens[4]; *c != '\0'; c++) {
-		if (*c == 'n') {
-			sticky |= LTK_STICKY_TOP;
-		} else if (*c == 's') {
-			sticky |= LTK_STICKY_BOTTOM;
-		} else if (*c == 'e') {
-			sticky |= LTK_STICKY_RIGHT;
-		} else if (*c == 'w') {
-			sticky |= LTK_STICKY_LEFT;
-		} else if (*c != 'u') {
-			*errstr = "Invalid sticky specification.\n";
-			return 1;
+	if (num_tokens == 5) {
+		for (c = tokens[4]; *c != '\0'; c++) {
+			if (*c == 'n') {
+				sticky |= LTK_STICKY_TOP;
+			} else if (*c == 's') {
+				sticky |= LTK_STICKY_BOTTOM;
+			} else if (*c == 'e') {
+				sticky |= LTK_STICKY_RIGHT;
+			} else if (*c == 'w') {
+				sticky |= LTK_STICKY_LEFT;
+			} else {
+				*errstr = "Invalid sticky specification.\n";
+				return 1;
+			}
 		}
 	}
 
diff --git a/button.c b/button.c
@@ -213,8 +213,8 @@ ltk_button_create(ltk_window *window, const char *id, const char *text) {
 	button->tl = ltk_text_line_create(window->xwindow, font_size, text_copy, -1);
 	int text_w, text_h;
 	ltk_text_line_get_size(button->tl, &text_w, &text_h);
-	button->widget.rect.w = text_w + theme.border_width * 2 + theme.pad * 2;
-	button->widget.rect.h = text_h + theme.border_width * 2 + theme.pad * 2;
+	button->widget.ideal_w = text_w + theme.border_width * 2 + theme.pad * 2;
+	button->widget.ideal_h = text_h + theme.border_width * 2 + theme.pad * 2;
 	/* render text */
 	ltk_button_change_state(button);
 
diff --git a/grid.c b/grid.c
@@ -21,8 +21,12 @@
  * SOFTWARE.
  */
 
-/* TODO: remove_widget function that also adjusts static width */
-/* TODO: widget size request */
+/* TODO: make ungrid function also adjust static row/column width/height
+   -> also, how should the grid deal with a widget spanning over multiple
+      rows/columns with static size - if all are static, it could just
+      divide the widget size (it would complicate things, though), but
+      what should happen if some rows/columns under the span do have a
+      positive weight? */
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -43,6 +47,7 @@ static ltk_grid *ltk_grid_create(ltk_window *window, const char *id,
     int rows, int columns);
 static void ltk_grid_destroy(ltk_grid *grid, int shallow);
 static void ltk_recalculate_grid(ltk_grid *grid);
+static void ltk_grid_child_size_change(ltk_grid *grid, ltk_widget *widget);
 static int ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
     int row, int column, int row_span, int column_span, unsigned short sticky, char **errstr);
 static int ltk_grid_ungrid(ltk_window *window, ltk_widget *widget, ltk_grid *grid, char **errstr);
@@ -113,6 +118,7 @@ ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) {
 	grid->widget.mouse_release = <k_grid_mouse_release;
 	grid->widget.motion_notify = <k_grid_motion_notify;
 	grid->widget.resize = <k_recalculate_grid;
+	grid->widget.child_size_change = <k_grid_child_size_change;
 	grid->widget.remove_child = <k_grid_ungrid;
 
 	grid->rows = rows;
@@ -239,7 +245,7 @@ ltk_recalculate_grid(ltk_grid *grid) {
 			}
 			if (orig_width != ptr->rect.w || orig_height != ptr->rect.h) {
 				if (ptr->resize) {
-					ptr->resize(ptr, orig_width, orig_height);
+					ptr->resize(ptr);
 				}
 			}
 
@@ -262,6 +268,30 @@ ltk_recalculate_grid(ltk_grid *grid) {
 	}
 }
 
+/* FIXME: Maybe add debug stuff to check that grid is actually parent of widget */
+static void
+ltk_grid_child_size_change(ltk_grid *grid, ltk_widget *widget) {
+	short size_changed = 0;
+	widget->rect.w = widget->ideal_w;
+	widget->rect.h = widget->ideal_h;
+	if (grid->column_weights[widget->column] == 0 &&
+	    widget->rect.w > grid->column_widths[widget->column]) {
+		grid->widget.ideal_w += widget->rect.w - grid->column_widths[widget->column];
+		grid->column_widths[widget->column] = widget->rect.w;
+		size_changed = 1;
+	}
+	if (grid->row_weights[widget->row] == 0 &&
+	    widget->rect.h > grid->row_heights[widget->row]) {
+		grid->widget.ideal_h += widget->rect.h - grid->row_heights[widget->row];
+		grid->row_heights[widget->row] = widget->rect.h;
+		size_changed = 1;
+	}
+	if (size_changed && grid->widget.parent && grid->widget.parent->child_size_change)
+		grid->widget.parent->child_size_change(grid->widget.parent, grid);
+	else
+		ltk_recalculate_grid(grid);
+}
+
 /* FIXME: Check if widget already exists at position */
 static int
 ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
@@ -275,19 +305,13 @@ ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
 	widget->column = column;
 	widget->row_span = row_span;
 	widget->column_span = column_span;
-	if (grid->column_weights[column] == 0 && widget->rect.w > grid->column_widths[column]) {
-		grid->column_widths[column] = widget->rect.w;
-	}
-	if (grid->row_weights[row] == 0 && widget->rect.h > grid->row_heights[row]) {
-		grid->row_heights[row] = widget->rect.h;
-	}
 	for (int i = row; i < row + row_span; i++) {
 		for (int j = column; j < column + column_span; j++) {
 			grid->widget_grid[i * grid->columns + j] = widget;
 		}
 	}
 	widget->parent = grid;
-	ltk_recalculate_grid(grid);
+	ltk_grid_child_size_change(grid, widget);
 	ltk_window_invalidate_rect(window, grid->widget.rect);
 
 	return 0;
@@ -374,7 +398,7 @@ ltk_grid_motion_notify(ltk_grid *grid, XEvent event) {
 		ltk_widget_motion_notify_event(ptr, event);
 }
 
-/* grid <grid id> add <widget id> <row> <column> <row_span> <column_span> <sticky> */
+/* grid <grid id> add <widget id> <row> <column> <row_span> <column_span> [sticky] */
 static int
 ltk_grid_cmd_add(
     ltk_window *window,
@@ -387,7 +411,7 @@ ltk_grid_cmd_add(
 	const char *errstr_num;
 
 	int row, column, row_span, column_span, sticky = 0;
-	if (num_tokens != 9) {
+	if (num_tokens != 8 && num_tokens != 9) {
 		*errstr = "Invalid number of arguments.\n";
 		return 1;
 	}
@@ -418,18 +442,20 @@ ltk_grid_cmd_add(
 		return 1;
 	}
 
-	for (c = tokens[8]; *c != '\0'; c++) {
-		if (*c == 'n') {
-			sticky |= LTK_STICKY_TOP;
-		} else if (*c == 's') {
-			sticky |= LTK_STICKY_BOTTOM;
-		} else if (*c == 'e') {
-			sticky |= LTK_STICKY_RIGHT;
-		} else if (*c == 'w') {
-			sticky |= LTK_STICKY_LEFT;
-		} else if (*c != 'u') {
-			*errstr = "Invalid sticky specification.\n";
-			return 1;
+	if (num_tokens == 9) {
+		for (c = tokens[8]; *c != '\0'; c++) {
+			if (*c == 'n') {
+				sticky |= LTK_STICKY_TOP;
+			} else if (*c == 's') {
+				sticky |= LTK_STICKY_BOTTOM;
+			} else if (*c == 'e') {
+				sticky |= LTK_STICKY_RIGHT;
+			} else if (*c == 'w') {
+				sticky |= LTK_STICKY_LEFT;
+			} else {
+				*errstr = "Invalid sticky specification.\n";
+				return 1;
+			}
 		}
 	}
 
diff --git a/label.c b/label.c
@@ -92,8 +92,8 @@ ltk_label_create(ltk_window *window, const char *id, const char *text) {
 	label->tl = ltk_text_line_create(window->xwindow, font_size, text_copy, -1);
 	int text_w, text_h;
 	ltk_text_line_get_size(label->tl, &text_w, &text_h);
-	label->widget.rect.w = text_w + theme.pad * 2;
-	label->widget.rect.h = text_h + theme.pad * 2;
+	label->widget.ideal_w = text_w + theme.pad * 2;
+	label->widget.ideal_h = text_h + theme.pad * 2;
 	ltk_text_line_render(label->tl, &window->theme.bg, &theme.text_color);
 
 	return label;
diff --git a/ltk.h b/ltk.h
@@ -70,12 +70,15 @@ typedef enum {
 typedef struct ltk_window ltk_window;
 
 typedef struct ltk_widget {
-	ltk_rect rect;
 	ltk_window *window;
 	struct ltk_widget *active_widget;
 	struct ltk_widget *parent;
 	char *id;
 
+	ltk_rect rect;
+	unsigned int ideal_w;
+	unsigned int ideal_h;
+
 	void (*key_press) (void *, XEvent);
 	void (*key_release) (void *, XEvent);
 	void (*mouse_press) (void *, XEvent);
@@ -84,11 +87,12 @@ typedef struct ltk_widget {
 	void (*mouse_leave) (void *, XEvent);
 	void (*mouse_enter) (void *, XEvent);
 
-	void (*resize) (void *, int, int);
+	void (*resize) (void *);
 	void (*draw) (void *);
 	void (*change_state) (void *);
 	void (*destroy) (void *, int);
 
+	void (*child_size_change) (struct ltk_widget *, struct ltk_widget *);
 	int (*remove_child) (ltk_window *, void *, void *, char **);
 
 	ltk_widget_type type;
diff --git a/ltkd.c b/ltkd.c
@@ -397,12 +397,11 @@ ltk_set_root_widget_cmd(
 	widget = ltk_get_widget(tokens[1], LTK_WIDGET, errstr);
 	if (!widget) return 1;
 	window->root_widget = widget;
-	int w = widget->rect.w;
-	int h = widget->rect.h;
+	ltk_window_invalidate_rect(window, widget->rect);
 	widget->rect.w = window->rect.w;
 	widget->rect.h = window->rect.h;
 	if (widget->resize)
-		widget->resize(widget, w, h);
+		widget->resize(widget);
 
 	return 0;
 }
@@ -548,10 +547,11 @@ ltk_window_other_event(ltk_window *window, XEvent event) {
 		if (orig_w != w || orig_h != h) {
 			window->rect.w = w;
 			window->rect.h = h;
+			ltk_window_invalidate_rect(window, window->rect);
 			if (ptr && ptr->resize) {
 				ptr->rect.w = w;
 				ptr->rect.h = h;
-				ptr->resize(ptr, orig_w, orig_h);
+				ptr->resize(ptr);
 			}
 		}
 	} else if (event.type == Expose && event.xexpose.count == 0) {
@@ -767,6 +767,7 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
 	widget->draw = draw;
 	widget->change_state = change_state;
 	widget->destroy = destroy;
+	widget->child_size_change = NULL;
 	widget->remove_child = NULL;
 
 	widget->needs_redraw = needs_redraw;
@@ -774,8 +775,10 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
 	widget->row = 0;
 	widget->rect.x = 0;
 	widget->rect.y = 0;
-	widget->rect.w = 100;
-	widget->rect.h = 100;
+	widget->rect.w = 0;
+	widget->rect.h = 0;
+	widget->ideal_w = 0;
+	widget->ideal_h = 0;
 
 	widget->row = 0;
 	widget->column = 0;
diff --git a/testbox.gui b/testbox.gui
@@ -2,5 +2,7 @@ box box1 create vertical
 set-root-widget box1
 button btn1 create "I'm a button!"
 button btn2 create "I'm also a button!"
+button btn3 create "I'm another boring button."
 box box1 add btn1 ew
 box box1 add btn2 e
+box box1 add btn3