implemented adaptive size divs

c3
Alessandro Mauri 3 days ago
parent 16adfd7cc5
commit 78e2c64da6
  1. 2
      TODO
  2. 4
      src/main.c3
  3. 2
      src/ugui_button.c3
  4. 52
      src/ugui_core.c3
  5. 13
      src/ugui_div.c3
  6. 125
      src/ugui_layout.c3
  7. 194
      src/ugui_shapes.c3

@ -22,7 +22,7 @@ to maintain focus until mouse release (fix scroll bars)
## Layout
[ ] Text reflow
[ ] Flexbox
[x] Flexbox
[ ] Center elements to the row/column
## Input

@ -149,7 +149,7 @@ fn int main(String[] args)
ui.frame_begin()!!;
// main div, fill the whole window
ui.div_begin("main", ugui::Rect{.w=ui.width/2})!!;
ui.div_begin("main", ugui::Rect{.w=-100})!!;
{|
ui.layout_set_column()!!;
if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) {
@ -211,7 +211,7 @@ fn int main(String[] args)
TimeStats uts = ui_times.get_stats();
ui.layout_set_floating()!!;
ui.div_begin("fps", ugui::Rect{0, ui.height-100, 200, 100})!!;
ui.div_begin("fps", ugui::Rect{0, ui.height-100, -300, 100})!!;
{|
ui.layout_set_column()!!;
ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;

@ -58,7 +58,7 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
Rect text_size = ctx.get_text_bounds(label)!;
Rect btn_size = rect_add(text_size, Rect{0,0,10,10});
Rect btn_size = text_size.add(Rect{0,0,10,10});
// 2. Layout
elem.bounds = ctx.position_element(parent, btn_size, true);

@ -7,17 +7,6 @@ import fifo;
import std::io;
import std::core::string;
struct Rect {
short x, y, w, h;
}
struct Point {
short x, y;
}
struct Color{
char r, g, b, a;
}
// element ids are just long ints
def Id = usz;
@ -144,45 +133,6 @@ struct Ctx {
isz active_div; // tree node indicating the current active div
}
macro point_in_rect(Point p, Rect r)
{
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
}
// return true if rect a contains b
macro bool Rect.contains(Rect a, Rect b)
{
return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h);
}
macro Rect Rect.intersection(Rect a, Rect b)
{
return Rect{
.x = (short)max(a.x, b.x),
.y = (short)max(a.y, b.y),
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
.h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y),
};
}
// rect intersection not null
macro bool Rect.collides(Rect a, Rect b)
{
return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y);
}
macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
macro Rect rect_add(Rect r1, Rect r2)
{
return Rect{
.x = r1.x + r2.x,
.y = r1.y + r2.y,
.w = r1.w + r2.w,
.h = r1.h + r2.h,
};
}
// return a pointer to the parent of the current active div
fn Elem*! Ctx.get_parent(&ctx)
{
@ -362,7 +312,7 @@ $endif
*>
macro bool Ctx.is_hovered(&ctx, Elem *elem)
{
return point_in_rect(ctx.input.mouse.pos, elem.bounds);
return ctx.input.mouse.pos.in_rect(elem.bounds);
}
macro bool Ctx.elem_focus(&ctx, Elem *elem)

@ -23,6 +23,11 @@ struct ElemDiv {
Point origin_r, origin_c;
}
// begin a widget container, or div, the size determines the offset (x,y) width and height.
// if the width or height are zero the width or height are set to the maximum available.
// if the width or height are negative the width or height will be calculated based on the children size
// sort similar to a flexbox, and the minimum size is set by the negative of the width or height
// FIXME: there is a bug if the size.w or size.h == -0
fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false)
{
Id id = ctx.gen_id(label)!;
@ -43,7 +48,13 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
elem.div.scroll_y.enabled = scroll_y;
// 2. layout the element
elem.bounds = ctx.position_element(parent, size);
Rect wanted_size = {
.x = size.x,
.y = size.y,
.w = size.w < 0 ? max(elem.div.pcb.w, (short)-size.w) : size.w,
.h = size.h < 0 ? max(elem.div.pcb.h, (short)-size.h) : size.h,
};
elem.bounds = ctx.position_element(parent, wanted_size);
elem.div.children_bounds = elem.bounds;
// update the ctx scissor

@ -80,6 +80,26 @@ fn void! Ctx.layout_next_column(&ctx)
parent.div.origin_r = parent.div.origin_c;
}
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 = -SCROLLBAR_DIM;
}
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 = -SCROLLBAR_DIM;
}
return elem.bounds.add(off);
}
macro Point Elem.get_view_off(&elem)
{
return elem.get_view().sub(elem.bounds).position();
}
// position the rectangle inside the parent according to the layout
// parent: parent div
// rect: the requested size
@ -90,11 +110,13 @@ fn void! Ctx.layout_next_column(&ctx)
*>
fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
{
Rect placement;
Point origin;
ElemDiv* div = &parent.div;
Rect parent_bounds, parent_view;
Rect child_placement, child_occupied;
// 1. Select the right origin
Point origin;
switch (div.layout) {
case LAYOUT_ROW:
origin = div.origin_r;
@ -107,81 +129,70 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
return Rect{};
}
// the bottom-right border of the element box
Point pl_corner;
// 2. Compute the parent's view
parent_bounds = parent.bounds;
parent_view = parent.get_view();
// 2. Calculate the placement
placement.x = (short)max(origin.x + rect.x, 0);
placement.y = (short)max(origin.y + rect.y, 0);
placement.w = rect.w > 0 ? rect.w : (short)max(parent.bounds.w - (placement.x - parent.bounds.x), 0);
placement.h = rect.h > 0 ? rect.h : (short)max(parent.bounds.h - (placement.y - parent.bounds.y), 0);
// 3. Compute the placement and occupied area
pl_corner.x = placement.x + placement.w;
pl_corner.y = placement.y + placement.h;
// grow rect (wanted size) when widht or height are less than zero
bool adapt_x = rect.w <= 0;
bool adapt_y = rect.h <= 0;
if (adapt_x) rect.w = parent_bounds.w - parent_bounds.x - origin.x;
if (adapt_y) rect.h = parent_bounds.h - parent_bounds.y - origin.y;
// 2.1 apply style, css box model
// offset placement and area
child_placement = child_placement.off(origin.add(rect.position()));
child_occupied = child_occupied.off(origin.add(rect.position()));
if (style) {
Rect margin = ctx.style.margin;
Rect border = ctx.style.border;
Rect padding = ctx.style.padding;
placement.x += margin.x;
placement.y += margin.y;
if (rect.w != 0) { placement.w += border.x+border.w + padding.x+padding.w; }
if (rect.h != 0) { placement.h += border.y+border.h + padding.y+padding.h; }
pl_corner.x = placement.x + placement.w + margin.w;
pl_corner.y = placement.y + placement.h + margin.h;
}
// 3. Update the origins of the parent
// padding, grows both the placement and occupied area
child_placement = child_placement.grow(padding.position().add(padding.size()));
child_occupied = child_occupied.grow(padding.position().add(padding.size()));
// border, grows both the placement and occupied area
child_placement = child_placement.grow(border.position().add(border.size()));
child_occupied = child_occupied.grow(border.position().add(border.size()));
// margin, offsets the placement and grows the occupied area
child_placement = child_placement.off(margin.position());
child_occupied = child_occupied.grow(margin.position().add(margin.size()));
// oh yeah also adjust the rect if i was to grow
if (adapt_x) rect.w -= padding.x+padding.w + border.x+border.w + margin.x+margin.w;
if (adapt_y) rect.h -= padding.y+padding.h + border.y+border.h + margin.y+margin.h;
}
// set the size
child_placement = child_placement.grow(rect.size());
child_occupied = child_occupied.grow(rect.size());
// 4. Update the parent's origin
div.origin_r = Point{
.x = pl_corner.x,
.x = child_occupied.bottom_right().x,
.y = origin.y,
};
div.origin_c = Point{
.x = origin.x,
.y = pl_corner.y,
.y = child_occupied.bottom_right().y,
};
// 4. Calculate the "scrolled" view
Rect off;
Rect* cb = &div.children_bounds;
if (div.scroll_x.enabled && div.scroll_x.on) {
off.x = (short)((float)(div.pcb.w - parent.bounds.w) * div.scroll_x.value);
off.w = SCROLLBAR_DIM;
}
if (div.scroll_y.enabled && div.scroll_y.on) {
off.y = (short)((float)(div.pcb.h - parent.bounds.h) * div.scroll_y.value);
off.h = SCROLLBAR_DIM;
}
Rect view = {
.x = parent.bounds.x + off.x,
.y = parent.bounds.y + off.y,
.w = parent.bounds.w - off.w,
.h = parent.bounds.h - off.h,
};
// 5. check if the placement overflows the children ounds, if so update them
if (!point_in_rect(pl_corner, *cb)) {
if (pl_corner.y > cb.y+cb.h) {
cb.h = pl_corner.y - cb.y;
// 5. Update the parent's children bounds
if (!child_occupied.bottom_right().in_rect(div.children_bounds)) {
// right overflow
if (child_occupied.bottom_right().x > div.children_bounds.bottom_right().x) {
div.children_bounds.w += child_occupied.bottom_right().x - div.children_bounds.bottom_right().x;
}
if (pl_corner.x > cb.x+cb.w) {
cb.w += pl_corner.x - (cb.x + cb.w);
// left overflow
if (child_occupied.bottom_right().y > div.children_bounds.bottom_right().y) {
div.children_bounds.h += child_occupied.bottom_right().y - div.children_bounds.bottom_right().y;
}
}
// 6. check if the placement is inside the view
if (placement.collides(view)) {
return Rect{
.x = placement.x - off.x,
.y = placement.y - off.y,
.w = placement.w,
.h = placement.h,
};
// 99. return the placement
if (child_placement.collides(parent_view)) {
return child_placement.off(parent.get_view_off().neg());
} else {
return Rect{};
}
}

@ -0,0 +1,194 @@
module ugui;
// ---------------------------------------------------------------------------------- //
// RECTANGLE //
// ---------------------------------------------------------------------------------- //
// Rect and it's methods
struct Rect {
short x, y, w, h;
}
// return true if rect a contains b
macro bool Rect.contains(Rect a, Rect b)
{
return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h);
}
// returns the intersection of a and b
macro Rect Rect.intersection(Rect a, Rect b)
{
return Rect{
.x = (short)max(a.x, b.x),
.y = (short)max(a.y, b.y),
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
.h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y),
};
}
// returns true if the intersection not null
macro bool Rect.collides(Rect a, Rect b)
{
return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y);
}
// check for empty rect
macro bool Rect.is_null(Rect r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
// returns the element-wise addition of r1 and r2
macro Rect Rect.add(Rect r1, Rect r2)
{
return Rect{
.x = r1.x + r2.x,
.y = r1.y + r2.y,
.w = r1.w + r2.w,
.h = r1.h + r2.h,
};
}
// returns the element-wise subtraction of r1 and r2
macro Rect Rect.sub(Rect r1, Rect r2)
{
return Rect{
.x = r1.x - r2.x,
.y = r1.y - r2.y,
.w = r1.w - r2.w,
.h = r1.h - r2.h,
};
}
// returns the element-wise multiplication of r1 and r2
macro Rect Rect.mul(Rect r1, Rect r2)
{
return Rect{
.x = r1.x * r2.x,
.y = r1.y * r2.y,
.w = r1.w * r2.w,
.h = r1.h * r2.h,
};
}
macro Point Rect.position(Rect r)
{
return Point{
.x = r.x,
.y = r.y,
};
}
macro Point Rect.size(Rect r)
{
return Point{
.x = r.w,
.y = r.h,
};
}
macro Rect Rect.max(Rect a, Rect b)
{
return Rect{
.x = max(a.x, b.x),
.y = max(a.y, b.y),
.w = max(a.w, b.w),
.h = max(a.h, b.h),
};
}
macro Rect Rect.min(Rect a, Rect b)
{
return Rect{
.x = min(a.x, b.x),
.y = min(a.y, b.y),
.w = min(a.w, b.w),
.h = min(a.h, b.h),
};
}
// Offset a rect by a point
macro Rect Rect.off(Rect r, Point p)
{
return Rect{
.x = r.x + p.x,
.y = r.y + p.y,
.w = r.w,
.h = r.h,
};
}
// Resize a rect width and height
macro Rect Rect.grow(Rect r, Point p)
{
return Rect{
.x = r.x,
.y = r.y,
.w = r.w + p.x,
.h = r.h + p.y,
};
}
// Return the bottom-right corner of a rectangle
macro Point Rect.bottom_right(Rect r)
{
return Point{
.x = r.x + r.w,
.y = r.y + r.h,
};
}
// ---------------------------------------------------------------------------------- //
// POINT //
// ---------------------------------------------------------------------------------- //
struct Point {
short x, y;
}
// returns true if a point is inside the rectangle
macro bool Point.in_rect(Point p, Rect r)
{
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
}
macro Point Point.add(Point a, Point b)
{
return Point{
.x = a.x + b.x,
.y = a.y + b.y,
};
}
macro Point Point.sub(Point a, Point b)
{
return Point{
.x = a.x - b.x,
.y = a.y - b.y,
};
}
macro Point Point.neg(Point p) => Point{-p.x, -p.y};
macro Point Point.max(Point a, Point b)
{
return Point{
.x = max(a.x, b.x),
.y = max(a.y, b.y),
};
}
macro Point Point.min(Point a, Point b)
{
return Point{
.x = min(a.x, b.x),
.y = min(a.y, b.y),
};
}
// ---------------------------------------------------------------------------------- //
// COLOR //
// ---------------------------------------------------------------------------------- //
struct Color{
char r, g, b, a;
}
Loading…
Cancel
Save