Merge branch 'c3' of https://git.alemauri.eu/alema/ugui into c3
This commit is contained in:
commit
2bd15ac981
24
TODO
24
TODO
@ -5,7 +5,7 @@
|
||||
[x] Port font system from C to C3 (rewrite1)
|
||||
[ ] Update ARCHITECTURE.md
|
||||
[ ] Write a README.md
|
||||
[ ] Use an arena allocator for cache
|
||||
[x] Use an arena allocator for cache
|
||||
[ ] Do not redraw if there was no update (no layout and no draw)
|
||||
[ ] Do command buffer damage tracking based on a context grid (see rxi writeup)
|
||||
[x] Better handling of the active and focused widgets, try
|
||||
@ -44,24 +44,34 @@ 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)
|
||||
[x] Implement column/row sizing (min, max)
|
||||
[x] Implement a way to size the element as the current row/column size
|
||||
* +-------------+
|
||||
* | |
|
||||
* +-------------+
|
||||
* +--+
|
||||
* | |
|
||||
* +--+
|
||||
* <------------->
|
||||
* column size
|
||||
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()
|
||||
[ ] Make functions to mark rows/columns as full, to fix the calculator demo
|
||||
[x] Make functions to mark rows/columns as full, to fix the calculator demo
|
||||
|
||||
## Input
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 076355e2d126e7546e53663b97e8dec22667d34d
|
||||
Subproject commit e7356df5d1d0c22a6bba822bda6994062b0b75d7
|
243
lib/ugui.c3l/LAYOUT
Normal file
243
lib/ugui.c3l/LAYOUT
Normal file
@ -0,0 +1,243 @@
|
||||
Div Children Alignment
|
||||
+------------------------------------------------+
|
||||
|TOP-LEFT TOP TOP-RIGHT|
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
|LEFT CENTER RIGHT|
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
|BOTTOM-LEFT BOTTOM BOTTOM-RIGHT|
|
||||
+------------------------------------------------+
|
||||
|
||||
ALIGNMENT CHART:
|
||||
+------------------------------+----------------------+------------------------------+-----------------------+------------------------------+----------------------+
|
||||
| TOP-LEFT, ROW: | TOP-LEFT, COLUMN: | BOTTOM, ROW: | BOTTOM, COLUMN: | TOP-RIGHT, ROW: | TOP-RIGHT, COLUMN: |
|
||||
| | | | | | |
|
||||
| +------------------------- - | +----------- - | | +----+ | | - -----------+ |
|
||||
| |+-------++-----++-----+ | |+-------+ | | | E1 | | | +-------+| |
|
||||
| || E1 || E2 || | | || E1 | | | | | | - -----------------------+ | | E1 || |
|
||||
| || |+-----+| E3 | | || | | | +----+ | +-------++-----++-----+| | | || |
|
||||
| |+-------+ | | | |+-------+ | +-------+ +---+ | +------+ | | E1 || E2 || || | +-------+| |
|
||||
| | +-----+ | |+----+ | | E1 |+------+|E3 | | | E2 | | | |+-----+| E3 || | +----+| |
|
||||
| ' | || E2 | | | || E2 || | | | | | +-------+ | || | | E2 || |
|
||||
| ' | |+----+ | +-------++------++---+ | +------+ | +-----+| | +----+| |
|
||||
| | |+---------+ | - ------------------------ - | +--+ | ' | +---------+| |
|
||||
| | || E3 | | | |E3| | ' | | E3 || |
|
||||
| | |+---------+ | | +--+ | | +---------+| |
|
||||
| | ' | | - ----------- - | | ' |
|
||||
| | ' | | | | ' |
|
||||
| | | | | | |
|
||||
+------------------------------+----------------------+------------------------------+-----------------------+------------------------------+----------------------+
|
||||
| LEFT, ROW: | LEFT, COLUMN: | BOTTOM-RIGHT, ROW: | BOTTOM-RIGHT, COLUMN: | TOP, ROW: | TOP, COLUMN: |
|
||||
| | | | | | |
|
||||
| | ' | | ' | | - -------------- - |
|
||||
| ' | |+-------+ | | +-------+| | | +----------+ |
|
||||
| | +----+ | || E1 | | ' | | E1 || | - ----------------------- - | | E1 | |
|
||||
| |+------+ | | | || | | +-----+| | | || | +------++----++-----+ | | | |
|
||||
| || |+-----+| | | |+-------+ | +-------+ | || | +-------+| | | E1 || E2 || E3 | | +----------+ |
|
||||
| || E1 || E2 || E3 | | |+----+ | | E1 |+-----+| E3 || | +----+| | | |+----+| | | +--------+ |
|
||||
| || |+-----+| | | || E2 | | | || E2 || || | | E2 || | +------+ | | | | E2 | |
|
||||
| |+------+ | | | |+----+ | +-------++-----++-----+| | +----+| | +-----+ | +--------+ |
|
||||
| | +----+ | |+---------+ | - -----------------------+ | +---------+| | | +------+ |
|
||||
| ' | || E3 | | | | E3 || | | | E3 | |
|
||||
| ' | |+---------+ | | +---------+| | | | | |
|
||||
| | ' | | - -----------+ | | +------+ |
|
||||
| | ' | | | | |
|
||||
+------------------------------+----------------------+------------------------------+-----------------------+------------------------------+----------------------+
|
||||
| BOTTOM-LEFT, ROW: | BOTTOM-LEFT, COLUMN: | RIGHT, ROW: | RIGHT, COLUMN: | CENTER, ROW: | CENTER, COLUMN: |
|
||||
| | | | | | |
|
||||
| | ' | | ' | | | |
|
||||
| | |+-------+ | | +-------+| | | | +-----------+ |
|
||||
| | || E1 | | ' | | E1 || | | | | E1 | |
|
||||
| ' | || | | +----+| | | || | | +----+ | | | | |
|
||||
| | +-----+ | |+-------+ | +------+ | || | +-------+| | +------+ | | | | +-----------+ |
|
||||
| |+-------+ | | | |+----+ | | |+-----+| || | +----+| | | |+----+| | | +---------+ |
|
||||
| || E1 |+-----+| E3 | | || E2 | | | E1 || E2 || E3 || | | E2 || | ---|--E1--||-E2-||-E3-|--- | ----|---E2----|---- |
|
||||
| || || E2 || | | |+----+ | | |+-----+| || | +----+| | | |+----+| | | +---------+ |
|
||||
| |+-------++-----++-----+ | |+---------+ | +------+ | || | +---------+| | +------+ | | | | +-------+ |
|
||||
| +------------------------- - | || E3 | | +----+| | | E3 || | | +----+ | | E3 | |
|
||||
| | |+---------+ | ' | +---------+| | | | | | | |
|
||||
| | +----------- - | ' | ' | | | +-------+ |
|
||||
| | | | ' | | | |
|
||||
| | | | | | |
|
||||
+------------------------------+----------------------+------------------------------+-----------------------+------------------------------+----------------------+
|
||||
|
||||
div (
|
||||
align: TOP-LEFT | LEFT | BOTTOM-LEFT | BOTTOM | BOTTOM-RIGHT | RIGHT | TOP-RIGHT | RIGHT | CENTER
|
||||
size_x/y: EXACT(x) | GROW() | FIT(min, max)
|
||||
scroll_x/y: true | false
|
||||
resize_x/y: true | false
|
||||
layout: ROW | COLUMN
|
||||
)
|
||||
|
||||
align: alignment of the children elements
|
||||
size: how the div should be sized
|
||||
scroll: enables scrollbars
|
||||
layout: the layout direction of the children
|
||||
|
||||
COLUMN ROW
|
||||
+--------------------+ +----------------------------------------------------+
|
||||
| +----------------+ | |+----------------+ |
|
||||
| | | | || |+------------+ |
|
||||
| | | | || || |+------------------+|
|
||||
| | E1 | | || E1 || E2 || E3 ||
|
||||
| | | | || || |+------------------+|
|
||||
| | | | || |+------------+ |
|
||||
| +----------------+ | |+----------------+ |
|
||||
| +------------+ | +----------------------------------------------------+
|
||||
| | | |
|
||||
| | E2 | |
|
||||
| | | | (both have center alignment)
|
||||
| +------------+ |
|
||||
|+------------------+|
|
||||
|| ||
|
||||
|| E3 ||
|
||||
|| ||
|
||||
|| ||
|
||||
|+------------------+|
|
||||
+--------------------+
|
||||
|
||||
Element {
|
||||
id: uint
|
||||
sizing: { min_w, min_h max_w, max_h }
|
||||
bounds: { x, y, w, h }
|
||||
}
|
||||
|
||||
id: unique identifier of the element
|
||||
sizing: the size that the element wants
|
||||
bounds: the absoulte bounds that the element got assigned
|
||||
|
||||
Rendering
|
||||
=========
|
||||
|
||||
Rendering happens when the element is called (immediately for leaf widgets like buttons and at the end
|
||||
for root widgets like divs). The drawing is done on the bounds assigned to the widget, these bounds
|
||||
have a one-frame delay on the current layout.
|
||||
|
||||
The layout is calculated by each div at the end of their block and at frame end all the sizes and positions
|
||||
are assigned at frame end by iterating the element tree.
|
||||
|
||||
ElemDiv {
|
||||
align: TOP-LEFT | LEFT | BOTTOM-LEFT | BOTTOM | BOTTOM-RIGHT | RIGHT | TOP-RIGHT | RIGHT | CENTER
|
||||
size_x/y: { min, max }
|
||||
scroll_x/y: true | false
|
||||
layout: ROW | COLUMN
|
||||
children_size_x/y: { min, max }
|
||||
}
|
||||
|
||||
size:
|
||||
- min != max -> FIT sizing, fit to the content but respect the min and max size
|
||||
- min == max == 0 -> GROW sizing, grow to the max amount of space possible
|
||||
- min == max != 0 -> EXACT sizing
|
||||
children_size: the size of the combined children sizes
|
||||
|
||||
|
||||
|
||||
root(size_x: screen width, size_y: screen height, layout: ROW) {
|
||||
|
||||
div1(layout: COLUMN, size_x: FIT, size_y GROW, resize_x: true) {
|
||||
E1()
|
||||
E2()
|
||||
E3()
|
||||
E4()
|
||||
} <-(end div 1)
|
||||
|
||||
div2(size_x: GROW, size_y: GROW) {
|
||||
...
|
||||
} <-(end div 2)
|
||||
|
||||
div3(layout: COLUMN, size_x: FIT, size_y: GROW) {
|
||||
E5()
|
||||
E6()
|
||||
E7()
|
||||
} <-(end div 3)
|
||||
|
||||
} <-(end root)
|
||||
(frame end)
|
||||
|
||||
|
||||
+-Root-Div------------------------------------------------+
|
||||
|+-Div-1----------++-Div-2--------------------++-Div-3---+|
|
||||
||+--------------+|| ||+-------+||
|
||||
||| E1 ||| ||| E5 |||
|
||||
||| ||| ||| |||
|
||||
||+--------------+|| ||+-------+|| [Root Div]
|
||||
||+--------------+|| ||+-------+|| |
|
||||
||| E2 ||| ||| E6 ||| +----------+----+-------+
|
||||
||| ||| ||| ||| v v v
|
||||
||+--------------+|| ||+-------+|| [Div 1] [Div 2] [Div 3]
|
||||
||+------+ || ||+-------+|| | |
|
||||
||| | || ||| E7 ||| +----+----+----+ |
|
||||
||| E3 | || ||| ||| v v v v |
|
||||
||| | || ||+-------+|| [E1] [E2] [E3] [E4] +----+----+
|
||||
||+------+ || || || v v v
|
||||
||+------+ || || || [E5] [E6] [E7]
|
||||
||| | || || ||
|
||||
||| E4 | || || ||
|
||||
||| | || || ||
|
||||
||+------+ || || ||
|
||||
|| || || ||
|
||||
|+----------------++--------------------------++---------+|
|
||||
+---------------------------------------------------------+
|
||||
|
||||
the call order is as follows
|
||||
|
||||
E1() -> updates the children size of div1
|
||||
E2() -> " "
|
||||
E3() -> " "
|
||||
E4() -> " "
|
||||
end div1() -> updates the children size of root
|
||||
end div2() -> updates the children size of root
|
||||
E5() -> updates the children size of div3
|
||||
E6() -> " "
|
||||
E7() -> " "
|
||||
end root() -> does nothing
|
||||
|
||||
at frame end:
|
||||
* Root: the root has a size constraint of fit so the bounds get assigned the whole window
|
||||
* Div 1: the width has a size of fit, so it gets set to the children bounds, the height is set
|
||||
to the root height since it has a height of GROW
|
||||
- E1 to E4 get laid out
|
||||
* Div 2: it has a width of GROW which is **along** the layout axis, so it gets added to grow list
|
||||
the height gets set to the root height
|
||||
* Div 3: the width is FIT, so it gets set to the content width, the height gets se to the root
|
||||
height.
|
||||
- E5 to E7 get laid out
|
||||
* Div 2: is given a width (if there were other contending grow divs along the layout axis they
|
||||
would also get sized).
|
||||
- Now that div 2 has a size all it's children can be given a size
|
||||
|
||||
Styling
|
||||
=======
|
||||
|
||||
The element bounds include the whole CSS box model:
|
||||
|
||||
+---------------------------------------------+
|
||||
| MARGIN |
|
||||
| +-----------------------------------+ |
|
||||
| |xxxxxxxxxxx BORDER xxxxxxxxxxxx| |
|
||||
| |x+-------------------------------+x| |
|
||||
| |x| PADDING |x| |
|
||||
| |x| +-----------------------+ |x| |
|
||||
| |x| | | |x| |
|
||||
| |x| | CONTENT | |x| |
|
||||
| |x| | | |x| |
|
||||
| |x| +-----------------------+ |x| |
|
||||
| |x| |x| |
|
||||
| |x+-------------------------------+x| |
|
||||
| |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| |
|
||||
| +-----------------------------------+ |
|
||||
| |
|
||||
+---------------------------------------------+
|
||||
|
||||
Styling happens via a .css file, the sizing strictly refers to the content, so if the the user
|
||||
requests an exact size of 100px*100px the content box will have those dimensions, but the element
|
||||
bounds will be larger.
|
@ -25,6 +25,7 @@ alias IdTableEntry = map::Entry{Key, usz};
|
||||
const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0);
|
||||
|
||||
struct Cache {
|
||||
Allocator allocator;
|
||||
BitArr present, used;
|
||||
IdTable table;
|
||||
Value[] pool;
|
||||
@ -43,19 +44,20 @@ macro Cache.cycle(&cache) @private {
|
||||
}
|
||||
}
|
||||
|
||||
fn void? Cache.init(&cache)
|
||||
fn void? Cache.init(&cache, Allocator allocator)
|
||||
{
|
||||
cache.table.init(allocator::heap(), capacity: SIZE);
|
||||
cache.allocator = allocator;
|
||||
cache.table.init(allocator, capacity: SIZE);
|
||||
// FIXME: this shit is SLOW
|
||||
foreach (idx, bit : cache.used) { cache.used[idx] = false; }
|
||||
foreach (idx, bit : cache.present) { cache.present[idx] = false; }
|
||||
cache.pool = mem::new_array(Value, SIZE);
|
||||
cache.pool = allocator::new_array(allocator, Value, SIZE);
|
||||
}
|
||||
|
||||
fn void Cache.free(&cache)
|
||||
{
|
||||
(void)cache.table.free();
|
||||
(void)mem::free(cache.pool);
|
||||
(void)allocator::free(cache.allocator, cache.pool);
|
||||
}
|
||||
|
||||
fn Value*? Cache.search(&cache, Key id)
|
||||
|
@ -9,21 +9,23 @@ import std::sort;
|
||||
// TODO: specify the allocator
|
||||
|
||||
struct Fifo {
|
||||
Allocator allocator;
|
||||
Type[] arr;
|
||||
usz out;
|
||||
usz count;
|
||||
}
|
||||
|
||||
fn void? Fifo.init(&fifo, usz size)
|
||||
fn void? Fifo.init(&fifo, usz size, Allocator allocator)
|
||||
{
|
||||
fifo.arr = mem::new_array(Type, size);
|
||||
fifo.allocator = allocator;
|
||||
fifo.arr = allocator::new_array(fifo.allocator, Type, size);
|
||||
fifo.out = 0;
|
||||
fifo.count = 0;
|
||||
}
|
||||
|
||||
fn void Fifo.free(&fifo)
|
||||
{
|
||||
(void)mem::free(fifo.arr);
|
||||
(void)allocator::free(fifo.allocator, fifo.arr);
|
||||
}
|
||||
|
||||
fn void? Fifo.enqueue(&fifo, Type *elem)
|
||||
@ -69,8 +71,8 @@ macro usz Fifo.len(&fifo) @operator(len)
|
||||
|
||||
fn void? Fifo.sort(&fifo)
|
||||
{
|
||||
Type[] arr = mem::new_array(Type, fifo.count);
|
||||
defer mem::free(arr);
|
||||
Type[] arr = allocator::new_array(fifo.allocator, Type, fifo.count);
|
||||
defer allocator::free(fifo.allocator, arr);
|
||||
|
||||
foreach(i, c: fifo) {
|
||||
arr[i] = c;
|
||||
|
@ -19,52 +19,56 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
|
||||
|
||||
Sprite* sprite = icon != "" ? ctx.sprite_atlas.get(icon)! : &&(Sprite){};
|
||||
|
||||
Rect min_size = {0, 0, style.size, style.size};
|
||||
Rect text_size = ctx.get_text_bounds(label)!;
|
||||
// TODO: get min size by style
|
||||
TextSize text_size = ctx.measure_string(label)!;
|
||||
Rect icon_size = sprite.rect();
|
||||
|
||||
ushort h_lh = (ushort)(ctx.font.line_height() / 2);
|
||||
ushort left_pad = label != "" ? h_lh : 0;
|
||||
ushort inner_pad = label != "" && icon != "" ? h_lh : 0;
|
||||
ushort right_pad = left_pad;
|
||||
|
||||
/* |left_pad
|
||||
* +--v-----------------------------------+
|
||||
* |<->+--------+ |
|
||||
ushort min_size = style.size;
|
||||
ushort half_lh = (ushort)(ctx.font.line_height() / 2);
|
||||
ushort inner_pad = label != "" && icon != "" ? half_lh : 0;
|
||||
/*
|
||||
* +--------------------------------------+
|
||||
* | +--------+ |
|
||||
* | | | +-----------------+ |
|
||||
* | | icon | | label | |
|
||||
* | | | +-----------------+ |
|
||||
* | +--------+<->| |<->|
|
||||
* +-------------^--------------------^---+
|
||||
* |inner_pad |right_pad
|
||||
* | +--------+<->| |
|
||||
* +-------------^------------------------+
|
||||
* |inner_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)),
|
||||
Point content_size = {
|
||||
.x = icon_size.w + inner_pad, // text sizing is handled differently
|
||||
.y = icon_size.h + inner_pad,
|
||||
};
|
||||
|
||||
elem.bounds = ctx.layout_element(parent, tot_size, style);
|
||||
if (elem.bounds.is_null()) return {};
|
||||
elem.layout.w = @fit(min_size);
|
||||
elem.layout.h = @fit(min_size);
|
||||
elem.layout.children.w = @exact(content_size.x);
|
||||
elem.layout.children.h = @exact(content_size.y);
|
||||
elem.layout.text = text_size;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_grow(elem, parent);
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
elem.events = ctx.get_elem_events(elem);
|
||||
Rect content_bounds = elem.content_bounds(style);
|
||||
|
||||
Rect text_bounds = {
|
||||
.x = content_bounds.x + icon_size.w + left_pad + inner_pad,
|
||||
.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 content_bounds = elem.content_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)
|
||||
.w = icon_size.w,
|
||||
.h = icon_size.h
|
||||
};
|
||||
icon_bounds = icon_size.center_to(icon_bounds);
|
||||
|
||||
|
||||
Rect text_bounds = {
|
||||
.x = content_bounds.x + icon_bounds.w + inner_pad,
|
||||
.y = content_bounds.y,
|
||||
.w = content_bounds.w - icon_bounds.w - inner_pad,
|
||||
.h = content_bounds.h,
|
||||
};
|
||||
//text_bounds = text_size.center_to(text_bounds);
|
||||
|
||||
bool is_active = ctx.elem_focus(elem) || elem.events.mouse_hover;
|
||||
Style s = *style;
|
||||
if (is_active) {
|
||||
@ -72,15 +76,17 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
|
||||
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)!;
|
||||
|
||||
ctx.push_rect(elem.bounds.pad(style.margin), parent.div.z_index, &s)!;
|
||||
if (icon != "") {
|
||||
ctx.push_sprite(icon_bounds, sprite.uv(), ctx.sprite_atlas.id, parent.div.z_index, type: sprite.type)!;
|
||||
}
|
||||
if (label != "") {
|
||||
ctx.layout_string(label, text_bounds, CENTER, parent.div.z_index, style.fg)!;
|
||||
}
|
||||
return elem.events;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
// FIXME: this should be inside the style
|
||||
macro Ctx.checkbox(&ctx, String desc, Point off, bool* active, String tick_sprite = {}, ...)
|
||||
@ -151,3 +157,4 @@ fn void? Ctx.toggle_id(&ctx, Id id, String description, Point off, bool* active)
|
||||
s.margin = s.border = s.padding = {};
|
||||
ctx.push_rect(t, parent.div.z_index, &s)!;
|
||||
}
|
||||
*/
|
@ -58,7 +58,26 @@ fn int Cmd.compare_to(Cmd a, Cmd b)
|
||||
// implement the Printable interface
|
||||
fn usz? Cmd.to_format(Cmd* cmd, Formatter *f) @dynamic
|
||||
{
|
||||
return f.printf("Cmd{ type: %s, z_index: %d }", cmd.type, cmd.z_index);
|
||||
usz ret;
|
||||
|
||||
ret += f.printf("Cmd{ type: %s, z_index: %d, ", cmd.type, cmd.z_index)!;
|
||||
switch (cmd.type) {
|
||||
case CMD_RECT:
|
||||
ret += f.print("CmdRect")!;
|
||||
ret += io::struct_to_format(cmd.rect, f, false)!;
|
||||
case CMD_SCISSOR:
|
||||
ret += f.print("CmdScissor")!;
|
||||
ret += io::struct_to_format(cmd.scissor, f, false)!;
|
||||
case CMD_SPRITE:
|
||||
ret += f.print("CmdSprite")!;
|
||||
ret += io::struct_to_format(cmd.sprite, f, false)!;
|
||||
case CMD_UPDATE_ATLAS:
|
||||
ret += f.print("CmdUpdateAtlas")!;
|
||||
ret += io::struct_to_format(cmd.update_atlas, f, false)!;
|
||||
}
|
||||
|
||||
ret += f.print("}")!;
|
||||
return ret;
|
||||
}
|
||||
|
||||
macro bool cull_rect(Rect rect, Rect clip = {0,0,short.max,short.max})
|
||||
@ -77,7 +96,13 @@ macro Ctx.push_cmd(&ctx, Cmd *cmd, int z_index)
|
||||
case CMD_SPRITE: rect = cmd.sprite.rect;
|
||||
default: return ctx.cmd_queue.enqueue(cmd);
|
||||
}
|
||||
if (cull_rect(rect, ctx.div_scissor)) return;
|
||||
if (cull_rect(rect, ctx.div_scissor)) {
|
||||
// io::print("NOPE: ");
|
||||
// io::print(cmd.rect.rect);
|
||||
// io::printn(cmd.z_index);
|
||||
// unreachable();
|
||||
return;
|
||||
}
|
||||
return ctx.cmd_queue.enqueue(cmd);
|
||||
}
|
||||
|
||||
@ -90,6 +115,8 @@ fn void? Ctx.push_scissor(&ctx, Rect rect, int z_index)
|
||||
ctx.push_cmd(&sc, z_index)!;
|
||||
}
|
||||
|
||||
fn void? Ctx.reset_scissor(&ctx, int z_index) => ctx.push_cmd(&&(Cmd){.type=CMD_SCISSOR,.scissor.rect=ctx.div_scissor}, z_index)!;
|
||||
|
||||
fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
|
||||
{
|
||||
Rect border = style.border;
|
||||
@ -119,7 +146,6 @@ fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
|
||||
.rect.color = bg,
|
||||
.rect.radius = radius,
|
||||
};
|
||||
if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return;
|
||||
ctx.push_cmd(&cmd, z_index)!;
|
||||
}
|
||||
|
||||
@ -137,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.push_scissor({}, z_index)!;
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)
|
||||
{
|
||||
Cmd up = {
|
||||
|
@ -9,6 +9,15 @@ import std::core::string;
|
||||
import std::core::mem::allocator;
|
||||
|
||||
|
||||
macro println(...)
|
||||
{
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
io::print($vaexpr[$i]);
|
||||
$endfor
|
||||
io::printn();
|
||||
}
|
||||
|
||||
|
||||
// element ids are just long ints
|
||||
alias Id = uint;
|
||||
|
||||
@ -45,7 +54,9 @@ struct Elem {
|
||||
ElemFlags flags;
|
||||
ElemEvents events;
|
||||
Rect bounds;
|
||||
Rect children_bounds;
|
||||
ElemType type;
|
||||
Layout layout;
|
||||
union {
|
||||
ElemDiv div;
|
||||
ElemButton button;
|
||||
@ -131,6 +142,7 @@ const uint GOLDEN_RATIO = 0x9E3779B9;
|
||||
// with the Cantor pairing function
|
||||
fn Id? Ctx.gen_id(&ctx, Id id2)
|
||||
{
|
||||
// FIXME: this is SHIT
|
||||
Id id1 = ctx.tree.get(ctx.active_div)!;
|
||||
// Mix the two IDs non-linearly
|
||||
Id mixed = id1 ^ id2.rotate_left(13);
|
||||
@ -160,6 +172,7 @@ fn Elem*? Ctx.get_elem(&ctx, Id id, ElemType type)
|
||||
elem.flags = (ElemFlags)0;
|
||||
elem.flags.is_new = is_new;
|
||||
elem.id = id;
|
||||
elem.layout = {};
|
||||
if (is_new == false && elem.type != type) {
|
||||
return WRONG_ELEMENT_TYPE?;
|
||||
} else {
|
||||
@ -187,18 +200,18 @@ fn Elem*? Ctx.get_active_div(&ctx)
|
||||
return ctx.cache.search(id);
|
||||
}
|
||||
|
||||
fn void? Ctx.init(&ctx)
|
||||
fn void? Ctx.init(&ctx, Allocator allocator)
|
||||
{
|
||||
ctx.tree.init(MAX_ELEMENTS)!;
|
||||
ctx.tree.init(MAX_ELEMENTS, allocator)!;
|
||||
defer catch { (void)ctx.tree.free(); }
|
||||
|
||||
ctx.cache.init()!;
|
||||
ctx.cache.init(allocator)!;
|
||||
defer catch { (void)ctx.cache.free(); }
|
||||
|
||||
ctx.cmd_queue.init(MAX_CMDS)!;
|
||||
ctx.cmd_queue.init(MAX_CMDS, allocator)!;
|
||||
defer catch { (void)ctx.cmd_queue.free(); }
|
||||
|
||||
ctx.styles.init(allocator::heap());
|
||||
ctx.styles.init(allocator);
|
||||
ctx.styles.register_style(&DEFAULT_STYLE, @str_hash("default"));
|
||||
defer catch { ctx.styles.free(); }
|
||||
|
||||
@ -232,16 +245,15 @@ fn void? Ctx.frame_begin(&ctx)
|
||||
//elem.flags.has_focus = ctx.has_focus;
|
||||
|
||||
elem.bounds = {0, 0, ctx.width, ctx.height};
|
||||
elem.div.layout = LAYOUT_ROW;
|
||||
elem.div.z_index = 0;
|
||||
elem.div.children_bounds = elem.bounds;
|
||||
elem.div.scroll_x.enabled = false;
|
||||
elem.div.scroll_y.enabled = false;
|
||||
elem.div.pcb = {};
|
||||
elem.div.origin_c = {};
|
||||
elem.div.origin_r = {};
|
||||
elem.layout.dir = ROW;
|
||||
elem.layout.anchor = TOP_LEFT;
|
||||
elem.layout.w = @exact(ctx.width);
|
||||
elem.layout.h = @exact(ctx.height);
|
||||
|
||||
ctx.div_scissor = {0, 0, ctx.width, ctx.height};
|
||||
ctx.div_scissor = elem.bounds;
|
||||
|
||||
// The root element does not push anything to the stack
|
||||
// TODO: add a background color taken from a theme or config
|
||||
@ -253,7 +265,13 @@ 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
|
||||
Elem* root = ctx.get_active_div()!;
|
||||
if (root.id != ROOT_ID) return WRONG_ID?;
|
||||
if (root.id != ROOT_ID) {
|
||||
io::printn(root.id);
|
||||
return WRONG_ID?;
|
||||
}
|
||||
|
||||
// DO THE LAYOUT
|
||||
ctx.layout_element_tree();
|
||||
|
||||
// 1. clear the tree
|
||||
ctx.tree.nuke();
|
||||
@ -291,6 +309,11 @@ $endif
|
||||
|
||||
// sort the command buffer by the z-index
|
||||
ctx.cmd_queue.sort()!;
|
||||
|
||||
// foreach (i, c: ctx.cmd_queue) {
|
||||
// io::printf("[%d]: ", i);
|
||||
// io::printn(c);
|
||||
// }
|
||||
}
|
||||
|
||||
<*
|
||||
|
@ -5,7 +5,6 @@ import std::math;
|
||||
|
||||
// div element
|
||||
struct ElemDiv {
|
||||
Layout layout;
|
||||
struct scroll_x {
|
||||
bool enabled;
|
||||
bool on;
|
||||
@ -18,19 +17,42 @@ struct ElemDiv {
|
||||
}
|
||||
ushort scroll_size;
|
||||
int z_index;
|
||||
Rect children_bounds; // current frame children bounds
|
||||
Rect pcb; // previous frame children bounds
|
||||
Point origin_r, origin_c;
|
||||
}
|
||||
|
||||
// begin a widget container, or div, the size determines the offset (x,y) width and height.
|
||||
// if the width or height are zero the width or height are set to the maximum available.
|
||||
// if the width or height are negative the width or height will be calculated based on the children size
|
||||
// sort similar to a flexbox, and the minimum size is set by the negative of the width or height
|
||||
// FIXME: there is a bug if the size.w or size.h == -0
|
||||
macro Ctx.div_begin(&ctx, Rect size, bool scroll_x = false, bool scroll_y = false, ...)
|
||||
=> ctx.div_begin_id(@compute_id($vasplat), size, scroll_x, scroll_y);
|
||||
fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
|
||||
|
||||
// useful macro to start and end a div, capturing the trailing block
|
||||
macro Ctx.@div(&ctx,
|
||||
Size width = @grow, Size height = @grow,
|
||||
LayoutDirection dir = ROW,
|
||||
Anchor anchor = TOP_LEFT,
|
||||
bool scroll_x = false, bool scroll_y = false,
|
||||
...;
|
||||
@body()
|
||||
)
|
||||
{
|
||||
ctx.div_begin(width, height, dir, anchor, scroll_x, scroll_y, $vasplat)!;
|
||||
@body();
|
||||
ctx.div_end()!;
|
||||
}
|
||||
|
||||
macro Ctx.div_begin(&ctx,
|
||||
Size width = @grow(), Size height = @grow(),
|
||||
LayoutDirection dir = ROW,
|
||||
Anchor anchor = TOP_LEFT,
|
||||
bool scroll_x = false, bool scroll_y = false,
|
||||
...
|
||||
)
|
||||
{
|
||||
return ctx.div_begin_id(@compute_id($vasplat), width, height, dir, anchor, scroll_x, scroll_y);
|
||||
}
|
||||
|
||||
fn void? Ctx.div_begin_id(&ctx,
|
||||
Id id,
|
||||
Size width, Size height,
|
||||
LayoutDirection dir,
|
||||
Anchor anchor,
|
||||
bool scroll_x, bool scroll_y
|
||||
)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
@ -46,31 +68,23 @@ fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
|
||||
elem.div.scroll_size = slider_style.size ? slider_style.size : (style.size ? style.size : DEFAULT_STYLE.size);
|
||||
elem.div.z_index = parent.div.z_index + 1;
|
||||
|
||||
// 2. layout the element
|
||||
Rect wanted_size = {
|
||||
.x = size.x,
|
||||
.y = size.y,
|
||||
.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,
|
||||
};
|
||||
elem.bounds = ctx.layout_element(parent, wanted_size, style);
|
||||
elem.div.children_bounds = {};
|
||||
|
||||
// update the ctx scissor
|
||||
ctx.div_scissor = elem.bounds;
|
||||
ctx.push_scissor(elem.bounds, elem.div.z_index)!;
|
||||
|
||||
// 4. Fill the div fields
|
||||
elem.div.origin_c = {
|
||||
.x = elem.bounds.x,
|
||||
.y = elem.bounds.y
|
||||
// update layout with correct info
|
||||
elem.layout = {
|
||||
.w = width,
|
||||
.h = height,
|
||||
.dir = dir,
|
||||
.anchor = anchor,
|
||||
.content_offset = style.margin + style.border + style.padding,
|
||||
};
|
||||
elem.div.origin_r = elem.div.origin_c;
|
||||
elem.div.layout = parent.div.layout;
|
||||
|
||||
// update parent grow children
|
||||
update_parent_grow(elem, parent);
|
||||
|
||||
// Add the background to the draw stack
|
||||
bool do_border = parent.div.layout == LAYOUT_FLOATING;
|
||||
ctx.push_rect(elem.bounds, elem.div.z_index, style)!;
|
||||
ctx.push_rect(elem.bounds.pad(style.margin), elem.div.z_index, style)!;
|
||||
|
||||
elem.events = ctx.get_elem_events(elem);
|
||||
|
||||
@ -80,14 +94,9 @@ fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
|
||||
|
||||
fn void? Ctx.div_end(&ctx)
|
||||
{
|
||||
// swap the children bounds
|
||||
Elem* elem = ctx.get_active_div()!;
|
||||
elem.div.pcb = elem.div.children_bounds;
|
||||
|
||||
// FIXME: this causes all elements inside the div to loose focus since the mouse press happens
|
||||
// both inside the element and inside the div bounds
|
||||
//elem.events = ctx.get_elem_events(elem);
|
||||
|
||||
/* FIXME: Redo slider functionality
|
||||
Rect cb = elem.div.pcb;
|
||||
// children bounds bottom-right corner
|
||||
Point cbc = {
|
||||
@ -146,10 +155,13 @@ fn void? Ctx.div_end(&ctx)
|
||||
ctx.slider_hor_id(hsid_raw, hslider, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!;
|
||||
elem.div.layout = prev_l;
|
||||
}
|
||||
*/
|
||||
|
||||
// the active_div returns to the parent of the current one
|
||||
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;
|
||||
ctx.push_scissor(parent.bounds, elem.div.z_index)!;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
}
|
||||
|
@ -9,11 +9,34 @@ import std::io;
|
||||
import std::ascii;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// CODEPOINT //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
// unicode code point, different type for a different hash
|
||||
alias Codepoint = uint;
|
||||
|
||||
<*
|
||||
@require off != null
|
||||
@require str.ptr != null
|
||||
*>
|
||||
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();
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// FONT ATLAS //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
/* width and height of a glyph contain the kering advance
|
||||
* (u,v)
|
||||
* +-------------*---+ -
|
||||
@ -60,7 +83,7 @@ struct Font {
|
||||
|
||||
fn void? Font.load(&font, String name, ZString path, uint height, float scale)
|
||||
{
|
||||
font.table.init(allocator::heap(), capacity: FONT_CACHED);
|
||||
font.table.init(allocator::mem, capacity: FONT_CACHED);
|
||||
font.id = name.hash();
|
||||
|
||||
font.size = height*scale;
|
||||
@ -160,141 +183,16 @@ fn void Font.free(&font)
|
||||
schrift::freefont(font.sft.font);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// FONT LOAD AND QUERY //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
fn void? Ctx.load_font(&ctx, String name, ZString path, uint height, float scale = 1.0)
|
||||
{
|
||||
return ctx.font.load(name, path, height, scale);
|
||||
}
|
||||
|
||||
<*
|
||||
@require off != null
|
||||
@require str.ptr != null
|
||||
*>
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
TextInfo ti;
|
||||
ti.init(&ctx.font, text);
|
||||
while (ti.place_glyph(false)!);
|
||||
return ti.text_bounds;
|
||||
}
|
||||
|
||||
// TODO: check if the font is present in the context
|
||||
fn Id Ctx.get_font_id(&ctx, String label)
|
||||
{
|
||||
@ -312,3 +210,223 @@ fn Atlas*? Ctx.get_font_atlas(&ctx, String name)
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
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)!;
|
||||
}
|
||||
|
@ -195,6 +195,8 @@ fn void Ctx.input_char(&ctx, char c)
|
||||
ctx.input_text_utf8(b[..]);
|
||||
}
|
||||
|
||||
fn String Ctx.get_keys(&ctx) => (String)ctx.input.keyboard.text[:ctx.input.keyboard.text_len];
|
||||
|
||||
// Modifier keys, like control or backspace
|
||||
// TODO: make this call repetible to input modkeys one by one
|
||||
fn void Ctx.input_mod_keys(&ctx, ModKeys modkeys)
|
||||
|
@ -1,54 +1,129 @@
|
||||
module ugui;
|
||||
|
||||
enum Layout {
|
||||
LAYOUT_ROW,
|
||||
LAYOUT_COLUMN,
|
||||
LAYOUT_FLOATING,
|
||||
LAYOUT_ABSOLUTE,
|
||||
import std::math;
|
||||
import std::io;
|
||||
|
||||
enum LayoutDirection {
|
||||
ROW,
|
||||
COLUMN,
|
||||
}
|
||||
|
||||
fn void? Ctx.layout_set_row(&ctx)
|
||||
{
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
parent.div.layout = LAYOUT_ROW;
|
||||
enum Anchor {
|
||||
TOP_LEFT,
|
||||
LEFT,
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM,
|
||||
BOTTOM_RIGHT,
|
||||
RIGHT,
|
||||
TOP_RIGHT,
|
||||
TOP,
|
||||
CENTER
|
||||
}
|
||||
|
||||
fn void? Ctx.layout_set_column(&ctx)
|
||||
{
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
parent.div.layout = LAYOUT_COLUMN;
|
||||
struct Layout {
|
||||
Size w, h; // size of the CONTENT, does not include margin, border and padding
|
||||
struct children { // the children size includes the children's margin/border/pading
|
||||
Size w, h;
|
||||
}
|
||||
TextSize text;
|
||||
ushort grow_children;
|
||||
short occupied;
|
||||
struct origin {
|
||||
short x, y;
|
||||
}
|
||||
LayoutDirection dir;
|
||||
Anchor anchor;
|
||||
Rect content_offset; // combined effect of margin, border and padding
|
||||
}
|
||||
|
||||
fn void? Ctx.layout_set_floating(&ctx)
|
||||
// Total width of a layout element, complete with margin, border and padding
|
||||
macro Size Layout.total_width(&el)
|
||||
{
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
parent.div.layout = LAYOUT_FLOATING;
|
||||
Rect min = (Rect){.w = el.w.min, .h = el.h.min}.expand(el.content_offset);
|
||||
Rect max = (Rect){.w = el.w.max, .h = el.h.max}.expand(el.content_offset);
|
||||
return {.min = min.w, .max = max.w};
|
||||
}
|
||||
|
||||
fn void? Ctx.layout_next_row(&ctx)
|
||||
// Total height of a layout element, complete with margin, border and padding
|
||||
macro Size Layout.total_height(&el)
|
||||
{
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
Rect min = (Rect){.w = el.w.min, .h = el.h.min}.expand(el.content_offset);
|
||||
Rect max = (Rect){.w = el.w.max, .h = el.h.max}.expand(el.content_offset);
|
||||
return {.min = min.h, .max = max.h};
|
||||
}
|
||||
|
||||
parent.div.origin_r = {
|
||||
.x = parent.bounds.x,
|
||||
.y = parent.div.children_bounds.bottom_right().y,
|
||||
// Returns the width and height of a @FIT() element based on it's wanted size (min/max)
|
||||
// and the content size, this function is used to both update the parent's children size and
|
||||
// give the dimensions of a fit element
|
||||
macro Point Layout.get_dimensions(&el)
|
||||
{
|
||||
Size content_width = el.children.w + el.text.width;
|
||||
Size width = el.w.combine(content_width);
|
||||
short final_width = width.greater();
|
||||
|
||||
short text_height;
|
||||
if (el.text.area != 0) {
|
||||
short text_width = (@exact(final_width) - el.children.w).combine(el.text.width).min;
|
||||
text_height = @exact((short)(el.text.area / text_width)).combine(el.text.height).min;
|
||||
}
|
||||
|
||||
Size content_height = el.children.h + @exact(text_height);
|
||||
Size height = el.h.combine(content_height);
|
||||
short final_height = height.greater();
|
||||
|
||||
Point dim = {
|
||||
.x = final_width + el.content_offset.x + el.content_offset.w,
|
||||
.y = final_height + el.content_offset.y + el.content_offset.h,
|
||||
};
|
||||
parent.div.origin_c = parent.div.origin_r;
|
||||
return dim;
|
||||
}
|
||||
|
||||
fn void? Ctx.layout_next_column(&ctx)
|
||||
// The content space of the element
|
||||
macro Point Elem.content_space(&e)
|
||||
{
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
|
||||
parent.div.origin_c = {
|
||||
.x = parent.div.children_bounds.bottom_right().x,
|
||||
.y = parent.bounds.y,
|
||||
return {
|
||||
.x = e.bounds.w - e.layout.content_offset.x - e.layout.content_offset.w,
|
||||
.y = e.bounds.h - e.layout.content_offset.y - e.layout.content_offset.h,
|
||||
};
|
||||
parent.div.origin_r = parent.div.origin_c;
|
||||
}
|
||||
|
||||
// Update the parent element's grow children counter
|
||||
fn void update_parent_grow(Elem* child, Elem* parent)
|
||||
{
|
||||
switch (parent.layout.dir) {
|
||||
case ROW:
|
||||
if (child.layout.w.@is_grow()) parent.layout.grow_children++;
|
||||
case COLUMN:
|
||||
if (child.layout.h.@is_grow()) parent.layout.grow_children++;
|
||||
}
|
||||
}
|
||||
|
||||
macro Rect Elem.content_bounds(&elem, style) => elem.bounds.pad(style.border).pad(style.padding);
|
||||
// Update the parent element's children size
|
||||
fn void update_parent_size(Elem* child, Elem* parent)
|
||||
{
|
||||
Layout* cl = &child.layout;
|
||||
Layout* pl = &parent.layout;
|
||||
|
||||
Point child_size = cl.get_dimensions();
|
||||
|
||||
switch (pl.dir) {
|
||||
case ROW: // on rows grow the ch width by the child width and only grow ch height if it exceeds
|
||||
pl.children.w += @exact(child_size.x);
|
||||
pl.children.h = pl.children.h.comb_max(@exact(child_size.y));
|
||||
case COLUMN: // do the opposite on column
|
||||
pl.children.w = pl.children.w.comb_max(@exact(child_size.x));
|
||||
pl.children.h += @exact(child_size.y);
|
||||
}
|
||||
}
|
||||
|
||||
fn void update_children_bounds(Elem* child, Elem* parent)
|
||||
{
|
||||
parent.children_bounds = containing_rect(child.bounds, parent.bounds);
|
||||
}
|
||||
|
||||
macro Rect Elem.content_bounds(&elem) => elem.bounds.pad(elem.layout.content_offset);
|
||||
|
||||
/*
|
||||
macro Rect Elem.get_view(&elem)
|
||||
{
|
||||
Rect off;
|
||||
@ -67,91 +142,224 @@ macro Point Elem.get_view_off(&elem)
|
||||
{
|
||||
return elem.get_view().sub(elem.bounds).position();
|
||||
}
|
||||
*/
|
||||
|
||||
// position the rectangle inside the parent according to the layout
|
||||
// parent: parent div
|
||||
// rect: the requested size
|
||||
// style: apply style
|
||||
<*
|
||||
@require ctx != null
|
||||
@require parent.type == ETYPE_DIV
|
||||
*>
|
||||
fn Rect Ctx.layout_element(&ctx, Elem *parent, Rect rect, Style* style)
|
||||
// Assign the width and height of an element in the directions that it doesn't need to grow
|
||||
fn void resolve_dimensions(Elem* e, Elem* p)
|
||||
{
|
||||
ElemDiv* div = &parent.div;
|
||||
Layout* el = &e.layout;
|
||||
Layout* pl = &p.layout;
|
||||
|
||||
Point elem_dimensions = el.get_dimensions();
|
||||
|
||||
Rect parent_bounds, parent_view;
|
||||
Rect child_placement, child_occupied;
|
||||
|
||||
// 1. Select the right origin
|
||||
Point origin;
|
||||
switch (div.layout) {
|
||||
case LAYOUT_ROW:
|
||||
origin = div.origin_r;
|
||||
case LAYOUT_COLUMN:
|
||||
origin = div.origin_c;
|
||||
case LAYOUT_FLOATING: // none, relative to zero zero
|
||||
case LAYOUT_ABSOLUTE: // absolute position, this is a no-op, return the rect
|
||||
return rect;
|
||||
default: // error
|
||||
return {};
|
||||
short text_min_height;
|
||||
// ASSIGN WIDTH
|
||||
switch {
|
||||
case el.w.@is_exact():
|
||||
// if width is exact then assign it to the bounds
|
||||
e.bounds.w = el.total_width().min;
|
||||
case el.w.@is_grow():
|
||||
// done in another pass
|
||||
break;
|
||||
case el.w.@is_fit(): // fit the element's children
|
||||
e.bounds.w = elem_dimensions.x;
|
||||
default: unreachable("width is not exact, grow or fit");
|
||||
}
|
||||
|
||||
// 2. Compute the parent's view
|
||||
parent_bounds = parent.bounds;
|
||||
parent_view = parent.get_view();
|
||||
// ASSIGN HEIGHT
|
||||
switch {
|
||||
case el.h.@is_exact():
|
||||
// if width is exact then assign it to the bounds and add border and paddign
|
||||
e.bounds.h = el.total_height().min;
|
||||
case el.h.@is_grow():
|
||||
break;
|
||||
// done in another pass
|
||||
case el.h.@is_fit(): // fit the element's children
|
||||
e.bounds.h = elem_dimensions.y;
|
||||
default: unreachable("width is not exact, grow or fit");
|
||||
}
|
||||
|
||||
// 3. Compute the placement and occupied area
|
||||
|
||||
// grow rect (wanted size) when widht or height are less than zero
|
||||
bool adapt_x = rect.w <= 0;
|
||||
bool adapt_y = rect.h <= 0;
|
||||
if (adapt_x) rect.w = parent_bounds.w - parent_bounds.x - origin.x;
|
||||
if (adapt_y) rect.h = parent_bounds.h - parent_bounds.y - origin.y;
|
||||
|
||||
// offset placement and area
|
||||
child_placement = child_placement.off(origin.add(rect.position()));
|
||||
child_occupied = child_occupied.off(origin.add(rect.position()));
|
||||
|
||||
Rect margin = style.margin;
|
||||
Rect border = style.border;
|
||||
Rect padding = style.padding;
|
||||
|
||||
// padding, grows both the placement and occupied area
|
||||
child_placement = child_placement.grow(padding.position().add(padding.size()));
|
||||
child_occupied = child_occupied.grow(padding.position().add(padding.size()));
|
||||
// border, grows both the placement and occupied area
|
||||
child_placement = child_placement.grow(border.position().add(border.size()));
|
||||
child_occupied = child_occupied.grow(border.position().add(border.size()));
|
||||
// margin, offsets the placement and grows the occupied area
|
||||
child_placement = child_placement.off(margin.position());
|
||||
child_occupied = child_occupied.grow(margin.position().add(margin.size()));
|
||||
|
||||
// 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_y) rect.h -= padding.y+padding.h + border.y+border.h + margin.y+margin.h;
|
||||
|
||||
// set the size
|
||||
child_placement = child_placement.grow(rect.size());
|
||||
child_occupied = child_occupied.grow(rect.size());
|
||||
|
||||
// 4. Update the parent's origin
|
||||
div.origin_r = {
|
||||
.x = child_occupied.bottom_right().x,
|
||||
.y = origin.y,
|
||||
};
|
||||
div.origin_c = {
|
||||
.x = origin.x,
|
||||
.y = child_occupied.bottom_right().y,
|
||||
};
|
||||
|
||||
// 5. Update the parent's children bounds
|
||||
div.children_bounds = containing_rect(div.children_bounds, child_occupied);
|
||||
|
||||
// 99. return the placement
|
||||
if (child_placement.collides(parent_view)) {
|
||||
return child_placement.off(parent.get_view_off().neg());
|
||||
} else {
|
||||
return {};
|
||||
switch (pl.dir) {
|
||||
case ROW:
|
||||
if (!el.w.@is_grow()) pl.occupied += e.bounds.w;
|
||||
case COLUMN:
|
||||
if (!el.h.@is_grow()) pl.occupied += e.bounds.h;
|
||||
}
|
||||
}
|
||||
|
||||
fn void resolve_grow_elements(Elem* e, Elem* p)
|
||||
{
|
||||
// WIDTH
|
||||
if (e.layout.w.@is_grow()) {
|
||||
if (p.layout.dir == ROW) { // grow along the axis, divide the parent size
|
||||
short slot = (short)((p.content_space().x - p.layout.occupied) / p.layout.grow_children);
|
||||
// the space slot accounts for the total size, while the element bounds does not account
|
||||
// for the margin
|
||||
e.bounds.w = slot;
|
||||
p.layout.grow_children--;
|
||||
p.layout.occupied += slot;
|
||||
} else if (p.layout.dir == COLUMN) { // grow across the layout axis, inherit width of the parent
|
||||
e.bounds.w = p.content_space().x;
|
||||
}
|
||||
}
|
||||
|
||||
// HEIGHT
|
||||
if (e.layout.h.@is_grow()) {
|
||||
if (p.layout.dir == COLUMN) { // grow along the axis, divide the parent size
|
||||
short slot = (short)((p.content_space().y - p.layout.occupied) / p.layout.grow_children);
|
||||
e.bounds.h = slot;
|
||||
p.layout.grow_children--;
|
||||
p.layout.occupied += slot;
|
||||
} else if (p.layout.dir == ROW) { // grow across the layout axis, inherit width of the parent
|
||||
e.bounds.h = p.content_space().y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void resolve_placement(Elem* c, Elem* p)
|
||||
{
|
||||
Layout* pl = &p.layout;
|
||||
Layout* cl = &c.layout;
|
||||
|
||||
Point off = {
|
||||
.x = p.bounds.x + pl.origin.x,
|
||||
.y = p.bounds.y + pl.origin.y,
|
||||
};
|
||||
|
||||
switch (pl.anchor) {
|
||||
case TOP_LEFT:
|
||||
c.bounds.x = off.x;
|
||||
c.bounds.y = off.y;
|
||||
case LEFT:
|
||||
c.bounds.x = off.x;
|
||||
c.bounds.y = off.y + p.bounds.h/2;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied/2;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h/2;
|
||||
}
|
||||
case BOTTOM_LEFT:
|
||||
c.bounds.x = off.x;
|
||||
c.bounds.y = off.y + p.bounds.h ;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h;
|
||||
}
|
||||
case BOTTOM:
|
||||
c.bounds.x = off.x + p.bounds.w/2;
|
||||
c.bounds.y = off.y + p.bounds.h;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied;
|
||||
c.bounds.x -= c.bounds.w/2;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h;
|
||||
c.bounds.x -= pl.occupied/2;
|
||||
}
|
||||
case BOTTOM_RIGHT:
|
||||
c.bounds.x = off.x + p.bounds.w;
|
||||
c.bounds.y = off.y + p.bounds.h;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied;
|
||||
c.bounds.x -= c.bounds.w;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h;
|
||||
c.bounds.x -= pl.occupied;
|
||||
}
|
||||
case RIGHT:
|
||||
c.bounds.x = off.x + p.bounds.w;
|
||||
c.bounds.y = off.y + p.bounds.h/2;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied/2;
|
||||
c.bounds.x -= c.bounds.w;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h/2;
|
||||
c.bounds.x -= pl.occupied;
|
||||
}
|
||||
case TOP_RIGHT:
|
||||
c.bounds.x = off.x + p.bounds.w;
|
||||
c.bounds.y = off.y;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.x -= c.bounds.w;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.x -= pl.occupied;
|
||||
}
|
||||
case TOP:
|
||||
c.bounds.x = off.x + p.bounds.w/2;
|
||||
c.bounds.y = off.y;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.x -= c.bounds.w/2;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.x -= pl.occupied/2;
|
||||
}
|
||||
case CENTER:
|
||||
c.bounds.x = off.x + p.bounds.w/2;
|
||||
c.bounds.y = off.y + p.bounds.h/2;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.x -= c.bounds.w/2;
|
||||
c.bounds.y -= pl.occupied/2;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.x -= pl.occupied/2;
|
||||
c.bounds.y -= c.bounds.h/2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (pl.dir) {
|
||||
case ROW:
|
||||
pl.origin.x += c.bounds.w;
|
||||
case COLUMN:
|
||||
pl.origin.y += c.bounds.h;
|
||||
default: unreachable("unknown layout direction");
|
||||
}
|
||||
}
|
||||
|
||||
fn void Ctx.layout_element_tree(&ctx)
|
||||
{
|
||||
isz cursor = -1;
|
||||
isz current = ctx.tree.level_order_it(0, &cursor)!!;
|
||||
for (; current >= 0; current = ctx.tree.level_order_it(0, &cursor)!!) {
|
||||
Elem* p = ctx.find_elem(ctx.tree.get(current))!!;
|
||||
// offset the origin by the margin
|
||||
p.layout.origin.x = p.layout.content_offset.x;
|
||||
p.layout.origin.y = p.layout.content_offset.y;
|
||||
|
||||
// RESOLVE KNOWN DIMENSIONS
|
||||
isz ch_cur = 0;
|
||||
isz ch = ctx.tree.children_it(current, &ch_cur)!!;
|
||||
for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) {
|
||||
Elem* c = ctx.find_elem(ctx.tree.get(ch))!!;
|
||||
if (ctx.tree.is_root(ch)!!) {
|
||||
resolve_dimensions(p, &&{});
|
||||
} else {
|
||||
resolve_dimensions(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
// RESOLVE GROW CHILDREN
|
||||
ch_cur = 0;
|
||||
ch = ctx.tree.children_it(current, &ch_cur)!!;
|
||||
for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) {
|
||||
Elem* c = ctx.find_elem(ctx.tree.get(ch))!!;
|
||||
if (ctx.tree.is_root(ch)!!) {
|
||||
resolve_grow_elements(p, &&{});
|
||||
} else {
|
||||
resolve_grow_elements(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
// RESOLVE CHILDREN PLACEMENT
|
||||
ch_cur = 0;
|
||||
ch = ctx.tree.children_it(current, &ch_cur)!!;
|
||||
for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) {
|
||||
Elem* c = ctx.find_elem(ctx.tree.get(ch))!!;
|
||||
if (ctx.tree.is_root(ch)!!) {
|
||||
resolve_placement(p, &&{});
|
||||
update_children_bounds(c, p);
|
||||
} else {
|
||||
resolve_placement(c, p);
|
||||
update_children_bounds(c, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ macro Rect containing_rect(Rect a, Rect b)
|
||||
macro bool Rect.is_null(Rect r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
|
||||
|
||||
// returns the element-wise addition of r1 and r2
|
||||
macro Rect Rect.add(Rect r1, Rect r2)
|
||||
macro Rect Rect.add(Rect r1, Rect r2) @operator_s(+)
|
||||
{
|
||||
return {
|
||||
.x = r1.x + r2.x,
|
||||
@ -66,7 +66,7 @@ macro Rect Rect.add(Rect r1, Rect r2)
|
||||
}
|
||||
|
||||
// returns the element-wise subtraction of r1 and r2
|
||||
macro Rect Rect.sub(Rect r1, Rect r2)
|
||||
macro Rect Rect.sub(Rect r1, Rect r2) @operator_s(-)
|
||||
{
|
||||
return {
|
||||
.x = r1.x - r2.x,
|
||||
@ -77,7 +77,7 @@ macro Rect Rect.sub(Rect r1, Rect r2)
|
||||
}
|
||||
|
||||
// returns the element-wise multiplication of r1 and r2
|
||||
macro Rect Rect.mul(Rect r1, Rect r2)
|
||||
macro Rect Rect.mul(Rect r1, Rect r2) @operator_s(*)
|
||||
{
|
||||
return {
|
||||
.x = r1.x * r2.x,
|
||||
@ -174,6 +174,16 @@ macro Rect Rect.pad(Rect a, Rect b)
|
||||
};
|
||||
}
|
||||
|
||||
macro Rect Rect.expand(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 //
|
||||
@ -189,39 +199,12 @@ macro bool Point.in_rect(Point p, Rect r)
|
||||
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
|
||||
}
|
||||
|
||||
macro Point Point.add(Point a, Point b)
|
||||
{
|
||||
return {
|
||||
.x = a.x + b.x,
|
||||
.y = a.y + b.y,
|
||||
};
|
||||
}
|
||||
macro Point Point.add(Point a, Point b) @operator_s(+) => {.x = a.x+b.x, .y = a.y+b.y};
|
||||
macro Point Point.sub(Point a, Point b) @operator_s(-) => {.x = a.x-b.x, .y = a.y-b.y};
|
||||
macro Point Point.neg(Point p) @operator_s(-) => {-p.x, -p.y};
|
||||
|
||||
macro Point Point.sub(Point a, Point b)
|
||||
{
|
||||
return {
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y,
|
||||
};
|
||||
}
|
||||
|
||||
macro Point Point.neg(Point p) => {-p.x, -p.y};
|
||||
|
||||
macro Point Point.max(Point a, Point b)
|
||||
{
|
||||
return {
|
||||
.x = max(a.x, b.x),
|
||||
.y = max(a.y, b.y),
|
||||
};
|
||||
}
|
||||
|
||||
macro Point Point.min(Point a, Point b)
|
||||
{
|
||||
return {
|
||||
.x = min(a.x, b.x),
|
||||
.y = min(a.y, b.y),
|
||||
};
|
||||
}
|
||||
macro Point Point.max(Point a, Point b) => {.x = max(a.x, b.x), .y = max(a.y, b.y)};
|
||||
macro Point Point.min(Point a, Point b) => {.x = min(a.x, b.x), .y = min(a.y, b.y)};
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// COLOR //
|
||||
@ -252,8 +235,29 @@ macro Color uint.@to_rgba($u)
|
||||
};
|
||||
}
|
||||
|
||||
macro uint Color.to_uint(c)
|
||||
{
|
||||
uint u = c.r | (c.g << 8) | (c.b << 16) | (c.a << 24);
|
||||
return u;
|
||||
macro uint Color.to_uint(c) => c.r | (c.g << 8) | (c.b << 16) | (c.a << 24);
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// SIZE //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
struct Size {
|
||||
short min, max;
|
||||
}
|
||||
|
||||
macro Size @grow() => {.min = 0, .max = 0};
|
||||
macro Size @exact(short s) => {.min = s, .max = s};
|
||||
// PROBLEM WE ARE OVERFLOWING
|
||||
macro Size @fit(short min = 0, short max = 999) => {.min = min, .max = max};
|
||||
macro bool Size.@is_grow(s) => (s.min == 0 && s.max == 0);
|
||||
macro bool Size.@is_exact(s) => (s.min == s.max && s.min != 0);
|
||||
macro bool Size.@is_fit(s) => (s.min != s.max);
|
||||
|
||||
macro Size Size.add(a, Size b) @operator_s(+) => {.min = a.min+b.min, .max = a.max+b.max};
|
||||
macro Size Size.sub(a, Size b) @operator_s(-) => {.min = a.min-b.min, .max = a.max-b.max};
|
||||
|
||||
macro Size Size.combine(a, Size b) => {.min = max(a.min, b.min), .max = min(a.max, b.max)};
|
||||
macro Size Size.comb_max(a, Size b) => {.min = max(a.min, b.min), .max = max(a.max, b.max)};
|
||||
macro Size Size.comb_min(a, Size b) => {.min = min(a.min, b.min), .max = min(a.max, b.max)};
|
||||
|
||||
macro short Size.greater(a) => a.min > a.max ? a.min : a.max;
|
||||
|
@ -8,12 +8,12 @@ struct ElemSlider {
|
||||
Rect handle;
|
||||
}
|
||||
|
||||
|
||||
/* handle
|
||||
* +----+-----+---------------------+
|
||||
* | |#####| |
|
||||
* +----+-----+---------------------+
|
||||
*/
|
||||
/*
|
||||
macro Ctx.slider_hor(&ctx, Rect size, float* value, float hpercent = 0.25, ...)
|
||||
=> ctx.slider_hor_id(@compute_id($vasplat), size, value, hpercent);
|
||||
<*
|
||||
@ -63,6 +63,7 @@ fn ElemEvents? Ctx.slider_hor_id(&ctx, Id id, Rect size, float* value, float hpe
|
||||
|
||||
return elem.events;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
@ -77,6 +78,7 @@ fn ElemEvents? Ctx.slider_hor_id(&ctx, Id id, Rect size, float* value, float hpe
|
||||
* | |
|
||||
* +--+
|
||||
*/
|
||||
/*
|
||||
macro Ctx.slider_ver(&ctx, Rect size, float* value, float hpercent = 0.25, ...)
|
||||
=> ctx.slider_ver_id(@compute_id($vasplat), size, value, hpercent);
|
||||
fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Rect size, float* value, float hpercent = 0.25)
|
||||
@ -134,3 +136,4 @@ fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Rect size, float* value, float hpe
|
||||
macro short calc_slider(short off, short dim, float value) => (short)off + (short)(dim * value);
|
||||
macro float calc_value(short off, short mouse, short dim, short slider)
|
||||
=> math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f);
|
||||
*/
|
@ -44,7 +44,7 @@ fn void? SpriteAtlas.init(&this, String name, AtlasType type, ushort width, usho
|
||||
|
||||
this.id = name.hash();
|
||||
this.atlas.new(this.id, AtlasType.ATLAS_R8G8B8A8, width, height)!;
|
||||
this.sprites.init(allocator::heap(), capacity: SRITES_PER_ATLAS);
|
||||
this.sprites.init(allocator::mem, capacity: SRITES_PER_ATLAS);
|
||||
this.should_update = false;
|
||||
}
|
||||
|
||||
@ -104,13 +104,14 @@ fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t
|
||||
{
|
||||
QOIDesc desc;
|
||||
|
||||
char[] pixels = qoi::read(allocator::heap(), path, &desc, QOIChannels.RGBA)!;
|
||||
char[] pixels = qoi::read(allocator::mem, path, &desc, QOIChannels.RGBA)!;
|
||||
defer mem::free(pixels);
|
||||
|
||||
ctx.sprite_atlas.insert(name, type, pixels, (ushort)desc.width, (ushort)desc.height, (ushort)desc.width)!;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
macro Ctx.sprite(&ctx, String name, Point off = {0,0}, ...)
|
||||
=> ctx.sprite_id(@compute_id($vasplat), name, off);
|
||||
fn void? Ctx.sprite_id(&ctx, Id id, String name, Point off)
|
||||
@ -151,3 +152,4 @@ fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds, bool center = false
|
||||
|
||||
return ctx.push_sprite(bounds, sprite.uv(), tex_id, parent.div.z_index, type: sprite.type)!;
|
||||
}
|
||||
*/
|
@ -3,15 +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)!;
|
||||
|
||||
@ -21,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)
|
||||
@ -77,3 +80,4 @@ fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_le
|
||||
|
||||
return elem.events;
|
||||
}
|
||||
*/
|
@ -4,39 +4,51 @@ faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INV
|
||||
module vtree{ElemType};
|
||||
|
||||
import std::core::mem;
|
||||
import std::core::mem::allocator;
|
||||
import std::io;
|
||||
|
||||
struct VTree {
|
||||
Allocator allocator;
|
||||
usz elements;
|
||||
ElemType[] vector; // vector of element ids
|
||||
isz[] refs, ordered_refs;
|
||||
}
|
||||
|
||||
|
||||
macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); }
|
||||
macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; }
|
||||
macro VTree.size(&tree) { return tree.refs.len; }
|
||||
macro VTree.ref_is_valid(&tree, isz ref) => (ref >= 0 && ref < tree.refs.len);
|
||||
macro VTree.ref_is_present(&tree, isz ref) => tree.refs[ref] >= 0;
|
||||
macro VTree.size(&tree) => tree.refs.len;
|
||||
|
||||
// macro to zero an elemen
|
||||
// macro to zero an element
|
||||
macro @zero()
|
||||
{
|
||||
$if $assignable(0, ElemType):
|
||||
$if @assignable_to(0, ElemType):
|
||||
return 0;
|
||||
$else
|
||||
$endif
|
||||
|
||||
$if @assignable_to(null, ElemType):
|
||||
return null;
|
||||
$endif
|
||||
|
||||
$if @assignable_to({}, ElemType):
|
||||
return {};
|
||||
$endif
|
||||
|
||||
//$assert true == false : ElemType.nameof +++ " is not assignable to zero or equivalent";
|
||||
}
|
||||
|
||||
fn void? VTree.init(&tree, usz size)
|
||||
fn void? VTree.init(&tree, usz size, Allocator allocator)
|
||||
{
|
||||
tree.vector = mem::new_array(ElemType, size);
|
||||
defer catch { (void)mem::free(tree.vector); }
|
||||
tree.allocator = allocator;
|
||||
|
||||
tree.refs = mem::new_array(isz, size);
|
||||
defer catch { (void)mem::free(tree.refs); }
|
||||
tree.vector = allocator::new_array(tree.allocator, ElemType, size);
|
||||
defer catch { (void)allocator::free(tree.allocator, tree.vector); }
|
||||
|
||||
tree.ordered_refs = mem::new_array(isz, size);
|
||||
defer catch { (void)mem::free(tree.ordered_refs); }
|
||||
tree.refs = allocator::new_array(tree.allocator, isz, size);
|
||||
defer catch { (void)allocator::free(tree.allocator, tree.refs); }
|
||||
|
||||
tree.ordered_refs = allocator::new_array(tree.allocator, isz, size);
|
||||
defer catch { (void)allocator::free(tree.allocator, tree.ordered_refs); }
|
||||
|
||||
// set all refs to -1, meaning invalid (free) element
|
||||
tree.refs[..] = -1;
|
||||
@ -46,9 +58,9 @@ fn void? VTree.init(&tree, usz size)
|
||||
|
||||
fn void VTree.free(&tree)
|
||||
{
|
||||
(void)mem::free(tree.vector);
|
||||
(void)mem::free(tree.refs);
|
||||
(void)mem::free(tree.ordered_refs);
|
||||
(void)allocator::free(tree.allocator, tree.vector);
|
||||
(void)allocator::free(tree.allocator, tree.refs);
|
||||
(void)allocator::free(tree.allocator, tree.ordered_refs);
|
||||
}
|
||||
|
||||
fn void VTree.pack(&tree)
|
||||
@ -102,14 +114,14 @@ fn void? VTree.resize(&tree, usz newsize)
|
||||
|
||||
usz old_size = tree.size();
|
||||
|
||||
tree.vector = ((ElemType*)mem::realloc(tree.vector, newsize*ElemType.sizeof))[:newsize];
|
||||
defer catch { (void)mem::free(tree.vector); }
|
||||
tree.vector = ((ElemType*)allocator::realloc(tree.allocator, tree.vector, newsize*ElemType.sizeof))[:newsize];
|
||||
defer catch { (void)allocator::free(tree.allocator, tree.vector); }
|
||||
|
||||
tree.refs = ((isz*)mem::realloc(tree.refs, newsize*isz.sizeof))[:newsize];
|
||||
defer catch { (void)mem::free(tree.refs); }
|
||||
tree.refs = ((isz*)allocator::realloc(tree.allocator, tree.refs, newsize*isz.sizeof))[:newsize];
|
||||
defer catch { (void)allocator::free(tree.allocator, tree.refs); }
|
||||
|
||||
tree.ordered_refs = ((isz*)mem::realloc(tree.ordered_refs, newsize*isz.sizeof))[:newsize];
|
||||
defer catch { (void)mem::free(tree.ordered_refs); }
|
||||
tree.ordered_refs = ((isz*)allocator::realloc(tree.allocator, tree.ordered_refs, newsize*isz.sizeof))[:newsize];
|
||||
defer catch { (void)allocator::free(tree.allocator, tree.ordered_refs); }
|
||||
|
||||
if (newsize > tree.size()) {
|
||||
tree.vector[old_size..newsize-1] = @zero();
|
||||
@ -213,6 +225,8 @@ fn usz? VTree.subtree_size(&tree, isz ref)
|
||||
return count;
|
||||
}
|
||||
|
||||
fn bool? VTree.is_root(&tree, isz node) => node == tree.parentof(node)!;
|
||||
|
||||
// iterate through the first level children, use a cursor like strtok_r
|
||||
fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
|
||||
{
|
||||
|
@ -2,9 +2,11 @@ default {
|
||||
bg: #282828ff;
|
||||
fg: #fbf1c7ff;
|
||||
primary: #cc241dff;
|
||||
secondary: #458588ff;
|
||||
secondary: #6c19ca8f;
|
||||
accent: #fabd2fff;
|
||||
border: 1;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
|
152
src/main.c3
152
src/main.c3
@ -6,6 +6,7 @@ import std::time;
|
||||
import std::collections::ringbuffer;
|
||||
import std::core::string;
|
||||
import std::ascii;
|
||||
import std::core::mem::allocator;
|
||||
import sdlrenderer::ren;
|
||||
import sdl3::sdl;
|
||||
|
||||
@ -58,8 +59,13 @@ const char[*] STYLESHEET_PATH = "resources/style.css";
|
||||
|
||||
fn int main(String[] args)
|
||||
{
|
||||
ArenaAllocator arena;
|
||||
char[] mem = mem::new_array(char, 1024*1024);
|
||||
defer (void)mem::free(mem);
|
||||
arena.init(mem);
|
||||
|
||||
ugui::Ctx ui;
|
||||
ui.init()!!;
|
||||
ui.init(&arena)!!;
|
||||
defer ui.free();
|
||||
|
||||
ren::Renderer ren;
|
||||
@ -199,34 +205,32 @@ fn int main(String[] args)
|
||||
if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true;
|
||||
|
||||
const String APPLICATION = "calculator";
|
||||
$if APPLICATION == "debug":
|
||||
$switch APPLICATION:
|
||||
$case "debug":
|
||||
debug_app(&ui);
|
||||
$endif
|
||||
$if APPLICATION == "calculator":
|
||||
$case "calculator":
|
||||
calculator(&ui);
|
||||
$endif
|
||||
$endswitch
|
||||
|
||||
// Timings counter
|
||||
TimeStats dts = draw_times.get_stats();
|
||||
TimeStats uts = ui_times.get_stats();
|
||||
|
||||
/*
|
||||
ui.layout_set_floating()!!;
|
||||
// FIXME: I cannot anchor shit to the bottom of the screen
|
||||
ui.div_begin({0, ui.height-150, -300, 150})!!;
|
||||
{
|
||||
ui.@div({0, ui.height-150, -300, 150}) {
|
||||
ui.layout_set_column()!!;
|
||||
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("%s %s", mod.lctrl, (String)ui.input.keyboard.text[:ui.input.keyboard.text_len]))!!;
|
||||
};
|
||||
ui.div_end()!!;
|
||||
}!!;
|
||||
*/
|
||||
|
||||
ui.frame_end()!!;
|
||||
/* End UI Handling */
|
||||
ui_times.push(clock.mark());
|
||||
//ui_times.print_stats();
|
||||
|
||||
|
||||
/* Start UI Drawing */
|
||||
ren.begin_render(true);
|
||||
|
||||
@ -249,6 +253,7 @@ $endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
fn void debug_app(ugui::Ctx* ui)
|
||||
{
|
||||
static bool toggle;
|
||||
@ -287,13 +292,12 @@ fn void debug_app(ugui::Ctx* ui)
|
||||
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.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)!!;
|
||||
@ -321,39 +325,93 @@ fn void debug_app(ugui::Ctx* ui)
|
||||
};
|
||||
ui.div_end()!!;
|
||||
}
|
||||
*/
|
||||
|
||||
import std::os::process;
|
||||
|
||||
fn void calculator(ugui::Ctx* ui)
|
||||
{
|
||||
ui.div_begin(ugui::DIV_FILL)!!;
|
||||
static char[128] buffer;
|
||||
static usz len;
|
||||
bool eval;
|
||||
|
||||
// keyboard input
|
||||
switch(ui.get_keys()) {
|
||||
case "+": nextcase;
|
||||
case "-": nextcase;
|
||||
case "*": nextcase;
|
||||
case "/": nextcase;
|
||||
case "(": nextcase;
|
||||
case ")": nextcase;
|
||||
case ".": nextcase;
|
||||
case "0": nextcase;
|
||||
case "1": nextcase;
|
||||
case "2": nextcase;
|
||||
case "3": nextcase;
|
||||
case "4": nextcase;
|
||||
case "5": nextcase;
|
||||
case "6": nextcase;
|
||||
case "7": nextcase;
|
||||
case "8": nextcase;
|
||||
case "9":
|
||||
buffer[len++] = ui.get_keys()[0];
|
||||
case "\n":
|
||||
eval = true;
|
||||
case "c":
|
||||
len = 0;
|
||||
case "d":
|
||||
len--;
|
||||
}
|
||||
|
||||
ui.layout_set_row()!!;
|
||||
ui.div_begin({0,0,-300,50})!!;
|
||||
ui.text_unbounded("80085")!!;
|
||||
ui.div_end()!!;
|
||||
ui.layout_next_row()!!;
|
||||
// ui input/output
|
||||
ui.@div(ugui::@grow(), ugui::@grow(), ROW, CENTER) { // center everything on the screen
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN, TOP_LEFT) {
|
||||
|
||||
ui.@div(ugui::@grow(), ugui::@exact(100), ROW, RIGHT) {
|
||||
ui.text((String)buffer[:len])!!;
|
||||
}!!;
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), ROW, TOP_LEFT) {
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
|
||||
ui.button("7")!!.mouse_press ? buffer[len++] = '7' : 0;
|
||||
ui.button("4")!!.mouse_press ? buffer[len++] = '4' : 0;
|
||||
ui.button("1")!!.mouse_press ? buffer[len++] = '1' : 0;
|
||||
ui.button("0")!!.mouse_press ? buffer[len++] = '0' : 0;
|
||||
}!!;
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
|
||||
ui.button("8")!!.mouse_press ? buffer[len++] = '8' : 0;
|
||||
ui.button("5")!!.mouse_press ? buffer[len++] = '5' : 0;
|
||||
ui.button("2")!!.mouse_press ? buffer[len++] = '2' : 0;
|
||||
ui.button(".")!!.mouse_press ? buffer[len++] = '.' : 0;
|
||||
}!!;
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
|
||||
ui.button("9")!!.mouse_press ? buffer[len++] = '9' : 0;
|
||||
ui.button("6")!!.mouse_press ? buffer[len++] = '6' : 0;
|
||||
ui.button("3")!!.mouse_press ? buffer[len++] = '3' : 0;
|
||||
ui.button("(")!!.mouse_press ? buffer[len++] = '(' : 0;
|
||||
}!!;
|
||||
|
||||
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()!!;
|
||||
ui.@div(ugui::@exact(10), ugui::@exact(10)) {}!!;
|
||||
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
|
||||
ui.button("x")!!.mouse_press ? buffer[len++] = '*' : 0;
|
||||
ui.button("/")!!.mouse_press ? buffer[len++] = '/' : 0;
|
||||
ui.button("+")!!.mouse_press ? buffer[len++] = '+' : 0;
|
||||
ui.button(")")!!.mouse_press ? buffer[len++] = ')' : 0;
|
||||
}!!;
|
||||
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
|
||||
ui.button("C")!!.mouse_press ? len = 0 : 0;
|
||||
ui.button("D")!!.mouse_press ? len-- : 0;
|
||||
ui.button("-")!!.mouse_press ? buffer[len++] = '-' : 0;
|
||||
|
||||
// eval the expression with 'bc'
|
||||
if (ui.button("=")!!.mouse_press || eval) {
|
||||
char[128] out;
|
||||
String y = string::tformat("echo '%s' | bc", (String)buffer[:len]);
|
||||
String x = process::execute_stdout_to_buffer(out[:128], (String[]){"sh", "-c", y}) ?? "";
|
||||
buffer[:x.len] = x[..];
|
||||
len = x.len;
|
||||
}
|
||||
}!!;
|
||||
}!!;
|
||||
}!!; }!!;
|
||||
}
|
||||
|
436
test/test_tree_layout.c3
Normal file
436
test/test_tree_layout.c3
Normal file
@ -0,0 +1,436 @@
|
||||
import vtree;
|
||||
import std::io;
|
||||
import std::math;
|
||||
import std::thread;
|
||||
|
||||
const short WIDTH = 128;
|
||||
const short HEIGHT = 64;
|
||||
|
||||
struct Size {
|
||||
short min, max;
|
||||
}
|
||||
macro Size @grow() => {.min = 0, .max = 0};
|
||||
macro Size @exact(short s) => {.min = s, .max = s};
|
||||
macro Size @fit(short min = 0, short max = short.max) => {.min = min, .max = max};
|
||||
macro bool Size.@is_grow(s) => (s.min == 0 && s.max == 0);
|
||||
macro bool Size.@is_exact(s) => (s.min == s.max && s.min != 0);
|
||||
macro bool Size.@is_fit(s) => (s.min != s.max);
|
||||
|
||||
|
||||
|
||||
struct Rect {
|
||||
short x, y, w, h;
|
||||
}
|
||||
|
||||
enum LayoutDirection {
|
||||
ROW,
|
||||
COLUMN
|
||||
}
|
||||
|
||||
enum ElemType {
|
||||
DIV,
|
||||
ELEM
|
||||
}
|
||||
|
||||
enum Anchor {
|
||||
TOP_LEFT,
|
||||
LEFT,
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM,
|
||||
BOTTOM_RIGHT,
|
||||
RIGHT,
|
||||
TOP_RIGHT,
|
||||
TOP,
|
||||
CENTER
|
||||
}
|
||||
|
||||
struct Elem {
|
||||
ElemType type;
|
||||
Size w, h;
|
||||
Rect bounds;
|
||||
Size ch_w, ch_h; // children width / height
|
||||
uint grow_children; // how many children want to grow, decreased once a child has grown
|
||||
short orig_x, orig_y;
|
||||
short occupied; // occupied space in the layout direction
|
||||
LayoutDirection layout_dir;
|
||||
Anchor anchor;
|
||||
}
|
||||
|
||||
alias ElemTree = vtree::VTree{Elem*};
|
||||
|
||||
|
||||
char[HEIGHT][WIDTH] screen;
|
||||
fn void paint(Rect bounds, char c)
|
||||
{
|
||||
for (short x = bounds.x; x < WIDTH && x < bounds.x + bounds.w; x++) {
|
||||
for (short y = bounds.y; y < HEIGHT && y < bounds.y + bounds.h; y++) {
|
||||
screen[x][y] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn isz Elem.div_start(&e, ElemTree* tree, isz parent, Size w, Size h, LayoutDirection dir = ROW, Anchor anchor = TOP_LEFT, char c = ' ')
|
||||
{
|
||||
e.type = DIV;
|
||||
e.w = w;
|
||||
e.h = h;
|
||||
e.layout_dir = dir;
|
||||
e.anchor = anchor;
|
||||
|
||||
e.grow_children = 0;
|
||||
e.occupied = 0;
|
||||
e.ch_w = e.ch_h = {};
|
||||
e.orig_x = e.orig_y = 0;
|
||||
|
||||
// update grow children if necessary
|
||||
Elem* p = tree.get(parent) ?? &&{};
|
||||
if ((p.layout_dir == ROW && e.w.@is_grow()) || ((p.layout_dir == COLUMN && e.h.@is_grow()))) {
|
||||
p.grow_children++;
|
||||
}
|
||||
|
||||
paint(e.bounds, c);
|
||||
return tree.add(e, parent)!!;
|
||||
}
|
||||
|
||||
fn void update_parent_size(Elem* parent, Elem* child)
|
||||
{
|
||||
// update the parent children size
|
||||
switch (parent.layout_dir) {
|
||||
case ROW: // on rows grow the ch width by the child width and only grow ch height if it exceeds
|
||||
parent.ch_w.min += child.w.min;
|
||||
parent.ch_w.max += child.w.max;
|
||||
parent.ch_h.min = math::max(child.h.min, parent.ch_h.min);
|
||||
parent.ch_h.max = math::max(child.h.max, parent.ch_h.max);
|
||||
case COLUMN: // do the opposite on column
|
||||
parent.ch_w.min = math::max(child.w.min, parent.ch_w.min);
|
||||
parent.ch_w.max = math::max(child.w.max, parent.ch_w.max);
|
||||
parent.ch_h.min += child.h.min;
|
||||
parent.ch_h.max += child.h.max;
|
||||
}
|
||||
}
|
||||
|
||||
fn isz Elem.div_end(&e, ElemTree* tree, isz node)
|
||||
{
|
||||
isz parent = tree.parentof(node) ?? -1;
|
||||
if (parent > 0) {
|
||||
Elem* p = tree.get(parent)!!;
|
||||
update_parent_size(p, e);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
fn void resolve_dimensions(Elem* e, Elem* p)
|
||||
{
|
||||
// ASSIGN WIDTH
|
||||
switch {
|
||||
case e.w.@is_exact():
|
||||
e.bounds.w = e.w.min;
|
||||
case e.w.@is_grow():
|
||||
break;
|
||||
// done in another pass
|
||||
case e.w.@is_fit(): // fit the element's children
|
||||
short min = math::max(e.ch_w.min, e.w.min);
|
||||
short max = math::min(e.ch_w.max, e.w.max);
|
||||
if (max >= min) { // OK!
|
||||
e.bounds.w = max;
|
||||
} else {
|
||||
unreachable("cannot fit children");
|
||||
}
|
||||
default: unreachable("width is not exact, grow or fit");
|
||||
}
|
||||
|
||||
// ASSIGN HEIGHT
|
||||
switch {
|
||||
case e.h.@is_exact():
|
||||
e.bounds.h = e.h.min;
|
||||
case e.h.@is_grow():
|
||||
break;
|
||||
// done in another pass
|
||||
case e.h.@is_fit(): // fit the element's children
|
||||
short min = math::max(e.ch_h.min, e.h.min);
|
||||
short max = math::min(e.ch_h.max, e.h.max);
|
||||
if (max >= min) { // OK!
|
||||
e.bounds.h = max;
|
||||
} else {
|
||||
unreachable("cannot fit children");
|
||||
}
|
||||
default: unreachable("width is not exact, grow or fit");
|
||||
}
|
||||
|
||||
switch (p.layout_dir) {
|
||||
case ROW:
|
||||
if (!e.w.@is_grow()) p.occupied += e.bounds.w;
|
||||
case COLUMN:
|
||||
if (!e.h.@is_grow()) p.occupied += e.bounds.h;
|
||||
}
|
||||
}
|
||||
|
||||
fn void resolve_grow_elements(Elem* e, Elem* p)
|
||||
{
|
||||
// WIDTH
|
||||
if (e.w.@is_grow()) {
|
||||
if (p.layout_dir == ROW) { // grow along the axis, divide the parent size
|
||||
e.bounds.w = (short)((int)(p.bounds.w - p.occupied) / (int)p.grow_children);
|
||||
p.grow_children--;
|
||||
p.occupied += e.bounds.w;
|
||||
} else if (p.layout_dir == COLUMN) { // grow across the layout axis, inherit width of the parent
|
||||
e.bounds.w = p.bounds.w;
|
||||
}
|
||||
}
|
||||
|
||||
// HEIGHT
|
||||
if (e.h.@is_grow()) {
|
||||
if (p.layout_dir == COLUMN) { // grow along the axis, divide the parent size
|
||||
e.bounds.h = (short)((int)(p.bounds.h - p.occupied) / (int)p.grow_children);
|
||||
p.grow_children--;
|
||||
p.occupied += e.bounds.h;
|
||||
} else if (p.layout_dir == ROW) { // grow across the layout axis, inherit width of the parent
|
||||
e.bounds.h = p.bounds.h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void resolve_placement(Elem* e, Elem* p)
|
||||
{
|
||||
switch (p.anchor) {
|
||||
case TOP_LEFT:
|
||||
e.bounds.x = p.bounds.x + p.orig_x;
|
||||
e.bounds.y = p.bounds.y + p.orig_y;
|
||||
case LEFT:
|
||||
e.bounds.x = p.bounds.x + p.orig_x;
|
||||
e.bounds.y = p.bounds.y + p.orig_y + p.bounds.h/2;
|
||||
if (p.layout_dir == COLUMN) {
|
||||
e.bounds.y -= p.occupied/2;
|
||||
} else if (p.layout_dir == ROW) {
|
||||
e.bounds.y -= e.bounds.h/2;
|
||||
}
|
||||
case BOTTOM_LEFT:
|
||||
e.bounds.x = p.bounds.x + p.orig_x;
|
||||
e.bounds.y = p.bounds.y + p.bounds.h + p.orig_y;
|
||||
if (p.layout_dir == COLUMN) {
|
||||
e.bounds.y -= p.occupied;
|
||||
} else if (p.layout_dir == ROW) {
|
||||
e.bounds.y -= e.bounds.h;
|
||||
}
|
||||
case BOTTOM:
|
||||
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w/2;
|
||||
e.bounds.y = p.bounds.y + p.bounds.h + p.orig_y;
|
||||
if (p.layout_dir == COLUMN) {
|
||||
e.bounds.y -= p.occupied;
|
||||
e.bounds.x -= e.bounds.w/2;
|
||||
} else if (p.layout_dir == ROW) {
|
||||
e.bounds.y -= e.bounds.h;
|
||||
e.bounds.x -= p.occupied/2;
|
||||
}
|
||||
case BOTTOM_RIGHT:
|
||||
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w;
|
||||
e.bounds.y = p.bounds.y + p.bounds.h + p.orig_y;
|
||||
if (p.layout_dir == COLUMN) {
|
||||
e.bounds.y -= p.occupied;
|
||||
e.bounds.x -= e.bounds.w;
|
||||
} else if (p.layout_dir == ROW) {
|
||||
e.bounds.y -= e.bounds.h;
|
||||
e.bounds.x -= p.occupied;
|
||||
}
|
||||
case RIGHT:
|
||||
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w;
|
||||
e.bounds.y = p.bounds.y + p.orig_y + p.bounds.h/2;
|
||||
if (p.layout_dir == COLUMN) {
|
||||
e.bounds.y -= p.occupied/2;
|
||||
e.bounds.x -= e.bounds.w;
|
||||
} else if (p.layout_dir == ROW) {
|
||||
e.bounds.y -= e.bounds.h/2;
|
||||
e.bounds.x -= p.occupied;
|
||||
}
|
||||
case TOP_RIGHT:
|
||||
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w;
|
||||
e.bounds.y = p.bounds.y + p.orig_y;
|
||||
if (p.layout_dir == COLUMN) {
|
||||
e.bounds.x -= e.bounds.w;
|
||||
} else if (p.layout_dir == ROW) {
|
||||
e.bounds.x -= p.occupied;
|
||||
}
|
||||
case TOP:
|
||||
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w/2;
|
||||
e.bounds.y = p.bounds.y + p.orig_y;
|
||||
if (p.layout_dir == COLUMN) {
|
||||
e.bounds.x -= e.bounds.w/2;
|
||||
} else if (p.layout_dir == ROW) {
|
||||
e.bounds.x -= p.occupied/2;
|
||||
}
|
||||
case CENTER:
|
||||
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w/2;
|
||||
e.bounds.y = p.bounds.y + p.orig_y + p.bounds.h/2;
|
||||
if (p.layout_dir == COLUMN) {
|
||||
e.bounds.x -= e.bounds.w/2;
|
||||
e.bounds.y -= p.occupied/2;
|
||||
} else if (p.layout_dir == ROW) {
|
||||
e.bounds.x -= p.occupied/2;
|
||||
e.bounds.y -= e.bounds.h/2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
e.bounds.x = p.bounds.x + p.orig_x;
|
||||
e.bounds.y = p.bounds.y + p.orig_y;
|
||||
*/
|
||||
|
||||
switch (p.layout_dir) {
|
||||
case ROW:
|
||||
p.orig_x += e.bounds.w;
|
||||
case COLUMN:
|
||||
p.orig_y += e.bounds.h;
|
||||
default: unreachable("unknown layout direction");
|
||||
}
|
||||
}
|
||||
|
||||
fn void frame_end(ElemTree* tree, isz root)
|
||||
{
|
||||
// assign the element bounds
|
||||
isz cursor = -1;
|
||||
|
||||
/*
|
||||
// RESOLVE DIMENSIONS
|
||||
isz current = tree.level_order_it(root, &cursor)!!;
|
||||
for (; current >= 0; current = tree.level_order_it(root, &cursor)!!) {
|
||||
Elem* e = tree.get(current)!!;
|
||||
isz pi = tree.parentof(current)!!;
|
||||
Elem* p = (pi != current) ? tree.get(pi) ?? &&{} : &&{};
|
||||
resolve_dimensions(e, p);
|
||||
}
|
||||
|
||||
// RESOLVE GROW ELEMENTS
|
||||
cursor = -1;
|
||||
current = tree.level_order_it(root, &cursor)!!;
|
||||
for (; current >= 0; current = tree.level_order_it(root, &cursor)!!) {
|
||||
Elem* e = tree.get(current)!!;
|
||||
isz pi = tree.parentof(current)!!; if (ch == current) continue;
|
||||
Elem* p = (pi != current) ? tree.get(pi) ?? &&{} : &&{};
|
||||
|
||||
resolve_grow_elements(e, p);
|
||||
}
|
||||
|
||||
|
||||
// RESOLVE PLACEMENT
|
||||
cursor = -1;
|
||||
current = tree.level_order_it(root, &cursor)!!;
|
||||
for (; current >= 0; current = tree.level_order_it(root, &cursor)!!) {
|
||||
Elem* e = tree.get(current)!!;
|
||||
isz pi = tree.parentof(current)!!;
|
||||
Elem* p = (pi != current) ? tree.get(pi) ?? &&{} : &&{};
|
||||
|
||||
resolve_placement(e, p);
|
||||
}
|
||||
*/
|
||||
|
||||
cursor = -1;
|
||||
isz current = tree.level_order_it(root, &cursor)!!;
|
||||
for (; current >= 0; current = tree.level_order_it(root, &cursor)!!) {
|
||||
Elem* p = tree.get(current)!!;
|
||||
|
||||
// RESOLVE KNOWN DIMENSIONS
|
||||
isz ch_cur = 0;
|
||||
isz ch = tree.children_it(current, &ch_cur)!!;
|
||||
for (; ch >= 0; ch = tree.children_it(current, &ch_cur)!!) {
|
||||
Elem* c = tree.get(ch)!!;
|
||||
if (tree.is_root(ch)!!) {
|
||||
resolve_dimensions(p, &&{});
|
||||
} else {
|
||||
resolve_dimensions(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
// RESOLVE GROW CHILDREN
|
||||
ch_cur = 0;
|
||||
ch = tree.children_it(current, &ch_cur)!!;
|
||||
for (; ch >= 0; ch = tree.children_it(current, &ch_cur)!!) {
|
||||
Elem* c = tree.get(ch)!!;
|
||||
if (tree.is_root(ch)!!) {
|
||||
resolve_grow_elements(p, &&{});
|
||||
} else {
|
||||
resolve_grow_elements(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
// RESOLVE CHILDREN PLACEMENT
|
||||
ch_cur = 0;
|
||||
ch = tree.children_it(current, &ch_cur)!!;
|
||||
for (; ch >= 0; ch = tree.children_it(current, &ch_cur)!!) {
|
||||
Elem* c = tree.get(ch)!!;
|
||||
if (tree.is_root(ch)!!) {
|
||||
resolve_placement(p, &&{});
|
||||
} else {
|
||||
resolve_placement(c, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void main()
|
||||
{
|
||||
ElemTree tree;
|
||||
tree.init(64, mem)!!;
|
||||
isz parent;
|
||||
defer (void)tree.free();
|
||||
|
||||
Elem root; // root div
|
||||
Elem div1, div2, div3, div4;
|
||||
usz frame;
|
||||
while (true) {
|
||||
parent = root.div_start(&tree, parent, @exact(WIDTH), @exact(HEIGHT), ROW, anchor: RIGHT);
|
||||
/*
|
||||
{
|
||||
parent = div1.div_start(&tree, parent, @grow(), @grow(), dir: ROW, c: '1');
|
||||
{
|
||||
parent = div4.div_start(&tree, parent, @exact(30), @exact(30), dir: ROW, c: '4');
|
||||
parent = div4.div_end(&tree, parent);
|
||||
}
|
||||
parent = div1.div_end(&tree, parent);
|
||||
|
||||
if (frame < 200) {
|
||||
parent = div2.div_start(&tree, parent, @exact(20), @fit(), dir: COLUMN, c: '2');
|
||||
{
|
||||
parent = div3.div_start(&tree, parent, @exact(10), @exact(10), dir: ROW, c: '3');
|
||||
parent = div3.div_end(&tree, parent);
|
||||
}
|
||||
parent = div2.div_end(&tree, parent);
|
||||
}
|
||||
}
|
||||
*/
|
||||
parent = div3.div_start(&tree, parent, @fit(), @fit(), COLUMN, anchor: CENTER);
|
||||
{
|
||||
parent = div1.div_start(&tree, parent, @exact(20), @exact(20), dir: ROW, c: '1');
|
||||
parent = div1.div_end(&tree, parent);
|
||||
|
||||
parent = div2.div_start(&tree, parent, @exact(10), @exact(10), dir: ROW, c: '2');
|
||||
parent = div2.div_end(&tree, parent);
|
||||
}
|
||||
parent = div3.div_end(&tree, parent);
|
||||
|
||||
parent = root.div_end(&tree, parent);
|
||||
|
||||
frame_end(&tree, parent);
|
||||
tree.nuke();
|
||||
|
||||
|
||||
// draw the screen
|
||||
//io::print("\e[1;1H\e[2J");
|
||||
for (short x = 0; x < WIDTH+2; x++) io::printf("%c", x == 0 || x == WIDTH+1 ? '+' : '-');
|
||||
io::printn();
|
||||
for (short y = 0; y < HEIGHT; y++) {
|
||||
io::print("|");
|
||||
for (short x = 0; x < WIDTH; x++) {
|
||||
char c = screen[x][y] == 0 ? 'x' : screen[x][y];
|
||||
io::printf("%c", c);
|
||||
}
|
||||
io::print("|");
|
||||
io::printn();
|
||||
}
|
||||
for (short x = 0; x < WIDTH+2; x++) io::printf("%c", x == 0 || x == WIDTH+1 ? '+' : '-');
|
||||
io::printn("\n\n");
|
||||
|
||||
thread::sleep_ms(10);
|
||||
frame++;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user