ported rewrite2 to c3

This commit is contained in:
Alessandro Mauri 2024-10-29 22:45:47 +01:00
parent 2dcc1b582c
commit 39e78ea078
40 changed files with 1478 additions and 2911 deletions

0
src/.gitkeep Normal file
View file

133
src/cache.c3 Normal file
View file

@ -0,0 +1,133 @@
module cache(<Key, Value, SIZE>);
/* LRU Cache
* The cache uses a pool (array) to store all the elements, each element has
* a key (id) and a value. A HashMap correlates the ids to an index in the pool.
* To keep track of which items were recently used two bit arrays are kept, one
* stores the "used" flag for each index and anothe the "present" flag.
* Every NCYCLES operations the present and used arrays are updated to free up
* the elements that were not recently used.
*/
// FIXME: this module should really allocate all resources on an arena or temp
// allocator, since all memory allocations are connected and freeing
// happens at the same time
import std::core::mem;
import std::collections::bitset;
import std::collections::map;
def BitArr = bitset::BitSet(<SIZE>) @private;
def IdTable = map::Map(<Key, usz>) @private;
def IdTableEntry = map::Entry(<Key, usz>) @private;
const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0);
struct Cache {
BitArr present, used;
IdTable table;
Value[] pool;
usz cycle_count;
}
// Every CACHE_CYCLES operations mark as not-present the unused elements
macro Cache.cycle(&cache) @private {
cache.cycle_count++;
if (cache.cycle_count > CACHE_NCYCLES) {
for (usz i = 0; i < cache.present.data.len; i++) {
cache.present.data[i] &= cache.used.data[i];
cache.used.data[i] = 0;
}
cache.cycle_count = 0;
}
}
fn void! Cache.init(&cache)
{
cache.table = map::new(<Key, usz>)(SIZE);
// FIXME: this shit is SLOW
foreach (idx, bit : cache.used) { cache.used[idx] = false; }
foreach (idx, bit : cache.present) { cache.present[idx] = false; }
cache.pool = mem::new_array(Value, SIZE);
}
fn void Cache.free(&cache)
{
(void)cache.table.free();
(void)mem::free(cache.pool);
}
fn Value*! Cache.search(&cache, Key id)
{
// get_entry() faults on miss
IdTableEntry* entry = cache.table.get_entry(id)!;
/* MISS */
if (entry.key != id) {
return SearchResult.MISSING?;
}
/* MISS, the data is not valid (not present) */
if (!cache.present[entry.value]) {
// if the data is not present but it is still in the table, remove it
cache.table.remove(id)!;
return SearchResult.MISSING?;
}
/* HIT, set as recently used */
cache.used[entry.value] = true;
return &(cache.pool[entry.value]);
}
/* Look for a free spot in the present bitmap and return its index */
/* If there is no free space left then just return the first position */
fn usz Cache.get_free_spot(&cache) @private
{
// FIXME: This shit is SLOW, especially when clz() exists
foreach (idx, bit: cache.present) {
if (bit == false) {
return idx;
}
}
return 0;
}
fn Value*! Cache.insert_at(&cache, Value *g, Key id, usz index) @private
{
// TODO: verify index, g and id
Value* spot;
/* Set used and present */
cache.present.set(index);
cache.used.set(index);
cache.cycle();
spot = &(cache.pool[index]);
*spot = *g;
cache.table.set(id, index);
return spot;
}
// Insert an element in the cache, returns the index
fn Value*! Cache.insert_new(&cache, Value* g, Key id)
{
usz index = cache.get_free_spot();
return cache.insert_at(g, id, index);
}
fn Value*! Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null)
{
Value*! c = cache.search(id);
if (catch e = c) {
if (e != SearchResult.MISSING) {
return e?;
} else {
// if the element is new (inserted) set the is_new flag
if (is_new) *is_new = true;
return cache.insert_new(g, id);
}
} else {
if (is_new) *is_new = false;
return c;
}
}

49
src/fifo.c3 Normal file
View file

@ -0,0 +1,49 @@
module fifo(<Type>);
import std::core::mem;
fault FifoErr {
FULL,
EMPTY,
}
// TODO: specify the allocator
struct Fifo {
Type[] arr;
usz out;
usz count;
}
fn void! Fifo.init(&fifo, usz size)
{
fifo.arr = mem::new_array(Type, size);
fifo.out = 0;
fifo.count = 0;
}
fn void Fifo.free(&fifo)
{
(void)mem::free(fifo.arr);
}
fn void! Fifo.enqueue(&fifo, Type *elem)
{
if (fifo.count >= fifo.arr.len) {
return FifoErr.FULL?;
}
usz in = (fifo.out + fifo.count) % fifo.arr.len;
fifo.arr[in] = *elem;
fifo.count++;
}
fn Type*! Fifo.dequeue(&fifo)
{
if (fifo.count == 0) {
return FifoErr.EMPTY?;
}
Type *ret = &fifo.arr[fifo.out];
fifo.count--;
fifo.out = (fifo.out + 1) % fifo.arr.len;
return ret;
}

196
src/main.c3 Normal file
View file

@ -0,0 +1,196 @@
import std::io;
import vtree;
import cache;
import ugui;
import rl;
fn int main(String[] args)
{
ugui::Ctx ctx;
ctx.init()!!;
short width = 800;
short height = 450;
rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE);
rl::init_window(width, height, "Ugui Test");
ctx.input_window_size(width, height)!!;
double[10][4] median_times;
isz frame;
double median_input, median_layout, median_draw, median_tot;
// Main loop
while (!rl::window_should_close()) {
const int PARTIAL_INPUT = 0;
const int PARTIAL_LAYOUT = 1;
const int PARTIAL_DRAW = 2;
//timer_start();
/*** Start Input Handling ***/
if (rl::is_window_resized()) {
width = (short)rl::get_screen_width();
height = (short)rl::get_screen_height();
ctx.input_window_size(width, height)!!;
}
ctx.input_changefocus(rl::is_window_focused());
// FIXME: In raylib it doesn't seem to be a quick way to check if
// a mouse input event was received, so for now just use
// the delta information
rl::Vector2 mousedelta = rl::get_mouse_delta();
if (mousedelta.x || mousedelta.y) {
ctx.input_mouse_delta((short)mousedelta.x, (short)mousedelta.y);
}
ugui::MouseButtons buttons;
buttons.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT);
buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT);
buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE);
ctx.input_mouse_button(buttons);
//timer_partial(PARTIAL_INPUT);
/*** End Input Handling ***/
/*** Start UI Handling ***/
ctx.frame_begin()!!;
// main div, fill the whole window
ctx.div_begin("main", ugui::DIV_FILL)!!;
{|
ctx.layout_set_column()!!;
if (ctx.button("button0", ugui::Rect{.y = 100, .x = 100, .w = 30, .h = 30})!!.mouse_hold) {
io::printn("HOLDING button0");
}
ctx.layout_next_column()!!;
ctx.button("button1", ugui::Rect{.w = 30, .h = 30})!!;
ctx.layout_next_column()!!;
ctx.button("button2", ugui::Rect{.w = 30, .h = 30})!!;
|};
ctx.div_end()!!;
ctx.frame_end()!!;
//timer_partial(PARTIAL_LAYOUT);
/*** End UI Handling ***/
/*** Start UI Drawing ***/
rl::begin_drawing();
// ClearBackground(BLACK);
io::printn("----- Draw Begin -----");
rl::Color c;
for (Cmd* cmd; (cmd = ctx.cmd_queue.dequeue() ?? null) != null;) {
switch (cmd.type) {
case ugui::CmdType.CMD_RECT:
io::printfn(
"draw rect x=%d y=%d w=%d h=%d",
cmd.rect.rect.x,
cmd.rect.rect.y,
cmd.rect.rect.w,
cmd.rect.rect.h
);
c = rl::Color{
.r = cmd.rect.color.r,
.g = cmd.rect.color.g,
.b = cmd.rect.color.b,
.a = cmd.rect.color.a,
};
rl::draw_rectangle(
cmd.rect.rect.x,
cmd.rect.rect.y,
cmd.rect.rect.w,
cmd.rect.rect.h,
c
);
default:
io::printfn("Unknown cmd type: %d", cmd.type);
}
}
io::printf("----- Draw End -----\n\n");
rl::end_drawing();
//timer_partial(PARTIAL_DRAW);
//timer_stop();
/*** End UI Drawing ***/
/*
median_times[frame][PARTIAL_INPUT] =
1e3 * timer_get_sec(PARTIAL_INPUT);
median_times[frame][PARTIAL_LAYOUT] =
1e3 * timer_get_sec(PARTIAL_LAYOUT);
median_times[frame][PARTIAL_DRAW] =
1e3 * timer_get_sec(PARTIAL_DRAW);
median_times[frame][3] = 1e3 * timer_get_sec(-1);
*/
frame += 1;
frame %= 10;
/*
if (frame == 0) {
median_input = 0;
median_layout = 0;
median_draw = 0;
median_tot = 0;
for (size_t i = 0; i < 10; i++) {
median_input += median_times[i][PARTIAL_INPUT];
median_layout += median_times[i][PARTIAL_LAYOUT];
median_draw += median_times[i][PARTIAL_DRAW];
median_tot += median_times[i][3];
}
median_input /= 10;
median_layout /= 10;
median_draw /= 10;
median_tot /= 10;
}
printf("input time: %lfms\n", median_input);
printf("layout time: %lfms\n", median_layout);
printf("draw time: %lfms\n", median_draw);
printf("total time: %lfms\n", median_tot);
// Throttle Frames
// TODO: add an fps limit, time frame generation and log it
const float TARGET_FPS = 100;
float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0);
WaitTime(wait_time);
*/
}
rl::close_window();
ctx.free();
return 0;
}
fn void! test_vtree() @test
{
vtree::VTree(<String>) vt;
vt.init(10)!!;
defer vt.free();
assert(vt.size() == 10, "Size is incorrect");
isz ref = vt.add("Ciao Mamma", 0)!!;
String s = vt.get(ref)!!;
assert(s == "Ciao Mamma", "String is incorrect");
isz par = vt.parentof(0)!!;
assert(ref == par, "Not Root");
vt.print();
}
def StrCache = cache::Cache(<int, String, 256>);
fn void! test_cache() @test
{
StrCache cc;
cc.init()!!;
defer cc.free();
String*! r = cc.search(1);
if (catch ex = r) {
if (ex != SearchResult.MISSING) {
return ex?;
}
}
r = cc.get_or_insert(&&"Ciao Mamma", 1)!;
assert(*r!! == "Ciao Mamma", "incorrect string");
}

184
src/ugui_data.c3 Normal file
View file

@ -0,0 +1,184 @@
module ugui;
import vtree;
import cache;
import fifo;
struct Rect {
short x, y, w, h;
}
struct Point {
short x, y;
}
struct Color{
char r, g, b, a;
}
// element ids are just long ints
def Id = usz;
enum ElemType {
ETYPE_NONE,
ETYPE_DIV,
ETYPE_BUTTON,
}
bitstruct ElemFlags : uint {
bool updated : 0;
bool has_focus : 1;
}
bitstruct ElemEvents : uint {
bool key_press : 0;
bool key_release : 1;
bool key_hold : 2;
bool mouse_hover : 3;
bool mouse_press : 4;
bool mouse_release : 5;
bool mouse_hold : 6;
}
enum DivLayout {
LAYOUT_ROW,
LAYOUT_COLUMN,
LAYOUT_FLOATING,
}
// div element
struct Div {
DivLayout layout;
Point origin_r, origin_c;
Color color_bg;
}
// element structure
struct Elem {
Id id;
ElemFlags flags;
ElemEvents events;
Rect rect;
ElemType type;
union {
Div div;
}
}
// relationships between elements are stored in a tree, it stores just the ids
def IdTree = vtree::VTree(<Id>) @private;
// elements themselves are kept in a cache
const uint MAX_ELEMENTS = 2048;
def ElemCache = cache::Cache(<Id, Elem, MAX_ELEMENTS>) @private;
def CmdQueue = fifo::Fifo(<Cmd>);
fault UgError {
INVALID_SIZE,
EVENT_UNSUPPORTED,
UNEXPECTED_ELEMENT,
}
fn Id fnv1a(String str)
{
const ulong FNV_OFF = 0xcbf29ce484222325;
const ulong FNV_PRIME = 0x100000001b3;
ulong hash = FNV_OFF;
foreach (c : str) {
hash ^= c;
hash *= FNV_PRIME;
}
return hash;
}
macro hash(String str) { return fnv1a(str); }
macro uint_to_rgba(uint u) {
return Color{
.r = (char)((u >> 24) & 0xff),
.g = (char)((u >> 16) & 0xff),
.b = (char)((u >> 8) & 0xff),
.a = (char)((u >> 0) & 0xff)
};
}
const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 };
macro abs(a) { return a < 0 ? -a : a; }
macro clamp(x, min, max) { return x < min ? min : (x > max ? max : x); }
const uint STACK_STEP = 10;
const uint MAX_ELEMS = 128;
const uint MAX_CMDS = 256;
const uint ROOT_ID = 1;
// command type
enum CmdType {
CMD_RECT,
}
// command to draw a rect
struct CmdRect {
Rect rect;
Color color;
}
// command structure
struct Cmd {
CmdType type;
union {
CmdRect rect;
}
}
enum Layout {
ROW,
COLUMN,
FLOATING
}
// global style, similar to the css box model
struct Style { // css box model
Rect padding;
Rect border;
Rect margin;
Color bgcolor; // background color
Color fgcolor; // foreground color
Color bcolor; // border color
}
struct Ctx {
Layout layout;
IdTree tree;
ElemCache cache;
CmdQueue cmd_queue;
// total size in pixels of the context
ushort width, height;
Style style;
bool has_focus;
struct input {
InputEvents events;
struct mouse {
Point pos, delta;
// mouse_down: bitmap of mouse buttons that are held
// mouse_updated: bitmap of mouse buttons that have been updated
// mouse_released = mouse_updated & ~mouse_down
// mouse_pressed = mouse_updated & mouse_down
MouseButtons down;
MouseButtons updated;
}
}
isz active_div; // tree node indicating the current active div
}
macro 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);
}

244
src/ugui_impl.c3 Normal file
View file

@ -0,0 +1,244 @@
module ugui;
import std::io;
// return a pointer to the parent of the current active div
fn Elem*! Ctx.get_parent(&ctx)
{
// FIXME: if the tree held pointers to the elements then no more
// redundant cache search
Id parent_id = ctx.tree.get(ctx.active_div)!;
return ctx.cache.search(parent_id);
}
fn void! Ctx.init(&ctx)
{
ctx.tree.init(MAX_ELEMENTS)!;
defer catch { (void)ctx.tree.free(); }
//ug_fifo_init(&ctx.fifo, MAX_CMDS);
ctx.cache.init()!;
defer catch { (void)ctx.cache.free(); }
ctx.cmd_queue.init(MAX_ELEMENTS)!;
defer catch { (void)ctx.cmd_queue.free(); }
ctx.layout = Layout.ROW;
ctx.active_div = 0;
// TODO: add style config
ctx.style.margin = Rect{1, 1, 1, 1};
}
fn void Ctx.free(&ctx)
{
(void)ctx.tree.free();
(void)ctx.cache.free();
(void)ctx.cmd_queue.free();
}
fn void! Ctx.frame_begin(&ctx)
{
// 1. Create the root div element
// NOTE: in c3 everythong is zero initialized by default
Rect space = {
.w = ctx.width,
.h = ctx.height,
};
Elem root = {
.id = ROOT_ID,
.type = ETYPE_DIV,
.rect = space,
.div = {
.layout = LAYOUT_ROW,
},
};
// The root should have the updated flag only if the size of the window
// was changed between frames, this propagates an element size recalculation
// down the element tree
if (ctx.input.events.resize) {
root.flags.updated = true;
}
// if the window has focus then the root element also has focus, no other
// computation needed, child elements need to check the mouse positon and
// other stuff
if (ctx.has_focus) {
root.flags.has_focus = true;
}
// FIXME: check errors
// 2. Get the root element from the cache and update it
bool is_new;
Elem empty_elem;
Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, root.id, &is_new)!;
// flags always need to be set to the new flags
c_elem.flags = root.flags;
if (is_new || root.flags.updated) {
*c_elem = root;
}
// 3. Push the root element into the element tree
ctx.active_div = ctx.tree.add(root.id, 0)!;
// print_tree(ctx);
// The root element does not push anything to the stack
// TODO: add a background color taken from a theme or config
io::printn("##### Frame Begin #####");
}
fn void! Ctx.frame_end(&ctx)
{
// 1. clear the tree
ctx.tree.prune(0)!;
// 2. clear input fields
ctx.input.events = (InputEvents)0;
// draw mouse position
$if 1:
Cmd cmd = {
.type = CMD_RECT,
.rect.rect = {
.x = ctx.input.mouse.pos.x - 2,
.y = ctx.input.mouse.pos.y - 2,
.w = 4,
.h = 4,
},
.rect.color = uint_to_rgba(0xff00ffff)
};
ctx.cmd_queue.enqueue(&cmd)!;
$endif
io::printn("##### Frame End #####");
}
fn void! Ctx.div_begin(&ctx, String label, Rect size)
{
Id id = hash(label);
bool is_new;
Elem empty_elem;
Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!;
// FIXME: why save the id in the tree and not something more direct like
// the element pointer or the index into the cache vector?
isz div_node = ctx.tree.add(id, ctx.active_div)!;
Elem *parent = ctx.get_parent()!;
ctx.tree.print();
// Use the current div
ctx.active_div = div_node;
// 1. Fill the element fields
// this resets the flags
c_elem.type = ETYPE_DIV;
c_elem.flags = (ElemFlags)0;
// do layout and update flags only if the element was updated
if (is_new || parent.flags.updated) {
// 2. layout the element
c_elem.rect = ctx.position_element(parent, size);
// 3. Mark the element as updated
c_elem.flags.updated = true;
// 4. Fill the div fields
c_elem.div.layout = parent.div.layout;
c_elem.div.origin_c = Point{
.x = c_elem.rect.x,
.y = c_elem.rect.y,
};
c_elem.div.color_bg = uint_to_rgba(0xff0000ff);
c_elem.div.origin_r = c_elem.div.origin_c;
} else if (parent.flags.has_focus) {
if (point_in_rect(ctx.input.mouse.pos, c_elem.rect)) {
c_elem.flags.has_focus = true;
}
} else {
// TODO: check active
// TODO: check scrollbars
// TODO: check resizeable
}
// Add the background to the draw stack
Cmd cmd = {
.type = CMD_RECT,
.rect = {
.rect = c_elem.rect,
.color = c_elem.div.color_bg,
},
};
ctx.cmd_queue.enqueue(&cmd)!;
}
fn void! Ctx.div_end(&ctx)
{
// the active_div returns to the parent of the current one
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
}
// @ensure elem != null
fn bool Ctx.is_hovered(&ctx, Elem *elem)
{
return point_in_rect(ctx.input.mouse.pos, elem.rect);
}
fn ElemEvents! Ctx.button(&ctx, String label, Rect size)
{
Id id = hash(label);
// TODO: do layouting if the element is new or the parent has updated
bool is_new;
Elem empty_elem;
Elem *c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!;
// add it to the tree
ctx.tree.add(id, ctx.active_div)!;
ctx.tree.print();
Elem *parent = ctx.get_parent()!;
// 1. Fill the element fields
// this resets the flags
c_elem.type = ETYPE_BUTTON;
c_elem.flags = (ElemFlags)0;
Color bg_color = uint_to_rgba(0x0000ffff);
// if the element is new or the parent was updated then redo layout
if (is_new || parent.flags.updated) {
// 2. Layout
c_elem.rect = ctx.position_element(parent, size, true);
// TODO: 3. Fill the button specific fields
}
// TODO: Check for interactions
if (parent.flags.has_focus) {
if (ctx.is_hovered(c_elem)) {
c_elem.flags.has_focus = true;
c_elem.events.mouse_hover = true;
bg_color = uint_to_rgba(0x00ff00ff);
c_elem.events.mouse_hold = ctx.input.mouse.down.btn_left;
} else {
c_elem.events.mouse_hover = false;
}
}
// Draw the button
Cmd cmd = {
.type = CMD_RECT,
.rect = {
.rect = c_elem.rect,
.color = bg_color,
},
};
ctx.cmd_queue.enqueue(&cmd)!;
return c_elem.events;
}

78
src/ugui_input.c3 Normal file
View file

@ -0,0 +1,78 @@
module ugui;
import std::io;
// TODO: this could be a bitstruct
bitstruct InputEvents : uint {
bool resize : 0; // window size was changed
bool change_focus : 1; // window focus changed
bool mouse_move : 2; // mouse was moved
bool mouse_btn : 3; // mouse button pressed or released
}
// Window size was changed
fn void! Ctx.input_window_size(&ctx, short width, short height)
{
if (width <= 0 || height <= 0) {
return UgError.INVALID_SIZE?;
}
ctx.width = width;
ctx.height = height;
ctx.input.events.resize = true;
}
// Window gained/lost focus
fn void Ctx.input_changefocus(&ctx, bool has_focus)
{
// FIXME: raylib only has an API to query the focus status so we have to
// update the input flag only if the focus changed
if (ctx.has_focus != has_focus) {
ctx.input.events.change_focus = true;
}
ctx.has_focus = has_focus;
}
bitstruct MouseButtons : uint {
bool btn_left : 0;
bool btn_middle : 1;
bool btn_right : 2;
bool btn_4 : 3;
bool btn_5 : 4;
}
// Mouse Button moved
fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons)
{
ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons;
ctx.input.mouse.down = buttons;
ctx.input.events.mouse_btn = true;
io::printfn(
"Mouse Down: %s%s%s%s%s",
buttons.btn_left ? "BTN_LEFT " : "",
buttons.btn_right ? "BTN_RIGHT " : "",
buttons.btn_middle ? "BTN_MIDDLE " : "",
buttons.btn_4 ? "BTN_4 " : "",
buttons.btn_5 ? "BTN_5 " : ""
);
}
// Mouse was moved, report absolute position
// TODO: implement this
fn void Ctx.input_mouse_abs(&ctx, short x, short y) { return; }
// Mouse was moved, report relative motion
fn void Ctx.input_mouse_delta(&ctx, short dx, short dy)
{
ctx.input.mouse.delta.x = dx;
ctx.input.mouse.delta.y = dy;
short mx, my;
mx = ctx.input.mouse.pos.x + dx;
my = ctx.input.mouse.pos.y + dy;
ctx.input.mouse.pos.x = clamp(mx, 0u16, ctx.width);
ctx.input.mouse.pos.y = clamp(my, 0u16, ctx.height);
ctx.input.events.mouse_move = true;
}

153
src/ugui_layout.c3 Normal file
View file

@ -0,0 +1,153 @@
module ugui;
fn void! Ctx.layout_set_row(&ctx)
{
Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UgError.UNEXPECTED_ELEMENT?;
}
parent.div.layout = LAYOUT_ROW;
}
fn void! Ctx.layout_set_column(&ctx)
{
Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UgError.UNEXPECTED_ELEMENT?;
}
parent.div.layout = LAYOUT_COLUMN;
}
fn void! Ctx.layout_set_floating(&ctx)
{
Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UgError.UNEXPECTED_ELEMENT?;
}
parent.div.layout = LAYOUT_FLOATING;
}
fn void! Ctx.layout_next_row(&ctx)
{
Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UgError.UNEXPECTED_ELEMENT?;
}
parent.div.origin_r = Point{
.x = parent.rect.x,
.y = parent.div.origin_c.y,
};
parent.div.origin_c = parent.div.origin_r;
}
fn void! Ctx.layout_next_column(&ctx)
{
Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UgError.UNEXPECTED_ELEMENT?;
}
parent.div.origin_c = Point{
.x = parent.div.origin_r.x,
.y = parent.rect.y,
};
parent.div.origin_r = parent.div.origin_c;
}
// position the rectangle inside the parent according to the layout
fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
{
Rect elem_rect;
Point origin;
// 1. Select the right origin
switch (parent.div.layout) {
case LAYOUT_ROW:
origin = parent.div.origin_r;
case LAYOUT_COLUMN:
origin = parent.div.origin_c;
case LAYOUT_FLOATING: // none
default:
// Error
}
// 2. Position the rect
elem_rect.x = origin.x + rect.x;
elem_rect.y = origin.y + rect.y;
// 3. Calculate width & height
// TODO: what about negative values?
// FIXME: account for origin offset!!
elem_rect.w = rect.w > 0 ? rect.w : parent.rect.w;
elem_rect.h = rect.h > 0 ? rect.h : parent.rect.h;
// 4. Update the origins of the parent
parent.div.origin_r = Point{
.x = elem_rect.x + elem_rect.w,
.y = elem_rect.y,
};
parent.div.origin_c = Point{
.x = elem_rect.x,
.y = elem_rect.y + elem_rect.h,
};
// if using the style then apply margins
// FIXME: this does not work
if (style && parent.div.layout != LAYOUT_FLOATING) {
elem_rect.x += ctx.style.margin.x;
elem_rect.y += ctx.style.margin.y;
// total keep-out borders
Rect margin_tot = {
.x = ctx.style.padding.x + ctx.style.border.x +
ctx.style.margin.x,
.y = ctx.style.padding.y + ctx.style.border.y +
ctx.style.margin.y,
.w = ctx.style.padding.w + ctx.style.border.x +
ctx.style.margin.w,
.h = ctx.style.padding.h + ctx.style.border.x +
ctx.style.margin.h,
};
parent.div.origin_r.x += margin_tot.x + margin_tot.w;
// parent.div.origin_r.y += margin_tot.h;
// parent.div.origin_c.x += margin_tot.w;
parent.div.origin_c.y += margin_tot.y + margin_tot.h;
}
/*
printf(
"positioning rect: %lx {%d %d %d %d}(%d %d %d %d) . {%d %d %d
%d}\n", parent.id, rect.x, rect.y, rect.w, rect.h, parent.rect.x,
parent.rect.y,
parent.rect.w,
parent.rect.h,
elem_rect.x,
elem_rect.y,
elem_rect.w,
elem_rect.h
);
*/
return elem_rect;
}

333
src/vtree.c3 Normal file
View file

@ -0,0 +1,333 @@
module vtree(<ElemType>);
import std::core::mem;
import std::io;
struct VTree {
usz elements;
ElemType[] vector; // vector of element ids
isz[] refs, ordered_refs;
}
fault VTreeError {
CANNOT_SHRINK,
INVALID_REFERENCE,
TREE_FULL,
REFERENCE_NOT_PRESENT,
INVALID_ARGUMENT,
}
macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); }
macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; }
macro VTree.size(&tree) { return tree.refs.len; }
// macro to zero an elemen
macro @zero()
{
$if $assignable(0, ElemType):
return 0;
$else
return ElemType{0};
$endif
}
fn void! VTree.init(&tree, usz size)
{
tree.vector = mem::new_array(ElemType, size);
defer catch { (void)mem::free(tree.vector); }
tree.refs = mem::new_array(isz, size);
defer catch { (void)mem::free(tree.refs); }
tree.ordered_refs = mem::new_array(isz, size);
defer catch { (void)mem::free(tree.ordered_refs); }
// set all refs to -1, meaning invalid (free) element
tree.refs[..] = -1;
tree.elements = 0;
}
fn void VTree.free(&tree)
{
(void)mem::free(tree.vector);
(void)mem::free(tree.refs);
(void)mem::free(tree.ordered_refs);
}
fn void VTree.pack(&tree)
{
// TODO: add a PACKED flag to skip this
isz free_spot = -1;
for (usz i = 0; i < tree.size(); i++) {
if (tree.refs[i] == -1) {
free_spot = i;
continue;
}
// find a item that can be packed
if (free_spot >= 0 && tree.refs[i] >= 0) {
isz old_ref = i;
// move the item
tree.vector[free_spot] = tree.vector[i];
tree.refs[free_spot] = tree.refs[i];
tree.vector[i] = @zero();
tree.refs[i] = -1;
// and move all references
for (usz j = 0; j < tree.size(); j++) {
if (tree.refs[j] == old_ref) {
tree.refs[j] = free_spot;
}
}
// mark the free spot as used
free_spot = -1;
}
}
}
fn void! VTree.resize(&tree, usz newsize)
{
// return error when shrinking with too many elements
if (newsize < tree.elements) {
return VTreeError.CANNOT_SHRINK?;
}
// pack the vector when shrinking to avoid data loss
if ((int)newsize < tree.size()) {
// FIXME: packing destroys all references to elements of vec
// so shrinking may cause dangling pointers
return VTreeError.CANNOT_SHRINK?;
}
usz old_size = tree.size();
tree.vector = ((ElemType*)mem::realloc(tree.vector, newsize*ElemType.sizeof))[:newsize];
defer catch { (void)mem::free(tree.vector); }
tree.refs = ((isz*)mem::realloc(tree.refs, newsize*isz.sizeof))[:newsize];
defer catch { (void)mem::free(tree.refs); }
tree.ordered_refs = ((isz*)mem::realloc(tree.ordered_refs, newsize*isz.sizeof))[:newsize];
defer catch { (void)mem::free(tree.ordered_refs); }
if (newsize > tree.size()) {
tree.vector[old_size..newsize-1] = @zero();
tree.refs[old_size..newsize-1] = -1;
}
}
// add an element to the tree, return it's ref
fn isz! VTree.add(&tree, ElemType elem, isz parent)
{
// invalid parent
if (!tree.ref_is_valid(parent)) {
return VTreeError.INVALID_REFERENCE?;
}
// no space left
if (tree.elements >= tree.size()) {
return VTreeError.TREE_FULL?;
}
// check if the parent exists
// if there are no elements in the tree the first add will set the root
if (!tree.ref_is_present(parent) && tree.elements != 0) {
return VTreeError.REFERENCE_NOT_PRESENT?;
}
// get the first free spot
isz free_spot = -1;
for (usz i = 0; i < tree.size(); i++) {
if (tree.refs[i] == -1) {
free_spot = i;
break;
}
}
if (free_spot < 0) {
return VTreeError.TREE_FULL?;
}
// finally add the element
tree.vector[free_spot] = elem;
tree.refs[free_spot] = parent;
tree.elements++;
return free_spot;
}
// prune the tree starting from the ref
// returns the number of pruned elements
fn usz! VTree.prune(&tree, isz ref)
{
if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?;
}
if (!tree.ref_is_present(ref)) {
return 0;
}
tree.vector[ref] = @zero();
tree.refs[ref] = -1;
tree.elements--;
usz count = 1;
for (usz i = 0; tree.elements > 0 && i < tree.size(); i++) {
if (tree.refs[i] == ref) {
count += tree.prune(i)!;
}
}
return count;
}
// find the size of the subtree starting from ref
fn usz! VTree.subtree_size(&tree, isz ref)
{
if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?;
}
if (!tree.ref_is_present(ref)) {
return 0;
}
usz count = 1;
for (usz i = 0; i < tree.size(); i++) {
// only root has the reference to itself
if (tree.refs[i] == ref && ref != i) {
count += tree.subtree_size(i)!;
}
}
return count;
}
// iterate through the first level children, use a cursor like strtok_r
fn isz! VTree.children_it(&tree, isz parent, isz *cursor)
{
if (cursor == null) {
return VTreeError.INVALID_ARGUMENT?;
}
// if the cursor is out of bounds then we are done for sure
if (!tree.ref_is_valid(*cursor)) {
return VTreeError.INVALID_REFERENCE?;
}
// same for the parent, if it's invalid it can't have children
if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) {
return VTreeError.INVALID_REFERENCE?;
}
// find the first child, update the cursor and return the ref
for (isz i = *cursor; i < tree.size(); i++) {
if (tree.refs[i] == parent) {
*cursor = i + 1;
return i;
}
}
// if no children are found return -1
*cursor = -1;
return -1;
}
/* iterates trough every leaf of the subtree in the following manner
* node [x], x: visit order
* [0]
* / | \
* / [2] [3]
* [1] |
* / \ [6]
* [4] [5]
*/
fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor)
{
if (cursor == null) {
return VTreeError.INVALID_ARGUMENT?;
}
isz[] queue = tree.ordered_refs;
// TODO: this could also be done when adding or removing elements
// first call, create a ref array ordered like we desire
if (*cursor == -1) {
*cursor = 0;
queue[..] = -1;
// iterate through the queue appending found children
isz pos, off;
do {
// printf ("ref=%d\n", ref);
for (isz i = 0; i < tree.size(); i++) {
if (tree.refs[i] == ref) {
queue[pos++] = i;
}
}
for (; ref == queue[off] && off < tree.size(); off++);
ref = queue[off];
} while (tree.ref_is_valid(ref));
// This line is why tree.ordered_refs has to be size+1
queue[off + 1] = -1;
}
// PRINT_ARR(queue, tree.size());
// return -1;
// on successive calls just iterate through the queue until we find an
// invalid ref, if the user set the cursor to -1 it means it has found what
// he needed, so free
if (*cursor < 0) {
return -1;
} else if (tree.ref_is_valid(*cursor)) {
return queue[(*cursor)++];
}
return -1;
}
fn isz! VTree.parentof(&tree, isz ref)
{
if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?;
}
if (!tree.ref_is_present(ref)) {
return VTreeError.REFERENCE_NOT_PRESENT?;
}
return tree.refs[ref];
}
fn ElemType! VTree.get(&tree, isz ref)
{
if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?;
}
if (!tree.ref_is_present(ref)) {
return VTreeError.REFERENCE_NOT_PRESENT?;
}
return tree.vector[ref];
}
fn void VTree.print(&tree)
{
for (isz i = 0; i < tree.size(); i++) {
if (tree.refs[i] == -1) {
continue;
}
io::printf("[%d] {parent=%d, data=", i, tree.refs[i]);
io::print(tree.vector[i]);
io::printn("}");
}
}