draw text correctly
This commit is contained in:
parent
db63b2c6b1
commit
c3a6390404
12
TODO
12
TODO
@ -44,19 +44,19 @@ to maintain focus until mouse release (fix scroll bars)
|
||||
[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...
|
||||
[x] Investigate why the debug pointer (cyan rectangle) disappears...
|
||||
|
||||
## Layout
|
||||
|
||||
[x] Flexbox
|
||||
[ ] Center elements to the row/column
|
||||
[x] Center elements to the row/column
|
||||
[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)
|
||||
[x] Consider a multi-pass recursive approach to layout (like https://github.com/nicbarker/clay)
|
||||
instead of the curren multi-frame approach.
|
||||
[ ] Implement column/row sizing (min, max)
|
||||
[ ] Implement a way to size the element as the current row/column size
|
||||
[x] Implement column/row sizing (min, max)
|
||||
[x] Implement a way to size the element as the current row/column size
|
||||
* +-------------+
|
||||
* | |
|
||||
* +-------------+
|
||||
@ -68,7 +68,7 @@ to maintain focus until mouse release (fix scroll bars)
|
||||
See the calculator example for why it is useful
|
||||
[ ] 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] 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()
|
||||
[x] Make functions to mark rows/columns as full, to fix the calculator demo
|
||||
|
@ -40,7 +40,6 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
|
||||
.x = icon_size.w + inner_pad, // text sizing is handled differently
|
||||
.y = icon_size.h + inner_pad,
|
||||
};
|
||||
//elem.layout.w = @fit(min_size);
|
||||
elem.layout.w = @fit(min_size);
|
||||
elem.layout.h = @fit(min_size);
|
||||
elem.layout.children.w = @exact(content_size.x);
|
||||
@ -82,7 +81,7 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
|
||||
ctx.push_sprite(icon_bounds, sprite.uv(), ctx.sprite_atlas.id, parent.div.z_index, type: sprite.type)!;
|
||||
}
|
||||
if (label != "") {
|
||||
ctx.push_string(text_bounds, label, parent.div.z_index, style.fg, true)!;
|
||||
ctx.layout_string(label, text_bounds, CENTER, parent.div.z_index, style.fg)!;
|
||||
}
|
||||
return elem.events;
|
||||
}
|
||||
|
@ -163,32 +163,6 @@ fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_i
|
||||
ctx.push_cmd(&cmd, z_index)!;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ctx.push_scissor(bounds, z_index)!;
|
||||
|
||||
Id texture_id = ctx.font.id; // or ctx.font.atlas.id
|
||||
Rect text_bounds = {bounds.x, bounds.y, 0, 0};
|
||||
|
||||
TextInfo ti;
|
||||
ti.init(&ctx.font, (String)text, bounds);
|
||||
|
||||
while (ti.place_glyph(reflow)!) {
|
||||
if (!cull_rect(ti.glyph_bounds, bounds)) {
|
||||
ctx.push_sprite(ti.glyph_bounds, ti.glyph_uv, texture_id, z_index, hue)!;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.reset_scissor(z_index)!;
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)
|
||||
{
|
||||
Cmd up = {
|
||||
|
@ -218,121 +218,6 @@ fn int Font.line_height(&font) => (int)(font.ascender - font.descender + (float)
|
||||
|
||||
const uint TAB_SIZE = 4;
|
||||
|
||||
// TODO: change the name
|
||||
// TODO: reorder to make smaller
|
||||
struct TextInfo {
|
||||
Font* font;
|
||||
Rect bounds;
|
||||
String text;
|
||||
usz off;
|
||||
Size width, height;
|
||||
|
||||
// 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)
|
||||
{
|
||||
TextInfo ti;
|
||||
ti.init(&ctx.font, text);
|
||||
while (ti.place_glyph(false)!);
|
||||
return ti.text_bounds;
|
||||
}
|
||||
|
||||
|
||||
struct TextSize {
|
||||
Size width, height;
|
||||
int area;
|
||||
@ -345,6 +230,8 @@ struct TextSize {
|
||||
// height.max: the height of the string with each word broken up by a new line
|
||||
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();
|
||||
@ -412,3 +299,134 @@ fn TextSize? Ctx.measure_string(&ctx, String text)
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
fn void? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_index, Color hue)
|
||||
{
|
||||
if (text.len == 0 || bounds.w <= 0 || bounds.h <= 0) return;
|
||||
ctx.push_scissor(bounds, z_index)!;
|
||||
|
||||
Font* font = &ctx.font;
|
||||
Id texture_id = font.id;
|
||||
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;
|
||||
|
||||
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;
|
||||
for ITER: (usz x; (cp = str_to_codepoint(text[off..], &x)) != 0; off += x) {
|
||||
Glyph* gp = font.get_glyph(cp)!;
|
||||
|
||||
switch {
|
||||
case cp == '\n':
|
||||
break ITER;
|
||||
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 (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) break;
|
||||
|
||||
// with the line width calculate the right origin and layout the line
|
||||
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;
|
||||
case TOP: nextcase;
|
||||
case CENTER: nextcase;
|
||||
case BOTTOM:
|
||||
origin.x += (short)(bounds.w - line_width)/2+1;
|
||||
case TOP_RIGHT: nextcase;
|
||||
case RIGHT: nextcase;
|
||||
case BOTTOM_RIGHT:
|
||||
origin.x += (short)(bounds.w - line_width);
|
||||
}
|
||||
|
||||
// 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)!;
|
||||
|
||||
// update the text bounds
|
||||
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)!;
|
||||
//ctx.push_rect(b, z_index, &&(Style){.bg=0x0000ff66u.@to_rgba()})!;
|
||||
origin.x += gp.adv;
|
||||
}
|
||||
|
||||
}
|
||||
// done with the line
|
||||
line_start = line_end;
|
||||
origin.y += line_height + line_gap;
|
||||
} while(line_end < text.len);
|
||||
|
||||
ctx.reset_scissor(z_index)!;
|
||||
}
|
||||
|
@ -3,16 +3,14 @@ module ugui;
|
||||
import std::io;
|
||||
|
||||
struct ElemText {
|
||||
String str;
|
||||
usz cursor; // cursor offset
|
||||
Id hash;
|
||||
Rect bounds;
|
||||
TextSize size;
|
||||
}
|
||||
|
||||
/*
|
||||
macro Ctx.text_unbounded(&ctx, String text, ...)
|
||||
=> ctx.text_unbounded_id(@compute_id($vasplat), text);
|
||||
fn void? Ctx.text_unbounded_id(&ctx, Id id, String text)
|
||||
macro Ctx.text(&ctx, String text, ...)
|
||||
=> ctx.text_id(@compute_id($vasplat), text);
|
||||
fn void? Ctx.text_id(&ctx, Id id, String text)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
@ -22,18 +20,22 @@ fn void? Ctx.text_unbounded_id(&ctx, Id id, String 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.size = ctx.measure_string(text)!;
|
||||
}
|
||||
elem.text.str = text;
|
||||
elem.text.hash = text_hash;
|
||||
|
||||
// 2. Layout
|
||||
elem.bounds = ctx.layout_element(parent, elem.text.bounds, style);
|
||||
if (elem.bounds.is_null()) { return; }
|
||||
elem.layout.w = @fit(style.size);
|
||||
elem.layout.h = @fit(style.size);
|
||||
elem.layout.text = elem.text.size;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
ctx.push_string(elem.bounds, text, parent.div.z_index, style.fg)!;
|
||||
update_parent_grow(elem, parent);
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
ctx.layout_string(text, elem.bounds.pad(elem.layout.content_offset), TOP_LEFT, parent.div.z_index, style.fg)!;
|
||||
}
|
||||
|
||||
/*
|
||||
macro Ctx.text_box(&ctx, Rect size, char[] text, usz* text_len, ...)
|
||||
=> ctx.text_box_id(@compute_id($vasplat), size, text, text_len);
|
||||
fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_len)
|
||||
|
@ -343,6 +343,7 @@ fn void calculator(ugui::Ctx* ui)
|
||||
case "/": nextcase;
|
||||
case "(": nextcase;
|
||||
case ")": nextcase;
|
||||
case ".": nextcase;
|
||||
case "0": nextcase;
|
||||
case "1": nextcase;
|
||||
case "2": nextcase;
|
||||
@ -367,7 +368,7 @@ fn void calculator(ugui::Ctx* ui)
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN, TOP_LEFT) {
|
||||
|
||||
ui.@div(ugui::@grow(), ugui::@exact(100), ROW, RIGHT) {
|
||||
ui.button("TODO")!!;
|
||||
ui.text((String)buffer[:len])!!;
|
||||
}!!;
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), ROW, TOP_LEFT) {
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
|
||||
|
Loading…
Reference in New Issue
Block a user