Compare commits

...

6 Commits

11 changed files with 156 additions and 140 deletions

41
TODO
View File

@ -12,10 +12,10 @@
to maintain focus until mouse release (fix scroll bars) to maintain focus until mouse release (fix scroll bars)
[x] Clip element bounds to parent div, specifically text [x] Clip element bounds to parent div, specifically text
[ ] Resizeable divs [ ] Resizeable divs
[ ] Implement a z index and sort command buffer based on that [x] Implement a z index and sort command buffer based on that
[ ] Ctx.set_z_index() [ ] Ctx.set_z_index()
[ ] Sort command buffer on insertion [x] Sort command buffer on insertion
[ ] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it [x] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it
[x] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division [x] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division
[ ] Animations, somehow [ ] Animations, somehow
[ ] Maybe cache codepoint converted strings [ ] Maybe cache codepoint converted strings
@ -30,22 +30,27 @@ to maintain focus until mouse release (fix scroll bars)
[ ] gif support? [ ] gif support?
[ ] layout_set_max_rows() and layout_set_max_columns() [ ] layout_set_max_rows() and layout_set_max_columns()
[x] Maybe SDF sprites?? [x] Maybe SDF sprites??
[ ] Stylesheets and stylesheet import [x] Stylesheets and stylesheet import
[ ] use SDF to draw anti-aliased rounded rectangles https://zed.dev/blog/videogame [x] use SDF to draw anti-aliased rounded rectangles https://zed.dev/blog/videogame
[ ] Subdivide modules into ugui::ug for exported functions and ugui::core for [ ] Subdivide modules into ugui::ug for exported functions and ugui::core for
internal use functions (used to create widgets) internal use functions (used to create widgets)
[ ] The render loop RAPES the gpu, valve pls fix [x] The render loop RAPES the gpu, valve pls fix
[ ] The way the element structures are implemented wastes a lot of memory since [ ] The way the element structures are implemented wastes a lot of memory since
each struct Elem, struct Cmd, etc. is as big as the largest element. It would each struct Elem, struct Cmd, etc. is as big as the largest element. It would
be better to use a different allcation strategy. be better to use a different allcation strategy.
[ ] Add a way to handle time events like double clicks [ ] Add a way to handle time events like double clicks
[ ] Border and padding do not go well together if the library issues two rect commands, the visible
border is effectively the border size plus the padding since there is a gap between the border
rect and the internal rect. A better solution is to leave it up to the renderer to draw the rect
correctly
## Layout ## Layout
[ ] Text reflow
[x] Flexbox [x] Flexbox
[ ] For some reason padding is not correct, look at the sliders, they have 2px per side when the
theme specifies 4px per side
[ ] Center elements to the row/column [ ] Center elements to the row/column
[ ] Text wrapping [ ] Text wrapping / reflow
[ ] Consider a multi-pass recursive approach to layout (like https://github.com/nicbarker/clay) [ ] Consider a multi-pass recursive approach to layout (like https://github.com/nicbarker/clay)
instead of the curren multi-frame approach. instead of the curren multi-frame approach.
@ -78,16 +83,11 @@ to maintain focus until mouse release (fix scroll bars)
[x] Fix the missing alpha channel [x] Fix the missing alpha channel
[x] Fix the alignment [x] Fix the alignment
## Raylib
[x] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui::
[x] Implement pixel radius rounding for border radius
## Widgets ## Widgets
[x] Dynamic text box to implement an fps counter [x] Dynamic text box to implement an fps counter
[x] Button with label [x] Button with label
[ ] Text Input box [x] Text Input box
[ ] Icon Buttons [ ] Icon Buttons
[x] Switch [x] Switch
[x] Checkbox [x] Checkbox
@ -111,9 +111,10 @@ to maintain focus until mouse release (fix scroll bars)
## SDL3 Renderer ## SDL3 Renderer
- smart batching [x] smart batching
- maybe use instancing since we are always drawing the same geometry. With instancing every [x] maybe use instancing since we are always drawing the same geometry. With instancing every
different quad could have its coulour, border and radius with much better performance than different quad could have its coulour, border and radius with much better performance than
issuing a draw call for every quad (and uploading it) issuing a draw call for every quad (and uploading it)
https://rastertek.com/dx11win10tut48.html https://rastertek.com/dx11win10tut48.html
https://www.braynzarsoft.net/viewtutorial/q16390-33-instancing-with-indexed-primitives https://www.braynzarsoft.net/viewtutorial/q16390-33-instancing-with-indexed-primitives
[ ] implement min and max fps

View File

@ -147,7 +147,8 @@ fn void? Ctx.checkbox_id(&ctx, Id id, String description, Point off, bool* activ
Rect check = elem.bounds.add({x, x, -x*2, -x*2}); Rect check = elem.bounds.add({x, x, -x*2, -x*2});
Style s = *style; Style s = *style;
s.bg = s.primary; s.bg = s.primary;
s.margin = s.border = s.padding = {}; s.margin = s.padding = {};
s.border = 0;
ctx.push_rect(check, parent.div.z_index, &s)!; ctx.push_rect(check, parent.div.z_index, &s)!;
} }
} }
@ -180,6 +181,7 @@ fn void? Ctx.toggle_id(&ctx, Id id, String description, Point off, bool* active)
Rect t = elem.bounds.add({*active ? (style.size+3) : +3, +3, -style.size-6, -6}); Rect t = elem.bounds.add({*active ? (style.size+3) : +3, +3, -style.size-6, -6});
Style s = *style; Style s = *style;
s.bg = s.primary; s.bg = s.primary;
s.margin = s.border = s.padding = {}; s.margin = s.padding = {};
s.border = 0;
ctx.push_rect(t, parent.div.z_index, &s)!; ctx.push_rect(t, parent.div.z_index, &s)!;
} }

View File

@ -14,6 +14,7 @@ enum CmdType {
// command to draw a rect // command to draw a rect
struct CmdRect { struct CmdRect {
Rect rect; Rect rect;
ushort thickness;
ushort radius; ushort radius;
Color color; Color color;
} }
@ -92,18 +93,19 @@ fn void? Ctx.push_scissor(&ctx, Rect rect, int z_index)
fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style) fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
{ {
Rect border = style.border;
Rect padding = style.padding; Rect padding = style.padding;
ushort border = style.border;
ushort radius = style.radius; ushort radius = style.radius;
Color bg = style.bg; Color bg = style.bg;
Color border_color = style.secondary; Color border_color = style.secondary;
if (!border.is_null()) { if (border != 0) {
Cmd cmd = { Cmd cmd = {
.type = CMD_RECT, .type = CMD_RECT,
.rect.rect = rect, .rect.rect = rect,
.rect.color = border_color, .rect.color = border_color,
.rect.radius = radius, .rect.radius = radius+border,
.rect.thickness = border,
}; };
ctx.push_cmd(&cmd, z_index)!; ctx.push_cmd(&cmd, z_index)!;
} }
@ -111,13 +113,14 @@ fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
Cmd cmd = { Cmd cmd = {
.type = CMD_RECT, .type = CMD_RECT,
.rect.rect = { .rect.rect = {
.x = rect.x + border.x + padding.x, .x = rect.x + border + padding.x,
.y = rect.y + border.y + padding.y, .y = rect.y + border + padding.y,
.w = rect.w - (border.x+border.w) - (padding.x+padding.w), .h = rect.h - (border*2) - (padding.y+padding.h),
.h = rect.h - (border.y+border.h) - (padding.y+padding.h), .w = rect.w - (border*2) - (padding.x+padding.w),
}, },
.rect.color = bg, .rect.color = bg,
.rect.radius = radius, .rect.radius = radius,
.rect.thickness = max(rect.w, rect.h)/2+1,
}; };
if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return; if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return;
ctx.push_cmd(&cmd, z_index)!; ctx.push_cmd(&cmd, z_index)!;

View File

@ -144,22 +144,22 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, Style* style)
child_occupied = child_occupied.off(origin.add(rect.position())); child_occupied = child_occupied.off(origin.add(rect.position()));
Rect margin = style.margin; Rect margin = style.margin;
Rect border = style.border;
Rect padding = style.padding; Rect padding = style.padding;
ushort border = style.border;
// padding, grows both the placement and occupied area // padding, grows both the placement and occupied area
child_placement = child_placement.grow(padding.position().add(padding.size())); child_placement = child_placement.grow(padding.position().add(padding.size()));
child_occupied = child_occupied.grow(padding.position().add(padding.size())); child_occupied = child_occupied.grow(padding.position().add(padding.size()));
// border, grows both the placement and occupied area // border, grows both the placement and occupied area
child_placement = child_placement.grow(border.position().add(border.size())); child_placement = child_placement.grow({border*2, border*2});
child_occupied = child_occupied.grow(border.position().add(border.size())); child_occupied = child_occupied.grow({border*2, border*2});
// margin, offsets the placement and grows the occupied area // margin, offsets the placement and grows the occupied area
child_placement = child_placement.off(margin.position()); child_placement = child_placement.off(margin.position());
child_occupied = child_occupied.grow(margin.position().add(margin.size())); child_occupied = child_occupied.grow(margin.position().add(margin.size()));
// oh yeah also adjust the rect if i was to grow // 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_x) rect.w -= padding.x+padding.w + border*2 + margin.x+margin.w;
if (adapt_y) rect.h -= padding.y+padding.h + border.y+border.h + margin.y+margin.h; if (adapt_y) rect.h -= padding.y+padding.h + border*2 + margin.y+margin.h;
// set the size // set the size
child_placement = child_placement.grow(rect.size()); child_placement = child_placement.grow(rect.size());

View File

@ -42,7 +42,7 @@ fn ElemEvents? Ctx.slider_hor_id(&ctx, Id id, Rect size, float* value, float hpe
Point m = ctx.input.mouse.pos; Point m = ctx.input.mouse.pos;
elem.events = ctx.get_elem_events(elem); elem.events = ctx.get_elem_events(elem);
if (ctx.elem_focus(elem) && ctx.is_mouse_down(BTN_LEFT)) { if (ctx.elem_focus(elem) && ctx.is_mouse_down(BTN_LEFT)) {
*value = calc_value(elem.bounds.x, m.x, elem.bounds.w, hw); *value = calc_value(elem.bounds.x, m.x, elem.bounds.w, hw);
elem.slider.handle.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value); elem.slider.handle.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value);
@ -56,6 +56,7 @@ fn ElemEvents? Ctx.slider_hor_id(&ctx, Id id, Rect size, float* value, float hpe
ctx.push_rect(elem.bounds, parent.div.z_index, &s)!; ctx.push_rect(elem.bounds, parent.div.z_index, &s)!;
s.bg = s.primary; s.bg = s.primary;
s.padding = padding; s.padding = padding;
s.border = {};
ctx.push_rect(elem.slider.handle, parent.div.z_index, &s)!; ctx.push_rect(elem.slider.handle, parent.div.z_index, &s)!;
return elem.events; return elem.events;
@ -120,6 +121,7 @@ fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Rect size, float* value, float hpe
ctx.push_rect(elem.bounds, parent.div.z_index, &s)!; ctx.push_rect(elem.bounds, parent.div.z_index, &s)!;
s.bg = s.primary; s.bg = s.primary;
s.padding = padding; s.padding = padding;
s.border = {};
ctx.push_rect(elem.slider.handle, parent.div.z_index, &s)!; ctx.push_rect(elem.slider.handle, parent.div.z_index, &s)!;
return elem.events; return elem.events;

View File

@ -7,8 +7,10 @@ import std::io;
// global style, similar to the css box model // global style, similar to the css box model
struct Style { // css box model struct Style { // css box model
Rect padding; Rect padding;
Rect border;
Rect margin; Rect margin;
ushort border;
ushort radius;
ushort size;
Color bg; // background color Color bg; // background color
Color fg; // foreground color Color fg; // foreground color
@ -16,14 +18,12 @@ struct Style { // css box model
Color secondary; // secondary color Color secondary; // secondary color
Color accent; // accent color Color accent; // accent color
ushort radius;
short size;
} }
const Style DEFAULT_STYLE = { const Style DEFAULT_STYLE = {
.margin = {2, 2, 2, 2}, .margin = {2, 2, 2, 2},
.border = {2, 2, 2, 2}, .padding = {},
.padding = {1, 1, 1, 1}, .border = 2,
.radius = 12, .radius = 12,
.size = 16, .size = 16,
@ -89,8 +89,8 @@ fn int Ctx.import_style_from_file(&ctx, String path)
* Style can be serialized and deserialized with a subset of CSS * Style can be serialized and deserialized with a subset of CSS
* <style name> { * <style name> {
* padding: left right top bottom; * padding: left right top bottom;
* border: left right top bottom;
* margin: left right top bottoms; * margin: left right top bottoms;
* border: uint;
* radius: uint; * radius: uint;
* size: uint; * size: uint;
* Color: #RRGGBBAA; * Color: #RRGGBBAA;
@ -331,11 +331,6 @@ fn bool Parser.parse_property(&p)
if (p.parse_size(&padding) == false) return false; if (p.parse_size(&padding) == false) return false;
p.style.padding = padding; p.style.padding = padding;
case "border":
Rect border;
if (p.parse_size(&border) == false) return false;
p.style.border = border;
case "margin": case "margin":
Rect margin; Rect margin;
if (p.parse_size(&margin) == false) return false; if (p.parse_size(&margin) == false) return false;
@ -366,23 +361,32 @@ fn bool Parser.parse_property(&p)
if (p.parse_color(&accent) == false) return false; if (p.parse_color(&accent) == false) return false;
p.style.accent = accent; p.style.accent = accent;
case "radius": case "border":
short r; short border;
if (p.parse_number(&r) == false) return false; if (p.parse_number(&border) == false) return false;
if (r < 0) { if (border < 0) {
io::eprintfn("CSS parsing error at %d:%d: 'radius' must be a positive number, got %d", t.line, t.col, r); io::eprintfn("CSS parsing error at %d:%d: 'border' must be a positive number, got %d", t.line, t.col, border);
return false; return false;
} }
p.style.radius = (ushort)r; p.style.border = (ushort)border;
case "radius":
short radius;
if (p.parse_number(&radius) == false) return false;
if (radius < 0) {
io::eprintfn("CSS parsing error at %d:%d: 'radius' must be a positive number, got %d", t.line, t.col, radius);
return false;
}
p.style.radius = (ushort)radius;
case "size": case "size":
short s; short size;
if (p.parse_number(&s) == false) return false; if (p.parse_number(&size) == false) return false;
if (s < 0) { if (size < 0) {
io::eprintfn("CSS parsing error at %d:%d: 'size' must be a positive number, got %d", t.line, t.col, s); io::eprintfn("CSS parsing error at %d:%d: 'size' must be a positive number, got %d", t.line, t.col, size);
return false; return false;
} }
p.style.size = (ushort)s; p.style.size = (ushort)size;
default: default:

View File

@ -7,6 +7,7 @@ layout(set = 3, binding = 0) uniform Viewport {
layout(location = 0) in vec4 in_color; layout(location = 0) in vec4 in_color;
layout(location = 1) in vec4 in_quad_size; // x,y, w,h layout(location = 1) in vec4 in_quad_size; // x,y, w,h
layout(location = 2) in float in_radius; layout(location = 2) in float in_radius;
layout(location = 3) in float thickness;
layout(location = 0) out vec4 fragColor; layout(location = 0) out vec4 fragColor;
@ -16,12 +17,16 @@ float sdf_rr(vec2 p, vec2 half_size, float radius) {
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
} }
const float smoothness = 0.9;
void main() void main()
{ {
vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5; vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5;
vec2 half_size = in_quad_size.zw * 0.5; vec2 half_size = in_quad_size.zw * 0.5;
float distance = sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius); float distance = -sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius);
float alpha = 1.0 - smoothstep(0.0, 1.5, distance);
fragColor = vec4(in_color.rgb, in_color.a * alpha); float alpha_out = smoothstep(0.0-smoothness, 0.0, distance);
float alpha_in = 1.0 - smoothstep(thickness-smoothness, thickness, distance);
fragColor = vec4(in_color.rgb, in_color.a * alpha_out * alpha_in);
} }

View File

@ -12,6 +12,7 @@ layout(location = 3) in uvec4 color;
layout(location = 0) out vec4 out_color; layout(location = 0) out vec4 out_color;
layout(location = 1) out vec4 out_quad_size; layout(location = 1) out vec4 out_quad_size;
layout(location = 2) out float out_radius; layout(location = 2) out float out_radius;
layout(location = 3) out float out_thickness;
void main() void main()
{ {
@ -26,4 +27,5 @@ void main()
out_color = vec4(color) / 255.0; out_color = vec4(color) / 255.0;
out_quad_size = vec4(attr); out_quad_size = vec4(attr);
out_radius = float(abs(uv.x)); out_radius = float(abs(uv.x));
out_thickness = float(uv.y - uv.x);
} }

68
resources/style.css Normal file
View File

@ -0,0 +1,68 @@
default {
bg: #282828ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}
button {
margin: 2 2 2 2;
border: 2;
radius: 10;
bg: #3c3836ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}
button-active {
margin: 2 2 2 2;
border: 2;
radius: 10;
bg: #504945ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #cc241dff;
accent: #fabd2fff;
}
checkbox {
margin: 2 2 2 2;
border: 2;
radius: 10;
size: 16;
bg: #3c3836ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}
toggle {
margin: 2 2 2 2;
border: 2;
radius: 0;
size: 16;
bg: #3c3836ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}
slider {
margin: 2 2 2 2;
padding: 4 4 4 4;
border: 1;
radius: 4;
size: 8;
bg: #3c3836ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}

View File

@ -13,6 +13,7 @@ alias Times = ringbuffer::RingBuffer{time::NanoDuration[128]};
fn void Times.print_stats(&times) fn void Times.print_stats(&times)
{ {
if (times.written == 0);
time::NanoDuration min, max, avg, x; time::NanoDuration min, max, avg, x;
min = times.get(0); min = times.get(0);
for (usz i = 0; i < times.written; i++) { for (usz i = 0; i < times.written; i++) {
@ -21,7 +22,7 @@ fn void Times.print_stats(&times)
if (x > max) { max = x; } if (x > max) { max = x; }
avg += x; avg += x;
} }
avg = (NanoDuration)((ulong)avg/128.0); avg = (NanoDuration)((ulong)avg/times.written);
io::printfn("min=%s, max=%s, avg=%s", min, max, avg); io::printfn("min=%s, max=%s, avg=%s", min, max, avg);
} }
@ -32,6 +33,7 @@ struct TimeStats {
fn TimeStats Times.get_stats(&times) fn TimeStats Times.get_stats(&times)
{ {
if (times.written == 0) return {};
time::NanoDuration min, max, avg, x; time::NanoDuration min, max, avg, x;
min = times.get(0); min = times.get(0);
for (usz i = 0; i < times.written; i++) { for (usz i = 0; i < times.written; i++) {
@ -40,7 +42,7 @@ fn TimeStats Times.get_stats(&times)
if (x > max) { max = x; } if (x > max) { max = x; }
avg += x; avg += x;
} }
avg = (NanoDuration)((ulong)avg/128.0); avg = (NanoDuration)((ulong)avg/times.written);
return {.min = min, .max = max, .avg = avg}; return {.min = min, .max = max, .avg = avg};
} }
@ -54,80 +56,7 @@ const char[*] RECT_FS_PATH = "resources/shaders/compiled/rect.frag.spv";
const char[*] SPRITE_VS_PATH = "resources/shaders/compiled/sprite.vert.spv"; const char[*] SPRITE_VS_PATH = "resources/shaders/compiled/sprite.vert.spv";
const char[*] RECT_VS_PATH = "resources/shaders/compiled/rect.vert.spv"; const char[*] RECT_VS_PATH = "resources/shaders/compiled/rect.vert.spv";
const String STYLESHEET = ` const char[*] STYLESHEET_PATH = "resources/style.css";
default {
bg: #282828ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}
button {
margin: 2 2 2 2;
border: 2 2 2 2;
padding: 1 1 1 1;
radius: 10;
bg: #3c3836ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}
button-active {
margin: 2 2 2 2;
border: 2 2 2 2;
padding: 1 1 1 1;
radius: 10;
bg: #504945ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #cc241dff;
accent: #fabd2fff;
}
checkbox {
margin: 2 2 2 2;
border: 2 2 2 2;
padding: 1 1 1 1;
radius: 10;
size: 16;
bg: #3c3836ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}
toggle {
margin: 2 2 2 2;
border: 2 2 2 2;
padding: 1 1 1 1;
radius: 10;
size: 16;
bg: #3c3836ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}
slider {
margin: 2 2 2 2;
padding: 2 2 2 2;
radius: 4;
size: 8;
bg: #3c3836ff;
fg: #fbf1c7ff;
primary: #cc241dff;
secondary: #458588ff;
accent: #fabd2fff;
}
`;
fn int main(String[] args) fn int main(String[] args)
{ {
@ -188,7 +117,7 @@ fn int main(String[] args)
// CSS INPUT // CSS INPUT
io::printfn("imported %d styles", ui.import_style_from_string(STYLESHEET)); io::printfn("imported %d styles", ui.import_style_from_file(STYLESHEET_PATH));
isz frame; isz frame;
double fps; double fps;

View File

@ -651,11 +651,11 @@ fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u,
return self.map_quad(qa); return self.map_quad(qa);
} }
fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius = 0) fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius = 0, ushort thickness = 0)
{ {
QuadAttributes qa = { QuadAttributes qa = {
.pos = {.x = x, .y = y, .w = w, .h = h}, .pos = {.x = x, .y = y, .w = w, .h = h},
.uv = {.u = radius, .v = radius}, .uv = {.u = radius, .v = radius+thickness},
.color = color .color = color
}; };
@ -866,7 +866,7 @@ fn void Renderer.render_ugui(&self, CmdQueue* queue)
foreach (&c : queue) { foreach (&c : queue) {
if (c.type == CMD_RECT) { if (c.type == CMD_RECT) {
CmdRect r = c.rect; CmdRect r = c.rect;
self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius); self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius, r.thickness);
} else if (c.type == CMD_SPRITE) { } else if (c.type == CMD_SPRITE) {
CmdSprite s = c.sprite; CmdSprite s = c.sprite;
self.push_sprite(s.rect.x, s.rect.y, s.texture_rect.w, s.texture_rect.h, s.texture_rect.x, s.texture_rect.y, s.hue.to_uint()); self.push_sprite(s.rect.x, s.rect.y, s.texture_rect.w, s.texture_rect.h, s.texture_rect.x, s.texture_rect.y, s.hue.to_uint());