semi-working vertical slider

font_atlas
Alessandro Mauri 1 month ago
parent 763e9ba8d6
commit 73bc933eb5
  1. 74
      src/main.c3
  2. 2
      src/ugui_button.c3
  3. 32
      src/ugui_data.c3
  4. 76
      src/ugui_div.c3
  5. 32
      src/ugui_impl.c3
  6. 57
      src/ugui_layout.c3
  7. 4
      src/ugui_slider.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;
}

@ -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)
{

@ -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);
}

@ -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;

@ -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

@ -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{};
}
}

@ -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)!;

Loading…
Cancel
Save