From 8d4b353e88f66e6703f4872163f926eb6dac4aa7 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 18 Dec 2024 14:58:40 +0100 Subject: [PATCH] a lot of work on sliders --- src/main.c3 | 19 ++++-- src/ugui_button.c3 | 5 +- src/ugui_data.c3 | 9 --- src/ugui_div.c3 | 164 ++++++++++++++++++++++++++------------------- src/ugui_impl.c3 | 34 +++++----- src/ugui_input.c3 | 9 +-- src/ugui_layout.c3 | 4 +- src/ugui_slider.c3 | 111 ++++++++++++++++++------------ src/ugui_text.c3 | 17 ++--- 9 files changed, 203 insertions(+), 169 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index ee322f4..f1f0d25 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -138,21 +138,21 @@ fn int main(String[] args) io::printn("release button2"); } } - if (ui.slider_ver("slider", ugui::Rect{0,0,30,100})!!.update) { - ugui::Elem* e = ui.get_elem_by_label("slider")!!; - io::printfn("slider: %f", e.slider.value); + static float slider1; + if (ui.slider_ver("slider", ugui::Rect{0,0,30,100}, &slider1)!!.update) { + io::printfn("slider: %f", slider1); } ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!; |}; ui.div_end()!!; - ui.div_begin("second", ugui::DIV_FILL, scroll_y: true)!!; + ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; {| ui.layout_set_column()!!; - if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) { - ugui::Elem* e = ui.get_elem_by_label("slider_other")!!; - io::printfn("other slider: %f", e.slider.value); + static float slider2 = 0.5; + if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100}, &slider2)!!.update) { + io::printfn("other slider: %f", slider2); } ui.button("button10", ugui::Rect{0,0,50,50})!!; ui.button("button11", ugui::Rect{0,0,50,50})!!; @@ -164,6 +164,11 @@ fn int main(String[] args) ui.button("button16", ugui::Rect{0,0,50,50})!!; ui.button("button17", ugui::Rect{0,0,50,50})!!; } + ui.layout_next_column()!!; + ui.layout_set_row()!!; + static float f1, f2; + ui.slider_hor("hs1", ugui::Rect{0,0,100,30}, &f1)!!; + ui.slider_hor("hs2", ugui::Rect{0,0,100,30}, &f2)!!; |}; ui.div_end()!!; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index 362826a..7db15c1 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -33,10 +33,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) return UgError.WRONG_ELEMENT_TYPE?; } - // if the element is new or the parent was updated then redo layout - if (needs_layout) { - c_elem.bounds = ctx.position_element(parent, size, true); - } + c_elem.bounds = ctx.position_element(parent, size, true); // if the bounds are null the element is outside the div view, // no interaction should occur so just return diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index c65269a..a10b1f3 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -47,12 +47,6 @@ bitstruct ElemEvents : uint { bool update : 7; } -// slider element -struct ElemSlider { - float value; - Rect handle; -} - struct ElemText { char* str; } @@ -100,9 +94,6 @@ macro Color uint_to_rgba(uint $u) { const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; -macro abs(a) { return a < 0 ? -a : a; } -macro clamp(x, min, max) { return x < min ? min : (x > max ? max : x); } - const uint STACK_STEP = 10; const uint MAX_ELEMS = 128; const uint MAX_CMDS = 256; diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 970b722..fd43738 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -1,6 +1,7 @@ module ugui; import std::io; +import std::math; enum DivLayout { LAYOUT_ROW, @@ -19,7 +20,8 @@ struct ElemDiv { float value_x; float value_y; } - Rect children_bounds; + Rect children_bounds; // current frame children bounds + Rect pcb; // previous frame children bounds Point origin_r, origin_c; Color bgcolor; } @@ -38,12 +40,12 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo { Id id = label.hash(); - Elem *parent = ctx.get_parent()!; + Elem* parent = ctx.get_parent()!; Elem* c_elem = ctx.get_elem(id)!; isz div_node = ctx.tree.add(id, ctx.active_div)!; ctx.active_div = div_node; - bool needs_layout = c_elem.flags.is_new || parent.flags.updated; + bool is_new = c_elem.flags.is_new; if (c_elem.flags.is_new) { *c_elem = DIV_DEFAULTS; @@ -53,86 +55,104 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo c_elem.div.scroll.can_x = scroll_x; c_elem.div.scroll.can_y = scroll_y; - // do layout and update flags only if the element was updated - if (needs_layout) { - // 2. layout the element - c_elem.bounds = ctx.position_element(parent, size); - c_elem.div.children_bounds = c_elem.bounds; - c_elem.div.bgcolor = ctx.style.bgcolor; - - // 3. Mark the element as updated - c_elem.flags.updated = true; - // 4. Fill the div fields - c_elem.div.origin_c = Point{ - .x = c_elem.bounds.x, - .y = c_elem.bounds.y, - }; - c_elem.div.origin_r = c_elem.div.origin_c; - c_elem.div.layout = parent.div.layout; - } + // 2. layout the element + c_elem.bounds = ctx.position_element(parent, size); + c_elem.div.children_bounds = c_elem.bounds; + c_elem.div.bgcolor = ctx.style.bgcolor; + + // 4. Fill the div fields + c_elem.div.origin_c = Point{ + .x = c_elem.bounds.x, + .y = c_elem.bounds.y, + }; + c_elem.div.origin_r = c_elem.div.origin_c; + c_elem.div.layout = parent.div.layout; // Add the background to the draw stack bool do_border = parent.div.layout == LAYOUT_FLOATING; ctx.push_rect(c_elem.bounds, c_elem.div.bgcolor, do_border: do_border)!; + c_elem.events = ctx.get_elem_events(c_elem); + // TODO: check active // TODO: check resizeable - // check and draw scroll bars - if (!c_elem.bounds.contains(c_elem.div.children_bounds)) { - Point cbc = { - .x = c_elem.div.children_bounds.x + c_elem.div.children_bounds.w, - .y = c_elem.div.children_bounds.y + c_elem.div.children_bounds.h, + // FIXME: we cannot use slider elements as scrollbars because of id management + // scrollbars + Rect cb = c_elem.div.pcb; + // children bounds bottom-right corner + Point cbc = { + .x = cb.x + cb.w, + .y = cb.y + cb.h, + }; + // div bounds bottom-right corner + Point bc = { + .x = c_elem.bounds.x + c_elem.bounds.w, + .y = c_elem.bounds.y + c_elem.bounds.h, + }; + + + // set the scrollbar flag, is used in layout + // vertical overflow + c_elem.div.scroll.on_y = cbc.y > bc.y && c_elem.div.scroll.can_y; + // horizontal overflow + c_elem.div.scroll.on_x = cbc.x > bc.x && c_elem.div.scroll.can_x; + + if (c_elem.div.scroll.on_y) { + Rect vslider = { + .x = c_elem.bounds.x + c_elem.bounds.w - 10, + .y = c_elem.bounds.y, + .w = 10, + .h = c_elem.bounds.h, }; - Point bc = { - .x = c_elem.bounds.x + c_elem.bounds.w, - .y = c_elem.bounds.y + c_elem.bounds.h, + + short hh = (short)(max((float)bc.y / cbc.y, (float)0.15) * c_elem.bounds.h); + Rect vhandle = { + .x = c_elem.bounds.x + c_elem.bounds.w - 10, + .y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, c_elem.div.scroll.value_y), + .w = 10, + .h = hh, }; - // vertical overflow, check and draw scroll bar - if (cbc.y > bc.y && c_elem.div.scroll.can_y) { - // set the scrollbar flag, is used in layout - c_elem.div.scroll.on_y = true; - - Rect vslider = { - .x = c_elem.bounds.x + c_elem.bounds.w - 10, - .y = c_elem.bounds.y, - .w = 10, - .h = c_elem.bounds.h, - }; - - float vh = max((float)bc.y / cbc.y, (float)0.15); - Rect vhandle = { - .x = c_elem.bounds.x + c_elem.bounds.w - 10, - .y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1-vh) * c_elem.div.scroll.value_y)), - .w = 10, - .h = (short)(c_elem.bounds.h * vh), - }; - - c_elem.events = ctx.get_elem_events(c_elem); - if (parent.flags.has_focus && c_elem.events.mouse_hover && c_elem.events.mouse_hold && point_in_rect(ctx.input.mouse.pos, vhandle)) { - short y = (short)clamp(ctx.input.mouse.pos.y - vhandle.h/2, vslider.y, vslider.y + vslider.h - vhandle.h); - vhandle.y = y; - float v = (float)(vhandle.y-vslider.y) / (float)(vslider.h-vhandle.h); - c_elem.div.scroll.value_y = v; - c_elem.flags.updated = true; - c_elem.div.origin_c = Point{ - .x = c_elem.bounds.x, - .y = c_elem.bounds.y, - }; - c_elem.div.origin_r = c_elem.div.origin_c; - } - - ctx.push_rect(vslider, uint_to_rgba(0x999999ff))!; - ctx.push_rect(vhandle, uint_to_rgba(0x9999ffff))!; - } else { - c_elem.div.scroll.on_y = false; + + Point m = ctx.input.mouse.pos; + if (parent.flags.has_focus && c_elem.events.mouse_hover && + c_elem.events.mouse_hold && point_in_rect(m, vhandle)) { + vhandle.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, c_elem.div.scroll.value_y); + c_elem.div.scroll.value_y = calc_value(c_elem.bounds.y, m.y, c_elem.bounds.h, hh); + c_elem.flags.updated = true; } + + ctx.push_rect(vslider, uint_to_rgba(0x999999ff))!; + ctx.push_rect(vhandle, uint_to_rgba(0x9999ffff))!; } - // if the bounds are outside of the view then allocate space for scrollbars - DivLayout old_layout = c_elem.div.layout; - c_elem.div.layout = LAYOUT_FLOATING; - c_elem.div.layout = old_layout; + if (c_elem.div.scroll.on_x) { + Rect hslider = { + .x = c_elem.bounds.x, + .y = c_elem.bounds.y + c_elem.bounds.h - 10, + .w = c_elem.bounds.w, + .h = 10, + }; + + short hw = (short)(max((float)bc.x / cbc.x, (float)0.15) * c_elem.bounds.w); + Rect hhandle = { + .x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, c_elem.div.scroll.value_x), + .y = c_elem.bounds.y + c_elem.bounds.h - 10, + .w = hw, + .h = 10, + }; + + Point m = ctx.input.mouse.pos; + if (parent.flags.has_focus && c_elem.events.mouse_hover && + c_elem.events.mouse_hold && point_in_rect(m, hhandle)) { + hhandle.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, c_elem.div.scroll.value_x); + c_elem.div.scroll.value_x = calc_value(c_elem.bounds.x, m.x, c_elem.bounds.w, hw); + c_elem.flags.updated = true; + } + + ctx.push_rect(hslider, uint_to_rgba(0x999999ff))!; + ctx.push_rect(hhandle, uint_to_rgba(0x9999ffff))!; + } if (parent.flags.has_focus) { if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) { @@ -144,6 +164,10 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo fn void! Ctx.div_end(&ctx) { + // swap the children bounds + Elem* c_elem = ctx.get_elem_by_tree_idx(ctx.active_div)!; + c_elem.div.pcb = c_elem.div.children_bounds; + // the active_div returns to the parent of the current one ctx.active_div = ctx.tree.parentof(ctx.active_div)!; } diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 9b7d824..5503e34 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -85,26 +85,24 @@ fn void! Ctx.frame_begin(&ctx) // other stuff c_elem.flags.has_focus = ctx.has_focus; - if (c_elem.flags.is_new || c_elem.flags.updated) { - Elem def_root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .bounds = { + Elem def_root = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .bounds = { + .w = ctx.width, + .h = ctx.height, + }, + .div = { + .layout = LAYOUT_ROW, + .children_bounds = { .w = ctx.width, .h = ctx.height, - }, - .div = { - .layout = LAYOUT_ROW, - .children_bounds = { - .w = ctx.width, - .h = ctx.height, - } - }, - .flags = c_elem.flags, - }; - - *c_elem = def_root; - } + } + }, + .flags = c_elem.flags, + }; + + *c_elem = def_root; // 3. Push the root element into the element tree ctx.active_div = ctx.tree.add(ROOT_ID, 0)!; diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index 1b49ae5..0d55615 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -1,6 +1,7 @@ module ugui; import std::io; +import std::math; // TODO: this could be a bitstruct bitstruct InputEvents : uint { @@ -84,8 +85,8 @@ fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) // Mouse was moved, report absolute position fn void Ctx.input_mouse_abs(&ctx, short x, short y) { - ctx.input.mouse.pos.x = clamp(x, 0u16, ctx.width); - ctx.input.mouse.pos.y = clamp(y, 0u16, ctx.height); + ctx.input.mouse.pos.x = math::clamp(x, 0u16, ctx.width); + ctx.input.mouse.pos.y = math::clamp(y, 0u16, ctx.height); short dx, dy; dx = x - ctx.input.mouse.pos.x; @@ -107,8 +108,8 @@ fn void Ctx.input_mouse_delta(&ctx, short dx, short dy) mx = ctx.input.mouse.pos.x + dx; my = ctx.input.mouse.pos.y + dy; - ctx.input.mouse.pos.x = clamp(mx, 0u16, ctx.width); - ctx.input.mouse.pos.y = clamp(my, 0u16, ctx.height); + ctx.input.mouse.pos.x = math::clamp(mx, 0u16, ctx.width); + ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height); ctx.input.events.mouse_move = true; } diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 423f127..59e6c9a 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -140,10 +140,10 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) Point off; Rect* cb = &div.children_bounds; if (div.scroll.can_x && div.scroll.on_x) { - off.x = (short)(cb.w * div.scroll.value_x); + off.x = (short)((float)(div.pcb.w - parent.bounds.w) * div.scroll.value_x); } if (div.scroll.can_y && div.scroll.on_y) { - off.y = (short)((float)(cb.h - parent.bounds.h) * div.scroll.value_y); + off.y = (short)((float)(div.pcb.h - parent.bounds.h) * div.scroll.value_y); } Rect view = { .x = parent.bounds.x + off.x, diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 59ef66b..7d15450 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -1,15 +1,28 @@ module ugui; import std::io; +import std::math; + +// slider element +struct ElemSlider { + Rect handle; +} + +const Elem SLIDER_DEFAULT = { + .type = ETYPE_SLIDER, +}; /* handle * +----+-----+---------------------+ * | |#####| | * +----+-----+---------------------+ */ -fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) +<* +@require value != null +*> +fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size, float* value) { - Id id = label.hash(); + Id id = label.hash(); Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; @@ -17,29 +30,33 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) ctx.tree.add(id, ctx.active_div)!; // 1. Fill the element fields - c_elem.type = ETYPE_SLIDER; - - // if the element is new or the parent was updated then redo layout - if (c_elem.flags.is_new || parent.flags.updated) { - // 2. Layout - c_elem.bounds = ctx.position_element(parent, size, true); - c_elem.slider.handle = Rect{ - .x = (short)(c_elem.bounds.x + (int)(c_elem.bounds.w*(1.0-0.25) * c_elem.slider.value)), - .y = c_elem.bounds.y, - .w = (short)(c_elem.bounds.w * 0.25), - .h = c_elem.bounds.h, - }; + if (c_elem.flags.is_new) { + *c_elem = SLIDER_DEFAULT; + } else if (c_elem.type != SLIDER_DEFAULT.type) { + return UgError.WRONG_ELEMENT_TYPE?; } + // 2. Layout + c_elem.bounds = ctx.position_element(parent, size, true); + + // handle width + short hw = (short)(c_elem.bounds.w * 0.25); + Rect handle = { + .x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, *value), + .y = c_elem.bounds.y, + .w = hw, + .h = c_elem.bounds.h, + }; + c_elem.slider.handle = handle; + + Point m = ctx.input.mouse.pos; c_elem.events = ctx.get_elem_events(c_elem); - if (parent.flags.has_focus && c_elem.events.mouse_hover) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { - short x = (short)clamp(ctx.input.mouse.pos.x - c_elem.slider.handle.w/2, c_elem.bounds.x, c_elem.bounds.x + c_elem.bounds.w - c_elem.slider.handle.w); - float v = (float)(c_elem.slider.handle.x-c_elem.bounds.x) / (float)(c_elem.bounds.w-c_elem.slider.handle.w); - c_elem.slider.handle.x = x; - c_elem.slider.value = v; - c_elem.events.update = true; - } + + if (parent.flags.has_focus && c_elem.events.mouse_hover && + point_in_rect(m, handle) && c_elem.events.mouse_hold) { + *value = calc_value(c_elem.bounds.x, m.x, c_elem.bounds.w, hw); + c_elem.slider.handle.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, *value); + c_elem.events.update = true; } // Draw the slider background and handle @@ -63,7 +80,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) * | | * +-+ */ -fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) +fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size, float* value) { Id id = label.hash(); @@ -73,29 +90,34 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) ctx.tree.add(id, ctx.active_div)!; // 1. Fill the element fields - c_elem.type = ETYPE_SLIDER; - - // if the element is new or the parent was updated then redo layout - if (c_elem.flags.is_new || parent.flags.updated) { - // 2. Layout - c_elem.bounds = ctx.position_element(parent, size, true); - c_elem.slider.handle = Rect{ - .x = c_elem.bounds.x, - .y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1.0-0.25) * c_elem.slider.value)), - .w = c_elem.bounds.w, - .h = (short)(c_elem.bounds.h * 0.25), - }; + if (c_elem.flags.is_new) { + *c_elem = SLIDER_DEFAULT; + } else if (c_elem.type != SLIDER_DEFAULT.type) { + return UgError.WRONG_ELEMENT_TYPE?; } + // 2. Layout + c_elem.bounds = ctx.position_element(parent, size, true); + + // handle height + short hh = (short)(c_elem.bounds.h * 0.25); + Rect handle = { + .x = c_elem.bounds.x, + .y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, *value), + .w = c_elem.bounds.w, + .h = hh, + }; + c_elem.slider.handle = handle; + + Point m = ctx.input.mouse.pos; c_elem.events = ctx.get_elem_events(c_elem); - if (parent.flags.has_focus && c_elem.events.mouse_hover) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { - short y = (short)clamp(ctx.input.mouse.pos.y - c_elem.slider.handle.h/2, c_elem.bounds.y, c_elem.bounds.y + c_elem.bounds.h - c_elem.slider.handle.h); - float v = (float)(c_elem.slider.handle.y-c_elem.bounds.y) / (float)(c_elem.bounds.h-c_elem.slider.handle.h); - c_elem.slider.handle.y = y; - c_elem.slider.value = v; - c_elem.events.update = true; - } + + if (parent.flags.has_focus && c_elem.events.mouse_hover && + point_in_rect(m, handle) && + c_elem.events.mouse_hold) { + *value = calc_value(c_elem.bounds.y, m.y, c_elem.bounds.h, hh); + c_elem.slider.handle.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, *value); + c_elem.events.update = true; } // Draw the slider background and handle @@ -106,3 +128,6 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) return c_elem.events; } + +macro short calc_slider(ushort off, ushort dim, float value) => (short)off + (short)(dim * value); +macro float calc_value(ushort off, ushort mouse, ushort dim, ushort slider) => math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f); diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 88fb581..300825a 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -15,19 +15,12 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) // this resets the flags c_elem.type = ETYPE_TEXT; - short baseline = (short)ctx.font.ascender; - short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; - short line_gap = (short)ctx.font.linegap; // if the element is new or the parent was updated then redo layout - if (c_elem.flags.is_new || parent.flags.updated) { - Rect text_size = ctx.get_text_bounds(text)!; - - // 2. Layout - c_elem.bounds = ctx.position_element(parent, text_size, true); - - // 3. Fill the button specific fields - c_elem.text.str = text; - } + Rect text_size = ctx.get_text_bounds(text)!; + // 2. Layout + c_elem.bounds = ctx.position_element(parent, text_size, true); + // 3. Fill the button specific fields + c_elem.text.str = text; ctx.push_string(c_elem.bounds, text)!; }