Compare commits
4 Commits
c3
...
correct-pa
Author | SHA1 | Date | |
---|---|---|---|
f409a67130 | |||
d0a8e8a1ff | |||
fa334ed154 | |||
b35bb427a7 |
39
TODO
39
TODO
@ -15,12 +15,10 @@ 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
|
[x] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it
|
||||||
of boiler plate and reuse it
|
[x] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division
|
||||||
[x] The id combination in gen_id() uses an intger division, which is costly, use another combination
|
|
||||||
function that is non-linear and doesn't use division
|
|
||||||
[ ] Animations, somehow
|
[ ] Animations, somehow
|
||||||
[x] Maybe cache codepoint converted strings
|
[ ] 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
|
||||||
@ -41,27 +39,20 @@ 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
|
||||||
[x] Fix how padding is applied in push_rect. In CSS padding is applied between the border and the
|
[ ] Border and padding do not go well together if the library issues two rect commands, the visible
|
||||||
content, the background color is applied starting from the border. Right now push_rect() offsets
|
border is effectively the border size plus the padding since there is a gap between the border
|
||||||
the background rect by both border and padding
|
rect and the internal rect. A better solution is to leave it up to the renderer to draw the rect
|
||||||
[ ] Investigate why the debug pointer (cyan rectangle) disappears...
|
correctly
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
[x] Flexbox
|
[x] Flexbox
|
||||||
|
[ ] For some reason padding is not correct, look at the sliders, they have 2px per side when the
|
||||||
|
theme specifies 4px per side
|
||||||
[ ] Center elements to the row/column
|
[ ] Center elements to the row/column
|
||||||
[x] Text wrapping / reflow
|
[ ] 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
|
||||||
|
|
||||||
@ -70,8 +61,6 @@ 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
|
||||||
|
|
||||||
@ -80,9 +69,8 @@ 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
|
||||||
[x] Text command returns the text bounds, this way we can avoid the pattern
|
[ ] 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
|
||||||
|
|
||||||
@ -105,6 +93,11 @@ 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
|
||||||
|
@ -8,79 +8,111 @@ struct ElemButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
macro Ctx.button(&ctx, String label = "", String icon = "", ...)
|
// draw a button, return the events on that button
|
||||||
=> ctx.button_id(@compute_id($vasplat), label, icon);
|
macro Ctx.button(&ctx, Rect size, bool state = false, ...)
|
||||||
fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
|
=> ctx.button_id(@compute_id($vasplat), size, state);
|
||||||
|
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 = ctx.styles.get_style(@str_hash("button"));
|
Style* style_norm = 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,
|
||||||
Rect text_size = ctx.get_text_bounds(label)!;
|
// no interaction should occur so just return
|
||||||
Rect icon_size = sprite.rect();
|
if (elem.bounds.is_null()) { return {}; }
|
||||||
|
|
||||||
ushort h_lh = (ushort)(ctx.font.line_height() / 2);
|
Style* style_active = ctx.styles.get_style(@str_hash("button-active"));
|
||||||
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;
|
||||||
|
|
||||||
Rect text_bounds = {
|
// Draw the button
|
||||||
.x = content_bounds.x + icon_size.w + left_pad + inner_pad,
|
ctx.push_rect(elem.bounds, parent.div.z_index, is_active ? style_active : style_norm)!;
|
||||||
.y = content_bounds.y,
|
|
||||||
.w = content_bounds.w - icon_size.w - left_pad - inner_pad - right_pad,
|
|
||||||
.h = content_bounds.h
|
|
||||||
};
|
|
||||||
text_bounds = text_size.center_to(text_bounds);
|
|
||||||
|
|
||||||
Rect icon_bounds = {
|
|
||||||
.x = content_bounds.x,
|
|
||||||
.y = content_bounds.y,
|
|
||||||
.w = icon_size.w + right_pad + inner_pad,
|
|
||||||
.h = (short)max(icon_size.h, content_bounds.h)
|
|
||||||
};
|
|
||||||
icon_bounds = icon_size.center_to(icon_bounds);
|
|
||||||
|
|
||||||
bool is_active = ctx.elem_focus(elem) || elem.events.mouse_hover;
|
|
||||||
Style s = *style;
|
|
||||||
if (is_active) {
|
|
||||||
s.secondary = s.primary;
|
|
||||||
s.bg = s.accent;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.push_rect(elem.bounds, parent.div.z_index, &s)!;
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 btn_size = text_size.add({0,0,10,10});
|
||||||
|
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"));
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Draw the button
|
||||||
|
text_size.x = elem.bounds.x;
|
||||||
|
text_size.y = elem.bounds.y;
|
||||||
|
Point off = ctx.center_text(text_size, elem.bounds);
|
||||||
|
text_size.x += off.x;
|
||||||
|
text_size.y += off.y;
|
||||||
|
ctx.push_rect(elem.bounds, parent.div.z_index, style)!;
|
||||||
|
ctx.push_string(text_size, label, parent.div.z_index, style.fg)!;
|
||||||
|
|
||||||
|
return elem.events;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Ctx.button_icon(&ctx, String icon, String on_icon = "", bool active = false, ...)
|
||||||
|
=> 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)
|
||||||
|
{
|
||||||
|
id = ctx.gen_id(id)!;
|
||||||
|
|
||||||
|
Elem *parent = ctx.get_parent()!;
|
||||||
|
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, style)!;
|
||||||
|
|
||||||
|
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 = {}, ...)
|
||||||
@ -94,7 +126,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.layout_element(parent, size, style);
|
elem.bounds = ctx.position_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
|
||||||
@ -115,7 +147,8 @@ fn void? Ctx.checkbox_id(&ctx, Id id, String description, Point off, bool* activ
|
|||||||
Rect check = elem.bounds.add({x, x, -x*2, -x*2});
|
Rect check = elem.bounds.add({x, x, -x*2, -x*2});
|
||||||
Style s = *style;
|
Style s = *style;
|
||||||
s.bg = s.primary;
|
s.bg = s.primary;
|
||||||
s.margin = s.border = s.padding = {};
|
s.margin = s.padding = {};
|
||||||
|
s.border = 0;
|
||||||
ctx.push_rect(check, parent.div.z_index, &s)!;
|
ctx.push_rect(check, parent.div.z_index, &s)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,7 +166,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.layout_element(parent, size, style);
|
elem.bounds = ctx.position_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
|
||||||
@ -148,6 +181,7 @@ fn void? Ctx.toggle_id(&ctx, Id id, String description, Point off, bool* active)
|
|||||||
Rect t = elem.bounds.add({*active ? (style.size+3) : +3, +3, -style.size-6, -6});
|
Rect t = elem.bounds.add({*active ? (style.size+3) : +3, +3, -style.size-6, -6});
|
||||||
Style s = *style;
|
Style s = *style;
|
||||||
s.bg = s.primary;
|
s.bg = s.primary;
|
||||||
s.margin = s.border = s.padding = {};
|
s.margin = s.padding = {};
|
||||||
|
s.border = 0;
|
||||||
ctx.push_rect(t, parent.div.z_index, &s)!;
|
ctx.push_rect(t, parent.div.z_index, &s)!;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ enum CmdType {
|
|||||||
// command to draw a rect
|
// command to draw a rect
|
||||||
struct CmdRect {
|
struct CmdRect {
|
||||||
Rect rect;
|
Rect rect;
|
||||||
|
ushort thickness;
|
||||||
ushort radius;
|
ushort radius;
|
||||||
Color color;
|
Color color;
|
||||||
}
|
}
|
||||||
@ -92,18 +93,19 @@ fn void? Ctx.push_scissor(&ctx, Rect rect, int z_index)
|
|||||||
|
|
||||||
fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
|
fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
|
||||||
{
|
{
|
||||||
Rect border = style.border;
|
Rect padding = style.padding;
|
||||||
|
ushort border = style.border;
|
||||||
ushort radius = style.radius;
|
ushort radius = style.radius;
|
||||||
Color bg = style.bg;
|
Color bg = style.bg;
|
||||||
Color border_color = style.secondary;
|
Color border_color = style.secondary;
|
||||||
|
|
||||||
// FIXME: this implies that the border has to be uniform
|
if (border != 0) {
|
||||||
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 + border.x,
|
.rect.radius = radius+border,
|
||||||
|
.rect.thickness = border,
|
||||||
};
|
};
|
||||||
ctx.push_cmd(&cmd, z_index)!;
|
ctx.push_cmd(&cmd, z_index)!;
|
||||||
}
|
}
|
||||||
@ -111,19 +113,19 @@ 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,
|
.x = rect.x + border + padding.x,
|
||||||
.y = rect.y + border.y,
|
.y = rect.y + border + padding.y,
|
||||||
.w = rect.w - (border.x+border.w),
|
.h = rect.h - (border*2) - (padding.y+padding.h),
|
||||||
.h = rect.h - (border.y+border.h),
|
.w = rect.w - (border*2) - (padding.x+padding.w),
|
||||||
},
|
},
|
||||||
.rect.color = bg,
|
.rect.color = bg,
|
||||||
.rect.radius = radius,
|
.rect.radius = radius,
|
||||||
|
.rect.thickness = max(rect.w, rect.h)/2+1,
|
||||||
};
|
};
|
||||||
if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return;
|
if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return;
|
||||||
ctx.push_cmd(&cmd, z_index)!;
|
ctx.push_cmd(&cmd, z_index)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 = {
|
||||||
@ -137,30 +139,56 @@ 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)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: do not return the WHOLE TextInfo but instead something smaller
|
fn void? Ctx.push_string(&ctx, Rect bounds, char[] text, int z_index, Color hue)
|
||||||
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
|
||||||
Rect text_bounds = {bounds.x, bounds.y, 0, 0};
|
Point orig = {
|
||||||
|
.x = bounds.x,
|
||||||
|
.y = bounds.y,
|
||||||
|
};
|
||||||
|
|
||||||
TextInfo ti;
|
short line_len;
|
||||||
ti.init(&ctx.font, (String)text, bounds);
|
Codepoint cp;
|
||||||
|
usz off, x;
|
||||||
while (ti.place_glyph(reflow)!) {
|
while (off < text.len && (cp = str_to_codepoint(text[off..], &x)) != 0) {
|
||||||
if (!cull_rect(ti.glyph_bounds, bounds)) {
|
off += x;
|
||||||
ctx.push_sprite(ti.glyph_bounds, ti.glyph_uv, texture_id, z_index, hue)!;
|
Glyph* gp;
|
||||||
|
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)
|
||||||
|
@ -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, WRONG_ELEMENT_TYPE, WRONG_ID;
|
faultdef INVALID_SIZE, EVENT_UNSUPPORTED, UNEXPECTED_ELEMENT, 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,10 +117,7 @@ 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)!;
|
||||||
Elem*? parent = ctx.cache.search(parent_id);
|
return 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;
|
||||||
@ -253,7 +250,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()!;
|
||||||
if (root.id != ROOT_ID) return WRONG_ID?;
|
root.div.layout = LAYOUT_ROW;
|
||||||
|
|
||||||
// 1. clear the tree
|
// 1. clear the tree
|
||||||
ctx.tree.nuke();
|
ctx.tree.nuke();
|
||||||
|
@ -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* elem = ctx.get_elem(id, ETYPE_DIV)!;
|
|
||||||
Elem* parent = ctx.get_parent()!;
|
Elem* parent = ctx.get_parent()!;
|
||||||
|
Elem* elem = ctx.get_elem(id, ETYPE_DIV)!;
|
||||||
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.layout_element(parent, wanted_size, style);
|
elem.bounds = ctx.position_element(parent, wanted_size, style);
|
||||||
elem.div.children_bounds = {};
|
elem.div.children_bounds = {};
|
||||||
|
|
||||||
// update the ctx scissor
|
// update the ctx scissor
|
||||||
@ -81,6 +81,7 @@ 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;
|
||||||
|
|
||||||
@ -149,7 +150,4 @@ 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;
|
|
||||||
}
|
}
|
||||||
|
@ -167,7 +167,6 @@ 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)
|
||||||
{
|
{
|
||||||
@ -180,119 +179,72 @@ 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)
|
||||||
{
|
{
|
||||||
TextInfo ti;
|
Rect text_bounds;
|
||||||
ti.init(&ctx.font, text);
|
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
|
||||||
while (ti.place_glyph(false)!);
|
short line_gap = (short)ctx.font.linegap;
|
||||||
return ti.text_bounds;
|
text_bounds.h = line_height;
|
||||||
|
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
|
||||||
|
@ -9,25 +9,51 @@ enum Layout {
|
|||||||
|
|
||||||
fn void? Ctx.layout_set_row(&ctx)
|
fn void? Ctx.layout_set_row(&ctx)
|
||||||
{
|
{
|
||||||
Elem *parent = ctx.get_parent()!;
|
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
Elem *parent = ctx.get_parent()!;
|
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
Elem *parent = ctx.get_parent()!;
|
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
Elem *parent = ctx.get_parent()!;
|
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||||
|
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,
|
||||||
@ -38,7 +64,12 @@ fn void? Ctx.layout_next_row(&ctx)
|
|||||||
|
|
||||||
fn void? Ctx.layout_next_column(&ctx)
|
fn void? Ctx.layout_next_column(&ctx)
|
||||||
{
|
{
|
||||||
Elem *parent = ctx.get_parent()!;
|
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||||
|
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,
|
||||||
@ -47,7 +78,6 @@ 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)
|
||||||
{
|
{
|
||||||
@ -76,7 +106,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.layout_element(&ctx, Elem *parent, Rect rect, Style* style)
|
fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, Style* style)
|
||||||
{
|
{
|
||||||
ElemDiv* div = &parent.div;
|
ElemDiv* div = &parent.div;
|
||||||
|
|
||||||
@ -114,22 +144,22 @@ fn Rect Ctx.layout_element(&ctx, Elem *parent, Rect rect, Style* style)
|
|||||||
child_occupied = child_occupied.off(origin.add(rect.position()));
|
child_occupied = child_occupied.off(origin.add(rect.position()));
|
||||||
|
|
||||||
Rect margin = style.margin;
|
Rect margin = style.margin;
|
||||||
Rect border = style.border;
|
|
||||||
Rect padding = style.padding;
|
Rect padding = style.padding;
|
||||||
|
ushort border = style.border;
|
||||||
|
|
||||||
// padding, grows both the placement and occupied area
|
// padding, grows both the placement and occupied area
|
||||||
child_placement = child_placement.grow(padding.position().add(padding.size()));
|
child_placement = child_placement.grow(padding.position().add(padding.size()));
|
||||||
child_occupied = child_occupied.grow(padding.position().add(padding.size()));
|
child_occupied = child_occupied.grow(padding.position().add(padding.size()));
|
||||||
// border, grows both the placement and occupied area
|
// border, grows both the placement and occupied area
|
||||||
child_placement = child_placement.grow(border.position().add(border.size()));
|
child_placement = child_placement.grow({border*2, border*2});
|
||||||
child_occupied = child_occupied.grow(border.position().add(border.size()));
|
child_occupied = child_occupied.grow({border*2, border*2});
|
||||||
// margin, offsets the placement and grows the occupied area
|
// margin, offsets the placement and grows the occupied area
|
||||||
child_placement = child_placement.off(margin.position());
|
child_placement = child_placement.off(margin.position());
|
||||||
child_occupied = child_occupied.grow(margin.position().add(margin.size()));
|
child_occupied = child_occupied.grow(margin.position().add(margin.size()));
|
||||||
|
|
||||||
// oh yeah also adjust the rect if i was to grow
|
// oh yeah also adjust the rect if i was to grow
|
||||||
if (adapt_x) rect.w -= padding.x+padding.w + border.x+border.w + margin.x+margin.w;
|
if (adapt_x) rect.w -= padding.x+padding.w + border*2 + margin.x+margin.w;
|
||||||
if (adapt_y) rect.h -= padding.y+padding.h + border.y+border.h + margin.y+margin.h;
|
if (adapt_y) rect.h -= padding.y+padding.h + border*2 + margin.y+margin.h;
|
||||||
|
|
||||||
// set the size
|
// set the size
|
||||||
child_placement = child_placement.grow(rect.size());
|
child_placement = child_placement.grow(rect.size());
|
||||||
@ -146,7 +176,16 @@ fn Rect Ctx.layout_element(&ctx, Elem *parent, Rect rect, Style* style)
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 5. Update the parent's children bounds
|
// 5. Update the parent's children bounds
|
||||||
div.children_bounds = containing_rect(div.children_bounds, child_occupied);
|
if (!child_occupied.bottom_right().in_rect(div.children_bounds)) {
|
||||||
|
// 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)) {
|
||||||
|
@ -10,9 +10,6 @@ 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)
|
||||||
{
|
{
|
||||||
@ -36,21 +33,6 @@ 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;
|
||||||
|
|
||||||
@ -154,26 +136,6 @@ 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 //
|
||||||
|
@ -28,17 +28,15 @@ 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.layout_element(parent, size, style);
|
elem.bounds = ctx.position_element(parent, size, style);
|
||||||
if (elem.bounds.is_null()) return {};
|
|
||||||
Rect content_bounds = elem.content_bounds(style);
|
|
||||||
|
|
||||||
// handle width
|
// handle width
|
||||||
short hw = (short)(content_bounds.w * hpercent);
|
short hw = (short)(elem.bounds.w * hpercent);
|
||||||
Rect handle = {
|
Rect handle = {
|
||||||
.x = calc_slider(content_bounds.x, content_bounds.w-hw, *value),
|
.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value),
|
||||||
.y = content_bounds.y,
|
.y = elem.bounds.y,
|
||||||
.w = hw,
|
.w = hw,
|
||||||
.h = content_bounds.h,
|
.h = elem.bounds.h,
|
||||||
};
|
};
|
||||||
elem.slider.handle = handle;
|
elem.slider.handle = handle;
|
||||||
|
|
||||||
@ -46,8 +44,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(content_bounds.x, m.x, content_bounds.w, hw);
|
*value = calc_value(elem.bounds.x, m.x, elem.bounds.w, hw);
|
||||||
elem.slider.handle.x = calc_slider(content_bounds.x, content_bounds.w-hw, *value);
|
elem.slider.handle.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value);
|
||||||
elem.events.update = true;
|
elem.events.update = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,16 +93,14 @@ fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Rect size, float* value, float hpe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Layout
|
// 2. Layout
|
||||||
elem.bounds = ctx.layout_element(parent, size, style);
|
elem.bounds = ctx.position_element(parent, size, style);
|
||||||
if (elem.bounds.is_null()) return {};
|
|
||||||
Rect content_bounds = elem.content_bounds(style);
|
|
||||||
|
|
||||||
// handle height
|
// handle height
|
||||||
short hh = (short)(content_bounds.h * hpercent);
|
short hh = (short)(elem.bounds.h * hpercent);
|
||||||
Rect handle = {
|
Rect handle = {
|
||||||
.x = content_bounds.x,
|
.x = elem.bounds.x,
|
||||||
.y = calc_slider(content_bounds.y, content_bounds.h-hh, *value),
|
.y = calc_slider(elem.bounds.y, elem.bounds.h-hh, *value),
|
||||||
.w = content_bounds.w,
|
.w = elem.bounds.w,
|
||||||
.h = hh,
|
.h = hh,
|
||||||
};
|
};
|
||||||
elem.slider.handle = handle;
|
elem.slider.handle = handle;
|
||||||
@ -113,8 +109,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(content_bounds.y, m.y, content_bounds.h, hh);
|
*value = calc_value(elem.bounds.y, m.y, elem.bounds.h, hh);
|
||||||
elem.slider.handle.y = calc_slider(content_bounds.y, content_bounds.h-hh, *value);
|
elem.slider.handle.y = calc_slider(elem.bounds.y, elem.bounds.h-hh, *value);
|
||||||
elem.events.update = true;
|
elem.events.update = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.layout_element(parent, bounds.off(off), style);
|
elem.bounds = ctx.position_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,
|
||||||
|
@ -7,8 +7,10 @@ import std::io;
|
|||||||
// global style, similar to the css box model
|
// global style, similar to the css box model
|
||||||
struct Style { // css box model
|
struct Style { // css box model
|
||||||
Rect padding;
|
Rect padding;
|
||||||
Rect border;
|
|
||||||
Rect margin;
|
Rect margin;
|
||||||
|
ushort border;
|
||||||
|
ushort radius;
|
||||||
|
ushort size;
|
||||||
|
|
||||||
Color bg; // background color
|
Color bg; // background color
|
||||||
Color fg; // foreground color
|
Color fg; // foreground color
|
||||||
@ -16,14 +18,12 @@ struct Style { // css box model
|
|||||||
Color secondary; // secondary color
|
Color secondary; // secondary color
|
||||||
Color accent; // accent color
|
Color accent; // accent color
|
||||||
|
|
||||||
ushort radius;
|
|
||||||
short size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Style DEFAULT_STYLE = {
|
const Style DEFAULT_STYLE = {
|
||||||
.margin = {2, 2, 2, 2},
|
.margin = {2, 2, 2, 2},
|
||||||
.border = {2, 2, 2, 2},
|
.padding = {},
|
||||||
.padding = {1, 1, 1, 1},
|
.border = 2,
|
||||||
.radius = 12,
|
.radius = 12,
|
||||||
.size = 16,
|
.size = 16,
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ fn int Ctx.import_style_from_file(&ctx, String path)
|
|||||||
* Style can be serialized and deserialized with a subset of CSS
|
* Style can be serialized and deserialized with a subset of CSS
|
||||||
* <style name> {
|
* <style name> {
|
||||||
* padding: left right top bottom;
|
* padding: left right top bottom;
|
||||||
* border: left right top bottom;
|
|
||||||
* margin: left right top bottoms;
|
* margin: left right top bottoms;
|
||||||
|
* border: uint;
|
||||||
* radius: uint;
|
* radius: uint;
|
||||||
* size: uint;
|
* size: uint;
|
||||||
* Color: #RRGGBBAA;
|
* Color: #RRGGBBAA;
|
||||||
@ -115,10 +115,7 @@ import std::io;
|
|||||||
enum TokenType {
|
enum TokenType {
|
||||||
INVALID,
|
INVALID,
|
||||||
IDENTIFIER,
|
IDENTIFIER,
|
||||||
RCURLY,
|
PUNCT,
|
||||||
LCURLY,
|
|
||||||
SEMICOLON,
|
|
||||||
COLON,
|
|
||||||
NUMBER,
|
NUMBER,
|
||||||
COLOR,
|
COLOR,
|
||||||
EOF,
|
EOF,
|
||||||
@ -194,15 +191,9 @@ 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;
|
||||||
@ -285,6 +276,17 @@ 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();
|
||||||
@ -304,7 +306,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(&t, LCURLY) == false) return false;
|
if (p.expect_text(&t, PUNCT, "{") == false) return false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (p.parse_property() == false) return false;
|
if (p.parse_property() == false) return false;
|
||||||
@ -312,7 +314,7 @@ fn bool Parser.parse_style(&p)
|
|||||||
if (t.type != IDENTIFIER) break;
|
if (t.type != IDENTIFIER) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.expect(&t, RCURLY) == false) return false;
|
if (p.expect_text(&t, PUNCT, "}") == false) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -321,7 +323,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(&t, COLON) == false) return false;
|
if (p.expect_text(&t, PUNCT, ":") == false) return false;
|
||||||
|
|
||||||
switch (prop.text) {
|
switch (prop.text) {
|
||||||
case "padding":
|
case "padding":
|
||||||
@ -329,11 +331,6 @@ fn bool Parser.parse_property(&p)
|
|||||||
if (p.parse_size(&padding) == false) return false;
|
if (p.parse_size(&padding) == false) return false;
|
||||||
p.style.padding = padding;
|
p.style.padding = padding;
|
||||||
|
|
||||||
case "border":
|
|
||||||
Rect border;
|
|
||||||
if (p.parse_size(&border) == false) return false;
|
|
||||||
p.style.border = border;
|
|
||||||
|
|
||||||
case "margin":
|
case "margin":
|
||||||
Rect margin;
|
Rect margin;
|
||||||
if (p.parse_size(&margin) == false) return false;
|
if (p.parse_size(&margin) == false) return false;
|
||||||
@ -364,30 +361,39 @@ fn bool Parser.parse_property(&p)
|
|||||||
if (p.parse_color(&accent) == false) return false;
|
if (p.parse_color(&accent) == false) return false;
|
||||||
p.style.accent = accent;
|
p.style.accent = accent;
|
||||||
|
|
||||||
case "radius":
|
case "border":
|
||||||
short r;
|
short border;
|
||||||
if (p.parse_number(&r) == false) return false;
|
if (p.parse_number(&border) == false) return false;
|
||||||
if (r < 0) {
|
if (border < 0) {
|
||||||
io::eprintfn("CSS parsing error at %d:%d: 'radius' must be a positive number, got %d", t.line, t.col, r);
|
io::eprintfn("CSS parsing error at %d:%d: 'border' must be a positive number, got %d", t.line, t.col, border);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
p.style.radius = (ushort)r;
|
p.style.border = (ushort)border;
|
||||||
|
|
||||||
|
case "radius":
|
||||||
|
short radius;
|
||||||
|
if (p.parse_number(&radius) == false) return false;
|
||||||
|
if (radius < 0) {
|
||||||
|
io::eprintfn("CSS parsing error at %d:%d: 'radius' must be a positive number, got %d", t.line, t.col, radius);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
p.style.radius = (ushort)radius;
|
||||||
|
|
||||||
case "size":
|
case "size":
|
||||||
short s;
|
short size;
|
||||||
if (p.parse_number(&s) == false) return false;
|
if (p.parse_number(&size) == false) return false;
|
||||||
if (s < 0) {
|
if (size < 0) {
|
||||||
io::eprintfn("CSS parsing error at %d:%d: 'size' must be a positive number, got %d", t.line, t.col, s);
|
io::eprintfn("CSS parsing error at %d:%d: 'size' must be a positive number, got %d", t.line, t.col, size);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
p.style.size = (ushort)s;
|
p.style.size = (ushort)size;
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
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(&t, SEMICOLON) == false) return false;
|
if (p.expect_text(&t, PUNCT, ";") == false) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,7 +432,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 == SEMICOLON) {
|
} else if (t.type == PUNCT && t.text == ";") {
|
||||||
// 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;
|
||||||
|
@ -3,10 +3,8 @@ module ugui;
|
|||||||
import std::io;
|
import std::io;
|
||||||
|
|
||||||
struct ElemText {
|
struct ElemText {
|
||||||
String str;
|
char[] 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, ...)
|
||||||
@ -19,15 +17,12 @@ 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.layout_element(parent, elem.text.bounds, style);
|
elem.bounds = ctx.position_element(parent, text_size, 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)!;
|
||||||
@ -43,10 +38,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 = (String)text;
|
elem.text.str = text;
|
||||||
|
|
||||||
// layout the text box
|
// layout the text box
|
||||||
elem.bounds = ctx.layout_element(parent, size, style);
|
elem.bounds = ctx.position_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);
|
||||||
@ -69,11 +64,27 @@ 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;
|
Rect text_box = elem.bounds.sub({0,0,0,line_height});
|
||||||
ctx.push_rect(text_box, parent.div.z_index, style)!;
|
Rect input_box = {
|
||||||
ctx.push_string(text_box, text[:*text_len], parent.div.z_index, style.fg, true)!;
|
.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());
|
||||||
|
|
||||||
// TODO: draw cursor
|
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_rect(input_box, parent.div.z_index, style)!;
|
||||||
|
ctx.push_rect(cursor, parent.div.z_index, style)!;
|
||||||
|
|
||||||
return elem.events;
|
return elem.events;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ layout(set = 3, binding = 0) uniform Viewport {
|
|||||||
layout(location = 0) in vec4 in_color;
|
layout(location = 0) in vec4 in_color;
|
||||||
layout(location = 1) in vec4 in_quad_size; // x,y, w,h
|
layout(location = 1) in vec4 in_quad_size; // x,y, w,h
|
||||||
layout(location = 2) in float in_radius;
|
layout(location = 2) in float in_radius;
|
||||||
|
layout(location = 3) in float thickness;
|
||||||
|
|
||||||
layout(location = 0) out vec4 fragColor;
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
@ -16,12 +17,16 @@ float sdf_rr(vec2 p, vec2 half_size, float radius) {
|
|||||||
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
|
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const float smoothness = 0.9;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5;
|
vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5;
|
||||||
vec2 half_size = in_quad_size.zw * 0.5;
|
vec2 half_size = in_quad_size.zw * 0.5;
|
||||||
float distance = sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius);
|
float distance = -sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius);
|
||||||
float alpha = 1.0 - smoothstep(0.0, 1.5, distance);
|
|
||||||
|
|
||||||
fragColor = vec4(in_color.rgb, in_color.a * alpha);
|
float alpha_out = smoothstep(0.0-smoothness, 0.0, distance);
|
||||||
|
float alpha_in = 1.0 - smoothstep(thickness-smoothness, thickness, distance);
|
||||||
|
|
||||||
|
fragColor = vec4(in_color.rgb, in_color.a * alpha_out * alpha_in);
|
||||||
}
|
}
|
@ -12,6 +12,7 @@ layout(location = 3) in uvec4 color;
|
|||||||
layout(location = 0) out vec4 out_color;
|
layout(location = 0) out vec4 out_color;
|
||||||
layout(location = 1) out vec4 out_quad_size;
|
layout(location = 1) out vec4 out_quad_size;
|
||||||
layout(location = 2) out float out_radius;
|
layout(location = 2) out float out_radius;
|
||||||
|
layout(location = 3) out float out_thickness;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
@ -26,4 +27,5 @@ void main()
|
|||||||
out_color = vec4(color) / 255.0;
|
out_color = vec4(color) / 255.0;
|
||||||
out_quad_size = vec4(attr);
|
out_quad_size = vec4(attr);
|
||||||
out_radius = float(abs(uv.x));
|
out_radius = float(abs(uv.x));
|
||||||
|
out_thickness = float(uv.y - uv.x);
|
||||||
}
|
}
|
||||||
|
@ -4,27 +4,35 @@ default {
|
|||||||
primary: #cc241dff;
|
primary: #cc241dff;
|
||||||
secondary: #458588ff;
|
secondary: #458588ff;
|
||||||
accent: #fabd2fff;
|
accent: #fabd2fff;
|
||||||
border: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin: 2;
|
margin: 2 2 2 2;
|
||||||
border: 2;
|
border: 2;
|
||||||
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: #504945ff;
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button-active {
|
||||||
|
margin: 2 2 2 2;
|
||||||
|
border: 2;
|
||||||
|
radius: 10;
|
||||||
|
|
||||||
|
bg: #504945ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #cc241dff;
|
||||||
|
accent: #fabd2fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkbox {
|
checkbox {
|
||||||
margin: 2;
|
margin: 2 2 2 2;
|
||||||
border: 2;
|
border: 2;
|
||||||
padding: 1;
|
|
||||||
radius: 10;
|
radius: 10;
|
||||||
size: 16;
|
size: 16;
|
||||||
bg: #3c3836ff;
|
bg: #3c3836ff;
|
||||||
@ -35,10 +43,9 @@ checkbox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggle {
|
toggle {
|
||||||
margin: 2;
|
margin: 2 2 2 2;
|
||||||
border: 2;
|
border: 2;
|
||||||
padding: 1;
|
radius: 0;
|
||||||
radius: 10;
|
|
||||||
size: 16;
|
size: 16;
|
||||||
bg: #3c3836ff;
|
bg: #3c3836ff;
|
||||||
fg: #fbf1c7ff;
|
fg: #fbf1c7ff;
|
||||||
@ -48,8 +55,8 @@ toggle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slider {
|
slider {
|
||||||
margin: 2;
|
margin: 2 2 2 2;
|
||||||
padding: 2;
|
padding: 4 4 4 4;
|
||||||
border: 1;
|
border: 1;
|
||||||
radius: 4;
|
radius: 4;
|
||||||
size: 8;
|
size: 8;
|
||||||
|
193
src/main.c3
193
src/main.c3
@ -13,6 +13,7 @@ alias Times = ringbuffer::RingBuffer{time::NanoDuration[128]};
|
|||||||
|
|
||||||
fn void Times.print_stats(×)
|
fn void Times.print_stats(×)
|
||||||
{
|
{
|
||||||
|
if (times.written == 0);
|
||||||
time::NanoDuration min, max, avg, x;
|
time::NanoDuration min, max, avg, x;
|
||||||
min = times.get(0);
|
min = times.get(0);
|
||||||
for (usz i = 0; i < times.written; i++) {
|
for (usz i = 0; i < times.written; i++) {
|
||||||
@ -21,7 +22,7 @@ fn void Times.print_stats(×)
|
|||||||
if (x > max) { max = x; }
|
if (x > max) { max = x; }
|
||||||
avg += x;
|
avg += x;
|
||||||
}
|
}
|
||||||
avg = (NanoDuration)((ulong)avg/128.0);
|
avg = (NanoDuration)((ulong)avg/times.written);
|
||||||
|
|
||||||
io::printfn("min=%s, max=%s, avg=%s", min, max, avg);
|
io::printfn("min=%s, max=%s, avg=%s", min, max, avg);
|
||||||
}
|
}
|
||||||
@ -32,6 +33,7 @@ struct TimeStats {
|
|||||||
|
|
||||||
fn TimeStats Times.get_stats(×)
|
fn TimeStats Times.get_stats(×)
|
||||||
{
|
{
|
||||||
|
if (times.written == 0) return {};
|
||||||
time::NanoDuration min, max, avg, x;
|
time::NanoDuration min, max, avg, x;
|
||||||
min = times.get(0);
|
min = times.get(0);
|
||||||
for (usz i = 0; i < times.written; i++) {
|
for (usz i = 0; i < times.written; i++) {
|
||||||
@ -40,7 +42,7 @@ fn TimeStats Times.get_stats(×)
|
|||||||
if (x > max) { max = x; }
|
if (x > max) { max = x; }
|
||||||
avg += x;
|
avg += x;
|
||||||
}
|
}
|
||||||
avg = (NanoDuration)((ulong)avg/128.0);
|
avg = (NanoDuration)((ulong)avg/times.written);
|
||||||
|
|
||||||
return {.min = min, .max = max, .avg = avg};
|
return {.min = min, .max = max, .avg = avg};
|
||||||
}
|
}
|
||||||
@ -119,6 +121,7 @@ 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;
|
||||||
@ -198,13 +201,74 @@ 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;
|
||||||
|
|
||||||
const String APPLICATION = "calculator";
|
ui.div_begin({.w=-100})!!;
|
||||||
$if APPLICATION == "debug":
|
{
|
||||||
debug_app(&ui);
|
ui.layout_set_column()!!;
|
||||||
$endif
|
if (ui.button({0,0,30,30}, toggle)!!.mouse_press) {
|
||||||
$if APPLICATION == "calculator":
|
io::printn("press button0");
|
||||||
calculator(&ui);
|
toggle = !toggle;
|
||||||
$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();
|
||||||
@ -217,7 +281,7 @@ $endif
|
|||||||
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.input.keyboard.text_len]))!!;
|
ui.text_unbounded(string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[..]))!!;
|
||||||
};
|
};
|
||||||
ui.div_end()!!;
|
ui.div_end()!!;
|
||||||
|
|
||||||
@ -248,112 +312,3 @@ $endif
|
|||||||
|
|
||||||
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()!!;
|
|
||||||
}
|
|
||||||
|
@ -651,11 +651,11 @@ fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u,
|
|||||||
return self.map_quad(qa);
|
return self.map_quad(qa);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius = 0)
|
fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius = 0, ushort thickness = 0)
|
||||||
{
|
{
|
||||||
QuadAttributes qa = {
|
QuadAttributes qa = {
|
||||||
.pos = {.x = x, .y = y, .w = w, .h = h},
|
.pos = {.x = x, .y = y, .w = w, .h = h},
|
||||||
.uv = {.u = radius, .v = radius},
|
.uv = {.u = radius, .v = radius+thickness},
|
||||||
.color = color
|
.color = color
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -866,7 +866,7 @@ fn void Renderer.render_ugui(&self, CmdQueue* queue)
|
|||||||
foreach (&c : queue) {
|
foreach (&c : queue) {
|
||||||
if (c.type == CMD_RECT) {
|
if (c.type == CMD_RECT) {
|
||||||
CmdRect r = c.rect;
|
CmdRect r = c.rect;
|
||||||
self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius);
|
self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius, r.thickness);
|
||||||
} else if (c.type == CMD_SPRITE) {
|
} else if (c.type == CMD_SPRITE) {
|
||||||
CmdSprite s = c.sprite;
|
CmdSprite s = c.sprite;
|
||||||
self.push_sprite(s.rect.x, s.rect.y, s.texture_rect.w, s.texture_rect.h, s.texture_rect.x, s.texture_rect.y, s.hue.to_uint());
|
self.push_sprite(s.rect.x, s.rect.y, s.texture_rect.w, s.texture_rect.h, s.texture_rect.x, s.texture_rect.y, s.hue.to_uint());
|
||||||
|
Loading…
Reference in New Issue
Block a user