Compare commits

...

13 Commits

14 changed files with 522 additions and 491 deletions

64
TODO
View File

@ -12,13 +12,15 @@
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
[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 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
[ ] Animations, somehow [ ] Animations, somehow
[ ] Maybe cache codepoint converted strings [x] Maybe cache codepoint converted strings
[x] Fix scroll wheel when div is scrolled [x] Fix scroll wheel when div is scrolled
[ ] Be consistent with the initialization methods some are foo.new() and some are foo.init() [ ] Be consistent with the initialization methods some are foo.new() and some are foo.init()
[ ] Implement image loading (.bmp, .ff, .qoi and .png), in the future even lossy images like .jpg [ ] Implement image loading (.bmp, .ff, .qoi and .png), in the future even lossy images like .jpg
@ -30,24 +32,36 @@ 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
[x] Fix how padding is applied in push_rect. In CSS padding is applied between the border and the
content, the background color is applied starting from the border. Right now push_rect() offsets
the background rect by both border and padding
[ ] Investigate why the debug pointer (cyan rectangle) disappears...
## Layout ## Layout
[ ] Text reflow
[x] Flexbox [x] Flexbox
[ ] Center elements to the row/column [ ] Center elements to the row/column
[ ] Text wrapping [x] Text wrapping / reflow
[x] Implement a better and unified way to place a glyph and get the cursor position, maybe with a struct
[ ] Correct whitespace handling in text (\t \r etc)
[ ] 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.
[ ] Implement column/row sizing (min, max)
[ ] Find a way to concile pixel measurements to the mm ones used in css, for example in min/max sizing
of elements
[ ] Center elements to div (center children_bounds to the center of the div bounds and shift the origin accordingly)
[x] Use containing_rect() in position_element() to skip some computing and semplify the function
[x] Rename position_element() to layout_element()
[ ] Make functions to mark rows/columns as full, to fix the calculator demo
## Input ## Input
@ -56,6 +70,8 @@ to maintain focus until mouse release (fix scroll bars)
[ ] Touch input [ ] Touch input
[x] Do not set input event to true if the movement was zero (like no mouse movement) [x] Do not set input event to true if the movement was zero (like no mouse movement)
[ ] Use input event flags, for example to consume the input event [ ] Use input event flags, for example to consume the input event
[ ] Fix bug in text box: when spamming keys you can get multiple characters in the text input field
of the context, this causes a bug where only the first char is actually used
## Commands ## Commands
@ -64,8 +80,9 @@ to maintain focus until mouse release (fix scroll bars)
- border radius - border radius
[x] add a command to update an atlas [x] add a command to update an atlas
[ ] New window command, useful for popups [ ] New window command, useful for popups
[ ] Text command returns the text bounds, this way we can avoid the pattern [x] Text command returns the text bounds, this way we can avoid the pattern
draw_text(a, pos) -> off = compute_bounds(a) -> draw_text(b, pos+off) -> ... draw_text(a, pos) -> off = compute_bounds(a) -> draw_text(b, pos+off) -> ...
[ ] Rounded rectangle with different radius for each corner
## Atlas ## Atlas
@ -78,26 +95,16 @@ 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
[ ] Selectable text box [ ] Selectable text box
## Main / exaple
[ ] Create maps from ids to textures and images instead of hardcoding them
## API ## API
[ ] Introduce a Layout structure that specifies the positioning of elements inside [ ] Introduce a Layout structure that specifies the positioning of elements inside
@ -111,9 +118,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

@ -8,112 +8,80 @@ struct ElemButton {
} }
// draw a button, return the events on that button macro Ctx.button(&ctx, String label = "", String icon = "", ...)
macro Ctx.button(&ctx, Rect size, bool state = false, ...) => ctx.button_id(@compute_id($vasplat), label, icon);
=> ctx.button_id(@compute_id($vasplat), size, state); fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
fn ElemEvents? Ctx.button_id(&ctx, Id id, Rect size, bool active)
{ {
id = ctx.gen_id(id)!; id = ctx.gen_id(id)!;
Elem *parent = ctx.get_parent()!; Elem *parent = ctx.get_parent()!;
Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!; Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!;
Style* style_norm = ctx.styles.get_style(@str_hash("button")); Style* style = ctx.styles.get_style(@str_hash("button"));
Sprite* sprite = icon != "" ? ctx.sprite_atlas.get(icon)! : &&(Sprite){};
elem.bounds = ctx.position_element(parent, size, style_norm); Rect min_size = {0, 0, style.size, style.size};
// if the bounds are null the element is outside the div view,
// no interaction should occur so just return
if (elem.bounds.is_null()) { return {}; }
Style* style_active = ctx.styles.get_style(@str_hash("button-active"));
elem.events = ctx.get_elem_events(elem);
bool is_active = active || ctx.elem_focus(elem) || elem.events.mouse_hover;
// Draw the button
ctx.push_rect(elem.bounds, parent.div.z_index, is_active ? style_active : style_norm)!;
return elem.events;
}
macro Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,short.max}, bool active = false, ...)
=> ctx.button_label_id(@compute_id($vasplat), label, size, active);
fn ElemEvents? Ctx.button_label_id(&ctx, Id id, String label, Rect size, bool active)
{
id = ctx.gen_id(id)!;
Elem *parent = ctx.get_parent()!;
Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!;
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
Rect text_size = ctx.get_text_bounds(label)!; Rect text_size = ctx.get_text_bounds(label)!;
Rect btn_size = text_size.add({0,0,10,10}); Rect icon_size = sprite.rect();
Style* style_norm = ctx.styles.get_style(@str_hash("button"));
// 2. Layout
elem.bounds = ctx.position_element(parent, btn_size, style_norm);
if (elem.bounds.is_null()) { return {}; }
Style* style_active = ctx.styles.get_style(@str_hash("button-active"));
ushort h_lh = (ushort)(ctx.font.line_height() / 2);
ushort left_pad = label != "" ? h_lh : 0;
ushort inner_pad = label != "" && icon != "" ? h_lh : 0;
ushort right_pad = left_pad;
/* |left_pad
* +--v-----------------------------------+
* |<->+--------+ |
* | | | +-----------------+ |
* | | icon | | label | |
* | | | +-----------------+ |
* | +--------+<->| |<->|
* +-------------^--------------------^---+
* |inner_pad |right_pad
*/
Rect tot_size = {
.w = (short)max(min_size.w, icon_size.w + text_size.w + left_pad + inner_pad + right_pad),
.h = (short)max(min_size.h, max(icon_size.h, text_size.h)),
};
elem.bounds = ctx.layout_element(parent, tot_size, style);
if (elem.bounds.is_null()) return {};
elem.events = ctx.get_elem_events(elem); elem.events = ctx.get_elem_events(elem);
Rect content_bounds = elem.content_bounds(style);
bool is_active = active || ctx.elem_focus(elem) || elem.events.mouse_hover;
Style* style = is_active ? style_active : style_norm; Rect text_bounds = {
.x = content_bounds.x + icon_size.w + left_pad + inner_pad,
// Draw the button .y = content_bounds.y,
text_size.x = elem.bounds.x; .w = content_bounds.w - icon_size.w - left_pad - inner_pad - right_pad,
text_size.y = elem.bounds.y; .h = content_bounds.h
Point off = ctx.center_text(text_size, elem.bounds); };
text_size.x += off.x; text_bounds = text_size.center_to(text_bounds);
text_size.y += off.y;
ctx.push_rect(elem.bounds, parent.div.z_index, style)!; Rect icon_bounds = {
ctx.push_string(text_size, label, parent.div.z_index, style.fg)!; .x = content_bounds.x,
.y = content_bounds.y,
return elem.events; .w = icon_size.w + right_pad + inner_pad,
} .h = (short)max(icon_size.h, content_bounds.h)
};
macro Ctx.button_icon(&ctx, String icon, String on_icon = "", bool active = false, ...) icon_bounds = icon_size.center_to(icon_bounds);
=> ctx.button_icon_id(@compute_id($vasplat), icon, on_icon, active);
fn ElemEvents? Ctx.button_icon_id(&ctx, Id id, String icon, String on_icon, bool active) bool is_active = ctx.elem_focus(elem) || elem.events.mouse_hover;
{ Style s = *style;
id = ctx.gen_id(id)!; if (is_active) {
s.secondary = s.primary;
Elem *parent = ctx.get_parent()!; s.bg = s.accent;
Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!;
Sprite* def_sprite = ctx.sprite_atlas.get(icon)!;
Sprite* on_sprite = ctx.sprite_atlas.get(on_icon) ?? &&(Sprite){};
Rect max_size = def_sprite.rect().max(on_sprite.rect());
Style* style_norm = ctx.styles.get_style(@str_hash("button"));
elem.bounds = ctx.position_element(parent, max_size, style_norm);
// if the bounds are null the element is outside the div view,
// no interaction should occur so just return
if (elem.bounds.is_null()) { return {}; }
Style* style_active = ctx.styles.get_style(@str_hash("button-active"));
elem.events = ctx.get_elem_events(elem);
bool is_active = active || ctx.elem_focus(elem) || elem.events.mouse_hover;
Style* style = is_active ? style_active : style_norm;
Id tex_id = ctx.sprite_atlas.id;
if (active && on_icon != "") {
ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, parent.div.z_index, type: on_sprite.type)!;
} else {
ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, parent.div.z_index, type: def_sprite.type)!;
} }
// Draw the button ctx.push_rect(elem.bounds, parent.div.z_index, &s)!;
ctx.push_rect(elem.bounds, parent.div.z_index, style)!; ctx.push_sprite(icon_bounds, sprite.uv(), ctx.sprite_atlas.id, parent.div.z_index, type: sprite.type)!;
// Style ss = {.bg = 0x000000ffu.@to_rgba()};
// ctx.push_rect(content_bounds, parent.div.z_index, &ss)!;
ctx.push_string(text_bounds, label, parent.div.z_index, style.fg)!;
return elem.events; return elem.events;
} }
// FIXME: this should be inside the style // FIXME: this should be inside the style
macro Ctx.checkbox(&ctx, String desc, Point off, bool* active, String tick_sprite = {}, ...) macro Ctx.checkbox(&ctx, String desc, Point off, bool* active, String tick_sprite = {}, ...)
=> ctx.checkbox_id(@compute_id($vasplat), desc, off, active, tick_sprite); => ctx.checkbox_id(@compute_id($vasplat), desc, off, active, tick_sprite);
@ -126,7 +94,7 @@ fn void? Ctx.checkbox_id(&ctx, Id id, String description, Point off, bool* activ
Style* style = ctx.styles.get_style(@str_hash("checkbox")); Style* style = ctx.styles.get_style(@str_hash("checkbox"));
Rect size = {off.x, off.y, style.size, style.size}; Rect size = {off.x, off.y, style.size, style.size};
elem.bounds = ctx.position_element(parent, size, style); elem.bounds = ctx.layout_element(parent, size, style);
// if the bounds are null the element is outside the div view, // if the bounds are null the element is outside the div view,
// no interaction should occur so just return // no interaction should occur so just return
@ -165,7 +133,7 @@ fn void? Ctx.toggle_id(&ctx, Id id, String description, Point off, bool* active)
Style* style = ctx.styles.get_style(@str_hash("toggle")); Style* style = ctx.styles.get_style(@str_hash("toggle"));
Rect size = {off.x, off.y, style.size*2, style.size}; Rect size = {off.x, off.y, style.size*2, style.size};
elem.bounds = ctx.position_element(parent, size, style); elem.bounds = ctx.layout_element(parent, size, style);
// if the bounds are null the element is outside the div view, // if the bounds are null the element is outside the div view,
// no interaction should occur so just return // no interaction should occur so just return

View File

@ -93,17 +93,17 @@ 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 border = style.border;
Rect padding = style.padding;
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;
// FIXME: this implies that the border has to be uniform
if (!border.is_null()) { if (!border.is_null()) {
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.x,
}; };
ctx.push_cmd(&cmd, z_index)!; ctx.push_cmd(&cmd, z_index)!;
} }
@ -111,10 +111,10 @@ 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.x,
.y = rect.y + border.y + padding.y, .y = rect.y + border.y,
.w = rect.w - (border.x+border.w) - (padding.x+padding.w), .w = rect.w - (border.x+border.w),
.h = rect.h - (border.y+border.h) - (padding.y+padding.h), .h = rect.h - (border.y+border.h),
}, },
.rect.color = bg, .rect.color = bg,
.rect.radius = radius, .rect.radius = radius,
@ -123,6 +123,7 @@ fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
ctx.push_cmd(&cmd, z_index)!; ctx.push_cmd(&cmd, z_index)!;
} }
// TODO: accept a Sprite* instead of all this shit
fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_index, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL) fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_index, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL)
{ {
Cmd cmd = { Cmd cmd = {
@ -136,56 +137,30 @@ fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_i
ctx.push_cmd(&cmd, z_index)!; ctx.push_cmd(&cmd, z_index)!;
} }
fn void? Ctx.push_string(&ctx, Rect bounds, char[] text, int z_index, Color hue) // TODO: do not return the WHOLE TextInfo but instead something smaller
fn TextInfo? Ctx.push_string(&ctx, Rect bounds, char[] text, int z_index, Color hue, bool reflow = false)
{ {
if (text.len == 0) { if (text.len == 0) {
return; return {};
} }
ctx.push_scissor(bounds, z_index)!; ctx.push_scissor(bounds, z_index)!;
short baseline = (short)ctx.font.ascender;
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
short line_gap = (short)ctx.font.linegap;
Id texture_id = ctx.font.id; // or ctx.font.atlas.id Id texture_id = ctx.font.id; // or ctx.font.atlas.id
Point orig = { Rect text_bounds = {bounds.x, bounds.y, 0, 0};
.x = bounds.x,
.y = bounds.y,
};
short line_len; TextInfo ti;
Codepoint cp; ti.init(&ctx.font, (String)text, bounds);
usz off, x;
while (off < text.len && (cp = str_to_codepoint(text[off..], &x)) != 0) { while (ti.place_glyph(reflow)!) {
off += x; if (!cull_rect(ti.glyph_bounds, bounds)) {
Glyph* gp; ctx.push_sprite(ti.glyph_bounds, ti.glyph_uv, texture_id, z_index, hue)!;
if (!ascii::is_cntrl((char)cp)) {
gp = ctx.font.get_glyph(cp)!;
Rect gb = {
.x = orig.x + line_len + gp.ox,
.y = orig.y + gp.oy + baseline,
.w = gp.w,
.h = gp.h,
};
Rect gt = {
.x = gp.u,
.y = gp.v,
.w = gp.w,
.h = gp.h,
};
// push the sprite only if it collides with the bounds
if (!cull_rect(gb, bounds)) ctx.push_sprite(gb, gt, texture_id, z_index, hue)!;
line_len += gp.adv;
} else if (cp == '\n'){
orig.y += line_height + line_gap;
line_len = 0;
} else {
continue;
} }
} }
// FIXME: we never get here if an error was thrown before
ctx.push_scissor({}, z_index)!; ctx.push_scissor({}, z_index)!;
return ti;
} }
fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas) fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)

View File

@ -65,7 +65,7 @@ alias ElemCache = cache::Cache{Id, Elem, MAX_ELEMENTS};
alias CmdQueue = fifo::Fifo{Cmd}; alias CmdQueue = fifo::Fifo{Cmd};
faultdef INVALID_SIZE, EVENT_UNSUPPORTED, UNEXPECTED_ELEMENT, WRONG_ELEMENT_TYPE, WRONG_ID; faultdef INVALID_SIZE, EVENT_UNSUPPORTED, WRONG_ELEMENT_TYPE, WRONG_ID;
const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 };
@ -117,7 +117,10 @@ struct Ctx {
fn Elem*? Ctx.get_parent(&ctx) fn Elem*? Ctx.get_parent(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Id parent_id = ctx.tree.get(ctx.active_div)!;
return ctx.cache.search(parent_id); Elem*? parent = ctx.cache.search(parent_id);
if (catch parent) return parent;
if (parent.type != ETYPE_DIV) return WRONG_ELEMENT_TYPE?;
return parent;
} }
macro @bits(#a) => $typeof(#a).sizeof*8; macro @bits(#a) => $typeof(#a).sizeof*8;
@ -250,7 +253,7 @@ fn void? Ctx.frame_end(&ctx)
{ {
// FIXME: this is not guaranteed to be root. the user might forget to close a div or some other element // FIXME: this is not guaranteed to be root. the user might forget to close a div or some other element
Elem* root = ctx.get_active_div()!; Elem* root = ctx.get_active_div()!;
root.div.layout = LAYOUT_ROW; if (root.id != ROOT_ID) return WRONG_ID?;
// 1. clear the tree // 1. clear the tree
ctx.tree.nuke(); ctx.tree.nuke();

View File

@ -34,8 +34,8 @@ fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
{ {
id = ctx.gen_id(id)!; id = ctx.gen_id(id)!;
Elem* parent = ctx.get_parent()!;
Elem* elem = ctx.get_elem(id, ETYPE_DIV)!; Elem* elem = ctx.get_elem(id, ETYPE_DIV)!;
Elem* parent = ctx.get_parent()!;
ctx.active_div = elem.tree_idx; ctx.active_div = elem.tree_idx;
Style* style = ctx.styles.get_style(@str_hash("default")); Style* style = ctx.styles.get_style(@str_hash("default"));
@ -53,7 +53,7 @@ fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
.w = size.w < 0 ? max(elem.div.pcb.w, (short)-size.w) : size.w, .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, .h = size.h < 0 ? max(elem.div.pcb.h, (short)-size.h) : size.h,
}; };
elem.bounds = ctx.position_element(parent, wanted_size, style); elem.bounds = ctx.layout_element(parent, wanted_size, style);
elem.div.children_bounds = {}; elem.div.children_bounds = {};
// update the ctx scissor // update the ctx scissor
@ -81,7 +81,6 @@ fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
fn void? Ctx.div_end(&ctx) fn void? Ctx.div_end(&ctx)
{ {
// swap the children bounds // swap the children bounds
Elem* parent = ctx.get_parent()!;
Elem* elem = ctx.get_active_div()!; Elem* elem = ctx.get_active_div()!;
elem.div.pcb = elem.div.children_bounds; elem.div.pcb = elem.div.children_bounds;
@ -150,4 +149,7 @@ fn void? Ctx.div_end(&ctx)
// the active_div returns to the parent of the current one // the active_div returns to the parent of the current one
ctx.active_div = ctx.tree.parentof(ctx.active_div)!; ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
Elem* parent = ctx.get_parent()!;
// TODO: reset the scissor back to the parent div
ctx.div_scissor = parent.bounds;
} }

View File

@ -167,6 +167,7 @@ fn void? Ctx.load_font(&ctx, String name, ZString path, uint height, float scale
<* <*
@require off != null @require off != null
@require str.ptr != null
*> *>
fn Codepoint str_to_codepoint(char[] str, usz* off) fn Codepoint str_to_codepoint(char[] str, usz* off)
{ {
@ -179,72 +180,119 @@ fn Codepoint str_to_codepoint(char[] str, usz* off)
return cp; return cp;
} }
const uint TAB_SIZE = 4;
// TODO: change the name
// TODO: reorder to make smaller
struct TextInfo {
Font* font;
Rect bounds;
String text;
usz off;
// current glyph info
Point origin;
Rect glyph_bounds;
Rect glyph_uv;
// cursor info
usz cursor_idx;
Point cursor_pos;
// current text bounds
Rect text_bounds;
}
<*
@require f != null
*>
fn void TextInfo.init(&self, Font* f, String s, Rect bounds = RECT_MAX)
{
*self = {};
self.font = f;
self.text = s;
self.bounds = bounds;
self.origin = bounds.position();
self.text_bounds = { .x = bounds.x, .y = bounds.y };
}
fn void TextInfo.reset(&self)
{
TextInfo old = *self;
self.init(old.font, old.text, old.bounds);
}
fn bool? TextInfo.place_glyph(&self, bool reflow)
{
if (self.off >= self.text.len) {
return false;
}
if (!self.origin.in_rect(self.bounds)) {
return false;
}
short baseline = (short)self.font.ascender;
short line_height = (short)self.font.ascender - (short)self.font.descender;
short line_gap = (short)self.font.linegap;
Codepoint cp;
Glyph* gp;
usz x;
cp = str_to_codepoint(self.text[self.off..], &x);
if (cp == 0) return false;
self.off += x;
if (ascii::is_cntrl((char)cp) == false) {
gp = self.font.get_glyph(cp)!;
self.glyph_uv = {
.x = gp.u,
.y = gp.v,
.w = gp.w,
.h = gp.h,
};
self.glyph_bounds = {
.x = self.origin.x + gp.ox,
.y = self.origin.y + gp.oy + baseline,
.w = gp.w,
.h = gp.h,
};
// try to wrap the text if the charcater goes outside of the bounds
if (reflow && !self.bounds.contains(self.glyph_bounds)) {
self.origin.y += line_height + line_gap;
self.glyph_bounds.y += line_height + line_gap;
self.origin.x = self.bounds.x;
self.glyph_bounds.x = self.bounds.x;
}
// handle tab
if (cp == '\t') {
self.origin.x += gp.adv*TAB_SIZE;
} else {
self.origin.x += gp.adv;
}
} else if (cp == '\n'){
self.origin.y += line_height + line_gap;
self.origin.x = self.bounds.x;
}
self.text_bounds = containing_rect(self.text_bounds, self.glyph_bounds);
if (self.off == self.cursor_idx) {
self.cursor_pos = self.origin;
}
return true;
}
fn Rect? Ctx.get_text_bounds(&ctx, String text) fn Rect? Ctx.get_text_bounds(&ctx, String text)
{ {
Rect text_bounds; TextInfo ti;
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; ti.init(&ctx.font, text);
short line_gap = (short)ctx.font.linegap; while (ti.place_glyph(false)!);
text_bounds.h = line_height; return ti.text_bounds;
Glyph* gp;
// TODO: account for unicode codepoints
short line_len;
Codepoint cp;
usz off, x;
while ((cp = str_to_codepoint(text[off..], &x)) != 0) {
off += x;
bool n;
if (!ascii::is_cntrl((char)cp)) {
gp = ctx.font.get_glyph(cp)!;
line_len += gp.adv;
} else if (cp == '\n'){
text_bounds.h += line_height + line_gap;
line_len = 0;
} else {
continue;
}
if (line_len > text_bounds.w) {
text_bounds.w = line_len;
}
}
return text_bounds;
}
fn Point? Ctx.get_cursor_position(&ctx, String text)
{
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
short line_gap = (short)ctx.font.linegap;
Glyph* gp;
// TODO: account for unicode codepoints
Point line;
Codepoint cp;
usz off, x;
while ((cp = str_to_codepoint(text[off..], &x)) != 0) {
off += x;
bool n;
if (!ascii::is_cntrl((char)cp)) {
gp = ctx.font.get_glyph(cp)!;
line.x += gp.adv;
} else if (cp == '\n'){
line.y += line_height + line_gap;
line.x = 0;
} else {
continue;
}
}
return line;
}
fn Point Ctx.center_text(&ctx, Rect text_bounds, Rect bounds)
{
short dw = bounds.w - text_bounds.w;
short dh = bounds.h - text_bounds.h;
return {.x = dw/2, .y = dh/2};
} }
// TODO: check if the font is present in the context // TODO: check if the font is present in the context

View File

@ -9,51 +9,25 @@ enum Layout {
fn void? Ctx.layout_set_row(&ctx) fn void? Ctx.layout_set_row(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.get_parent()!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UNEXPECTED_ELEMENT?;
}
parent.div.layout = LAYOUT_ROW; parent.div.layout = LAYOUT_ROW;
} }
fn void? Ctx.layout_set_column(&ctx) fn void? Ctx.layout_set_column(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.get_parent()!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UNEXPECTED_ELEMENT?;
}
parent.div.layout = LAYOUT_COLUMN; parent.div.layout = LAYOUT_COLUMN;
} }
fn void? Ctx.layout_set_floating(&ctx) fn void? Ctx.layout_set_floating(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.get_parent()!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UNEXPECTED_ELEMENT?;
}
parent.div.layout = LAYOUT_FLOATING; parent.div.layout = LAYOUT_FLOATING;
} }
fn void? Ctx.layout_next_row(&ctx) fn void? Ctx.layout_next_row(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.get_parent()!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
return UNEXPECTED_ELEMENT?;
}
parent.div.origin_r = { parent.div.origin_r = {
.x = parent.bounds.x, .x = parent.bounds.x,
@ -64,12 +38,7 @@ fn void? Ctx.layout_next_row(&ctx)
fn void? Ctx.layout_next_column(&ctx) fn void? Ctx.layout_next_column(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.get_parent()!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
return UNEXPECTED_ELEMENT?;
}
parent.div.origin_c = { parent.div.origin_c = {
.x = parent.div.children_bounds.bottom_right().x, .x = parent.div.children_bounds.bottom_right().x,
@ -78,6 +47,7 @@ fn void? Ctx.layout_next_column(&ctx)
parent.div.origin_r = parent.div.origin_c; parent.div.origin_r = parent.div.origin_c;
} }
macro Rect Elem.content_bounds(&elem, style) => elem.bounds.pad(style.border).pad(style.padding);
macro Rect Elem.get_view(&elem) macro Rect Elem.get_view(&elem)
{ {
@ -106,7 +76,7 @@ macro Point Elem.get_view_off(&elem)
@require ctx != null @require ctx != null
@require parent.type == ETYPE_DIV @require parent.type == ETYPE_DIV
*> *>
fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, Style* style) fn Rect Ctx.layout_element(&ctx, Elem *parent, Rect rect, Style* style)
{ {
ElemDiv* div = &parent.div; ElemDiv* div = &parent.div;
@ -176,16 +146,7 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, Style* style)
}; };
// 5. Update the parent's children bounds // 5. Update the parent's children bounds
if (!child_occupied.bottom_right().in_rect(div.children_bounds)) { div.children_bounds = containing_rect(div.children_bounds, child_occupied);
// 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;
}
// bottom 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;
}
}
// 99. return the placement // 99. return the placement
if (child_placement.collides(parent_view)) { if (child_placement.collides(parent_view)) {

View File

@ -10,6 +10,9 @@ struct Rect {
short x, y, w, h; short x, y, w, h;
} }
// TODO: find another name
const Rect RECT_MAX = {0, 0, short.max, short.max};
// return true if rect a contains b // return true if rect a contains b
macro bool Rect.contains(Rect a, Rect b) macro bool Rect.contains(Rect a, Rect b)
{ {
@ -33,6 +36,21 @@ 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); 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 rect that contains both rects, a bounding box of both
macro Rect containing_rect(Rect a, Rect b)
{
short min_x = (short)min(a.x, b.x);
short min_y = (short)min(a.y, b.y);
short max_x = (short)max(a.x + a.w, b.x + b.w);
short max_y = (short)max(a.y + a.h, b.y + b.h);
return {
.x = min_x,
.y = min_y,
.w = (short)(max_x - min_x),
.h = (short)(max_y - min_y)
};
}
// check for empty rect // check for empty rect
macro bool Rect.is_null(Rect r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; macro bool Rect.is_null(Rect r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
@ -136,6 +154,26 @@ macro Point Rect.bottom_right(Rect r)
}; };
} }
macro Rect Rect.center_to(Rect a, Rect b)
{
return {
.x = b.x + (b.w - a.w)/2,
.y = b.y + (b.h - a.h)/2,
.w = a.w,
.h = a.h,
};
}
macro Rect Rect.pad(Rect a, Rect b)
{
return {
.x = a.x + b.x,
.y = a.y + b.y,
.w = a.w - b.x - b.w,
.h = a.h - b.y - b.h,
};
}
// ---------------------------------------------------------------------------------- // // ---------------------------------------------------------------------------------- //
// POINT // // POINT //

View File

@ -28,24 +28,26 @@ fn ElemEvents? Ctx.slider_hor_id(&ctx, Id id, Rect size, float* value, float hpe
Style* style = ctx.styles.get_style(@str_hash("slider")); Style* style = ctx.styles.get_style(@str_hash("slider"));
// 2. Layout // 2. Layout
elem.bounds = ctx.position_element(parent, size, style); elem.bounds = ctx.layout_element(parent, size, style);
if (elem.bounds.is_null()) return {};
Rect content_bounds = elem.content_bounds(style);
// handle width // handle width
short hw = (short)(elem.bounds.w * hpercent); short hw = (short)(content_bounds.w * hpercent);
Rect handle = { Rect handle = {
.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value), .x = calc_slider(content_bounds.x, content_bounds.w-hw, *value),
.y = elem.bounds.y, .y = content_bounds.y,
.w = hw, .w = hw,
.h = elem.bounds.h, .h = content_bounds.h,
}; };
elem.slider.handle = handle; elem.slider.handle = handle;
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(content_bounds.x, m.x, content_bounds.w, hw);
elem.slider.handle.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value); elem.slider.handle.x = calc_slider(content_bounds.x, content_bounds.w-hw, *value);
elem.events.update = true; elem.events.update = true;
} }
@ -56,6 +58,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;
@ -92,14 +95,16 @@ fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Rect size, float* value, float hpe
} }
// 2. Layout // 2. Layout
elem.bounds = ctx.position_element(parent, size, style); elem.bounds = ctx.layout_element(parent, size, style);
if (elem.bounds.is_null()) return {};
Rect content_bounds = elem.content_bounds(style);
// handle height // handle height
short hh = (short)(elem.bounds.h * hpercent); short hh = (short)(content_bounds.h * hpercent);
Rect handle = { Rect handle = {
.x = elem.bounds.x, .x = content_bounds.x,
.y = calc_slider(elem.bounds.y, elem.bounds.h-hh, *value), .y = calc_slider(content_bounds.y, content_bounds.h-hh, *value),
.w = elem.bounds.w, .w = content_bounds.w,
.h = hh, .h = hh,
}; };
elem.slider.handle = handle; elem.slider.handle = handle;
@ -108,8 +113,8 @@ fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Rect size, float* value, float hpe
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.y, m.y, elem.bounds.h, hh); *value = calc_value(content_bounds.y, m.y, content_bounds.h, hh);
elem.slider.handle.y = calc_slider(elem.bounds.y, elem.bounds.h-hh, *value); elem.slider.handle.y = calc_slider(content_bounds.y, content_bounds.h-hh, *value);
elem.events.update = true; elem.events.update = true;
} }
@ -120,6 +125,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

@ -126,7 +126,7 @@ fn void? Ctx.sprite_id(&ctx, Id id, String name, Point off)
Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h }; Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h };
Rect bounds = { 0, 0, sprite.w, sprite.h }; Rect bounds = { 0, 0, sprite.w, sprite.h };
elem.bounds = ctx.position_element(parent, bounds.off(off), style); elem.bounds = ctx.layout_element(parent, bounds.off(off), style);
elem.sprite.id = ctx.get_sprite_atlas_id(name); elem.sprite.id = ctx.get_sprite_atlas_id(name);
// if the bounds are null the element is outside the div view, // if the bounds are null the element is outside the div view,

View File

@ -115,7 +115,10 @@ import std::io;
enum TokenType { enum TokenType {
INVALID, INVALID,
IDENTIFIER, IDENTIFIER,
PUNCT, RCURLY,
LCURLY,
SEMICOLON,
COLON,
NUMBER, NUMBER,
COLOR, COLOR,
EOF, EOF,
@ -191,10 +194,16 @@ fn Token Lexer.next_token(&lex)
switch (true) { switch (true) {
case ascii::is_punct_m(lex.peep()) && lex.peep() != '#': // punctuation case ascii::is_punct_m(lex.peep()) && lex.peep() != '#': // punctuation
t.type = PUNCT;
t.text = lex.text[lex.off:1]; t.text = lex.text[lex.off:1];
if (lex.advance() == 0) { t.type = INVALID; break; } if (lex.advance() == 0) { t.type = INVALID; break; }
switch (t.text[0]) {
case ':': t.type = COLON;
case ';': t.type = SEMICOLON;
case '{': t.type = LCURLY;
case '}': t.type = RCURLY;
default: t.type = INVALID;
}
case lex.peep() == '#': // color case lex.peep() == '#': // color
t.type = COLOR; t.type = COLOR;
if (lex.advance() == 0) { t.type = INVALID; break; } if (lex.advance() == 0) { t.type = INVALID; break; }
@ -276,17 +285,6 @@ struct Parser {
float mm_to_px; float mm_to_px;
} }
macro bool Parser.expect_text(&p, Token* t, TokenType type, String text)
{
*t = p.lex.next_token();
if (t.type == type && t.text == text) {
return true;
}
io::eprintfn("CSS parsing error at %d:%d: expected type:%s text:'%s' but got type:%s text:'%s'",
t.line, t.col, type, text, t.type, t.text);
return false;
}
macro bool Parser.expect(&p, Token* t, TokenType type) macro bool Parser.expect(&p, Token* t, TokenType type)
{ {
*t = p.lex.next_token(); *t = p.lex.next_token();
@ -306,7 +304,7 @@ fn bool Parser.parse_style(&p)
p.style_id = t.text.hash(); p.style_id = t.text.hash();
// style body // style body
if (p.expect_text(&t, PUNCT, "{") == false) return false; if (p.expect(&t, LCURLY) == false) return false;
while (true) { while (true) {
if (p.parse_property() == false) return false; if (p.parse_property() == false) return false;
@ -314,7 +312,7 @@ fn bool Parser.parse_style(&p)
if (t.type != IDENTIFIER) break; if (t.type != IDENTIFIER) break;
} }
if (p.expect_text(&t, PUNCT, "}") == false) return false; if (p.expect(&t, RCURLY) == false) return false;
return true; return true;
} }
@ -323,7 +321,7 @@ fn bool Parser.parse_property(&p)
{ {
Token t, prop; Token t, prop;
if (p.expect(&prop, IDENTIFIER) == false) return false; if (p.expect(&prop, IDENTIFIER) == false) return false;
if (p.expect_text(&t, PUNCT, ":") == false) return false; if (p.expect(&t, COLON) == false) return false;
switch (prop.text) { switch (prop.text) {
case "padding": case "padding":
@ -389,7 +387,7 @@ fn bool Parser.parse_property(&p)
io::eprintfn("CSS parsing error at %d:%d: '%s' is not a valid property", prop.line, prop.col, prop.text); io::eprintfn("CSS parsing error at %d:%d: '%s' is not a valid property", prop.line, prop.col, prop.text);
return false; return false;
} }
if (p.expect_text(&t, PUNCT, ";") == false) return false; if (p.expect(&t, SEMICOLON) == false) return false;
return true; return true;
} }
@ -428,7 +426,7 @@ fn bool Parser.parse_size(&p, Rect* r)
if (p.parse_number(&x) == false) return false; if (p.parse_number(&x) == false) return false;
r.h = x; r.h = x;
return true; return true;
} else if (t.type == PUNCT && t.text == ";") { } else if (t.type == SEMICOLON) {
// just one number, all dimensions are the same // just one number, all dimensions are the same
r.x = r.y = r.w = r.h = x; r.x = r.y = r.w = r.h = x;
return true; return true;

View File

@ -3,8 +3,10 @@ module ugui;
import std::io; import std::io;
struct ElemText { struct ElemText {
char[] str; String str;
usz cursor; // cursor offset usz cursor; // cursor offset
Id hash;
Rect bounds;
} }
macro Ctx.text_unbounded(&ctx, String text, ...) macro Ctx.text_unbounded(&ctx, String text, ...)
@ -17,12 +19,15 @@ fn void? Ctx.text_unbounded_id(&ctx, Id id, String text)
Elem *elem = ctx.get_elem(id, ETYPE_TEXT)!; Elem *elem = ctx.get_elem(id, ETYPE_TEXT)!;
Style* style = ctx.styles.get_style(@str_hash("text")); Style* style = ctx.styles.get_style(@str_hash("text"));
Id text_hash = text.hash();
if (elem.flags.is_new || elem.text.hash != text_hash) {
elem.text.bounds = ctx.get_text_bounds(text)!;
}
elem.text.str = text; elem.text.str = text;
elem.text.hash = text_hash;
// if the element is new or the parent was updated then redo layout
Rect text_size = ctx.get_text_bounds(text)!;
// 2. Layout // 2. Layout
elem.bounds = ctx.position_element(parent, text_size, style); elem.bounds = ctx.layout_element(parent, elem.text.bounds, style);
if (elem.bounds.is_null()) { return; } if (elem.bounds.is_null()) { return; }
ctx.push_string(elem.bounds, text, parent.div.z_index, style.fg)!; ctx.push_string(elem.bounds, text, parent.div.z_index, style.fg)!;
@ -38,10 +43,10 @@ fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_le
Elem *elem = ctx.get_elem(id, ETYPE_TEXT)!; Elem *elem = ctx.get_elem(id, ETYPE_TEXT)!;
Style* style = ctx.styles.get_style(@str_hash("text-box")); Style* style = ctx.styles.get_style(@str_hash("text-box"));
elem.text.str = text; elem.text.str = (String)text;
// layout the text box // layout the text box
elem.bounds = ctx.position_element(parent, size, style); elem.bounds = ctx.layout_element(parent, size, style);
// check input and update the text // check input and update the text
elem.events = ctx.get_elem_events(elem); elem.events = ctx.get_elem_events(elem);
@ -64,27 +69,11 @@ fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_le
// draw the box // draw the box
short line_height = (short)ctx.font.line_height(); short line_height = (short)ctx.font.line_height();
Rect text_box = elem.bounds.sub({0,0,0,line_height}); Rect text_box = elem.bounds;
Rect input_box = {
.x = elem.bounds.x,
.y = elem.bounds.y + elem.bounds.h - line_height,
.w = elem.bounds.w,
.h = line_height,
};
Rect cursor;
Point b = ctx.get_cursor_position((String)text[:elem.text.cursor])!;
cursor = {
.x = b.x,
.y = b.y,
.w = 3,
.h = line_height,
};
cursor = cursor.off(elem.bounds.position());
ctx.push_rect(text_box, parent.div.z_index, style)!; ctx.push_rect(text_box, parent.div.z_index, style)!;
ctx.push_string(text_box, text[:*text_len], parent.div.z_index, style.fg)!; ctx.push_string(text_box, text[:*text_len], parent.div.z_index, style.fg, true)!;
ctx.push_rect(input_box, parent.div.z_index, style)!;
ctx.push_rect(cursor, parent.div.z_index, style)!; // TODO: draw cursor
return elem.events; return elem.events;
} }

61
resources/style.css Normal file
View File

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

View File

@ -54,80 +54,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,11 +115,10 @@ 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;
bool toggle = true;
time::Clock clock; time::Clock clock;
time::Clock fps_clock; time::Clock fps_clock;
time::Clock sleep_clock; time::Clock sleep_clock;
@ -272,74 +198,13 @@ fn int main(String[] args)
if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true; if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true;
ui.div_begin({.w=-100})!!; const String APPLICATION = "calculator";
{ $if APPLICATION == "debug":
ui.layout_set_column()!!; debug_app(&ui);
if (ui.button({0,0,30,30}, toggle)!!.mouse_press) { $endif
io::printn("press button0"); $if APPLICATION == "calculator":
toggle = !toggle; calculator(&ui);
} $endif
//ui.layout_next_column()!!;
if (ui.button({0,0,30,30})!!.mouse_press) {
io::printn("press button1");
}
//ui.layout_next_column()!!;
if (ui.button({0,0,30,30})!!.mouse_release) {
io::printn("release button2");
}
ui.layout_set_row()!!;
ui.layout_next_row()!!;
static float rf, gf, bf, af;
ui.slider_ver({0,0,30,100}, &rf)!!;
ui.slider_ver({0,0,30,100}, &gf)!!;
ui.slider_ver({0,0,30,100}, &bf)!!;
ui.slider_ver({0,0,30,100}, &af)!!;
ui.layout_next_column()!!;
ui.text_unbounded("Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!;
ui.layout_next_column()!!;
ui.button_label("Continua!")!!;
ui.layout_next_row()!!;
static bool check;
ui.checkbox("", {}, &check, "tick")!!;
ui.checkbox("", {}, &check)!!;
ui.toggle("", {}, &toggle)!!;
};
ui.sprite("tux")!!;
static char[128] text_box = "ciao mamma";
static usz text_len = "ciao mamma".len;
ui.text_box({0,0,200,200}, text_box[..], &text_len)!!;
ui.div_end()!!;
ui.div_begin(ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
{
ui.layout_set_column()!!;
static float slider2 = 0.5;
if (ui.slider_ver({0,0,30,100}, &slider2)!!.update) {
io::printfn("other slider: %f", slider2);
}
ui.button({0,0,50,50})!!;
ui.button({0,0,50,50})!!;
ui.button({0,0,50,50})!!;
ui.button({0,0,50,50})!!;
if (toggle) {
ui.button({0,0,50,50})!!;
ui.button({0,0,50,50})!!;
ui.button({0,0,50,50})!!;
ui.button({0,0,50,50})!!;
}
ui.layout_next_column()!!;
ui.layout_set_row()!!;
static float f1, f2;
ui.slider_hor({0,0,100,30}, &f1)!!;
ui.slider_hor({0,0,100,30}, &f2)!!;
};
ui.div_end()!!;
// Timings counter // Timings counter
TimeStats dts = draw_times.get_stats(); TimeStats dts = draw_times.get_stats();
@ -352,7 +217,7 @@ fn int main(String[] args)
ui.layout_set_column()!!; ui.layout_set_column()!!;
ui.text_unbounded(string::tformat("frame %d, fps = %.2f", frame, fps))!!; ui.text_unbounded(string::tformat("frame %d, fps = %.2f", frame, fps))!!;
ui.text_unbounded(string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; ui.text_unbounded(string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
ui.text_unbounded(string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[..]))!!; ui.text_unbounded(string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[:ui.input.keyboard.text_len]))!!;
}; };
ui.div_end()!!; ui.div_end()!!;
@ -383,3 +248,112 @@ fn int main(String[] args)
return 0; return 0;
} }
fn void debug_app(ugui::Ctx* ui)
{
static bool toggle;
ui.div_begin({.w=-100})!!;
{
ui.layout_set_column()!!;
if (ui.button(icon:"tux")!!.mouse_press) {
io::printn("press button0");
toggle = !toggle;
}
//ui.layout_next_column()!!;
if (ui.button(label: "ciao", icon: "tick")!!.mouse_press) {
io::printn("press button1");
}
//ui.layout_next_column()!!;
if (ui.button()!!.mouse_release) {
io::printn("release button2");
}
ui.layout_set_row()!!;
ui.layout_next_row()!!;
static float rf, gf, bf, af;
ui.slider_ver({0,0,30,100}, &rf)!!;
ui.slider_ver({0,0,30,100}, &gf)!!;
ui.slider_ver({0,0,30,100}, &bf)!!;
ui.slider_ver({0,0,30,100}, &af)!!;
ui.layout_next_column()!!;
ui.text_unbounded("Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!;
ui.layout_next_column()!!;
ui.button("Continua!")!!;
ui.layout_next_row()!!;
static bool check;
ui.checkbox("", {}, &check, "tick")!!;
ui.checkbox("", {}, &check)!!;
ui.toggle("", {}, &toggle)!!;
};
ui.sprite("tux")!!;
static char[128] text_box = "ciao mamma";
static usz text_len = "ciao mamma".len;
ui.text_box({0,0,200,200}, text_box[..], &text_len)!!;
ui.div_end()!!;
ui.div_begin(ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
{
ui.layout_set_column()!!;
static float slider2 = 0.5;
if (ui.slider_ver({0,0,30,100}, &slider2)!!.update) {
io::printfn("other slider: %f", slider2);
}
ui.button()!!;
ui.button()!!;
ui.button()!!;
ui.button()!!;
if (toggle) {
ui.button()!!;
ui.button()!!;
ui.button()!!;
ui.button()!!;
}
ui.layout_next_column()!!;
ui.layout_set_row()!!;
static float f1, f2;
ui.slider_hor({0,0,100,30}, &f1)!!;
ui.slider_hor({0,0,100,30}, &f2)!!;
};
ui.div_end()!!;
}
fn void calculator(ugui::Ctx* ui)
{
ui.div_begin(ugui::DIV_FILL)!!;
ui.layout_set_row()!!;
ui.div_begin({0,0,-300,50})!!;
ui.text_unbounded("80085")!!;
ui.div_end()!!;
ui.layout_next_row()!!;
ui.button("7")!!;
ui.button("8")!!;
ui.button("9")!!;
ui.layout_next_row()!!;
ui.button("4")!!;
ui.button("5")!!;
ui.button("6")!!;
ui.layout_next_row()!!;
ui.button("3")!!;
ui.button("2")!!;
ui.button("1")!!;
ui.layout_next_row()!!;
ui.button(".")!!;
ui.button("0")!!;
ui.button("")!!;
ui.layout_next_column()!!;
ui.layout_set_column()!!;
ui.button("+")!!;
ui.button("-")!!;
ui.button("*")!!;
ui.button("/")!!;
ui.div_end()!!;
}