diff --git a/src/dlib b/src/dlib index b4bcfb7..c7f7752 160000 --- a/src/dlib +++ b/src/dlib @@ -1 +1 @@ -Subproject commit b4bcfb7929ca87d59a35719c43b34d64ecaec9a0 +Subproject commit c7f77523c4b7c86b625149ef7fde76257f6fcf51 diff --git a/src/editor/editor.d b/src/editor/editor.d index fe02df6..4cf18ff 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -20,6 +20,7 @@ const u8[] FRAGMENT_BYTES = import("gui.frag.spv"); const UI_COUNT = 5000; +f32 g_delta = 0.0; Editor* g_editor; struct Editor @@ -47,6 +48,8 @@ struct Editor FontFace font; FontAtlasBuf atlas_buf; + Stack!(UIPanel*) panel_stack; + UVec2 res; } @@ -199,7 +202,7 @@ Cycle(Editor* ed, Inputs* inputs) } u32 prev_box_count = box_count; - f32 delta = DeltaTime(&timer); + g_delta = DeltaTime(&timer); for(auto ev = inputs.list.first; ev != null; ev = ev.next) { count += 1; @@ -218,106 +221,28 @@ Cycle(Editor* ed, Inputs* inputs) } } - static UIPanel* panel = null; - if (panel == null) - { - panel = Alloc!(UIPanel); + PanelStart("##Base", Vec3(0.5)); - panel.split_axis = A2D.X; - panel.percent = 1.0; - panel.first = panel.last = Alloc!(UIPanel); + PanelStart("##horizontal1", Vec3(0.2, 0.3, 0.7)); - panel.first.percent = 1.0; + PanelStart("##horizontal2", Vec3(0.2, 0.3, 0.7)); - UIPanel* child = panel.first; - foreach(i; 0 .. 8) - { - UIPanel* p = Alloc!(UIPanel); - - child.next = p; - panel.last = p; - child = p; - } - } - - char[128] buf = 0; + char[128] buf; f32 x_pct = 1.0/box_count; - - if (box_count > prev_box_count) - { - u32 added = box_count - prev_box_count; - f32 insert_size = 0.0; - UIPanel* child = panel.first; - foreach(i; 0 .. prev_box_count) - { - child.anim.start = child.percent; - child.anim.end = x_pct; - child.anim.time_start = 0.2; - child.anim.time_remaining = 0.2; - - child = child.next; - } - - foreach(i; 0 .. box_count-prev_box_count) - { - child.anim.start = insert_size; - child.anim.end = x_pct; - child.anim.time_start = 0.2; - child.anim.time_remaining = 0.2; - child.percent = insert_size; - - child = child.next; - } - } - else if (box_count < prev_box_count) - { - UIPanel* child = panel.first; - foreach(i; 0 .. box_count) - { - child.anim.start = child.percent; - child.anim.end = x_pct; - child.anim.time_start = 0.2; - child.anim.time_remaining = 0.2; - - child = child.next; - } - } - else - { - UIPanel* child = panel.first; - foreach(i; 0 .. box_count) - { - if (child.anim.time_remaining == 0.0) break; - - f32 r = child.anim.time_remaining; - - r -= delta; - if (r < 0.0) - { - r = 0.0; - } - - f32 t = Remap(r, 0.0, child.anim.time_start, 1.0, 0.0); - child.percent = Mix(child.anim.start, child.anim.end, t); - - child.anim.time_remaining = r; - if (r == 0.0) - { - child.anim.start = child.anim.end = child.anim.time_start = 0.0; - } - - child = child.next; - } - } - - UIPanel* child = panel.first; foreach(i; 0 .. box_count) { buf.sformat("##%s", i); - DrawPanel(cast(u8[])buf, Vec4(Vec3(x_pct * i), 1.0), child); - child = child.next; + PanelStart(cast(u8[])buf, Vec3(x_pct * i)); + PanelEnd(); } + PanelEnd(); + + PanelStart("##horizontal3", Vec3(0.2, 0.5, 0.9)); + PanelEnd(); + + PanelEnd(); + BeginFrame(&ed.rd); UVec2 ext = UVec2(GetExtent(&ed.rd)); diff --git a/src/editor/ui.d b/src/editor/ui.d index db89413..e9b8a80 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -5,6 +5,7 @@ import dlib.alloc; import dlib.fonts; import vulkan; import widgets; +import std.stdio; import editor; @@ -30,13 +31,9 @@ struct UIContext Rect padding; u32 tab_width; f32 text_scale; - Stack!(UIItem) parent_stack; + UIItem* parent_stack; Axis2D layout_axis; -} - -struct Stack(T) -{ - T* first; + Vec2 position; } struct UIBuffer @@ -63,6 +60,7 @@ enum CtxProperty TextScale, TabWidth, LayoutAxis, + Position, } alias CtxP = CtxProperty; @@ -154,11 +152,15 @@ struct UIItem UIItem* prev; UIItem* parent; + UIItem* list_next; + UIItem* list_prev; + + debug { u8[] debug_id; } + UISize[A2D.max] size; Vec4[4] bg_color; Vec4[4] border_color; Rect padding; - f32[A2D.max] computed_rel_pos; f32[A2D.max] computed_size; f32[A2D.max] fixed_position; Rect rect; @@ -194,10 +196,9 @@ UIBeginBuild() ui.buffer.count = 0; - ClearStack(&g_ui_ctx.parent_stack, g_UI_NIL); + ClearParentStack(); ui.root = RootItem(); - PushParent(ui.root); } void @@ -223,26 +224,25 @@ UIFinishBuild() } // 2. PercentOfParent (Upward dependant) size (pre-order) (Ignore downward dependant sizes) - u32 count = 0; - for(UIItem* item = ui.root; !Nil(item); item = Recurse!(UIR.PreOrder)(item, ui.root).next) + foreach(axis; A2D.min .. A2D.max) { - for(UIItem* child = item.first; !Nil(child); child = child.next) + for(UIItem* item = ui.root; !Nil(item); item = Recurse!(UIR.PreOrder)(item, ui.root).next) { - foreach(i, ref s; child.size) + for(UIItem* child = item.first; !Nil(child); child = child.next) { - if (s.kind == SK.PercentOfParent) + if (child.size[axis].kind == SK.PercentOfParent) { UIItem* fixed_parent = g_UI_NIL; for(UIItem* p = child.parent; !Nil(p); p = p.parent) { - if (p.size[i].kind == SK.Pixels || p.size[i].kind == SK.TextContent) + if (p.size[axis].kind == SK.Pixels || p.size[axis].kind == SK.TextContent) { fixed_parent = p; break; } } - child.computed_size[i] = fixed_parent.computed_size[i] * s.value; + child.computed_size[axis] = fixed_parent.computed_size[axis] * child.size[axis].value; } } } @@ -259,11 +259,33 @@ UIFinishBuild() i32 pop_index = 0; for(UIItem* i = item; !Nil(item) && pop_index <= rec.pop_count; i = i.parent, pop_index += 1) { + if (i.size[axis].kind == SK.ChildrenSum) + { + f32 sum = 0.0; + for(UIItem* child = i.first; !Nil(child); child = child.next) + { + sum = axis == child.layout_axis ? sum + child.computed_size[axis] : Max(sum, child.computed_size[axis]); + } + i.computed_size[axis] = sum; + } } } } } // 4. Solve violations e.g. extending past boundaries of parent (pre-order) + foreach(axis; A2D.min .. A2D.max) + { + for(UIItem* item = ui.root; !Nil(item); item = Recurse!(UIR.PreOrder)(item, ui.root).next) + { + if (!Nil(item.parent)) + { + if (item.parent.computed_size[axis] < item.computed_size[axis]) + { + item.computed_size[axis] = item.parent.computed_size[axis]; + } + } + } + } // 5. Compute relative positions of each widgets (pre-order) foreach(axis; A2D.min .. A2D.max) @@ -271,12 +293,12 @@ UIFinishBuild() for(UIItem* item = ui.root; !Nil(item); item = Recurse!(UIR.PreOrder)(item, ui.root).next) { f32 layout_position = 0.0; - for(UIItem* child = item.first; !Nil(child); child = child.next) { - child.computed_rel_pos[axis] = layout_position; - child.rect.vec0.v[axis] = layout_position; - child.rect.vec1.v[axis] = layout_position + child.computed_size[axis]; + child.fixed_position[axis] = layout_position; + + child.rect.vec0.v[axis] = item.rect.vec0.v[axis] + child.fixed_position[axis]; + child.rect.vec1.v[axis] = child.rect.vec0.v[axis] + child.computed_size[axis]; if (axis == item.layout_axis) { @@ -286,19 +308,46 @@ UIFinishBuild() } } + u32 count = 0; for(UIItem* item = ui.root; !Nil(item); item = Recurse!(UIR.PreOrder)(item, ui.root).next) { - for(UIItem* child = item.first; !Nil(child); child = child.next) + if (item.flags & UIP.DrawBackground) { - if (!Nil(child)) - { - if (child.flags & UIP.DrawBackground) - { - DrawRect(ui, child); - } - } + DrawRect(ui, item); } } + + WidgetsComplete(); +} + +void +PrintTree(UIItem* item, u32 count = 0, string prefix = "", bool is_left = false) +{ + if (!Nil(item)) + { + writeln(is_left ? "f" : "l", count, prefix, is_left ? "├──" : "└──", item.debug_id); + + prefix ~= is_left ? "│ " : " "; + PrintTree(item.first, count + 1, prefix, true); + PrintTree(item.next, count + 1, prefix, false); + } +} + +void +PushParent(UIItem* parent) +{ + auto ctx = GetCtx(); + parent.list_next = ctx.parent_stack; + ctx.parent_stack = parent; +} + +UIItem* +PopParent() +{ + auto ctx = GetCtx(); + UIItem* parent = ctx.parent_stack; + ctx.parent_stack = parent.list_next; + return parent; } UIItem* @@ -307,8 +356,8 @@ BuildItem(UIKey key, UIProperties flags) UIContext* ui = GetCtx(); UIItem* item = Get(key); - item.first = item.last = item.next = item.prev = item.parent = g_UI_NIL; - item.computed_rel_pos = item.computed_size = 0; + item.first = item.last = item.next = item.prev = item.parent = item.list_next = item.list_prev = g_UI_NIL; + item.computed_size = 0; item.flags = flags; item.size = ui.size_axes; @@ -328,31 +377,25 @@ BuildItem(UIKey key, UIProperties flags) item.text_scale = ui.text_scale; } - item.parent = ui.parent_stack.first; + item.parent = ui.parent_stack; if (!Nil(item.parent)) { DLLPush(item.parent, item, g_UI_NIL); } + debug + { + item.debug_id = key.text; + } + return item; } void -ClearStack(T)(Stack!(T)* stack, T* nil) +ClearParentStack() { - while(SPop(stack, nil) != nil) {} -} - -void -PushParent(UIItem* parent) -{ - SPush(&g_ui_ctx.parent_stack, parent, g_UI_NIL); -} - -UIItem* -PopParent() -{ - return SPop(&g_ui_ctx.parent_stack, g_UI_NIL); + auto ctx = GetCtx(); + ctx.parent_stack = g_UI_NIL; } void @@ -378,7 +421,8 @@ InitUIContext(Renderer* rd) g_ui_ctx.buffer.mapped_idx = idx; g_ui_ctx.buffer.vtx = vtx.data; g_ui_ctx.buffer.idx = idx.data; - g_ui_ctx.parent_stack.first = g_UI_NIL; + + InitWidgetContext(); } UIKey @@ -551,17 +595,23 @@ Recurse(alias mode)(UIItem* item, UIItem* root) UIItem* sibling, child; UIItemRec result = UIItemRec(next: g_UI_NIL); - static if (mode == UIR.PostOrder) sibling = item.next; else sibling = item.prev; - static if (mode == UIR.PostOrder) child = item.prev; else child = item.last; + static if (mode == UIR.PostOrder) child = item.last; else child = item.first; if (!Nil(child)) { result.next = child; result.push_count = 1; } - else for(UIItem* i = sibling; !Nil(i) && i != root; i = i.parent) + else for(UIItem* i = item; !Nil(i) && i != root; i = i.parent) { - static if (mode == UIR.PostOrder) sibling = i.next; else sibling = i.prev; + static if (mode == UIR.PostOrder) + { + sibling = i.prev; + } + else + { + sibling = i.next; + } if (!Nil(sibling)) { @@ -584,6 +634,11 @@ SetProp(alias P, T)(T value) static if (P == CtxP.LayoutAxis) g_ui_ctx.layout_axis = value; else static assert(false, "Unknown property for type Axis2D"); } + else static if (is(T: Vec2)) + { + static if (P == CtxP.Position) g_ui_ctx.position = value; + else static assert(false, "Unknown property for type Vec2"); + } else static if (is(T: Vec4)) { static if (P == CtxP.BGColor) g_ui_ctx.bg_cols = [value, value, value, value]; diff --git a/src/editor/widgets.d b/src/editor/widgets.d index e5ea738..af8ad50 100644 --- a/src/editor/widgets.d +++ b/src/editor/widgets.d @@ -1,5 +1,6 @@ import dlib; +import editor; import ui; struct WidgetAnimation @@ -10,6 +11,18 @@ struct WidgetAnimation f32 time_remaining; } +WidgetContext g_widget_ctx; +const UIPanel g_ui_panel; +UIPanel* g_PANEL_NIL; + +struct WidgetContext +{ + u64 frame; + Arena arena; + HashTable!(UIHash, UIPanel*) panels; + UIPanel* parent_panel; +} + struct UIPanel { Axis2D split_axis; @@ -17,12 +30,41 @@ struct UIPanel UIPanel* first; UIPanel* last; + UIPanel* prev; UIPanel* next; - u32 count; + UIPanel* parent; + + UIPanel* list_next; + + u64 last_frame; + bool new_children; + u32 child_count; WidgetAnimation anim; } +void +InitWidgetContext() +{ + g_PANEL_NIL = cast(UIPanel*)&g_ui_panel; + + g_widget_ctx.arena = CreateArena(MB(1)); + g_widget_ctx.parent_panel = g_PANEL_NIL; + g_widget_ctx.panels = CreateHashTable!(UIHash, UIPanel*)(6); +} + +void +WidgetsComplete() +{ + g_widget_ctx.frame += 1; +} + +bool +Nil(UIPanel* panel) +{ + return panel == g_PANEL_NIL || panel == null; +} + UIItem* RootItem() { @@ -37,22 +79,141 @@ RootItem() UIItem* item = BuildItem(key, UIP.DrawBackground); + UIContext* ui = GetCtx(); + + PushParent(ui.root); + + assert(g_widget_ctx.parent_panel == g_PANEL_NIL); + return item; } UIItem* -DrawPanel(u8[] str, Vec4 col, UIPanel* panel) +PanelStart(string str, Vec3 col) +{ + u8[] u8_str = (cast(u8*)str.ptr)[0 .. str.length]; + return PanelStart(u8_str, col); +} + +UIPanel* +GetPanel(UIKey key) +{ + UIPanel* panel; + Result!(UIPanel*) result = g_widget_ctx.panels[key.hash]; + if (result.ok) + { + panel = result.value; + } + else + { + panel = Alloc!(UIPanel)(&g_widget_ctx.arena); + panel.last_frame = u64.max; + g_widget_ctx.panels[key.hash] = panel; + } + + return panel; +} + +UIItem* +PanelStart(u8[] str, Vec3 col) { UIKey key = MakeKey(str); + UIPanel* panel = GetPanel(key); - f32 x_pct = panel.split_axis == A2D.X ? panel.percent : 1.0; - f32 y_pct = panel.split_axis == A2D.Y ? panel.percent : 1.0; + panel.first = panel.last = panel.next = panel.list_next = g_PANEL_NIL; + panel.child_count = 0; - SetProp!(CtxP.AxisX)(UISize(SK.PercentOfParent, x_pct)); - SetProp!(CtxP.AxisY)(UISize(SK.PercentOfParent, y_pct)); - SetProp!(CtxP.BGColor)(col); + if (panel.anim.time_remaining > 0.0) + { + f32 r = panel.anim.time_remaining; + + r -= g_delta; + if (r < 0.0) + { + r = 0.0; + } + Logf("%f", r); + + f32 t = Remap(r, 0.0, panel.anim.time_start, 1.0, 0.0); + panel.percent = Lerp(panel.anim.start, panel.anim.end, t); + + panel.anim.time_remaining = r; + if (r == 0.0) + { + panel.anim.start = panel.anim.end = panel.anim.time_start = 0.0; + } + } + + panel.parent = g_widget_ctx.parent_panel; + panel.list_next = g_widget_ctx.parent_panel; + g_widget_ctx.parent_panel = panel; + + f32 x_pct = 1.0, y_pct = 1.0; + if (!Nil(panel.parent)) + { + if (panel.parent.split_axis == A2D.X) + { + x_pct = panel.percent; + } + else + { + y_pct = panel.percent; + } + + if (panel.last_frame != g_widget_ctx.frame) + { + Logf("new"); + panel.parent.new_children = true; + panel.anim.start = panel.anim.end = panel.anim.time_start = panel.anim.time_remaining = 0.0; + } + + panel.child_count += 1; + DLLPush(panel.parent, panel, g_PANEL_NIL); + } + + SetProp!(CtxP.LayoutAxis)(panel.split_axis); + SetProp!(CtxP.AxisX)(UISize(SK.PercentOfParent, x_pct, 1.0)); + SetProp!(CtxP.AxisY)(UISize(SK.PercentOfParent, y_pct, 1.0)); + SetProp!(CtxP.BGColor)(Vec4(col, 1.0)); UIItem* item = BuildItem(key, UIP.DrawBackground); + PushParent(item); + + panel.last_frame = g_widget_ctx.frame + 1; + return item; } + +void +PanelEnd() +{ + UIPanel* parent = g_widget_ctx.parent_panel; + if (parent.new_children) + { + f32 new_percent = 1.0/cast(f32)(parent.child_count); + for(UIPanel* child = parent.first; !Nil(child); child = child.next) + { + f32 percent = child.percent; + if (percent > 0.0) + { + percent -= new_percent/cast(f32)(parent.child_count-1); + } + else + { + percent = new_percent; + } + + child.anim.start = child.percent; + child.anim.end = new_percent; + child.anim.time_start = 0.2; + child.anim.time_remaining = 0.2; + child.percent = percent; + } + + parent.new_children = false; + } + + g_widget_ctx.parent_panel = g_widget_ctx.parent_panel.list_next; + PopParent(); +}