436 lines
11 KiB
Plaintext
436 lines
11 KiB
Plaintext
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++;
|
|
}
|
|
} |