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); } // get or push an element from the cache, return a pointer to it // resets all flags except is_new which is set accordingly macro Ctx.get_elem(&ctx, Id id) { Elem empty_elem; bool is_new; Elem* c_elem; c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; c_elem.flags = (ElemFlags)0; c_elem.flags.is_new = is_new; return c_elem; } // this searches an element in the cache by label, it does not create a new element // if it does't find one macro Ctx.get_elem_by_label(&ctx, String label) { Id id = hash(label); return ctx.cache.search(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) { // 2. Get the root element from the cache and update it Elem* c_elem = ctx.get_elem(ROOT_ID)!; // 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 c_elem.flags.updated = ctx.input.events.resize; // 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 c_elem.flags.has_focus = ctx.has_focus; if (c_elem.flags.is_new || c_elem.flags.updated) { Elem def_root = { .id = ROOT_ID, .type = ETYPE_DIV, .rect = { .w = ctx.width, .h = ctx.height, }, .div = { .layout = LAYOUT_ROW, }, .flags = c_elem.flags, }; *c_elem = def_root; } // 3. Push the root element into the element tree ctx.active_div = ctx.tree.add(ROOT_ID, 0)!; // The root element does not push anything to the stack // TODO: add a background color taken from a theme or config } 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 } fn void! Ctx.div_begin(&ctx, String label, Rect size) { Id id = hash(label); Elem *parent = ctx.get_parent()!; Elem* c_elem = ctx.get_elem(id)!; isz div_node = ctx.tree.add(id, ctx.active_div)!; ctx.active_div = div_node; // 1. Fill the element fields c_elem.type = ETYPE_DIV; // do layout and update flags only if the element was updated if (c_elem.flags.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); }