From 18a86e8aaba6b6bb26343c80bb98df63fd9dcec5 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 26 Oct 2025 17:48:42 +0100 Subject: [PATCH 01/13] vertical and horizontal lines, @row and @column return their ids like @div --- src/widgets/div.c3 | 6 ++--- src/widgets/separator.c3 | 48 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/widgets/div.c3 b/src/widgets/div.c3 index cd74c23..4d03129 100644 --- a/src/widgets/div.c3 +++ b/src/widgets/div.c3 @@ -20,21 +20,21 @@ struct ElemDiv { macro Ctx.@center(&ctx, LayoutDirection dir = ROW, ...; @body()) { - ctx.@div(@grow(), @grow(), dir, CENTER) { + return ctx.@div(@grow(), @grow(), dir, CENTER) { @body(); }!; } macro Ctx.@row(&ctx, Anchor anchor = TOP_LEFT, ...; @body()) { - ctx.@div(@fit(), @fit(), ROW, anchor: anchor) { + return ctx.@div(@fit(), @fit(), ROW, anchor: anchor) { @body(); }!; } macro Ctx.@column(&ctx, Anchor anchor = TOP_LEFT, ...; @body()) { - ctx.@div(@fit(), @fit(), COLUMN, anchor: anchor) { + return ctx.@div(@fit(), @fit(), COLUMN, anchor: anchor) { @body(); }!; } diff --git a/src/widgets/separator.c3 b/src/widgets/separator.c3 index 1c1adae..505245f 100644 --- a/src/widgets/separator.c3 +++ b/src/widgets/separator.c3 @@ -1,16 +1,56 @@ module ugui; -macro Ctx.separator(&ctx, int width, int height, ...) +macro Ctx.separator(&ctx, Size width, Size height, ...) => ctx.separator_id(@compute_id($vasplat), width, height); -fn void? Ctx.separator_id(&ctx, Id id, int width, int height) +fn void? Ctx.separator_id(&ctx, Id id, Size width, Size height) { id = ctx.gen_id(id)!; Elem* parent, elem; ctx.get_elem(id, ETYPE_NONE)!.unpack(&elem, &parent); - elem.layout.w = @exact((short)width); - elem.layout.h = @exact((short)height); + elem.layout.w = width; + elem.layout.h = height; update_parent_size(elem, parent); +} + + +macro Ctx.hor_line(&ctx, ...) + => ctx.hor_line_id(@compute_id($vasplat)); +fn void? Ctx.hor_line_id(&ctx, Id id) +{ + id = ctx.gen_id(id)!; + Elem* parent, elem; + ctx.get_elem(id, ETYPE_NONE)!.unpack(&elem, &parent); + Style* style = ctx.styles.get_style(@str_hash("separator")); + + elem.layout.w = @grow(); + elem.layout.h = @exact(style.size); + elem.layout.content_offset = style.margin + style.border + style.padding; + + update_parent_size(elem, parent); + + Rect r = elem.bounds.pad(elem.layout.content_offset); + ctx.push_rect(r, elem.z_index, style)!; +} + + +macro Ctx.ver_line(&ctx, ...) + => ctx.ver_line_id(@compute_id($vasplat)); +fn void? Ctx.ver_line_id(&ctx, Id id) +{ + id = ctx.gen_id(id)!; + Elem* parent, elem; + ctx.get_elem(id, ETYPE_NONE)!.unpack(&elem, &parent); + Style* style = ctx.styles.get_style(@str_hash("separator")); + + elem.layout.w = @exact(style.size); + elem.layout.h = @grow(); + elem.layout.content_offset = style.margin + style.border + style.padding; + + update_parent_size(elem, parent); + + Rect r = elem.bounds.pad(elem.layout.content_offset); + ctx.push_rect(r, elem.z_index, style)!; } \ No newline at end of file From 49666294dc101c5770d68ecfd61ee4d0eba76b2d Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 27 Oct 2025 11:01:50 +0100 Subject: [PATCH 02/13] fixed @compute_id() to accept non-constants --- src/core.c3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core.c3 b/src/core.c3 index 8fc1e59..c0441de 100644 --- a/src/core.c3 +++ b/src/core.c3 @@ -162,7 +162,7 @@ macro Id @compute_id(...) { Id id = (Id)$$LINE.hash() ^ (Id)@str_hash($$FILE); $for var $i = 0; $i < $vacount; $i++: - id ^= (Id)$vaconst[$i].hash(); + id ^= (Id)$vaexpr[$i].hash(); $endfor return id; } From 1ec6eb88c9c5b8aca580f412c5327304df2d99c3 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 27 Oct 2025 11:02:11 +0100 Subject: [PATCH 03/13] implemented @popup() macro --- src/widgets/div.c3 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/widgets/div.c3 b/src/widgets/div.c3 index 4d03129..8f3453c 100644 --- a/src/widgets/div.c3 +++ b/src/widgets/div.c3 @@ -172,6 +172,21 @@ fn Id? Ctx.div_end(&ctx) return elem.id; } +<* @param[&inout] state *> +macro Ctx.@popup(&ctx, bool* state, + Point pos, + Size width, Size height, + LayoutDirection dir = ROW, Anchor anchor = TOP_LEFT, + bool scroll_x = false, bool scroll_y = false, + ...; @body()) +{ + if (*state) { + *state = ctx.popup_begin(pos, width, height, dir, anchor, scroll_x, scroll_y)!; + @body(); + ctx.div_end()!; + } +} + macro bool? Ctx.popup_begin(&ctx, Point pos, Size width, Size height, LayoutDirection dir = ROW, Anchor anchor = TOP_LEFT, From 3ac556b541edefdae799368e0044bf72a677ffdd Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 27 Oct 2025 12:53:13 +0100 Subject: [PATCH 04/13] fix negative grow element size --- src/layout.c3 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/layout.c3 b/src/layout.c3 index 8f63c5f..8ed464d 100644 --- a/src/layout.c3 +++ b/src/layout.c3 @@ -104,8 +104,8 @@ macro Point Layout.get_dimensions(&el) macro Point Elem.content_space(&e) { return { - .x = e.bounds.w - e.layout.content_offset.x - e.layout.content_offset.w, - .y = e.bounds.h - e.layout.content_offset.y - e.layout.content_offset.h, + .x = (short)max(e.bounds.w - e.layout.content_offset.x - e.layout.content_offset.w, 0), + .y = (short)max(e.bounds.h - e.layout.content_offset.y - e.layout.content_offset.h, 0), }; } @@ -168,7 +168,7 @@ fn void resolve_grow_elements(Elem* e, Elem* p) if (e.layout.absolute) { // absolute children do not need to share space e.bounds.w = p.content_space().x; } else if (p.layout.dir == ROW) { // grow along the axis, divide the parent size - short slot = (short)((p.content_space().x - p.layout.occupied) / p.layout.grow_children); + short slot = (short)max(((p.content_space().x - p.layout.occupied) / p.layout.grow_children), 0); e.bounds.w = slot; p.layout.grow_children--; p.layout.occupied += slot; @@ -182,7 +182,7 @@ fn void resolve_grow_elements(Elem* e, Elem* p) if (e.layout.absolute) { // absolute children do not need to share space e.bounds.h = p.content_space().y; } else if (p.layout.dir == COLUMN) { // grow along the axis, divide the parent size - short slot = (short)((p.content_space().y - p.layout.occupied) / p.layout.grow_children); + short slot = (short)max(((p.content_space().y - p.layout.occupied) / p.layout.grow_children), 0); e.bounds.h = slot; p.layout.grow_children--; p.layout.occupied += slot; From 6a88ea55ec1edad4cdc8dd1921414bdac8b7bf91 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 27 Oct 2025 13:00:34 +0100 Subject: [PATCH 05/13] add license --- LICENSE | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/LICENSE b/LICENSE index e69de29..0a04128 100644 --- a/LICENSE +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. From 66acf8d4a33b677066701ca63e4f0f12611b2e6c Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 27 Oct 2025 20:03:24 +0100 Subject: [PATCH 06/13] skip frame on resize --- src/input.c3 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/input.c3 b/src/input.c3 index 5bc7c31..9f1ba9b 100644 --- a/src/input.c3 +++ b/src/input.c3 @@ -93,6 +93,7 @@ fn void? Ctx.input_window_size(&ctx, short width, short height) ctx.current_input.events.resize = ctx.width != width || ctx.height != height; ctx.width = width; ctx.height = height; + if (ctx.current_input.events.resize) ctx.skip_frame = true; } // Window gained/lost focus From 665e10fa3054bc1cc21910a7881d6aab1757d0ed Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 28 Oct 2025 00:26:18 +0100 Subject: [PATCH 07/13] fix absolute placement and scrollbar --- src/layout.c3 | 2 +- src/widgets/slider.c3 | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/layout.c3 b/src/layout.c3 index 8ed464d..6e3ee05 100644 --- a/src/layout.c3 +++ b/src/layout.c3 @@ -205,7 +205,7 @@ fn void resolve_placement(Elem* c, Elem* p) // if the element has absolute position assign the origin and do not update the parent if (cl.absolute) { c.bounds.x = p.bounds.x + pl.content_offset.x + cl.origin.x; - c.bounds.y = p.bounds.x + pl.content_offset.x + cl.origin.y; + c.bounds.y = p.bounds.y + pl.content_offset.y + cl.origin.y; return; } diff --git a/src/widgets/slider.c3 b/src/widgets/slider.c3 index 70927e7..b54cc18 100644 --- a/src/widgets/slider.c3 +++ b/src/widgets/slider.c3 @@ -140,23 +140,22 @@ fn void? Ctx.scrollbar(&ctx, Id id, float *value, float handle_percent, bool ver Style* style = ctx.styles.get_style(@str_hash("scrollbar")); Rect pb = parent.bounds.pad(parent.layout.content_offset); - elem.bounds.x = vertical ? pb.bottom_right().x - style.size: pb.x; - elem.bounds.y = vertical ? pb.y : pb.bottom_right().y - style.size; if (vertical) { elem.layout.w = @exact(style.size); elem.layout.h = @grow(); - elem.bounds.x -= style.margin.x + style.margin.w + style.border.x + style.border.w; + elem.layout.origin.x = pb.w - style.size; + elem.layout.origin.y = 0; } else { elem.layout.w = @grow(); elem.layout.h = @exact(style.size); - elem.bounds.y -= style.margin.y + style.margin.h + style.border.y + style.border.h; + elem.layout.origin.x = 0; + elem.layout.origin.y = pb.h - style.size; } elem.layout.content_offset = style.margin + style.border + style.padding; elem.layout.absolute = true; update_parent_size(elem, parent); Rect content_bounds = elem.bounds.pad(elem.layout.content_offset); - //elem.events = ctx.get_elem_events(elem); short o = vertical ? content_bounds.y : content_bounds.x; short m = vertical ? ctx.input.mouse.pos.y : ctx.input.mouse.pos.x; From da001601e554b863784bcd6e58cd2faa07336c80 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 28 Oct 2025 00:26:37 +0100 Subject: [PATCH 08/13] fix wrong sprite scale --- src/widgets/sprite.c3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/sprite.c3 b/src/widgets/sprite.c3 index ab15797..00df363 100644 --- a/src/widgets/sprite.c3 +++ b/src/widgets/sprite.c3 @@ -21,8 +21,8 @@ fn void? Ctx.sprite_id(&ctx, Id id, String name, short size = 0) short height = sprite.h; if (size > 0) { if (sprite.w >= sprite.h) { - width = size; height = (short)(size * (float)height/width); + width = size; } else { width = (short)(size * (float)width/height); height = size; From 8e39eee4af9569d6df5146a142c686156f875fe8 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 28 Oct 2025 12:56:51 +0100 Subject: [PATCH 09/13] fix scolling for nested divs --- src/layout.c3 | 25 +++++++++++++++++-------- src/widgets/div.c3 | 10 +++++----- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/layout.c3 b/src/layout.c3 index 6e3ee05..d22363d 100644 --- a/src/layout.c3 +++ b/src/layout.c3 @@ -132,12 +132,6 @@ fn void update_parent_size(Elem* child, Elem* parent) } } -fn void update_children_bounds(Elem* child, Elem* parent) -{ - if (child.layout.absolute) return; - parent.children_bounds = containing_rect(child.bounds + parent.layout.scroll_offset, parent.bounds); -} - macro Rect Elem.content_bounds(&elem) => elem.bounds.pad(elem.layout.content_offset); // Assign the width and height of an element in the directions that it doesn't need to grow @@ -295,6 +289,23 @@ fn void resolve_placement(Elem* c, Elem* p) pl.origin.y += c.bounds.h; default: unreachable("unknown layout direction"); } + + // update the parent children_bounds + // FIXME: this causes scollbars to flicker in/out during resize because the current frames children_bounds are updated + // with the previous' frame child bounds. It would be better to implement another pass during layout. + // FIXME: this long way around to compute the children bounds works and reduces flickering, but it is very ugly + Rect ncb = c.children_bounds; + ncb.x = c.bounds.x; + ncb.y = c.bounds.y; + Rect cb = containing_rect(c.bounds, ncb); + Point o = p.layout.scroll_offset; + p.children_bounds = containing_rect(cb + o, p.children_bounds); + + // reset the children bounds + c.children_bounds = { + .x = c.bounds.x, + .y = c.bounds.y + }; } fn void? Ctx.layout_element_tree(&ctx) @@ -330,10 +341,8 @@ fn void? Ctx.layout_element_tree(&ctx) Elem* c = ctx.find_elem(ctx.tree.get(ch))!; if (ctx.tree.is_root(ch)) { resolve_placement(p, &&{}); - update_children_bounds(p, &&{}); } else { resolve_placement(c, p); - update_children_bounds(c, p); } // FIXME: this stuff would be better elsewhere but we are already iteraring through all diff --git a/src/widgets/div.c3 b/src/widgets/div.c3 index 8f3453c..1dbe978 100644 --- a/src/widgets/div.c3 +++ b/src/widgets/div.c3 @@ -105,8 +105,8 @@ fn void? Ctx.div_begin_id(&ctx, ctx.push_rect(elem.bounds.pad(style.margin), elem.z_index, style)!; // update the ctx scissor, it HAS to be after drawing the background - ctx.div_scissor = elem.bounds.pad(elem.layout.content_offset); - ctx.push_scissor(elem.bounds.pad(elem.layout.content_offset), elem.z_index)!; + ctx.div_scissor = elem.bounds.pad(elem.layout.content_offset).max({0,0,0,0}); + ctx.push_scissor(ctx.div_scissor, elem.z_index)!; //elem.events = ctx.get_elem_events(elem); @@ -164,7 +164,7 @@ fn Id? Ctx.div_end(&ctx) // the active_div returns to the parent of the current one ctx.active_div = ctx.tree.parentof(ctx.active_div)!; Elem* parent = ctx.get_parent()!; - ctx.div_scissor = parent.bounds.pad(parent.layout.content_offset); + ctx.div_scissor = parent.bounds.pad(parent.layout.content_offset).max({0,0,0,0}); ctx.reset_scissor(elem.z_index)!; update_parent_size(elem, parent); @@ -236,8 +236,8 @@ fn bool? Ctx.popup_begin_id(&ctx, ctx.push_rect(elem.bounds.pad(style.margin), elem.z_index, style)!; // update the ctx scissor, it HAS to be after drawing the background - ctx.div_scissor = elem.bounds.pad(elem.layout.content_offset); - ctx.push_scissor(elem.bounds.pad(elem.layout.content_offset), elem.z_index)!; + ctx.div_scissor = elem.bounds.pad(elem.layout.content_offset).max({0,0,0,0}); + ctx.push_scissor(ctx.div_scissor, elem.z_index)!; //elem.events = ctx.get_elem_events(elem); From 19051914535410d0d0aef304a1582d1566bd2993 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 28 Oct 2025 22:00:29 +0100 Subject: [PATCH 10/13] fix wrong id in scrollbar --- src/widgets/div.c3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/div.c3 b/src/widgets/div.c3 index 1dbe978..f7e723b 100644 --- a/src/widgets/div.c3 +++ b/src/widgets/div.c3 @@ -155,7 +155,7 @@ fn Id? Ctx.div_end(&ctx) elem.div.scroll_x.value = math::clamp(elem.div.scroll_x.value, 0.0f, 1.0f); } } - ctx.scrollbar(vsid_raw, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15), false)!; + ctx.scrollbar(hsid_raw, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15), false)!; elem.layout.scroll_offset.x = (short)(elem.div.scroll_x.value*(float)(elem.children_bounds.w-elem.bounds.w)); } else { elem.div.scroll_x.value = 0; From 0c08a42f8ca9e9c429106366c7f0504a1aa300dc Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 17 Nov 2025 17:45:48 +0100 Subject: [PATCH 11/13] use std::math for sat_add() --- src/shapes.c3 | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/shapes.c3 b/src/shapes.c3 index 9a22184..ce69478 100644 --- a/src/shapes.c3 +++ b/src/shapes.c3 @@ -1,5 +1,5 @@ module ugui; - +import std::math; // ---------------------------------------------------------------------------------- // // RECTANGLE // @@ -244,8 +244,6 @@ macro uint Color.to_uint(c) => c.r | (c.g << 8) | (c.b << 16) | (c.a << 24); // SIZE // // ---------------------------------------------------------------------------------- // -macro short short.add_no_of(short a, short b) => (short)max(min((int)a + (int)b, short.max), short.min) @inline; - struct Size { short min, max; } @@ -258,8 +256,8 @@ macro bool Size.@is_grow(s) => (s.min == 0 && s.max == 0); macro bool Size.@is_exact(s) => (s.min == s.max && s.min != 0); macro bool Size.@is_fit(s) => (s.min != s.max); -macro Size Size.add(a, Size b) @operator_s(+) => {.min = a.min.add_no_of(b.min), .max = a.max.add_no_of(b.max)}; -macro Size Size.sub(a, Size b) @operator_s(-) => {.min = a.min.add_no_of(-b.min), .max = a.max.add_no_of(-b.max)}; +macro Size Size.add(a, Size b) @operator_s(+) => {.min = a.min.sat_add(b.min), .max = a.max.sat_add(b.max)}; +macro Size Size.sub(a, Size b) @operator_s(-) => {.min = a.min.sat_sub(b.min), .max = a.max.sat_sub(b.max)}; macro Size Size.combine(a, Size b) => {.min = max(a.min, b.min), .max = min(a.max, b.max)}; macro Size Size.comb_max(a, Size b) => {.min = max(a.min, b.min), .max = max(a.max, b.max)}; From c04f63f7be61782e0372153fc0fcbdaf0532d8d8 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 4 Dec 2025 19:43:41 +0100 Subject: [PATCH 12/13] correctly update mouse delta --- src/core.c3 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core.c3 b/src/core.c3 index c0441de..d4cb6d4 100644 --- a/src/core.c3 +++ b/src/core.c3 @@ -276,7 +276,9 @@ fn void? Ctx.frame_end(&ctx) } // 2. clear input fields + Point mdelta = ctx.current_input.mouse.pos - ctx.input.mouse.pos; ctx.input = ctx.current_input; + ctx.input.mouse.delta = mdelta; ctx.current_input.events = {}; ctx.current_input.mouse.scroll = {}; ctx.current_input.mouse.updated = BTN_NONE; From 60bec17d36aa2fd1bddb3ffd4413bbfe867b6716 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 4 Dec 2025 19:44:46 +0100 Subject: [PATCH 13/13] resizeable divs --- TODO | 4 ++ src/layout.c3 | 14 ++++++- src/widgets/div.c3 | 93 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/TODO b/TODO index d9bcb08..f2fa0fa 100644 --- a/TODO +++ b/TODO @@ -44,6 +44,9 @@ content, the background color is applied starting from the border. Right now push_rect() offsets the background rect by both border and padding [x] Investigate why the debug pointer (cyan rectangle) disappears... +[ ] Selectable divs +[ ] Selectable text +[ ] Copy buffer ## Layout @@ -71,6 +74,7 @@ [x] Use containing_rect() in position_element() to skip some computing and semplify the function [x] Rename position_element() to layout_element() [x] Make functions to mark rows/columns as full, to fix the calculator demo +[ ] Grids ## Input diff --git a/src/layout.c3 b/src/layout.c3 index d22363d..f3834c9 100644 --- a/src/layout.c3 +++ b/src/layout.c3 @@ -20,6 +20,18 @@ enum Anchor { CENTER } +bitstruct ResizeDirection : char { + bool x : 0; + bool y : 1; +} + +bitstruct ResizeAnchor : char { + bool top : 0; + bool bottom : 1; + bool left : 2; + bool right : 3; +} + struct Layout { Size w, h; // size of the CONTENT, does not include margin, border and padding struct children { // the children size includes the children's margin/border/pading @@ -39,7 +51,7 @@ struct Layout { Rect content_offset; // combined effect of margin, border and padding } -// Returns the width and height of a @FIT() element based on it's wanted size (min/max) +// Returns the width and height of a @fit() element based on it's wanted size (min/max) // and the content size, this function is used to both update the parent's children size and // give the dimensions of a fit element // TODO: test and cleanup this function diff --git a/src/widgets/div.c3 b/src/widgets/div.c3 index f7e723b..0c12a14 100644 --- a/src/widgets/div.c3 +++ b/src/widgets/div.c3 @@ -15,6 +15,10 @@ struct ElemDiv { bool on; float value; } + ResizeAnchor resize_anchor; + ResizeAnchor resize_now; + ResizeDirection resized; // the element has been manually resized + Point resize_size; } @@ -39,7 +43,6 @@ macro Ctx.@column(&ctx, Anchor anchor = TOP_LEFT, ...; @body()) }!; } - // useful macro to start and end a div, capturing the trailing block macro Ctx.@div(&ctx, Size width = @grow, Size height = @grow, @@ -47,25 +50,26 @@ macro Ctx.@div(&ctx, Anchor anchor = TOP_LEFT, bool absolute = false, Point off = {}, bool scroll_x = false, bool scroll_y = false, + ResizeAnchor resize = {}, ...; @body() ) { - ctx.div_begin(width, height, dir, anchor, absolute, off, scroll_x, scroll_y, $vasplat)!; + ctx.div_begin(width, height, dir, anchor, absolute, off, scroll_x, scroll_y, resize, $vasplat)!; @body(); return ctx.div_end()!; } macro Ctx.div_begin(&ctx, Size width = @grow(), Size height = @grow(), - LayoutDirection dir = ROW, - Anchor anchor = TOP_LEFT, - bool absolute = false, Point off = {}, - bool scroll_x = false, bool scroll_y = false, + LayoutDirection dir, Anchor anchor, + bool absolute, Point off, + bool scroll_x, bool scroll_y, + ResizeAnchor resize, ... ) { - return ctx.div_begin_id(@compute_id($vasplat), width, height, dir, anchor, absolute, off, scroll_x, scroll_y); + return ctx.div_begin_id(@compute_id($vasplat), width, height, dir, anchor, absolute, off, scroll_x, scroll_y, resize); } fn void? Ctx.div_begin_id(&ctx, @@ -74,7 +78,8 @@ fn void? Ctx.div_begin_id(&ctx, LayoutDirection dir, Anchor anchor, bool absolute, Point off, - bool scroll_x, bool scroll_y + bool scroll_x, bool scroll_y, + ResizeAnchor resize ) { id = ctx.gen_id(id)!; @@ -87,7 +92,21 @@ fn void? Ctx.div_begin_id(&ctx, elem.div.scroll_x.enabled = scroll_x; elem.div.scroll_y.enabled = scroll_y; + elem.div.resize_anchor = resize; + // check if the div is resizeable + bool resized = elem.div.resized && (resize != (ResizeAnchor){}); + if (resize != (ResizeAnchor){}) { + // if the element was not resized yet then the size is as-specified + // if the element was resized the size is the same as the last frame + if (elem.div.resized.x == true) { + width = @exact(elem.div.resize_size.x); + } + if (elem.div.resized.y == true) { + height = @exact(elem.div.resize_size.y); + } + } + // update layout with correct info elem.layout = { .w = width, @@ -161,6 +180,63 @@ fn Id? Ctx.div_end(&ctx) elem.div.scroll_x.value = 0; } + // check resize action + /* + * top border + * +-------------------------------------------+ + * | +---------------------------------------+ | + * | | | | + * | | | | + * | | | | + * | | | | + * left | | | | right + * border | | | | border + * | | | | + * | | | | + * | | | | + * | | | | + * | +---------------------------------------+ | + * +-------------------------------------------+ + * bottom border + */ + if (elem.div.resize_anchor != (ResizeAnchor){}) { + Rect b = elem.bounds; + Rect s = style.border + style.margin + style.padding; + Rect b_l = {.x = b.x, .y = b.y, .w = s.x, .h = b.h}; + Rect b_r = {.x = b.x+b.w-s.w, .y = b.y, .w = s.w, .h = b.h}; + Rect b_t = {.x = b.x, .y = b.y, .w = b.w, .h = s.y}; + Rect b_b = {.x = b.x, .y = b.y+b.h, .w = b.w, .h = s.h}; + + Rect content_bounds = elem.bounds.pad(elem.layout.content_offset); + Point m = ctx.input.mouse.pos; + if (elem.events.mouse_hover && elem.events.mouse_press) { + if (elem.div.resize_anchor.right && m.in_rect(b_r)) elem.div.resize_now.right = true; + if (elem.div.resize_anchor.left && m.in_rect(b_l)) elem.div.resize_now.left = true; + if (elem.div.resize_anchor.top && m.in_rect(b_t)) elem.div.resize_now.top = true; + if (elem.div.resize_anchor.bottom && m.in_rect(b_b)) elem.div.resize_now.bottom = true; + } else if (ctx.is_mouse_released(BTN_ANY)) { + elem.div.resize_now = {}; + } + + if (elem.div.resize_now.right == true) { + elem.div.resized.x = true; + elem.div.resize_size.x = content_bounds.w - (elem.bounds.x + elem.bounds.w - m.x); + } + if (elem.div.resize_now.bottom == true) { + elem.div.resized.y = true; + elem.div.resize_size.y = content_bounds.h - (elem.bounds.y + elem.bounds.h - m.y); + } + if (elem.div.resize_now.left == true) { + elem.div.resized.x = true; + elem.div.resize_size.x = content_bounds.w - (elem.bounds.x - m.x); + } + if (elem.div.resize_now.top == true) { + elem.div.resized.y = true; + elem.div.resize_size.y = content_bounds.h - (elem.bounds.y - m.y); + } + + } + // the active_div returns to the parent of the current one ctx.active_div = ctx.tree.parentof(ctx.active_div)!; Elem* parent = ctx.get_parent()!; @@ -247,6 +323,5 @@ fn bool? Ctx.popup_begin_id(&ctx, } // TODO: check active - // TODO: check resizeable return true; }