Compare commits

...

11 Commits

14 changed files with 458 additions and 427 deletions

37
TODO
View File

@ -15,10 +15,12 @@ to maintain focus until mouse release (fix scroll bars)
[x] 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()
[x] Sort command buffer on insertion [x] Sort command buffer on insertion
[x] 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
@ -39,18 +41,27 @@ to maintain focus until mouse release (fix scroll bars)
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 [x] Fix how padding is applied in push_rect. In CSS padding is applied between the border and the
border is effectively the border size plus the padding since there is a gap between the border content, the background color is applied starting from the border. Right now push_rect() offsets
rect and the internal rect. A better solution is to leave it up to the renderer to draw the rect the background rect by both border and padding
correctly [ ] Investigate why the debug pointer (cyan rectangle) disappears...
## Layout ## Layout
[x] Flexbox [x] Flexbox
[ ] Center elements to the row/column [ ] Center elements to the row/column
[ ] Text wrapping / reflow [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
@ -59,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
@ -67,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
@ -91,11 +105,6 @@ to maintain focus until mouse release (fix scroll bars)
[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

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,15 +28,17 @@ 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;
@ -44,8 +46,8 @@ fn ElemEvents? Ctx.slider_hor_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.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;
} }
@ -93,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;
@ -109,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;
} }

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

View File

@ -4,38 +4,27 @@ default {
primary: #cc241dff; primary: #cc241dff;
secondary: #458588ff; secondary: #458588ff;
accent: #fabd2fff; accent: #fabd2fff;
border: 1;
} }
button { button {
margin: 2 2 2 2; margin: 2;
border: 2 2 2 2; border: 2;
padding: 1 1 1 1; padding: 2;
radius: 10; radius: 10;
size: 32;
bg: #3c3836ff; bg: #3c3836ff;
fg: #fbf1c7ff; fg: #fbf1c7ff;
primary: #cc241dff; primary: #cc241dff;
secondary: #458588ff; secondary: #458588ff;
accent: #fabd2fff; accent: #504945ff;
}
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 { checkbox {
margin: 2 2 2 2; margin: 2;
border: 2 2 2 2; border: 2;
padding: 1 1 1 1; padding: 1;
radius: 10; radius: 10;
size: 16; size: 16;
bg: #3c3836ff; bg: #3c3836ff;
@ -46,9 +35,9 @@ checkbox {
} }
toggle { toggle {
margin: 2 2 2 2; margin: 2;
border: 2 2 2 2; border: 2;
padding: 1 1 1 1; padding: 1;
radius: 10; radius: 10;
size: 16; size: 16;
bg: #3c3836ff; bg: #3c3836ff;
@ -59,9 +48,9 @@ toggle {
} }
slider { slider {
margin: 2 2 2 2; margin: 2;
padding: 2 2 2 2; padding: 2;
border: 1 1 1 1; border: 1;
radius: 4; radius: 4;
size: 8; size: 8;
bg: #3c3836ff; bg: #3c3836ff;

View File

@ -119,7 +119,6 @@ fn int main(String[] args)
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;
@ -199,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();
@ -279,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()!!;
@ -310,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()!!;
}