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)
|
[x] Port font system from C to C3 (rewrite1)
|
||||||
[ ] Update ARCHITECTURE.md
|
[ ] Update ARCHITECTURE.md
|
||||||
[ ] Write a README.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 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)
|
[ ] Do command buffer damage tracking based on a context grid (see rxi writeup)
|
||||||
[x] Better handling of the active and focused widgets, try
|
[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
|
[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
|
content, the background color is applied starting from the border. Right now push_rect() offsets
|
||||||
the background rect by both border and padding
|
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
|
## Layout
|
||||||
|
|
||||||
[x] Flexbox
|
[x] Flexbox
|
||||||
[ ] Center elements to the row/column
|
[x] Center elements to the row/column
|
||||||
[x] Text wrapping / reflow
|
[x] Text wrapping / reflow
|
||||||
[x] Implement a better and unified way to place a glyph and get the cursor position, maybe with a struct
|
[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)
|
[ ] 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.
|
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
|
[ ] Find a way to concile pixel measurements to the mm ones used in css, for example in min/max sizing
|
||||||
of elements
|
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] Use containing_rect() in position_element() to skip some computing and semplify the function
|
||||||
[x] Rename position_element() to layout_element()
|
[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
|
## 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);
|
const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0);
|
||||||
|
|
||||||
struct Cache {
|
struct Cache {
|
||||||
|
Allocator allocator;
|
||||||
BitArr present, used;
|
BitArr present, used;
|
||||||
IdTable table;
|
IdTable table;
|
||||||
Value[] pool;
|
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
|
// FIXME: this shit is SLOW
|
||||||
foreach (idx, bit : cache.used) { cache.used[idx] = false; }
|
foreach (idx, bit : cache.used) { cache.used[idx] = false; }
|
||||||
foreach (idx, bit : cache.present) { cache.present[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)
|
fn void Cache.free(&cache)
|
||||||
{
|
{
|
||||||
(void)cache.table.free();
|
(void)cache.table.free();
|
||||||
(void)mem::free(cache.pool);
|
(void)allocator::free(cache.allocator, cache.pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Value*? Cache.search(&cache, Key id)
|
fn Value*? Cache.search(&cache, Key id)
|
||||||
|
@ -9,21 +9,23 @@ import std::sort;
|
|||||||
// TODO: specify the allocator
|
// TODO: specify the allocator
|
||||||
|
|
||||||
struct Fifo {
|
struct Fifo {
|
||||||
|
Allocator allocator;
|
||||||
Type[] arr;
|
Type[] arr;
|
||||||
usz out;
|
usz out;
|
||||||
usz count;
|
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.out = 0;
|
||||||
fifo.count = 0;
|
fifo.count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void Fifo.free(&fifo)
|
fn void Fifo.free(&fifo)
|
||||||
{
|
{
|
||||||
(void)mem::free(fifo.arr);
|
(void)allocator::free(fifo.allocator, fifo.arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void? Fifo.enqueue(&fifo, Type *elem)
|
fn void? Fifo.enqueue(&fifo, Type *elem)
|
||||||
@ -69,8 +71,8 @@ macro usz Fifo.len(&fifo) @operator(len)
|
|||||||
|
|
||||||
fn void? Fifo.sort(&fifo)
|
fn void? Fifo.sort(&fifo)
|
||||||
{
|
{
|
||||||
Type[] arr = mem::new_array(Type, fifo.count);
|
Type[] arr = allocator::new_array(fifo.allocator, Type, fifo.count);
|
||||||
defer mem::free(arr);
|
defer allocator::free(fifo.allocator, arr);
|
||||||
|
|
||||||
foreach(i, c: fifo) {
|
foreach(i, c: fifo) {
|
||||||
arr[i] = c;
|
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){};
|
Sprite* sprite = icon != "" ? ctx.sprite_atlas.get(icon)! : &&(Sprite){};
|
||||||
|
|
||||||
Rect min_size = {0, 0, style.size, style.size};
|
// TODO: get min size by style
|
||||||
Rect text_size = ctx.get_text_bounds(label)!;
|
TextSize text_size = ctx.measure_string(label)!;
|
||||||
Rect icon_size = sprite.rect();
|
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
|
ushort min_size = style.size;
|
||||||
* +--v-----------------------------------+
|
ushort half_lh = (ushort)(ctx.font.line_height() / 2);
|
||||||
* |<->+--------+ |
|
ushort inner_pad = label != "" && icon != "" ? half_lh : 0;
|
||||||
|
/*
|
||||||
|
* +--------------------------------------+
|
||||||
|
* | +--------+ |
|
||||||
* | | | +-----------------+ |
|
* | | | +-----------------+ |
|
||||||
* | | icon | | label | |
|
* | | icon | | label | |
|
||||||
* | | | +-----------------+ |
|
* | | | +-----------------+ |
|
||||||
* | +--------+<->| |<->|
|
* | +--------+<->| |
|
||||||
* +-------------^--------------------^---+
|
* +-------------^------------------------+
|
||||||
* |inner_pad |right_pad
|
* |inner_pad
|
||||||
*/
|
*/
|
||||||
|
Point content_size = {
|
||||||
Rect tot_size = {
|
.x = icon_size.w + inner_pad, // text sizing is handled differently
|
||||||
.w = (short)max(min_size.w, icon_size.w + text_size.w + left_pad + inner_pad + right_pad),
|
.y = icon_size.h + inner_pad,
|
||||||
.h = (short)max(min_size.h, max(icon_size.h, text_size.h)),
|
|
||||||
};
|
};
|
||||||
|
elem.layout.w = @fit(min_size);
|
||||||
elem.bounds = ctx.layout_element(parent, tot_size, style);
|
elem.layout.h = @fit(min_size);
|
||||||
if (elem.bounds.is_null()) return {};
|
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);
|
elem.events = ctx.get_elem_events(elem);
|
||||||
Rect content_bounds = elem.content_bounds(style);
|
Rect content_bounds = elem.content_bounds();
|
||||||
|
|
||||||
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 icon_bounds = {
|
Rect icon_bounds = {
|
||||||
.x = content_bounds.x,
|
.x = content_bounds.x,
|
||||||
.y = content_bounds.y,
|
.y = content_bounds.y,
|
||||||
.w = icon_size.w + right_pad + inner_pad,
|
.w = icon_size.w,
|
||||||
.h = (short)max(icon_size.h, content_bounds.h)
|
.h = icon_size.h
|
||||||
};
|
};
|
||||||
icon_bounds = icon_size.center_to(icon_bounds);
|
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;
|
bool is_active = ctx.elem_focus(elem) || elem.events.mouse_hover;
|
||||||
Style s = *style;
|
Style s = *style;
|
||||||
if (is_active) {
|
if (is_active) {
|
||||||
@ -72,15 +76,17 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
|
|||||||
s.bg = s.accent;
|
s.bg = s.accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.push_rect(elem.bounds, parent.div.z_index, &s)!;
|
ctx.push_rect(elem.bounds.pad(style.margin), parent.div.z_index, &s)!;
|
||||||
ctx.push_sprite(icon_bounds, sprite.uv(), ctx.sprite_atlas.id, parent.div.z_index, type: sprite.type)!;
|
if (icon != "") {
|
||||||
// Style ss = {.bg = 0x000000ffu.@to_rgba()};
|
ctx.push_sprite(icon_bounds, sprite.uv(), ctx.sprite_atlas.id, parent.div.z_index, type: sprite.type)!;
|
||||||
// ctx.push_rect(content_bounds, parent.div.z_index, &ss)!;
|
}
|
||||||
ctx.push_string(text_bounds, label, parent.div.z_index, style.fg)!;
|
if (label != "") {
|
||||||
|
ctx.layout_string(label, text_bounds, CENTER, parent.div.z_index, style.fg)!;
|
||||||
|
}
|
||||||
return elem.events;
|
return elem.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
// FIXME: this should be inside the style
|
// FIXME: this should be inside the style
|
||||||
macro Ctx.checkbox(&ctx, String desc, Point off, bool* active, String tick_sprite = {}, ...)
|
macro Ctx.checkbox(&ctx, String desc, Point off, bool* active, String tick_sprite = {}, ...)
|
||||||
@ -151,3 +157,4 @@ fn void? Ctx.toggle_id(&ctx, Id id, String description, Point off, bool* active)
|
|||||||
s.margin = s.border = s.padding = {};
|
s.margin = s.border = s.padding = {};
|
||||||
ctx.push_rect(t, parent.div.z_index, &s)!;
|
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
|
// implement the Printable interface
|
||||||
fn usz? Cmd.to_format(Cmd* cmd, Formatter *f) @dynamic
|
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})
|
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;
|
case CMD_SPRITE: rect = cmd.sprite.rect;
|
||||||
default: return ctx.cmd_queue.enqueue(cmd);
|
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);
|
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)!;
|
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)
|
fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
|
||||||
{
|
{
|
||||||
Rect border = style.border;
|
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.color = bg,
|
||||||
.rect.radius = radius,
|
.rect.radius = radius,
|
||||||
};
|
};
|
||||||
if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return;
|
|
||||||
ctx.push_cmd(&cmd, z_index)!;
|
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)!;
|
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)
|
fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)
|
||||||
{
|
{
|
||||||
Cmd up = {
|
Cmd up = {
|
||||||
|
@ -9,6 +9,15 @@ import std::core::string;
|
|||||||
import std::core::mem::allocator;
|
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
|
// element ids are just long ints
|
||||||
alias Id = uint;
|
alias Id = uint;
|
||||||
|
|
||||||
@ -45,7 +54,9 @@ struct Elem {
|
|||||||
ElemFlags flags;
|
ElemFlags flags;
|
||||||
ElemEvents events;
|
ElemEvents events;
|
||||||
Rect bounds;
|
Rect bounds;
|
||||||
|
Rect children_bounds;
|
||||||
ElemType type;
|
ElemType type;
|
||||||
|
Layout layout;
|
||||||
union {
|
union {
|
||||||
ElemDiv div;
|
ElemDiv div;
|
||||||
ElemButton button;
|
ElemButton button;
|
||||||
@ -131,6 +142,7 @@ const uint GOLDEN_RATIO = 0x9E3779B9;
|
|||||||
// with the Cantor pairing function
|
// with the Cantor pairing function
|
||||||
fn Id? Ctx.gen_id(&ctx, Id id2)
|
fn Id? Ctx.gen_id(&ctx, Id id2)
|
||||||
{
|
{
|
||||||
|
// FIXME: this is SHIT
|
||||||
Id id1 = ctx.tree.get(ctx.active_div)!;
|
Id id1 = ctx.tree.get(ctx.active_div)!;
|
||||||
// Mix the two IDs non-linearly
|
// Mix the two IDs non-linearly
|
||||||
Id mixed = id1 ^ id2.rotate_left(13);
|
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 = (ElemFlags)0;
|
||||||
elem.flags.is_new = is_new;
|
elem.flags.is_new = is_new;
|
||||||
elem.id = id;
|
elem.id = id;
|
||||||
|
elem.layout = {};
|
||||||
if (is_new == false && elem.type != type) {
|
if (is_new == false && elem.type != type) {
|
||||||
return WRONG_ELEMENT_TYPE?;
|
return WRONG_ELEMENT_TYPE?;
|
||||||
} else {
|
} else {
|
||||||
@ -187,18 +200,18 @@ fn Elem*? Ctx.get_active_div(&ctx)
|
|||||||
return ctx.cache.search(id);
|
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(); }
|
defer catch { (void)ctx.tree.free(); }
|
||||||
|
|
||||||
ctx.cache.init()!;
|
ctx.cache.init(allocator)!;
|
||||||
defer catch { (void)ctx.cache.free(); }
|
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(); }
|
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"));
|
ctx.styles.register_style(&DEFAULT_STYLE, @str_hash("default"));
|
||||||
defer catch { ctx.styles.free(); }
|
defer catch { ctx.styles.free(); }
|
||||||
|
|
||||||
@ -232,16 +245,15 @@ fn void? Ctx.frame_begin(&ctx)
|
|||||||
//elem.flags.has_focus = ctx.has_focus;
|
//elem.flags.has_focus = ctx.has_focus;
|
||||||
|
|
||||||
elem.bounds = {0, 0, ctx.width, ctx.height};
|
elem.bounds = {0, 0, ctx.width, ctx.height};
|
||||||
elem.div.layout = LAYOUT_ROW;
|
|
||||||
elem.div.z_index = 0;
|
elem.div.z_index = 0;
|
||||||
elem.div.children_bounds = elem.bounds;
|
|
||||||
elem.div.scroll_x.enabled = false;
|
elem.div.scroll_x.enabled = false;
|
||||||
elem.div.scroll_y.enabled = false;
|
elem.div.scroll_y.enabled = false;
|
||||||
elem.div.pcb = {};
|
elem.layout.dir = ROW;
|
||||||
elem.div.origin_c = {};
|
elem.layout.anchor = TOP_LEFT;
|
||||||
elem.div.origin_r = {};
|
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
|
// The root element does not push anything to the stack
|
||||||
// TODO: add a background color taken from a theme or config
|
// 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
|
// FIXME: this is not guaranteed to be root. the user might forget to close a div or some other element
|
||||||
Elem* root = ctx.get_active_div()!;
|
Elem* root = ctx.get_active_div()!;
|
||||||
if (root.id != ROOT_ID) return WRONG_ID?;
|
if (root.id != ROOT_ID) {
|
||||||
|
io::printn(root.id);
|
||||||
|
return WRONG_ID?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO THE LAYOUT
|
||||||
|
ctx.layout_element_tree();
|
||||||
|
|
||||||
// 1. clear the tree
|
// 1. clear the tree
|
||||||
ctx.tree.nuke();
|
ctx.tree.nuke();
|
||||||
@ -291,6 +309,11 @@ $endif
|
|||||||
|
|
||||||
// sort the command buffer by the z-index
|
// sort the command buffer by the z-index
|
||||||
ctx.cmd_queue.sort()!;
|
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
|
// div element
|
||||||
struct ElemDiv {
|
struct ElemDiv {
|
||||||
Layout layout;
|
|
||||||
struct scroll_x {
|
struct scroll_x {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
bool on;
|
bool on;
|
||||||
@ -18,19 +17,42 @@ struct ElemDiv {
|
|||||||
}
|
}
|
||||||
ushort scroll_size;
|
ushort scroll_size;
|
||||||
int z_index;
|
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.
|
// useful macro to start and end a div, capturing the trailing block
|
||||||
// if the width or height are negative the width or height will be calculated based on the children size
|
macro Ctx.@div(&ctx,
|
||||||
// sort similar to a flexbox, and the minimum size is set by the negative of the width or height
|
Size width = @grow, Size height = @grow,
|
||||||
// FIXME: there is a bug if the size.w or size.h == -0
|
LayoutDirection dir = ROW,
|
||||||
macro Ctx.div_begin(&ctx, Rect size, bool scroll_x = false, bool scroll_y = false, ...)
|
Anchor anchor = TOP_LEFT,
|
||||||
=> ctx.div_begin_id(@compute_id($vasplat), size, scroll_x, scroll_y);
|
bool scroll_x = false, bool scroll_y = false,
|
||||||
fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y)
|
...;
|
||||||
|
@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)!;
|
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.scroll_size = slider_style.size ? slider_style.size : (style.size ? style.size : DEFAULT_STYLE.size);
|
||||||
elem.div.z_index = parent.div.z_index + 1;
|
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
|
// update the ctx scissor
|
||||||
ctx.div_scissor = elem.bounds;
|
ctx.div_scissor = elem.bounds;
|
||||||
ctx.push_scissor(elem.bounds, elem.div.z_index)!;
|
ctx.push_scissor(elem.bounds, elem.div.z_index)!;
|
||||||
|
|
||||||
// 4. Fill the div fields
|
// update layout with correct info
|
||||||
elem.div.origin_c = {
|
elem.layout = {
|
||||||
.x = elem.bounds.x,
|
.w = width,
|
||||||
.y = elem.bounds.y
|
.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
|
ctx.push_rect(elem.bounds.pad(style.margin), elem.div.z_index, style)!;
|
||||||
bool do_border = parent.div.layout == LAYOUT_FLOATING;
|
|
||||||
ctx.push_rect(elem.bounds, elem.div.z_index, style)!;
|
|
||||||
|
|
||||||
elem.events = ctx.get_elem_events(elem);
|
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)
|
fn void? Ctx.div_end(&ctx)
|
||||||
{
|
{
|
||||||
// swap the children bounds
|
|
||||||
Elem* elem = ctx.get_active_div()!;
|
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;
|
Rect cb = elem.div.pcb;
|
||||||
// children bounds bottom-right corner
|
// children bounds bottom-right corner
|
||||||
Point cbc = {
|
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))!;
|
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;
|
elem.div.layout = prev_l;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// the active_div returns to the parent of the current one
|
// the active_div returns to the parent of the current one
|
||||||
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
|
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
|
||||||
Elem* parent = ctx.get_parent()!;
|
Elem* parent = ctx.get_parent()!;
|
||||||
// TODO: reset the scissor back to the parent div
|
|
||||||
ctx.div_scissor = parent.bounds;
|
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;
|
import std::ascii;
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
// CODEPOINT //
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
|
||||||
// unicode code point, different type for a different hash
|
// unicode code point, different type for a different hash
|
||||||
alias Codepoint = uint;
|
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();
|
//macro uint Codepoint.hash(self) => ((uint)self).hash();
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
// FONT ATLAS //
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
|
||||||
/* width and height of a glyph contain the kering advance
|
/* width and height of a glyph contain the kering advance
|
||||||
* (u,v)
|
* (u,v)
|
||||||
* +-------------*---+ -
|
* +-------------*---+ -
|
||||||
@ -60,7 +83,7 @@ struct Font {
|
|||||||
|
|
||||||
fn void? Font.load(&font, String name, ZString path, uint height, float scale)
|
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.id = name.hash();
|
||||||
|
|
||||||
font.size = height*scale;
|
font.size = height*scale;
|
||||||
@ -160,141 +183,16 @@ fn void Font.free(&font)
|
|||||||
schrift::freefont(font.sft.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)
|
fn void? Ctx.load_font(&ctx, String name, ZString path, uint height, float scale = 1.0)
|
||||||
{
|
{
|
||||||
return ctx.font.load(name, path, height, scale);
|
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
|
// TODO: check if the font is present in the context
|
||||||
fn Id Ctx.get_font_id(&ctx, String label)
|
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);
|
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[..]);
|
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
|
// Modifier keys, like control or backspace
|
||||||
// TODO: make this call repetible to input modkeys one by one
|
// TODO: make this call repetible to input modkeys one by one
|
||||||
fn void Ctx.input_mod_keys(&ctx, ModKeys modkeys)
|
fn void Ctx.input_mod_keys(&ctx, ModKeys modkeys)
|
||||||
|
@ -1,54 +1,129 @@
|
|||||||
module ugui;
|
module ugui;
|
||||||
|
|
||||||
enum Layout {
|
import std::math;
|
||||||
LAYOUT_ROW,
|
import std::io;
|
||||||
LAYOUT_COLUMN,
|
|
||||||
LAYOUT_FLOATING,
|
enum LayoutDirection {
|
||||||
LAYOUT_ABSOLUTE,
|
ROW,
|
||||||
|
COLUMN,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void? Ctx.layout_set_row(&ctx)
|
enum Anchor {
|
||||||
{
|
TOP_LEFT,
|
||||||
Elem *parent = ctx.get_parent()!;
|
LEFT,
|
||||||
parent.div.layout = LAYOUT_ROW;
|
BOTTOM_LEFT,
|
||||||
|
BOTTOM,
|
||||||
|
BOTTOM_RIGHT,
|
||||||
|
RIGHT,
|
||||||
|
TOP_RIGHT,
|
||||||
|
TOP,
|
||||||
|
CENTER
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void? Ctx.layout_set_column(&ctx)
|
struct Layout {
|
||||||
{
|
Size w, h; // size of the CONTENT, does not include margin, border and padding
|
||||||
Elem *parent = ctx.get_parent()!;
|
struct children { // the children size includes the children's margin/border/pading
|
||||||
parent.div.layout = LAYOUT_COLUMN;
|
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()!;
|
Rect min = (Rect){.w = el.w.min, .h = el.h.min}.expand(el.content_offset);
|
||||||
parent.div.layout = LAYOUT_FLOATING;
|
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 = {
|
// Returns the width and height of a @FIT() element based on it's wanted size (min/max)
|
||||||
.x = parent.bounds.x,
|
// and the content size, this function is used to both update the parent's children size and
|
||||||
.y = parent.div.children_bounds.bottom_right().y,
|
// 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()!;
|
return {
|
||||||
|
.x = e.bounds.w - e.layout.content_offset.x - e.layout.content_offset.w,
|
||||||
parent.div.origin_c = {
|
.y = e.bounds.h - e.layout.content_offset.y - e.layout.content_offset.h,
|
||||||
.x = parent.div.children_bounds.bottom_right().x,
|
|
||||||
.y = parent.bounds.y,
|
|
||||||
};
|
};
|
||||||
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)
|
macro Rect Elem.get_view(&elem)
|
||||||
{
|
{
|
||||||
Rect off;
|
Rect off;
|
||||||
@ -67,91 +142,224 @@ macro Point Elem.get_view_off(&elem)
|
|||||||
{
|
{
|
||||||
return elem.get_view().sub(elem.bounds).position();
|
return elem.get_view().sub(elem.bounds).position();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// position the rectangle inside the parent according to the layout
|
// Assign the width and height of an element in the directions that it doesn't need to grow
|
||||||
// parent: parent div
|
fn void resolve_dimensions(Elem* e, Elem* p)
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
ElemDiv* div = &parent.div;
|
Layout* el = &e.layout;
|
||||||
|
Layout* pl = &p.layout;
|
||||||
|
|
||||||
|
Point elem_dimensions = el.get_dimensions();
|
||||||
|
|
||||||
Rect parent_bounds, parent_view;
|
short text_min_height;
|
||||||
Rect child_placement, child_occupied;
|
// ASSIGN WIDTH
|
||||||
|
switch {
|
||||||
// 1. Select the right origin
|
case el.w.@is_exact():
|
||||||
Point origin;
|
// if width is exact then assign it to the bounds
|
||||||
switch (div.layout) {
|
e.bounds.w = el.total_width().min;
|
||||||
case LAYOUT_ROW:
|
case el.w.@is_grow():
|
||||||
origin = div.origin_r;
|
// done in another pass
|
||||||
case LAYOUT_COLUMN:
|
break;
|
||||||
origin = div.origin_c;
|
case el.w.@is_fit(): // fit the element's children
|
||||||
case LAYOUT_FLOATING: // none, relative to zero zero
|
e.bounds.w = elem_dimensions.x;
|
||||||
case LAYOUT_ABSOLUTE: // absolute position, this is a no-op, return the rect
|
default: unreachable("width is not exact, grow or fit");
|
||||||
return rect;
|
|
||||||
default: // error
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Compute the parent's view
|
// ASSIGN HEIGHT
|
||||||
parent_bounds = parent.bounds;
|
switch {
|
||||||
parent_view = parent.get_view();
|
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
|
switch (pl.dir) {
|
||||||
|
case ROW:
|
||||||
// grow rect (wanted size) when widht or height are less than zero
|
if (!el.w.@is_grow()) pl.occupied += e.bounds.w;
|
||||||
bool adapt_x = rect.w <= 0;
|
case COLUMN:
|
||||||
bool adapt_y = rect.h <= 0;
|
if (!el.h.@is_grow()) pl.occupied += e.bounds.h;
|
||||||
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
|
fn void resolve_grow_elements(Elem* e, Elem* p)
|
||||||
child_placement = child_placement.off(origin.add(rect.position()));
|
{
|
||||||
child_occupied = child_occupied.off(origin.add(rect.position()));
|
// WIDTH
|
||||||
|
if (e.layout.w.@is_grow()) {
|
||||||
Rect margin = style.margin;
|
if (p.layout.dir == ROW) { // grow along the axis, divide the parent size
|
||||||
Rect border = style.border;
|
short slot = (short)((p.content_space().x - p.layout.occupied) / p.layout.grow_children);
|
||||||
Rect padding = style.padding;
|
// the space slot accounts for the total size, while the element bounds does not account
|
||||||
|
// for the margin
|
||||||
// padding, grows both the placement and occupied area
|
e.bounds.w = slot;
|
||||||
child_placement = child_placement.grow(padding.position().add(padding.size()));
|
p.layout.grow_children--;
|
||||||
child_occupied = child_occupied.grow(padding.position().add(padding.size()));
|
p.layout.occupied += slot;
|
||||||
// border, grows both the placement and occupied area
|
} else if (p.layout.dir == COLUMN) { // grow across the layout axis, inherit width of the parent
|
||||||
child_placement = child_placement.grow(border.position().add(border.size()));
|
e.bounds.w = p.content_space().x;
|
||||||
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()));
|
// HEIGHT
|
||||||
|
if (e.layout.h.@is_grow()) {
|
||||||
// oh yeah also adjust the rect if i was to grow
|
if (p.layout.dir == COLUMN) { // grow along the axis, divide the parent size
|
||||||
if (adapt_x) rect.w -= padding.x+padding.w + border.x+border.w + margin.x+margin.w;
|
short slot = (short)((p.content_space().y - p.layout.occupied) / p.layout.grow_children);
|
||||||
if (adapt_y) rect.h -= padding.y+padding.h + border.y+border.h + margin.y+margin.h;
|
e.bounds.h = slot;
|
||||||
|
p.layout.grow_children--;
|
||||||
// set the size
|
p.layout.occupied += slot;
|
||||||
child_placement = child_placement.grow(rect.size());
|
} else if (p.layout.dir == ROW) { // grow across the layout axis, inherit width of the parent
|
||||||
child_occupied = child_occupied.grow(rect.size());
|
e.bounds.h = p.content_space().y;
|
||||||
|
}
|
||||||
// 4. Update the parent's origin
|
}
|
||||||
div.origin_r = {
|
}
|
||||||
.x = child_occupied.bottom_right().x,
|
|
||||||
.y = origin.y,
|
fn void resolve_placement(Elem* c, Elem* p)
|
||||||
};
|
{
|
||||||
div.origin_c = {
|
Layout* pl = &p.layout;
|
||||||
.x = origin.x,
|
Layout* cl = &c.layout;
|
||||||
.y = child_occupied.bottom_right().y,
|
|
||||||
};
|
Point off = {
|
||||||
|
.x = p.bounds.x + pl.origin.x,
|
||||||
// 5. Update the parent's children bounds
|
.y = p.bounds.y + pl.origin.y,
|
||||||
div.children_bounds = containing_rect(div.children_bounds, child_occupied);
|
};
|
||||||
|
|
||||||
// 99. return the placement
|
switch (pl.anchor) {
|
||||||
if (child_placement.collides(parent_view)) {
|
case TOP_LEFT:
|
||||||
return child_placement.off(parent.get_view_off().neg());
|
c.bounds.x = off.x;
|
||||||
} else {
|
c.bounds.y = off.y;
|
||||||
return {};
|
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;
|
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
|
// 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 {
|
return {
|
||||||
.x = r1.x + r2.x,
|
.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
|
// 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 {
|
return {
|
||||||
.x = r1.x - r2.x,
|
.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
|
// 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 {
|
return {
|
||||||
.x = r1.x * r2.x,
|
.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 //
|
// 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);
|
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)
|
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};
|
||||||
return {
|
macro Point Point.neg(Point p) @operator_s(-) => {-p.x, -p.y};
|
||||||
.x = a.x + b.x,
|
|
||||||
.y = a.y + b.y,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro Point Point.sub(Point a, Point b)
|
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)};
|
||||||
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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------- //
|
// ---------------------------------------------------------------------------------- //
|
||||||
// COLOR //
|
// COLOR //
|
||||||
@ -252,8 +235,29 @@ macro Color uint.@to_rgba($u)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro uint Color.to_uint(c)
|
macro uint Color.to_uint(c) => c.r | (c.g << 8) | (c.b << 16) | (c.a << 24);
|
||||||
{
|
|
||||||
uint u = c.r | (c.g << 8) | (c.b << 16) | (c.a << 24);
|
// ---------------------------------------------------------------------------------- //
|
||||||
return u;
|
// 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;
|
Rect handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* handle
|
/* handle
|
||||||
* +----+-----+---------------------+
|
* +----+-----+---------------------+
|
||||||
* | |#####| |
|
* | |#####| |
|
||||||
* +----+-----+---------------------+
|
* +----+-----+---------------------+
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
macro Ctx.slider_hor(&ctx, Rect size, float* value, float hpercent = 0.25, ...)
|
macro Ctx.slider_hor(&ctx, Rect size, float* value, float hpercent = 0.25, ...)
|
||||||
=> ctx.slider_hor_id(@compute_id($vasplat), size, value, hpercent);
|
=> 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;
|
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, ...)
|
macro Ctx.slider_ver(&ctx, Rect size, float* value, float hpercent = 0.25, ...)
|
||||||
=> ctx.slider_ver_id(@compute_id($vasplat), size, value, hpercent);
|
=> 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)
|
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 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)
|
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);
|
=> 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.id = name.hash();
|
||||||
this.atlas.new(this.id, AtlasType.ATLAS_R8G8B8A8, width, height)!;
|
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;
|
this.should_update = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,13 +104,14 @@ fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t
|
|||||||
{
|
{
|
||||||
QOIDesc desc;
|
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);
|
defer mem::free(pixels);
|
||||||
|
|
||||||
ctx.sprite_atlas.insert(name, type, pixels, (ushort)desc.width, (ushort)desc.height, (ushort)desc.width)!;
|
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}, ...)
|
macro Ctx.sprite(&ctx, String name, Point off = {0,0}, ...)
|
||||||
=> ctx.sprite_id(@compute_id($vasplat), name, off);
|
=> ctx.sprite_id(@compute_id($vasplat), name, off);
|
||||||
fn void? Ctx.sprite_id(&ctx, Id id, String name, Point 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)!;
|
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;
|
import std::io;
|
||||||
|
|
||||||
struct ElemText {
|
struct ElemText {
|
||||||
String str;
|
|
||||||
usz cursor; // cursor offset
|
usz cursor; // cursor offset
|
||||||
Id hash;
|
Id hash;
|
||||||
Rect bounds;
|
TextSize size;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro Ctx.text_unbounded(&ctx, String text, ...)
|
macro Ctx.text(&ctx, String text, ...)
|
||||||
=> ctx.text_unbounded_id(@compute_id($vasplat), text);
|
=> ctx.text_id(@compute_id($vasplat), text);
|
||||||
fn void? Ctx.text_unbounded_id(&ctx, Id id, String text)
|
fn void? Ctx.text_id(&ctx, Id id, String text)
|
||||||
{
|
{
|
||||||
id = ctx.gen_id(id)!;
|
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();
|
Id text_hash = text.hash();
|
||||||
if (elem.flags.is_new || elem.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;
|
elem.text.hash = text_hash;
|
||||||
|
|
||||||
// 2. Layout
|
elem.layout.w = @fit(style.size);
|
||||||
elem.bounds = ctx.layout_element(parent, elem.text.bounds, style);
|
elem.layout.h = @fit(style.size);
|
||||||
if (elem.bounds.is_null()) { return; }
|
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, ...)
|
macro Ctx.text_box(&ctx, Rect size, char[] text, usz* text_len, ...)
|
||||||
=> ctx.text_box_id(@compute_id($vasplat), size, text, 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)
|
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;
|
return elem.events;
|
||||||
}
|
}
|
||||||
|
*/
|
@ -4,39 +4,51 @@ faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INV
|
|||||||
module vtree{ElemType};
|
module vtree{ElemType};
|
||||||
|
|
||||||
import std::core::mem;
|
import std::core::mem;
|
||||||
|
import std::core::mem::allocator;
|
||||||
import std::io;
|
import std::io;
|
||||||
|
|
||||||
struct VTree {
|
struct VTree {
|
||||||
|
Allocator allocator;
|
||||||
usz elements;
|
usz elements;
|
||||||
ElemType[] vector; // vector of element ids
|
ElemType[] vector; // vector of element ids
|
||||||
isz[] refs, ordered_refs;
|
isz[] refs, ordered_refs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < 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) { return tree.refs[ref] >= 0; }
|
macro VTree.ref_is_present(&tree, isz ref) => tree.refs[ref] >= 0;
|
||||||
macro VTree.size(&tree) { return tree.refs.len; }
|
macro VTree.size(&tree) => tree.refs.len;
|
||||||
|
|
||||||
// macro to zero an elemen
|
// macro to zero an element
|
||||||
macro @zero()
|
macro @zero()
|
||||||
{
|
{
|
||||||
$if $assignable(0, ElemType):
|
$if @assignable_to(0, ElemType):
|
||||||
return 0;
|
return 0;
|
||||||
$else
|
$endif
|
||||||
|
|
||||||
|
$if @assignable_to(null, ElemType):
|
||||||
|
return null;
|
||||||
|
$endif
|
||||||
|
|
||||||
|
$if @assignable_to({}, ElemType):
|
||||||
return {};
|
return {};
|
||||||
$endif
|
$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);
|
tree.allocator = allocator;
|
||||||
defer catch { (void)mem::free(tree.vector); }
|
|
||||||
|
|
||||||
tree.refs = mem::new_array(isz, size);
|
tree.vector = allocator::new_array(tree.allocator, ElemType, size);
|
||||||
defer catch { (void)mem::free(tree.refs); }
|
defer catch { (void)allocator::free(tree.allocator, tree.vector); }
|
||||||
|
|
||||||
tree.ordered_refs = mem::new_array(isz, size);
|
tree.refs = allocator::new_array(tree.allocator, isz, size);
|
||||||
defer catch { (void)mem::free(tree.ordered_refs); }
|
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
|
// set all refs to -1, meaning invalid (free) element
|
||||||
tree.refs[..] = -1;
|
tree.refs[..] = -1;
|
||||||
@ -46,9 +58,9 @@ fn void? VTree.init(&tree, usz size)
|
|||||||
|
|
||||||
fn void VTree.free(&tree)
|
fn void VTree.free(&tree)
|
||||||
{
|
{
|
||||||
(void)mem::free(tree.vector);
|
(void)allocator::free(tree.allocator, tree.vector);
|
||||||
(void)mem::free(tree.refs);
|
(void)allocator::free(tree.allocator, tree.refs);
|
||||||
(void)mem::free(tree.ordered_refs);
|
(void)allocator::free(tree.allocator, tree.ordered_refs);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void VTree.pack(&tree)
|
fn void VTree.pack(&tree)
|
||||||
@ -102,14 +114,14 @@ fn void? VTree.resize(&tree, usz newsize)
|
|||||||
|
|
||||||
usz old_size = tree.size();
|
usz old_size = tree.size();
|
||||||
|
|
||||||
tree.vector = ((ElemType*)mem::realloc(tree.vector, newsize*ElemType.sizeof))[:newsize];
|
tree.vector = ((ElemType*)allocator::realloc(tree.allocator, tree.vector, newsize*ElemType.sizeof))[:newsize];
|
||||||
defer catch { (void)mem::free(tree.vector); }
|
defer catch { (void)allocator::free(tree.allocator, tree.vector); }
|
||||||
|
|
||||||
tree.refs = ((isz*)mem::realloc(tree.refs, newsize*isz.sizeof))[:newsize];
|
tree.refs = ((isz*)allocator::realloc(tree.allocator, tree.refs, newsize*isz.sizeof))[:newsize];
|
||||||
defer catch { (void)mem::free(tree.refs); }
|
defer catch { (void)allocator::free(tree.allocator, tree.refs); }
|
||||||
|
|
||||||
tree.ordered_refs = ((isz*)mem::realloc(tree.ordered_refs, newsize*isz.sizeof))[:newsize];
|
tree.ordered_refs = ((isz*)allocator::realloc(tree.allocator, tree.ordered_refs, newsize*isz.sizeof))[:newsize];
|
||||||
defer catch { (void)mem::free(tree.ordered_refs); }
|
defer catch { (void)allocator::free(tree.allocator, tree.ordered_refs); }
|
||||||
|
|
||||||
if (newsize > tree.size()) {
|
if (newsize > tree.size()) {
|
||||||
tree.vector[old_size..newsize-1] = @zero();
|
tree.vector[old_size..newsize-1] = @zero();
|
||||||
@ -213,6 +225,8 @@ fn usz? VTree.subtree_size(&tree, isz ref)
|
|||||||
return count;
|
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
|
// iterate through the first level children, use a cursor like strtok_r
|
||||||
fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
|
fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
|
||||||
{
|
{
|
||||||
|
@ -2,9 +2,11 @@ default {
|
|||||||
bg: #282828ff;
|
bg: #282828ff;
|
||||||
fg: #fbf1c7ff;
|
fg: #fbf1c7ff;
|
||||||
primary: #cc241dff;
|
primary: #cc241dff;
|
||||||
secondary: #458588ff;
|
secondary: #6c19ca8f;
|
||||||
accent: #fabd2fff;
|
accent: #fabd2fff;
|
||||||
border: 1;
|
border: 1;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
152
src/main.c3
152
src/main.c3
@ -6,6 +6,7 @@ import std::time;
|
|||||||
import std::collections::ringbuffer;
|
import std::collections::ringbuffer;
|
||||||
import std::core::string;
|
import std::core::string;
|
||||||
import std::ascii;
|
import std::ascii;
|
||||||
|
import std::core::mem::allocator;
|
||||||
import sdlrenderer::ren;
|
import sdlrenderer::ren;
|
||||||
import sdl3::sdl;
|
import sdl3::sdl;
|
||||||
|
|
||||||
@ -58,8 +59,13 @@ const char[*] STYLESHEET_PATH = "resources/style.css";
|
|||||||
|
|
||||||
fn int main(String[] args)
|
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;
|
ugui::Ctx ui;
|
||||||
ui.init()!!;
|
ui.init(&arena)!!;
|
||||||
defer ui.free();
|
defer ui.free();
|
||||||
|
|
||||||
ren::Renderer ren;
|
ren::Renderer ren;
|
||||||
@ -199,34 +205,32 @@ fn int main(String[] args)
|
|||||||
if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true;
|
if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true;
|
||||||
|
|
||||||
const String APPLICATION = "calculator";
|
const String APPLICATION = "calculator";
|
||||||
$if APPLICATION == "debug":
|
$switch APPLICATION:
|
||||||
|
$case "debug":
|
||||||
debug_app(&ui);
|
debug_app(&ui);
|
||||||
$endif
|
$case "calculator":
|
||||||
$if APPLICATION == "calculator":
|
|
||||||
calculator(&ui);
|
calculator(&ui);
|
||||||
$endif
|
$endswitch
|
||||||
|
|
||||||
// Timings counter
|
// Timings counter
|
||||||
TimeStats dts = draw_times.get_stats();
|
TimeStats dts = draw_times.get_stats();
|
||||||
TimeStats uts = ui_times.get_stats();
|
TimeStats uts = ui_times.get_stats();
|
||||||
|
|
||||||
|
/*
|
||||||
ui.layout_set_floating()!!;
|
ui.layout_set_floating()!!;
|
||||||
// FIXME: I cannot anchor shit to the bottom of the screen
|
ui.@div({0, ui.height-150, -300, 150}) {
|
||||||
ui.div_begin({0, ui.height-150, -300, 150})!!;
|
|
||||||
{
|
|
||||||
ui.layout_set_column()!!;
|
ui.layout_set_column()!!;
|
||||||
ui.text_unbounded(string::tformat("frame %d, fps = %.2f", frame, fps))!!;
|
ui.text_unbounded(string::tformat("frame %d, fps = %.2f", frame, fps))!!;
|
||||||
ui.text_unbounded(string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
|
ui.text_unbounded(string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
|
||||||
ui.text_unbounded(string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[:ui.input.keyboard.text_len]))!!;
|
ui.text_unbounded(string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[:ui.input.keyboard.text_len]))!!;
|
||||||
};
|
}!!;
|
||||||
ui.div_end()!!;
|
*/
|
||||||
|
|
||||||
ui.frame_end()!!;
|
ui.frame_end()!!;
|
||||||
/* End UI Handling */
|
/* End UI Handling */
|
||||||
ui_times.push(clock.mark());
|
ui_times.push(clock.mark());
|
||||||
//ui_times.print_stats();
|
//ui_times.print_stats();
|
||||||
|
|
||||||
|
|
||||||
/* Start UI Drawing */
|
/* Start UI Drawing */
|
||||||
ren.begin_render(true);
|
ren.begin_render(true);
|
||||||
|
|
||||||
@ -249,6 +253,7 @@ $endif
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
fn void debug_app(ugui::Ctx* ui)
|
fn void debug_app(ugui::Ctx* ui)
|
||||||
{
|
{
|
||||||
static bool toggle;
|
static bool toggle;
|
||||||
@ -287,13 +292,12 @@ fn void debug_app(ugui::Ctx* ui)
|
|||||||
ui.checkbox("", {}, &check, "tick")!!;
|
ui.checkbox("", {}, &check, "tick")!!;
|
||||||
ui.checkbox("", {}, &check)!!;
|
ui.checkbox("", {}, &check)!!;
|
||||||
ui.toggle("", {}, &toggle)!!;
|
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_end()!!;
|
||||||
|
|
||||||
ui.div_begin(ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
|
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()!!;
|
ui.div_end()!!;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
import std::os::process;
|
||||||
|
|
||||||
fn void calculator(ugui::Ctx* ui)
|
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 input/output
|
||||||
ui.div_begin({0,0,-300,50})!!;
|
ui.@div(ugui::@grow(), ugui::@grow(), ROW, CENTER) { // center everything on the screen
|
||||||
ui.text_unbounded("80085")!!;
|
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN, TOP_LEFT) {
|
||||||
ui.div_end()!!;
|
|
||||||
ui.layout_next_row()!!;
|
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.@div(ugui::@exact(10), ugui::@exact(10)) {}!!;
|
||||||
ui.button("8")!!;
|
|
||||||
ui.button("9")!!;
|
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
|
||||||
ui.layout_next_row()!!;
|
ui.button("x")!!.mouse_press ? buffer[len++] = '*' : 0;
|
||||||
ui.button("4")!!;
|
ui.button("/")!!.mouse_press ? buffer[len++] = '/' : 0;
|
||||||
ui.button("5")!!;
|
ui.button("+")!!.mouse_press ? buffer[len++] = '+' : 0;
|
||||||
ui.button("6")!!;
|
ui.button(")")!!.mouse_press ? buffer[len++] = ')' : 0;
|
||||||
ui.layout_next_row()!!;
|
}!!;
|
||||||
ui.button("3")!!;
|
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN) {
|
||||||
ui.button("2")!!;
|
ui.button("C")!!.mouse_press ? len = 0 : 0;
|
||||||
ui.button("1")!!;
|
ui.button("D")!!.mouse_press ? len-- : 0;
|
||||||
ui.layout_next_row()!!;
|
ui.button("-")!!.mouse_press ? buffer[len++] = '-' : 0;
|
||||||
ui.button(".")!!;
|
|
||||||
ui.button("0")!!;
|
// eval the expression with 'bc'
|
||||||
ui.button("")!!;
|
if (ui.button("=")!!.mouse_press || eval) {
|
||||||
|
char[128] out;
|
||||||
ui.layout_next_column()!!;
|
String y = string::tformat("echo '%s' | bc", (String)buffer[:len]);
|
||||||
ui.layout_set_column()!!;
|
String x = process::execute_stdout_to_buffer(out[:128], (String[]){"sh", "-c", y}) ?? "";
|
||||||
ui.button("+")!!;
|
buffer[:x.len] = x[..];
|
||||||
ui.button("-")!!;
|
len = x.len;
|
||||||
ui.button("*")!!;
|
}
|
||||||
ui.button("/")!!;
|
}!!;
|
||||||
|
}!!;
|
||||||
ui.div_end()!!;
|
}!!; }!!;
|
||||||
}
|
}
|
||||||
|
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