From 96fda0c5e9259f8f6f196723f51029eea9e979a2 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 29 Sep 2025 23:51:02 +0200 Subject: [PATCH] re-implemented scrollbars --- lib/ugui.c3l/src/ugui_layout.c3 | 35 ++++------------- lib/ugui.c3l/src/ugui_shapes.c3 | 4 +- lib/ugui.c3l/src/widgets/ugui_div.c3 | 39 +++++++++++-------- lib/ugui.c3l/src/widgets/ugui_slider.c3 | 51 +++++++++++++++++++++++++ resources/style.css | 13 ++++++- src/main.c3 | 45 +++++++++++++++------- 6 files changed, 127 insertions(+), 60 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_layout.c3 b/lib/ugui.c3l/src/ugui_layout.c3 index 2c7496d..92702e4 100644 --- a/lib/ugui.c3l/src/ugui_layout.c3 +++ b/lib/ugui.c3l/src/ugui_layout.c3 @@ -28,9 +28,8 @@ struct Layout { TextSize text; ushort grow_children; short occupied; - struct origin { - short x, y; - } + Point origin; + Point scroll_offset; // false: the element is laid out according to the parent // true: the element is laid out separate from all other children and the relative position to // the parent is the .origin field @@ -108,7 +107,7 @@ macro Point Elem.content_space(&e) .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, }; - } +} // Update the parent element's children size fn void update_parent_size(Elem* child, Elem* parent) @@ -135,32 +134,12 @@ fn void update_parent_size(Elem* child, Elem* parent) fn void update_children_bounds(Elem* child, Elem* parent) { - parent.children_bounds = containing_rect(child.bounds, parent.bounds); + 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); -/* -macro Rect Elem.get_view(&elem) -{ - Rect off; - if (elem.div.scroll_x.enabled && elem.div.scroll_x.on) { - off.x = (short)((float)(elem.div.pcb.w - elem.bounds.w) * elem.div.scroll_x.value); - off.w = -elem.div.scroll_size; - } - if (elem.div.scroll_y.enabled && elem.div.scroll_y.on) { - off.y = (short)((float)(elem.div.pcb.h - elem.bounds.h) * elem.div.scroll_y.value); - off.h = -elem.div.scroll_size; - } - return elem.bounds.add(off); -} - -macro Point Elem.get_view_off(&elem) -{ - return elem.get_view().sub(elem.bounds).position(); -} -*/ - // Assign the width and height of an element in the directions that it doesn't need to grow fn void resolve_dimensions(Elem* e, Elem* p) { @@ -170,7 +149,7 @@ fn void resolve_dimensions(Elem* e, Elem* p) Point elem_dimensions = el.get_dimensions(); e.bounds.w = elem_dimensions.x; e.bounds.h = elem_dimensions.y; - + // if the element has absolute position do not update the parent if (el.absolute) return; @@ -323,7 +302,7 @@ fn void Ctx.layout_element_tree(&ctx) int current; for (int n; (current = ctx.tree.level_order_it(0, n)) >= 0; n++) { Elem* p = ctx.find_elem(ctx.tree.get(current)); - //if (ctx.tree.is_root(current)!!) p = &&{}; + p.layout.origin = -p.layout.scroll_offset; int ch; // RESOLVE KNOWN DIMENSIONS diff --git a/lib/ugui.c3l/src/ugui_shapes.c3 b/lib/ugui.c3l/src/ugui_shapes.c3 index 53a7635..f76479d 100644 --- a/lib/ugui.c3l/src/ugui_shapes.c3 +++ b/lib/ugui.c3l/src/ugui_shapes.c3 @@ -124,7 +124,7 @@ macro Rect Rect.min(Rect a, Rect b) } // Offset a rect by a point -macro Rect Rect.off(Rect r, Point p) +macro Rect Rect.off(Rect r, Point p) @operator_s(+) { return { .x = r.x + p.x, @@ -260,4 +260,4 @@ macro Size Size.combine(a, Size b) => {.min = max(a.min, b.min), .max = min(a.ma macro Size Size.comb_max(a, Size b) => {.min = max(a.min, b.min), .max = max(a.max, b.max)}; macro Size Size.comb_min(a, Size b) => {.min = min(a.min, b.min), .max = min(a.max, b.max)}; -macro short Size.greater(a) => a.min > a.max ? a.min : a.max; +macro short Size.greater(a) => a.min > a.max ? a.min : a.max; diff --git a/lib/ugui.c3l/src/widgets/ugui_div.c3 b/lib/ugui.c3l/src/widgets/ugui_div.c3 index 50de082..5574501 100644 --- a/lib/ugui.c3l/src/widgets/ugui_div.c3 +++ b/lib/ugui.c3l/src/widgets/ugui_div.c3 @@ -15,7 +15,6 @@ struct ElemDiv { bool on; float value; } - ushort scroll_size; int z_index; } @@ -40,7 +39,7 @@ macro Ctx.div_begin(&ctx, Size width = @grow(), Size height = @grow(), LayoutDirection dir = ROW, Anchor anchor = TOP_LEFT, - bool absolute = false, Point off = {}, + bool absolute = false, Point off = {}, bool scroll_x = false, bool scroll_y = false, ... ) @@ -63,12 +62,10 @@ fn void? Ctx.div_begin_id(&ctx, Elem* parent = ctx.get_parent()!; ctx.active_div = elem.tree_idx; - Style* style = ctx.styles.get_style(@str_hash("default")); - Style* slider_style = ctx.styles.get_style(@str_hash("slider")); + Style* style = ctx.styles.get_style(@str_hash("div")); elem.div.scroll_x.enabled = scroll_x; elem.div.scroll_y.enabled = scroll_y; - elem.div.scroll_size = slider_style.size ? slider_style.size : (style.size ? style.size : DEFAULT_STYLE.size); elem.div.z_index = parent.div.z_index + 1; // update layout with correct info @@ -100,8 +97,9 @@ fn void? Ctx.div_begin_id(&ctx, fn Id? Ctx.div_end(&ctx) { Elem* elem = ctx.get_active_div()!; + Style* style = ctx.styles.get_style(@str_hash("div")); + Rect bounds = elem.bounds.pad(style.margin + style.border); -/* FIXME: this needs the absolute positioning to work // set the scrollbar flag, is used in layout Point cbc = elem.children_bounds.bottom_right(); Point bc = elem.bounds.bottom_right(); @@ -112,27 +110,36 @@ fn Id? Ctx.div_end(&ctx) Id hsid_raw = @str_hash("div_scrollbar_horizontal"); Id vsid_raw = @str_hash("div_scrollbar_vertical"); - Id hsid_real = ctx.gen_id(@str_hash("div_scrollbar_horizontal"))!; - Id vsid_real = ctx.gen_id(@str_hash("div_scrollbar_vertical"))!; - short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid_real || ctx.is_hovered(ctx.find_elem(vsid_real)) ? elem.div.scroll_size*2 : elem.div.scroll_size) : 0; - short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid_real || ctx.is_hovered(ctx.find_elem(hsid_real)) ? elem.div.scroll_size*2 : elem.div.scroll_size) : 0; + Id hsid_real = ctx.gen_id(hsid_raw)!; + Id vsid_real = ctx.gen_id(vsid_raw)!; if (elem.div.scroll_y.on) { - if (ctx.input.events.mouse_scroll && ctx.hover_id == elem.id) { + if (ctx.input.events.mouse_scroll && ctx.hover_id == elem.id && !(ctx.get_mod() & KMOD_SHIFT)) { elem.div.scroll_y.value += ctx.input.mouse.scroll.y * 0.07f; elem.div.scroll_y.value = math::clamp(elem.div.scroll_y.value, 0.0f, 1.0f); } - ctx.slider_ver_id(vsid_raw, @exact(wdim), @exact(elem.bounds.h - hdim), &elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!; + ctx.scrollbar(vsid_raw, &elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!; + elem.layout.scroll_offset.y = (short)(elem.div.scroll_y.value*(float)(elem.children_bounds.h-elem.bounds.h)); + } else { + elem.div.scroll_y.value = 0; } + if (elem.div.scroll_x.on) { if (ctx.input.events.mouse_scroll && ctx.hover_id == elem.id) { - elem.div.scroll_x.value += ctx.input.mouse.scroll.x * 0.07f; - elem.div.scroll_x.value = math::clamp(elem.div.scroll_x.value, 0.0f, 1.0f); + if (ctx.get_mod() & KMOD_SHIFT) { // horizontal scroll with shift + elem.div.scroll_x.value += ctx.input.mouse.scroll.y * 0.07f; + elem.div.scroll_x.value = math::clamp(elem.div.scroll_x.value, 0.0f, 1.0f); + } else { + elem.div.scroll_x.value += ctx.input.mouse.scroll.x * 0.07f; + elem.div.scroll_x.value = math::clamp(elem.div.scroll_x.value, 0.0f, 1.0f); + } } - ctx.slider_hor_id(hsid_raw, @exact(elem.bounds.w - wdim), @exact(hdim), &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!; + ctx.scrollbar(vsid_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; } -*/ // the active_div returns to the parent of the current one ctx.active_div = ctx.tree.parentof(ctx.active_div); diff --git a/lib/ugui.c3l/src/widgets/ugui_slider.c3 b/lib/ugui.c3l/src/widgets/ugui_slider.c3 index 5bc8038..1fb8c32 100644 --- a/lib/ugui.c3l/src/widgets/ugui_slider.c3 +++ b/lib/ugui.c3l/src/widgets/ugui_slider.c3 @@ -130,6 +130,57 @@ fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Size w, Size h, float* value, floa return elem.events; } + +fn void? Ctx.scrollbar(&ctx, Id id, float *value, float handle_percent, bool vertical = true) +{ + id = ctx.gen_id(id)!; + + Elem *parent = ctx.get_parent()!; + Elem *elem = ctx.get_elem(id, ETYPE_SLIDER)!; + 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; + } 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.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; + short s = vertical ? content_bounds.h : content_bounds.w; + short h = (short)((float)s * handle_percent); + if (elem.events.has_focus && ctx.is_mouse_down(BTN_LEFT)) { + *value = calc_value(o, m, s, h); + elem.events.update = true; + } + short handle_pos = calc_slider(o, s-h, *value); + + elem.slider.handle = { + .x = vertical ? content_bounds.x : handle_pos, + .y = vertical ? handle_pos : content_bounds.y, + .w = vertical ? content_bounds.w : h, + .h = vertical ? h : content_bounds.h, + }; + + + Rect bg_bounds = elem.bounds.pad(style.margin); + ctx.push_rect(bg_bounds, parent.div.z_index, style)!; + ctx.push_rect(elem.slider.handle, parent.div.z_index, &&(Style){.bg = style.primary, .radius = style.radius})!; +} + macro short calc_slider(short off, short dim, float value) => (short)off + (short)(dim * value); macro float calc_value(short off, short mouse, short dim, short slider) => math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f); diff --git a/resources/style.css b/resources/style.css index 8fcef1a..a9a9bf1 100644 --- a/resources/style.css +++ b/resources/style.css @@ -1,4 +1,4 @@ -default { +div { bg: #282828ff; fg: #fbf1c7ff; primary: #cc241dff; @@ -63,6 +63,17 @@ slider { accent: #fabd2fff; } +scrollbar { + padding: 2; + size: 8; + bg: #45858842; + fg: #fbf1c7ff; + primary: #cc241dff; + secondary: #458588ff; + accent: #fabd2fff; +} + + text-box { bg: #4a4543ff; fg: #fbf1c7ff; diff --git a/src/main.c3 b/src/main.c3 index 926d10c..51fe5f0 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -150,14 +150,16 @@ fn int main(String[] args) ui.input_key_press(); if (e.key.repeat) ui.input_key_repeat(); - mod.rctrl = e.key.key == K_RCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.rctrl; - mod.lctrl = e.key.key == K_LCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.lctrl; - mod.bkspc = e.key.key == K_BACKSPACE ? !!(e.type == EVENT_KEY_DOWN) : mod.bkspc; - mod.del = e.key.key == K_DELETE ? !!(e.type == EVENT_KEY_DOWN) : mod.del; - mod.up = e.key.key == K_UP ? !!(e.type == EVENT_KEY_DOWN) : mod.up; - mod.down = e.key.key == K_DOWN ? !!(e.type == EVENT_KEY_DOWN) : mod.down; - mod.left = e.key.key == K_LEFT ? !!(e.type == EVENT_KEY_DOWN) : mod.left; - mod.right = e.key.key == K_RIGHT ? !!(e.type == EVENT_KEY_DOWN) : mod.right; + mod.rctrl = e.key.key == K_RCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.rctrl; + mod.lctrl = e.key.key == K_LCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.lctrl; + mod.rshift = e.key.key == K_RSHIFT ? !!(e.type == EVENT_KEY_DOWN) : mod.rshift; + mod.lshift = e.key.key == K_LSHIFT ? !!(e.type == EVENT_KEY_DOWN) : mod.lshift; + mod.bkspc = e.key.key == K_BACKSPACE ? !!(e.type == EVENT_KEY_DOWN) : mod.bkspc; + mod.del = e.key.key == K_DELETE ? !!(e.type == EVENT_KEY_DOWN) : mod.del; + mod.up = e.key.key == K_UP ? !!(e.type == EVENT_KEY_DOWN) : mod.up; + mod.down = e.key.key == K_DOWN ? !!(e.type == EVENT_KEY_DOWN) : mod.down; + mod.left = e.key.key == K_LEFT ? !!(e.type == EVENT_KEY_DOWN) : mod.left; + mod.right = e.key.key == K_RIGHT ? !!(e.type == EVENT_KEY_DOWN) : mod.right; // pressing ctrl+key or alt+key does not generate a character as such no // TEXT_INPUT event is generated. When those keys are pressed we have to @@ -207,7 +209,7 @@ fn int main(String[] args) if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true; -const String APPLICATION = "calculator"; +const String APPLICATION = "debug"; $switch APPLICATION: $case "debug": debug_app(&ui); @@ -219,7 +221,7 @@ $endswitch TimeStats dts = draw_times.get_stats(); TimeStats uts = ui_times.get_stats(); - ui.@div(ugui::@fit(), ugui::@fit(), COLUMN, TOP_LEFT, true) { + ui.@div(ugui::@fit(), ugui::@fit(), COLUMN, TOP_LEFT, absolute: true, off: {10, 10}) { ui.text(string::tformat("frame %d, fps = %.2f", frame, fps))!!; ui.text(string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; }!!; @@ -251,6 +253,25 @@ $endswitch return 0; } +fn void debug_app(ugui::Ctx* ui) +{ + static LayoutDirection d = ROW; + ui.@div(ugui::@grow(), ugui::@grow(), anchor: CENTER) { + ui.@div(ugui::@exact(300), ugui::@exact(300), d, scroll_x: true, scroll_y: true) { + if (ui.button("one")!!.mouse_release) d = COLUMN; + if (ui.button("two")!!.mouse_release) d = ROW; + ui.button("three")!!; + ui.button("four")!!; + ui.button("five")!!; + ui.button("six")!!; + ui.button("seven")!!; + ui.button("eight")!!; + ui.button("nine")!!; + ui.button("ten")!!; + }!!; + }!!; +} + /* fn void debug_app(ugui::Ctx* ui) { @@ -410,7 +431,6 @@ fn void calculator(ugui::Ctx* ui, TextEdit* te) } }!!; }!!; - ui.@div(ugui::@grow(), ugui::@fit(), anchor: CENTER) { static bool state; ui.checkbox("boolean", &state, "tick")!!; @@ -422,9 +442,8 @@ fn void calculator(ugui::Ctx* ui, TextEdit* te) ui.slider_hor(ugui::@exact(100), ugui::@exact(20), &f)!!; ui.slider_ver(ugui::@exact(20), ugui::@exact(100), &f)!!; }!!; - ui.@div(ugui::@grow(), ugui::@fit(), anchor: CENTER, scroll_y: true) { + ui.@div(ugui::@grow(), ugui::@fit(), anchor: CENTER) { ui.text_box(ugui::@grow(), ugui::@exact(100), te, RIGHT)!!; }!!; - }!!; }!!; }