From e3c0bac9ca23f03422b6f8f23145b9331dd85fba Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 13 Oct 2025 23:55:41 +0200 Subject: [PATCH] rewrote string layout --- lib/ugui.c3l/src/font.c3 | 299 --------------------- lib/ugui.c3l/src/shapes.c3 | 1 - lib/ugui.c3l/src/string.c3 | 441 +++++++++++++++++++++++++++++++ lib/ugui.c3l/src/textedit.c3 | 5 + lib/ugui.c3l/src/widgets/text.c3 | 15 +- src/main.c3 | 3 +- 6 files changed, 454 insertions(+), 310 deletions(-) diff --git a/lib/ugui.c3l/src/font.c3 b/lib/ugui.c3l/src/font.c3 index fd2cab7..3362a6a 100644 --- a/lib/ugui.c3l/src/font.c3 +++ b/lib/ugui.c3l/src/font.c3 @@ -1,7 +1,6 @@ module ugui; import schrift; -import grapheme; import std::collections::map; import std::core::mem; import std::core::mem::allocator; @@ -16,22 +15,6 @@ import std::ascii; // unicode code point, different type for a different hash alias Codepoint = uint; -<* -@require str.ptr != null: "string pointer must be non null" -@param [in] str -@param [&inout] off -*> -fn Codepoint str_to_codepoint(char[] str, usz* off) -{ - Codepoint cp; - isz b = grapheme::decode_utf8(str, str.len, (uint*)&cp); - if (b == 0 || b > str.len) { - return 0; - } - *off = b; - return cp; -} - //macro uint Codepoint.hash(self) => ((uint)self).hash(); // ---------------------------------------------------------------------------------- // @@ -236,285 +219,3 @@ fn Atlas*? Ctx.get_font_atlas(&ctx, String name) <* @param [&in] font *> fn int Font.line_height(&font) => (int)(font.ascender - font.descender + (float)0.5); - -// ---------------------------------------------------------------------------------- // -// TEXT MEASUREMENT // -// ---------------------------------------------------------------------------------- // - - -const uint TAB_SIZE = 4; - -struct TextSize { - Size width, height; - int area; -} - -// Measeure the size of a string. -// width.min: as if each word is broken up by a new line -// width.max: the width of the string left as-is -// height.min: the height of the string left as-is -// height.max: the height of the string with each word broken up by a new line -<* -@param [&in] ctx -@param [in] text -*> -fn TextSize? Ctx.measure_string(&ctx, String text) -{ - if (text == "") return (TextSize){}; - - Font* font = &ctx.font; - short baseline = (short)font.ascender; - short line_height = (short)font.line_height(); - short line_gap = (short)font.linegap; - short space_width = font.get_glyph(' ').adv!; - short tab_width = space_width * TAB_SIZE; - - isz off; - usz x; - - TextSize ts; - - short word_width; - short words = 1; - Rect bounds; // unaltered text bounds; - Point origin; - - Codepoint cp = str_to_codepoint(text[off..], &x); - for (; cp != 0; cp = str_to_codepoint(text[off..], &x)) { - off += x; - Glyph* gp = font.get_glyph(cp)!; - - // update the text bounds - switch { - case cp == '\n': - origin.x = 0; - origin.y += line_height + line_gap; - case cp == '\t': - origin.x += tab_width; - case ascii::is_cntrl((char)cp): - break; - default: - Rect b = { - .x = origin.x + gp.ox, - .y = origin.y + gp.oy + baseline, - .w = gp.w, - .h = gp.h, - }; - bounds = containing_rect(bounds, b); - origin.x += gp.adv; - } - - // update the word width - switch { - case ascii::is_space((char)cp): - if (word_width > ts.width.min) ts.width.min = word_width; - word_width = 0; - words++; - default: - //word_width += gp.w + gp.ox; - if (off < text.len) { - word_width += gp.adv; - } else { - word_width += gp.w + gp.ox; - } - } - } - // end of string is also end of word - if (word_width > ts.width.min) ts.width.min = word_width; - - ts.width.max = bounds.w; - ts.height.min = bounds.h; - ts.height.max = words * line_height + line_gap * (words-1); - ts.area = bounds.w * bounds.h; - - return ts; -} - -// layout a string inside a bounding box, following the given alignment (anchor). -// returns the position of the cursor, the returned height is the line height and the width is the last -// character's advance value -// TODO: implement a "reflow" flag to toggle reflow if a character goes out of bounds -// TODO: also return the total bounds of the laid out string -// TODO: Following improvements -// [ ] struct LineInfo which stores the line information, like offset, width and height, during -// the measurement stage each line is scanned and pushed to the list, then to layout -// simply pop each line. The LineInfo list can be stored on the stack with the tmem allocator -// [ ] if the alignment is TOP_LEFT we can simply skip measuring the string, opting to layout -// it directly -// [ ] implement a macro to fetch and layout each character, this can be used to reduce code -// repetition both here and in measure_string -// [ ] the cursor position can be determined by a separate function like get_cursor_position() -// this way the function can terminate early if the cursor position is found -// [ ] implement a function hit_test_string() to get the character position at point, this can -// be used to implement mouse interactions, like cursor movement and selection -<* -@param [&in] ctx -@param [in] text -*> -fn Rect? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_index, Color hue, isz cursor = -1) -{ - Font* font = &ctx.font; - short line_height = (short)font.line_height(); - Rect cursor_rect = {.h = line_height}; - - if (bounds.w <= 0 || bounds.h <= 0) return cursor_rect; - ctx.push_scissor(bounds, z_index)!; - - if (text == "") { - // when text is empty but we need to draw a cursor use a visually empty string - if (cursor >= 0) { - text = "\f"; - } else { - return cursor_rect; - } - } - - Id texture_id = font.id; - short baseline = (short)font.ascender; - short line_gap = (short)font.linegap; - short space_width = font.get_glyph(' ').adv!; - short tab_width = space_width * TAB_SIZE; - - Point origin = bounds.position(); - - // measure string height inside the bounds to center it vertically - // FIXME: this is fast but it just gives back the height in LINES, most lines have only short - // characters which would result in a lower height - int string_height = line_height; - foreach (c: text) { - string_height += (line_height + line_gap) * (int)(c == '\n'); - } - - switch (anchor) { - case TOP_LEFT: nextcase; - case TOP: nextcase; - case TOP_RIGHT: - origin.y += 0; - case LEFT: nextcase; - case CENTER: nextcase; - case RIGHT: - origin.y += (short)(bounds.h - string_height)/2; - case BOTTOM_LEFT: nextcase; - case BOTTOM: nextcase; - case BOTTOM_RIGHT: - origin.y += (short)(bounds.h - string_height); - } - - // measure the line until it exits the bounds or the string ends - usz line_start, line_end; - do { - int line_width; - Point o = {.x = bounds.x, .y = bounds.y}; - - Codepoint cp; - isz off = line_start; - short first_off; - for ITER: (usz x; (cp = str_to_codepoint(text[off..], &x)) != 0; off += x) { - Glyph* gp = font.get_glyph(cp)!; - - switch { - case cp == '\n': - off += x; - break ITER; - case cp == '\t': - o.x += tab_width; - case ascii::is_cntrl((char)cp): - break; - default: - if (off == line_start) first_off = gp.ox; - - Rect b = { - .x = o.x + gp.ox, - .y = o.y + gp.oy + baseline, - .w = gp.w, - .h = gp.h, - }; - - if (b.x + b.w > bounds.x + bounds.w) { - break ITER; - } - o.x += gp.adv; - line_width += gp.adv; - } - } - line_end = off; - if (line_end == line_start) unreachable("something went wrong in measuring the line"); - - // with the line width calculate the right origin and layout the line - origin.x = bounds.x - first_off; - short next_line_x = bounds.x; // the x coordinate of the origin if the line_width is zero - switch (anchor) { - case TOP_LEFT: nextcase; - case LEFT: nextcase; - case BOTTOM_LEFT: - // TODO: we didn't need to measure the line width with this alignment - origin.x += 0; - next_line_x += 0; - case TOP: nextcase; - case CENTER: nextcase; - case BOTTOM: - origin.x += (short)(bounds.w - line_width)/2+1; - next_line_x += bounds.w/2; - case TOP_RIGHT: nextcase; - case RIGHT: nextcase; - case BOTTOM_RIGHT: - origin.x += (short)(bounds.w - line_width); - next_line_x += bounds.w; - } - - if (line_start <= cursor && cursor <= line_end) { - cursor_rect.x = origin.x; - cursor_rect.y = origin.y; - cursor_rect.w = 0; - } - - // see the fixme when measuring the height - //ctx.push_rect({.x = origin.x,.y=origin.y,.w=(short)line_width,.h=(short)string_height}, z_index, &&(Style){.bg=0xff000042u.@to_rgba()})!; - String line = text[line_start:line_end-line_start]; - off = 0; - for (usz x; (cp = str_to_codepoint(line[off..], &x)) != 0 && off < line.len; off += x) { - Glyph* gp = font.get_glyph(cp)!; - - switch { - case cp == '\t': - origin.x += tab_width; - case ascii::is_cntrl((char)cp): - break; - default: - Rect b = { - .x = origin.x + gp.ox, - .y = origin.y + gp.oy + baseline, - .w = gp.w, - .h = gp.h - }; - Rect uv = { - .x = gp.u, - .y = gp.v, - .w = gp.w, - .h = gp.h - }; - ctx.push_sprite(b, uv, texture_id, z_index, hue)!; - origin.x += gp.adv; - } - - if (line_start + off + x == cursor) { - cursor_rect.x = origin.x; - } - } - - // done with the line - line_start = line_end; - origin.y += line_height + line_gap; - - if (cursor == text.len && text[^1] == '\n') { - cursor_rect.x = next_line_x; - cursor_rect.y = origin.y; - } - - } while(line_end < text.len); - - - ctx.reset_scissor(z_index)!; - - return cursor_rect; -} diff --git a/lib/ugui.c3l/src/shapes.c3 b/lib/ugui.c3l/src/shapes.c3 index 822f259..9a22184 100644 --- a/lib/ugui.c3l/src/shapes.c3 +++ b/lib/ugui.c3l/src/shapes.c3 @@ -184,7 +184,6 @@ macro Rect Rect.expand(Rect a, Rect b) }; } - // ---------------------------------------------------------------------------------- // // POINT // // ---------------------------------------------------------------------------------- // diff --git a/lib/ugui.c3l/src/string.c3 b/lib/ugui.c3l/src/string.c3 index d7d9e1e..6f41030 100644 --- a/lib/ugui.c3l/src/string.c3 +++ b/lib/ugui.c3l/src/string.c3 @@ -1,4 +1,445 @@ module ugui; +import std::collections::list; +import std::core::string; + +struct LineInfo @local { + usz start, end; + short width, height; + short first_off; // first character offset +} + +alias LineStack @local = list::List{LineInfo}; + +fn short Rect.y_off(Rect bounds, short height, Anchor anchor) @local +{ + short off; + + switch (anchor) { + case TOP_LEFT: nextcase; + case TOP: nextcase; + case TOP_RIGHT: + off = 0; + case LEFT: nextcase; + case CENTER: nextcase; + case RIGHT: + off = (short)(bounds.h - height)/2; + case BOTTOM_LEFT: nextcase; + case BOTTOM: nextcase; + case BOTTOM_RIGHT: + off = (short)(bounds.h - height); + } + return off; +} + +fn short Rect.x_off(Rect bounds, short width, Anchor anchor) @local +{ + short off; + + switch (anchor) { + case TOP_LEFT: nextcase; + case LEFT: nextcase; + case BOTTOM_LEFT: + off = 0; + case TOP: nextcase; + case CENTER: nextcase; + case BOTTOM: + off = (short)(bounds.w - width)/2; + case TOP_RIGHT: nextcase; + case RIGHT: nextcase; + case BOTTOM_RIGHT: + off = (short)(bounds.w - width); + } + + return off; +} + + +// ---------------------------------------------------------------------------------- // +// STRING LAYOUT // +// ---------------------------------------------------------------------------------- // + + +<* +@param[&in] ctx +@param[in] text +*> +fn void? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_index, Color hue, bool reflow = false) +{ + if (anchor == TOP_LEFT) { + return ctx.layout_string_topleft(text, bounds, z_index, hue, reflow); + } else { + return ctx.layout_string_aligned(text, bounds, anchor, z_index, hue, reflow); + } +} + +// layout a string inside a bounding box, following the given alignment (anchor). +// TODO: Following improvements +// [ ] implement a macro to fetch and layout each character, this can be used to reduce code +// repetition both here and in measure_string +// [ ] the cursor position can be determined by a separate function like get_cursor_position() +// this way the function can terminate early if the cursor position is found +// [ ] implement a function hit_test_string() to get the character position at point, this can +// be used to implement mouse interactions, like cursor movement and selection +<* +@param [&in] ctx +@param [in] text +*> +fn void? Ctx.layout_string_aligned(&ctx, String text, Rect bounds, Anchor anchor, int z_index, Color hue, bool reflow = false) +{ + if (text == "") return; + if (bounds.w <= 0 || bounds.h <= 0) return; + ctx.push_scissor(bounds, z_index)!; + + Font* font = &ctx.font; + Id texture_id = font.id; + short line_height = (short)font.line_height(); + short baseline = (short)font.ascender; + short line_gap = (short)font.linegap; + short space_width = font.get_glyph(' ').adv!; + short tab_width = space_width * TAB_SIZE; + + Point origin = bounds.position(); + + LineStack lines; + lines.init(tmem, 1); + + Rect str_bounds; + + usz line_start; + LineInfo li; + Point o; + StringIterator ti = text.iterator(); + for (Codepoint cp; ti.has_next();) { + // FIXME: what if the interface changes? + cp = ti.next()!; + usz off = ti.current; + Glyph* gp = font.get_glyph(cp)!; + bool push = false; + + switch { + case cp == '\n': + push = true; + case cp == '\t': + o.x += tab_width; + case ascii::is_cntrl((char)cp): + break; + default: + if (off == line_start) li.first_off = gp.ox; + + Rect b = { + .x = o.x + gp.ox, + .y = o.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, + }; + + if (reflow && b.x + b.w > bounds.w) { + li.width += gp.ox + gp.w; + li.height = line_height; + push = true; + } else { + o.x += gp.adv; + li.width += gp.adv; + li.height = line_height; + } + } + + if (push) { + li.start = line_start; + li.end = off; + lines.push(li); + str_bounds.w = max(str_bounds.w, li.width); + str_bounds.h += li.height; + + o.x = 0; + o.y += line_height; + line_start = off; + + li.height = 0; + li.width = 0; + } + } + // FIXME: crap + li.start = line_start; + li.end = ti.current; + lines.push(li); + str_bounds.w = max(str_bounds.w, li.width); + str_bounds.h += li.height; + + // account for the line gap + str_bounds.h += (short)(lines.len() - 1)*line_gap; + + o = bounds.position(); + o.y += bounds.y_off(str_bounds.h, anchor); + foreach (idx, line : lines) { + o.x = bounds.x + bounds.x_off(line.width, anchor) - line.first_off; + + StringIterator s = text[line.start:line.end-line.start].iterator(); + for (Codepoint cp; s.has_next();) { + cp = s.next()!; + Glyph* gp = font.get_glyph(cp)!; + + switch { + case cp == '\n': + break; + case cp == '\t': + o.x += tab_width; + case ascii::is_cntrl((char)cp): + break; + default: + Rect b = { + .x = o.x + gp.ox, + .y = o.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, + }; + Rect uv = { + .x = gp.u, + .y = gp.v, + .w = gp.w, + .h = gp.h + }; + + ctx.push_sprite(b, uv, texture_id, z_index, hue)!; + o.x += gp.adv; + } + } + o.y += line.height + line_gap; + } + + ctx.reset_scissor(z_index)!; +// ctx.dbg_rect(str_bounds.off(bounds.position())); +} + +// layout a string inside a bounding box, with TOP_LEFT alignment. +<* +@param[&in] ctx +@param[in] text +*> +fn void? Ctx.layout_string_topleft(&ctx, String text, Rect bounds, int z_index, Color hue, bool reflow = false) +{ + if (text == "") return; + if (bounds.w <= 0 || bounds.h <= 0) return; + ctx.push_scissor(bounds, z_index)!; + + Font* font = &ctx.font; + Id texture_id = font.id; + short line_height = (short)font.line_height(); + short baseline = (short)font.ascender; + short line_gap = (short)font.linegap; + short space_width = font.get_glyph(' ').adv!; + short tab_width = space_width * TAB_SIZE; + + Point o = bounds.position(); + StringIterator it = text.iterator(); + for (Codepoint cp; it.has_next();) { + cp = it.next()!; + Glyph* gp = font.get_glyph(cp)!; + + switch { + case cp == '\n': + o.y += line_height; + o.x = bounds.x; + break; + case cp == '\t': + o.x += tab_width; + case ascii::is_cntrl((char)cp): + break; + default: + Rect b = { + .x = o.x + gp.ox, + .y = o.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, + }; + Rect uv = { + .x = gp.u, + .y = gp.v, + .w = gp.w, + .h = gp.h + }; + + if (reflow && b.x + b.w > bounds.x + bounds.w) { + o.y += line_height + line_gap; + o.x = bounds.x; + b.x = o.x + gp.ox; + b.y = o.y + gp.oy + baseline; + } + + ctx.push_sprite(b, uv, texture_id, z_index, hue)!; + o.x += gp.adv; + } + } + + ctx.reset_scissor(z_index)!; +} + + +// ---------------------------------------------------------------------------------- // +// CURSOR AND MOUSE // +// ---------------------------------------------------------------------------------- // + + +fn Rect? Ctx.get_cursor_position(&ctx, String text, Rect bounds, Anchor anchor, usz cursor, bool reflow = false) +{ + if (anchor != TOP_LEFT) { + unreachable("TODO: anchor has to be TOP_LEFT"); + } + + if (bounds.w <= 0 || bounds.h <= 0) return {}; + if (text == "") text = "\f"; + + Font* font = &ctx.font; + Id texture_id = font.id; + short line_height = (short)font.line_height(); + short baseline = (short)font.ascender; + short line_gap = (short)font.linegap; + short space_width = font.get_glyph(' ').adv!; + short tab_width = space_width * TAB_SIZE; + + Rect cursor_rect; + cursor_rect.x = bounds.x; + cursor_rect.y = bounds.y; + cursor_rect.h = line_height; + + if (cursor == 0) return cursor_rect; + + Point o = bounds.position(); + StringIterator it = text.iterator(); + usz off; + for (Codepoint cp; it.has_next();) { + cp = it.next()!; + off = it.current; + Glyph* gp = font.get_glyph(cp)!; + + switch { + case cp == '\n': + o.y += line_height; + o.x = bounds.x; + break; + case cp == '\t': + o.x += tab_width; + case ascii::is_cntrl((char)cp): + break; + default: + Rect b = { + .x = o.x + gp.ox, + .y = o.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, + }; + + if (reflow && b.x + b.w > bounds.x + bounds.w) { + o.y += line_height + line_gap; + o.x = bounds.x; + b.x = o.x + gp.ox; + b.y = o.y + gp.oy + baseline; + } + + o.x += gp.adv; + } + if (off == cursor) { + cursor_rect.x = o.x; + cursor_rect.y = o.y; + return cursor_rect; + } + } + return {}; +} + + +// ---------------------------------------------------------------------------------- // +// TEXT MEASUREMENT // +// ---------------------------------------------------------------------------------- // + + +const uint TAB_SIZE = 4; + +struct TextSize { + Size width, height; + int area; +} + +// Measeure the size of a string. +// width.min: as if each word is broken up by a new line +// width.max: the width of the string left as-is +// height.min: the height of the string left as-is +// height.max: the height of the string with each word broken up by a new line +<* +@param [&in] ctx +@param [in] text +*> +fn TextSize? Ctx.measure_string(&ctx, String text) +{ + if (text == "") return (TextSize){}; + + Font* font = &ctx.font; + short baseline = (short)font.ascender; + short line_height = (short)font.line_height(); + short line_gap = (short)font.linegap; + short space_width = font.get_glyph(' ').adv!; + short tab_width = space_width * TAB_SIZE; + + isz off; + usz x; + + TextSize ts; + + short word_width; + short words = 1; + Rect bounds; // unaltered text bounds; + Point origin; + + StringIterator it = text.iterator(); + for (Codepoint cp; it.has_next();) { + cp = it.next()!; + Glyph* gp = font.get_glyph(cp)!; + + // update the text bounds + switch { + case cp == '\n': + origin.x = 0; + origin.y += line_height + line_gap; + case cp == '\t': + origin.x += tab_width; + case ascii::is_cntrl((char)cp): + break; + default: + Rect b = { + .x = origin.x + gp.ox, + .y = origin.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, + }; + bounds = containing_rect(bounds, b); + origin.x += gp.adv; + } + + // update the word width + switch { + case ascii::is_space((char)cp): + if (word_width > ts.width.min) ts.width.min = word_width; + word_width = 0; + words++; + default: + //word_width += gp.w + gp.ox; + if (off < text.len) { + word_width += gp.adv; + } else { + word_width += gp.w + gp.ox; + } + } + } + // end of string is also end of word + if (word_width > ts.width.min) ts.width.min = word_width; + + ts.width.max = bounds.w; + ts.height.min = bounds.h; + ts.height.max = words * line_height + line_gap * (words-1); + ts.area = bounds.w * bounds.h; + + return ts; +} diff --git a/lib/ugui.c3l/src/textedit.c3 b/lib/ugui.c3l/src/textedit.c3 index 16d1a9e..761e25e 100644 --- a/lib/ugui.c3l/src/textedit.c3 +++ b/lib/ugui.c3l/src/textedit.c3 @@ -74,6 +74,7 @@ fn bool Ctx.text_edit(&ctx, TextEdit* te) after -= how_many; } } + // FIXME: this still doesn't work if (mod.up) { // back up to previous line if (te.cursor > 0) { @@ -97,6 +98,10 @@ fn bool Ctx.text_edit(&ctx, TextEdit* te) after = te.chars - te.cursor; } } + + // TODO: mod.home + // TODO: mod.end + // TODO: selection with shift+arrows } return free == 0; diff --git a/lib/ugui.c3l/src/widgets/text.c3 b/lib/ugui.c3l/src/widgets/text.c3 index e7c4caf..5cb709d 100644 --- a/lib/ugui.c3l/src/widgets/text.c3 +++ b/lib/ugui.c3l/src/widgets/text.c3 @@ -39,9 +39,9 @@ fn void? Ctx.text_id(&ctx, Id id, String text) } -macro Ctx.text_box(&ctx, Size w, Size h, TextEdit* te, Anchor text_alignment = TOP_LEFT, ...) - => ctx.text_box_id(@compute_id($vasplat), w, h, te, text_alignment); -fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Size w, Size h, TextEdit* te, Anchor text_alignment) +macro Ctx.text_box(&ctx, Size w, Size h, TextEdit* te, Anchor text_alignment = TOP_LEFT, bool reflow = true, ...) + => ctx.text_box_id(@compute_id($vasplat), w, h, te, text_alignment, reflow); +fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Size w, Size h, TextEdit* te, Anchor text_alignment, bool reflow) { id = ctx.gen_id(id)!; @@ -76,16 +76,13 @@ fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Size w, Size h, TextEdit* te, Anchor Rect bg_bounds = elem.bounds.pad(style.margin); Rect text_bounds = elem.bounds.pad(elem.layout.content_offset); ctx.push_rect(bg_bounds, parent.z_index, style)!; - Rect cur; - cur = ctx.layout_string(elem.text.te.to_string(), text_bounds, text_alignment, parent.z_index, style.fg, elem.text.te.cursor)!; + ctx.layout_string(elem.text.te.to_string(), text_bounds, text_alignment, parent.z_index, style.fg, reflow)!; // draw the cursor if the element has focus - cur.w = 2; - cur.x -= 2; if (elem.events.has_focus) { - ctx.push_scissor(text_bounds, parent.z_index)!; + Rect cur = ctx.get_cursor_position(elem.text.te.to_string(), text_bounds, text_alignment, elem.text.te.cursor, reflow)!; + cur.w = 2; ctx.push_rect(cur, parent.z_index, &&(Style){.bg = style.fg})!; - ctx.reset_scissor(parent.z_index)!; } return elem.events; } \ No newline at end of file diff --git a/src/main.c3 b/src/main.c3 index 90b068a..e911781 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -245,6 +245,7 @@ $endswitch // wait for the next event, timeout after 100ms int timeout = LIMIT_FPS ? (int)(100.0-sleep_clock.mark().to_ms()-0.5) : 0; + if (ui.skip_frame) timeout = 0; sdl::wait_event_timeout(&e, timeout); fps = 1.0 / fps_clock.mark().to_sec(); @@ -459,7 +460,7 @@ fn void calculator(ugui::Ctx* ui, TextEdit* te) ui.slider_ver(ugui::@exact(20), ugui::@exact(100), &f)!!; }!!; ui.@div(ugui::@grow(), ugui::@fit(), anchor: CENTER) { - ui.text_box(ugui::@grow(), ugui::@exact(100), te, RIGHT)!!; + ui.text_box(ugui::@grow(), ugui::@exact(100), te, TOP_LEFT)!!; }!!; }!!; }!!; }