diff --git a/src/main.c3 b/src/main.c3 index b64da0b..ed5d5b4 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -6,14 +6,15 @@ import rl; fn int main(String[] args) { - ugui::Ctx ctx; - ctx.init()!!; + ugui::Ctx ui; + ui.init()!!; short width = 800; short height = 450; rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE); rl::init_window(width, height, "Ugui Test"); - ctx.input_window_size(width, height)!!; + ui.input_window_size(width, height)!!; + rl::set_target_fps(30); isz frame; @@ -27,61 +28,68 @@ fn int main(String[] args) if (rl::is_window_resized()) { width = (short)rl::get_screen_width(); height = (short)rl::get_screen_height(); - ctx.input_window_size(width, height)!!; + ui.input_window_size(width, height)!!; } - ctx.input_changefocus(rl::is_window_focused()); + ui.input_changefocus(rl::is_window_focused()); - // FIXME: In raylib it doesn't seem to be a quick way to check if - // a mouse input event was received, so for now just use - // the delta information - rl::Vector2 mousedelta = rl::get_mouse_delta(); - if (mousedelta.x || mousedelta.y) { - ctx.input_mouse_delta((short)mousedelta.x, (short)mousedelta.y); - } + rl::Vector2 mpos = rl::get_mouse_position(); + ui.input_mouse_abs((short)mpos.x, (short)mpos.y); ugui::MouseButtons buttons; buttons.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT); buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT); buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE); - ctx.input_mouse_button(buttons); + ui.input_mouse_button(buttons); /* End Input Handling */ /* Start UI Handling */ - ctx.frame_begin()!!; + ui.frame_begin()!!; +/* // main div, fill the whole window - ctx.div_begin("main", ugui::Rect{.w=ctx.width/2})!!; + ui.div_begin("main", ugui::Rect{.w=ui.width/2})!!; {| - ctx.layout_set_row()!!; - if (ctx.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) { + ui.layout_set_row()!!; + if (ui.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) { io::printn("press button0"); } - //ctx.layout_next_column()!!; - if (ctx.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { + //ui.layout_next_column()!!; + if (ui.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { io::printn("press button1"); } - //ctx.layout_next_column()!!; - if (ctx.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { + //ui.layout_next_column()!!; + if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { io::printn("release button2"); } - if (ctx.slider_ver("slider", ugui::Rect{0,0,30,100})!!.update) { - ugui::Elem* e = ctx.get_elem_by_label("slider")!!; + 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); } |}; - ctx.div_end()!!; - - ctx.div_begin("second", ugui::DIV_FILL)!!; + ui.div_end()!!; +*/ + ui.div_begin("second", ugui::DIV_FILL)!!; + ugui::Elem* de = ui.get_elem_by_label("second")!!; + de.div.scroll.can_y = true; {| - if (ctx.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) { - ugui::Elem* e = ctx.get_elem_by_label("slider_other")!!; + 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); } + ui.button("button10", ugui::Rect{0,0,50,50})!!; + ui.button("button11", ugui::Rect{0,0,50,50})!!; + ui.button("button12", ugui::Rect{0,0,50,50})!!; + ui.button("button13", ugui::Rect{0,0,50,50})!!; + ui.button("button14", ugui::Rect{0,0,50,50})!!; + ui.button("button15", ugui::Rect{0,0,50,50})!!; + ui.button("button16", ugui::Rect{0,0,50,50})!!; + ui.button("button17", ugui::Rect{0,0,50,50})!!; |}; - ctx.div_end()!!; + ui.div_end()!!; - ctx.frame_end()!!; + ui.frame_end()!!; /* End UI Handling */ /* Start UI Drawing */ @@ -89,7 +97,7 @@ fn int main(String[] args) // ClearBackground(BLACK); rl::Color c; - for (Cmd* cmd; (cmd = ctx.cmd_queue.dequeue() ?? null) != null;) { + for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { switch (cmd.type) { case ugui::CmdType.CMD_RECT: c = rl::Color{ @@ -111,13 +119,11 @@ fn int main(String[] args) } rl::end_drawing(); - - // TODO: throttle FPS } rl::close_window(); - ctx.free(); + ui.free(); return 0; } diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index d601813..8d7aac4 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -1,5 +1,7 @@ module ugui; +import std::io; + // draw a button, return the events on that button fn ElemEvents! Ctx.button(&ctx, String label, Rect size) { diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 34bbb9a..7fd9c90 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -1,9 +1,13 @@ module ugui; +import std::io; +import std::core::string; + import vtree; import cache; import fifo; + struct Rect { short x, y, w, h; } @@ -52,10 +56,14 @@ enum DivLayout { // div element struct Div { DivLayout layout; - bool can_scroll_x; - bool can_scroll_y; - isz vertical_scroll_bar; - isz horizontal_scroll_bar; + struct scroll { + bool can_x; + bool can_y; + bool on_x; + bool on_y; + float value_x; + float value_y; + } Rect children_bounds; Point origin_r, origin_c; Color color_bg; @@ -201,21 +209,21 @@ macro point_in_rect(Point p, Rect r) // 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); + 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), - }; + 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); + 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); } diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 1ecb386..45d8387 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -1,5 +1,6 @@ module ugui; +import std::io; fn void! Ctx.div_begin(&ctx, String label, Rect size) { @@ -29,18 +30,18 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) .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; + c_elem.div.color_bg = uint_to_rgba(0xff0000ff); + c_elem.div.scroll.can_x = false; + c_elem.div.scroll.can_y = false; + c_elem.div.scroll.value_x = 0; + c_elem.div.scroll.value_y = 0; } // Add the background to the draw stack Cmd cmd = { - .type = CMD_RECT, - .rect = { + .type = CMD_RECT, + .rect = { .rect = c_elem.bounds, .color = c_elem.div.color_bg, }, @@ -50,7 +51,66 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) // TODO: check active // TODO: check resizeable - // TODO: check scrollbars + // check and draw scroll bars + if (!rect_contains(c_elem.bounds, 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, + }; + Point bc = { + .x = c_elem.bounds.x + c_elem.bounds.w, + .y = c_elem.bounds.y + c_elem.bounds.h, + }; + // 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; + } + + Cmd scrl = { + .type = CMD_RECT, + .rect.rect = vslider, + .rect.color = uint_to_rgba(0x999999ff), + }; + ctx.cmd_queue.enqueue(&scrl)!; + + scrl.rect.rect = vhandle; + scrl.rect.color = uint_to_rgba(0x9999ffff); + ctx.cmd_queue.enqueue(&scrl)!; + + } else { + c_elem.div.scroll.on_y = false; + } + } + // 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; diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index c916b40..17a2b04 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -79,20 +79,24 @@ fn void! Ctx.frame_begin(&ctx) 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 = { - .w = ctx.width, - .h = ctx.height, - }, - .div = { - .layout = LAYOUT_ROW, - }, - .flags = c_elem.flags, - }; - - *c_elem = def_root; + 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, + } + }, + .flags = c_elem.flags, + }; + + *c_elem = def_root; } // 3. Push the root element into the element tree diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index a8e47e5..aec6b1e 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -78,17 +78,22 @@ fn void! Ctx.layout_next_column(&ctx) // parent: parent div // rect: the requested size // style: apply style +<* +@require ctx != null +@require parent.type == ETYPE_DIV +*> fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) { Rect placement; Point origin; + Div* div = &parent.div; // 1. Select the right origin - switch (parent.div.layout) { + switch (div.layout) { case LAYOUT_ROW: - origin = parent.div.origin_r; + origin = div.origin_r; case LAYOUT_COLUMN: - origin = parent.div.origin_c; + origin = div.origin_c; case LAYOUT_FLOATING: // none default: // Error @@ -100,8 +105,8 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) // 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); + 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); pl_corner.x = placement.x + placement.w; pl_corner.y = placement.y + placement.h; @@ -122,28 +127,23 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) } // 3. Update the origins of the parent - parent.div.origin_r = Point{ + div.origin_r = Point{ .x = pl_corner.x, .y = origin.y, }; - parent.div.origin_c = Point{ + div.origin_c = Point{ .x = origin.x, .y = pl_corner.y, }; // 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); + Rect* cb = &div.children_bounds; + if (div.scroll.can_x && div.scroll.on_x) { + off.x = (short)(cb.w * div.scroll.value_x); } - 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); + if (div.scroll.can_y && div.scroll.on_y) { + off.y = (short)((float)(cb.h - parent.bounds.h) * div.scroll.value_y); } Rect view = { .x = parent.bounds.x + off.x, @@ -152,7 +152,26 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) .h = parent.bounds.h, }; - // TODO: 5. check if the placement is inside the view + // 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; + } + if (pl_corner.x > cb.x+cb.w) { + cb.w += pl_corner.x - (cb.x + cb.w); + } + } - return placement; + // 6. check if the placement is inside the view + if (rect_collision(placement, view)) { + return Rect{ + .x = placement.x - off.x, + .y = placement.y - off.y, + .w = placement.w, + .h = placement.h, + }; + } else { + return Rect{}; + } } + diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index c88e002..a32524a 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -1,5 +1,7 @@ module ugui; +import std::io; + /* handle * +----+-----+---------------------+ * | |#####| | @@ -79,7 +81,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) */ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) { - Id id = hash(label); + Id id = hash(label); Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!;