Compare commits

..

3 Commits

  1. 19
      src/main.c3
  2. 4
      src/ugui_button.c3
  3. 29
      src/ugui_data.c3
  4. 71
      src/ugui_div.c3
  5. 70
      src/ugui_impl.c3
  6. 111
      src/ugui_layout.c3
  7. 32
      src/ugui_slider.c3

@ -51,17 +51,17 @@ fn int main(String[] args)
ctx.frame_begin()!!;
// main div, fill the whole window
ctx.div_begin("main", ugui::DIV_FILL)!!;
ctx.div_begin("main", ugui::Rect{.w=ctx.width/2})!!;
{|
ctx.layout_set_column()!!;
if (ctx.button("button0", ugui::Rect{100,100,30,30})!!.mouse_press) {
ctx.layout_set_row()!!;
if (ctx.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) {
io::printn("press button0");
}
ctx.layout_next_column()!!;
//ctx.layout_next_column()!!;
if (ctx.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) {
io::printn("press button1");
}
ctx.layout_next_column()!!;
//ctx.layout_next_column()!!;
if (ctx.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) {
io::printn("release button2");
}
@ -72,6 +72,15 @@ fn int main(String[] args)
|};
ctx.div_end()!!;
ctx.div_begin("second", ugui::DIV_FILL)!!;
{|
if (ctx.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) {
ugui::Elem* e = ctx.get_elem_by_label("slider_other")!!;
io::printfn("other slider: %f", e.slider.value);
}
|};
ctx.div_end()!!;
ctx.frame_end()!!;
/* End UI Handling */

@ -18,7 +18,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size)
// 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.rect = ctx.position_element(parent, size, true);
c_elem.bounds = ctx.position_element(parent, size, true);
// TODO: 3. Fill the button specific fields
}
@ -35,7 +35,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size)
Cmd cmd = {
.type = CMD_RECT,
.rect = {
.rect = c_elem.rect,
.rect = c_elem.bounds,
.color = bg_color,
},
};

@ -52,6 +52,11 @@ enum DivLayout {
// div element
struct Div {
DivLayout layout;
bool can_scroll_x;
bool can_scroll_y;
isz vertical_scroll_bar;
isz horizontal_scroll_bar;
Rect children_bounds;
Point origin_r, origin_c;
Color color_bg;
}
@ -67,7 +72,7 @@ struct Elem {
Id id;
ElemFlags flags;
ElemEvents events;
Rect rect;
Rect bounds;
ElemType type;
union {
Div div;
@ -192,3 +197,25 @@ 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 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_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 rect_collision(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);
}

@ -0,0 +1,71 @@
module ugui;
fn void! Ctx.div_begin(&ctx, String label, Rect size)
{
Id id = hash(label);
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;
// 1. Fill the element fields
c_elem.type = ETYPE_DIV;
// do layout and update flags only if the element was updated
if (c_elem.flags.is_new || parent.flags.updated) {
// 2. layout the element
c_elem.bounds = ctx.position_element(parent, size);
if (c_elem.flags.is_new) {
c_elem.div.children_bounds = c_elem.bounds;
}
// 3. Mark the element as updated
c_elem.flags.updated = true;
// 4. Fill the div fields
c_elem.div.layout = parent.div.layout;
c_elem.div.origin_c = Point{
.x = c_elem.bounds.x,
.y = c_elem.bounds.y,
};
c_elem.div.color_bg = uint_to_rgba(0xff0000ff);
c_elem.div.origin_r = c_elem.div.origin_c;
c_elem.div.can_scroll_x = false;
c_elem.div.can_scroll_y = false;
c_elem.div.vertical_scroll_bar = -1;
c_elem.div.horizontal_scroll_bar = -1;
}
// Add the background to the draw stack
Cmd cmd = {
.type = CMD_RECT,
.rect = {
.rect = c_elem.bounds,
.color = c_elem.div.color_bg,
},
};
ctx.cmd_queue.enqueue(&cmd)!;
// TODO: check active
// TODO: check resizeable
// TODO: check scrollbars
// 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 (parent.flags.has_focus) {
if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) {
c_elem.flags.has_focus = true;
}
}
}
fn void! Ctx.div_end(&ctx)
{
// the active_div returns to the parent of the current one
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
}

@ -32,6 +32,12 @@ macro Ctx.get_elem_by_label(&ctx, String label)
return ctx.cache.search(id);
}
macro Ctx.get_elem_by_tree_idx(&ctx, isz idx) @private
{
Id id = ctx.tree.get(ctx.active_div)!;
return ctx.cache.search(id);
}
fn void! Ctx.init(&ctx)
{
ctx.tree.init(MAX_ELEMENTS)!;
@ -74,14 +80,14 @@ fn void! Ctx.frame_begin(&ctx)
if (c_elem.flags.is_new || c_elem.flags.updated) {
Elem def_root = {
.id = ROOT_ID,
.type = ETYPE_DIV,
.rect = {
.id = ROOT_ID,
.type = ETYPE_DIV,
.bounds = {
.w = ctx.width,
.h = ctx.height,
},
.div = {
.layout = LAYOUT_ROW,
.layout = LAYOUT_ROW,
},
.flags = c_elem.flags,
};
@ -120,64 +126,10 @@ $if 1:
$endif
}
fn void! Ctx.div_begin(&ctx, String label, Rect size)
{
Id id = hash(label);
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;
// 1. Fill the element fields
c_elem.type = ETYPE_DIV;
// do layout and update flags only if the element was updated
if (c_elem.flags.is_new || parent.flags.updated) {
// 2. layout the element
c_elem.rect = ctx.position_element(parent, size);
// 3. Mark the element as updated
c_elem.flags.updated = true;
// 4. Fill the div fields
c_elem.div.layout = parent.div.layout;
c_elem.div.origin_c = Point{
.x = c_elem.rect.x,
.y = c_elem.rect.y,
};
c_elem.div.color_bg = uint_to_rgba(0xff0000ff);
c_elem.div.origin_r = c_elem.div.origin_c;
} else if (parent.flags.has_focus) {
if (point_in_rect(ctx.input.mouse.pos, c_elem.rect)) {
c_elem.flags.has_focus = true;
}
} else {
// TODO: check active
// TODO: check scrollbars
// TODO: check resizeable
}
// Add the background to the draw stack
Cmd cmd = {
.type = CMD_RECT,
.rect = {
.rect = c_elem.rect,
.color = c_elem.div.color_bg,
},
};
ctx.cmd_queue.enqueue(&cmd)!;
}
fn void! Ctx.div_end(&ctx)
{
// the active_div returns to the parent of the current one
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
}
/**
* @ensure elem != null
**/
fn bool Ctx.is_hovered(&ctx, Elem *elem)
{
return point_in_rect(ctx.input.mouse.pos, elem.rect);
return point_in_rect(ctx.input.mouse.pos, elem.bounds);
}

@ -51,7 +51,7 @@ fn void! Ctx.layout_next_row(&ctx)
}
parent.div.origin_r = Point{
.x = parent.rect.x,
.x = parent.bounds.x,
.y = parent.div.origin_c.y,
};
parent.div.origin_c = parent.div.origin_r;
@ -69,15 +69,18 @@ fn void! Ctx.layout_next_column(&ctx)
parent.div.origin_c = Point{
.x = parent.div.origin_r.x,
.y = parent.rect.y,
.y = parent.bounds.y,
};
parent.div.origin_r = parent.div.origin_c;
}
// position the rectangle inside the parent according to the layout
// parent: parent div
// rect: the requested size
// style: apply style
fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
{
Rect elem_rect;
Rect placement;
Point origin;
// 1. Select the right origin
@ -91,63 +94,65 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
// Error
}
// 2. Position the rect
elem_rect.x = origin.x + rect.x;
elem_rect.y = origin.y + rect.y;
// the bottom-right border of the element box
Point pl_corner;
// 3. Calculate width & height
// TODO: what about negative values?
// FIXME: account for origin offset!!
elem_rect.w = rect.w > 0 ? rect.w : parent.rect.w;
elem_rect.h = rect.h > 0 ? rect.h : parent.rect.h;
// 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 : parent.bounds.w - (placement.x - parent.bounds.x);
placement.h = rect.h > 0 ? rect.h : parent.bounds.h - (placement.y - parent.bounds.y);
// 4. Update the origins of the parent
pl_corner.x = placement.x + placement.w;
pl_corner.y = placement.y + placement.h;
// 2.1 apply style, css box model
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 += margin.x+margin.w + padding.x+padding.w; }
if (rect.h != 0) { placement.h += margin.y+margin.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
parent.div.origin_r = Point{
.x = elem_rect.x + elem_rect.w,
.y = elem_rect.y,
.x = pl_corner.x,
.y = origin.y,
};
parent.div.origin_c = Point{
.x = elem_rect.x,
.y = elem_rect.y + elem_rect.h,
.x = origin.x,
.y = pl_corner.y,
};
// if using the style then apply margins
// FIXME: this does not work
if (style && parent.div.layout != LAYOUT_FLOATING) {
elem_rect.x += ctx.style.margin.x;
elem_rect.y += ctx.style.margin.y;
// total keep-out borders
Rect margin_tot = {
.x = ctx.style.padding.x + ctx.style.border.x +
ctx.style.margin.x,
.y = ctx.style.padding.y + ctx.style.border.y +
ctx.style.margin.y,
.w = ctx.style.padding.w + ctx.style.border.x +
ctx.style.margin.w,
.h = ctx.style.padding.h + ctx.style.border.x +
ctx.style.margin.h,
};
parent.div.origin_r.x += margin_tot.x + margin_tot.w;
// parent.div.origin_r.y += margin_tot.h;
// parent.div.origin_c.x += margin_tot.w;
parent.div.origin_c.y += margin_tot.y + margin_tot.h;
// 4. Calculate the "scrolled" view
Point off;
if (parent.div.can_scroll_x && parent.div.horizontal_scroll_bar != -1) {
Elem*! sx = ctx.get_elem_by_tree_idx(parent.div.horizontal_scroll_bar);
if (catch sx) { return Rect{}; }
// TODO: assert that the element is a slider
off.x = (short)(parent.div.children_bounds.w * sx.slider.value);
}
if (parent.div.can_scroll_y && parent.div.vertical_scroll_bar != -1) {
Elem*! sy = ctx.get_elem_by_tree_idx(parent.div.vertical_scroll_bar);
if (catch sy) { return Rect{}; }
// TODO: assert that the element is a slider
off.y = (short)(parent.div.children_bounds.h * sy.slider.value);
}
Rect view = {
.x = parent.bounds.x + off.x,
.y = parent.bounds.y + off.y,
.w = parent.bounds.w,
.h = parent.bounds.h,
};
// TODO: 5. check if the placement is inside the view
/*
printf(
"positioning rect: %lx {%d %d %d %d}(%d %d %d %d) . {%d %d %d
%d}\n", parent.id, rect.x, rect.y, rect.w, rect.h, parent.rect.x,
parent.rect.y,
parent.rect.w,
parent.rect.h,
elem_rect.x,
elem_rect.y,
elem_rect.w,
elem_rect.h
);
*/
return elem_rect;
return placement;
}

@ -20,20 +20,20 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size)
// 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.rect = ctx.position_element(parent, size, true);
c_elem.bounds = ctx.position_element(parent, size, true);
c_elem.slider.handle = Rect{
.x = (short)(c_elem.rect.x + (int)(c_elem.rect.w * c_elem.slider.value)),
.y = c_elem.rect.y,
.w = (short)(c_elem.rect.w * 0.25),
.h = c_elem.rect.h,
.x = (short)(c_elem.bounds.x + (int)(c_elem.bounds.w * c_elem.slider.value)),
.y = c_elem.bounds.y,
.w = (short)(c_elem.bounds.w * 0.25),
.h = c_elem.bounds.h,
};
}
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.rect.x, c_elem.rect.x + c_elem.rect.w - c_elem.slider.handle.w);
float v = (float)(c_elem.slider.handle.x-c_elem.rect.x) / (float)(c_elem.rect.w-c_elem.slider.handle.w);
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;
@ -47,7 +47,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size)
Cmd cmd = {
.type = CMD_RECT,
.rect = {
.rect = c_elem.rect,
.rect = c_elem.bounds,
.color = bg_color,
},
};
@ -92,20 +92,20 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size)
// 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.rect = ctx.position_element(parent, size, true);
c_elem.bounds = ctx.position_element(parent, size, true);
c_elem.slider.handle = Rect{
.x = c_elem.rect.x,
.y = (short)(c_elem.rect.y + (int)(c_elem.rect.h * c_elem.slider.value)),
.w = c_elem.rect.w,
.h = (short)(c_elem.rect.h * 0.25),
.x = c_elem.bounds.x,
.y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h * c_elem.slider.value)),
.w = c_elem.bounds.w,
.h = (short)(c_elem.bounds.h * 0.25),
};
}
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.rect.y, c_elem.rect.y + c_elem.rect.h - c_elem.slider.handle.h);
float v = (float)(c_elem.slider.handle.y-c_elem.rect.y) / (float)(c_elem.rect.h-c_elem.slider.handle.h);
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;
@ -119,7 +119,7 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size)
Cmd cmd = {
.type = CMD_RECT,
.rect = {
.rect = c_elem.rect,
.rect = c_elem.bounds,
.color = bg_color,
},
};

Loading…
Cancel
Save