diff --git a/TODO b/TODO index 556b6f5..6252fdc 100644 --- a/TODO +++ b/TODO @@ -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 diff --git a/src/main.c3 b/src/main.c3 index be5c2f3..7a88495 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -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))!!; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index e605c91..7afd4d1 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -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); diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 09c5348..1d4efd4 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -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) diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 1d02bdc..3f32e73 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -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 diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 095b41a..01c03bc 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -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 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; + // 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()); - // 3. Update the origins of the parent + // 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{}; } } - diff --git a/src/ugui_shapes.c3 b/src/ugui_shapes.c3 new file mode 100644 index 0000000..4b45377 --- /dev/null +++ b/src/ugui_shapes.c3 @@ -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; +}