ported rewrite2 to c3
This commit is contained in:
parent
2dcc1b582c
commit
39e78ea078
40 changed files with 1478 additions and 2911 deletions
0
src/.gitkeep
Normal file
0
src/.gitkeep
Normal file
133
src/cache.c3
Normal file
133
src/cache.c3
Normal 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
49
src/fifo.c3
Normal 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
196
src/main.c3
Normal 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
184
src/ugui_data.c3
Normal 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
244
src/ugui_impl.c3
Normal 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
78
src/ugui_input.c3
Normal 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
153
src/ugui_layout.c3
Normal 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
333
src/vtree.c3
Normal 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("}");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue