diff --git a/dub.json b/dub.json index 1e8488f..f4bcecf 100644 --- a/dub.json +++ b/dub.json @@ -16,8 +16,9 @@ "versions": ["VULKAN_DEBUG", "ENABLE_RENDERER"], "preGenerateCommands-linux": ["./build.sh"], "preGenerateCommands-windows": [], - "dflags": ["-Xcc=-mno-sse", "-P-I/usr/include/freetype2", "-Jbuild", "-Jassets", "-link-debuglib"], - "dflags-dmd": ["-P=-DSTBI_NO_SIMD"] + "dflags": ["-P-I/usr/include/freetype2", "-Jbuild", "-Jassets"], + "dflags-ldc2": ["-link-debuglib"], + "dflags-dmd": [] }, { "name": "editor-test", @@ -33,8 +34,9 @@ "versions": ["VULKAN_DEBUG"], "preGenerateCommands-linux": ["./build.sh"], "preGenerateCommands-windows": [], - "dflags": ["-Xcc=-mno-sse", "-P-I/usr/include/freetype2", "-Jbuild", "-Jassets", "-link-debuglib", "-unittest"], - "dflags-dmd": ["-P=-DSTBI_NO_SIMD"] + "dflags": ["-P-I/usr/include/freetype2", "-Jbuild", "-Jassets", "-unittest"], + "dflags-ldc2": ["-link-debuglib"], + "dflags-dmd": [] }, ] } diff --git a/src/dlib b/src/dlib index e5f91ca..617e03f 160000 --- a/src/dlib +++ b/src/dlib @@ -1 +1 @@ -Subproject commit e5f91cae6d90c40e458e1208c2ab8f8eb917c99a +Subproject commit 617e03f917911f6f9898086720a6539756f79758 diff --git a/src/editor/editor.d b/src/editor/editor.d index c1f5120..84e0f37 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -141,6 +141,29 @@ Cycle(EditorCtx* ctx, Inputs* inputs) BeginUI(inputs); + UICtx* ui_ctx = GetCtx(); + + Vec4[4] col0 = Vec4(0.2, 0.4, 0.8, 1.0); + Vec4[4] col1 = Vec4(0.8, 0.4, 0.2, 1.0); + Vec4[4] col_sep = Vec4(0.2, 0.8, 0.1, 1.0); + + Push!("size_info")(ui_ctx, MakeUISizeX(ST.Percentage, 0.5)); + + Push!("bg_col", true)(ui_ctx, col0); + UIItem* p0 = MakeItem("###p0", UIF.DrawBackground|UIF.Resizeable); + + Logf("%s", *ui_ctx.size_info.top); + Push!("size_info", true)(ui_ctx, MakeUISizeX(ST.Pixels, 2.0)); + Logf("%s", *ui_ctx.size_info.top); + Push!("bg_col", true)(ui_ctx, col_sep); + UIItem* sep = MakeItem("###sep", UIF.Draggable|UIF.DrawBackground|UIF.ResizeAdjacent); + Logf("%s", *ui_ctx.size_info.top); + + + Push!("bg_col", true)(ui_ctx, col1); + UIItem* p1 = MakeItem("###p1", UIF.DrawBackground|UIF.Resizeable); + + /* UIPanel* root = ctx.base_panel; root.size.x = g_ui_ctx.res.x; @@ -150,7 +173,7 @@ Cycle(EditorCtx* ctx, Inputs* inputs) static foreach(axis; A2D.min .. A2D.max) { - for(UIPanel* p = root; !Nil(p); p = Recurse(p, root, g_UI_NIL_PANEL)) + for(UIPanel* p = root; !Nil(p); p = Recurse(p, g_UI_NIL_PANEL)) { f32 pos = p.rect.p0.v[axis]; for(UIPanel* c = p.first; !Nil(c); c = c.next) @@ -167,7 +190,7 @@ Cycle(EditorCtx* ctx, Inputs* inputs) } } - for(auto p = ctx.base_panel; !Nil(p); p = Recurse(p, ctx.base_panel, g_UI_NIL_PANEL)) + for(auto p = ctx.base_panel; !Nil(p); p = Recurse(p, g_UI_NIL_PANEL)) { Panel(p); } @@ -181,6 +204,7 @@ Cycle(EditorCtx* ctx, Inputs* inputs) CommandPalette(&ctx.cmd); } + */ EndUI(); } diff --git a/src/editor/ui.d b/src/editor/ui.d index 0cc438e..1a7b85e 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -14,6 +14,7 @@ import std.math.traits : isNaN; import std.math.rounding : ceil, floor; import std.math.exponential : pow; import std.math.remainder : fmod; +import std.algorithm.comparison : clamp; import std.format : sformat; import core.stdc.string : memset; import core.stdc.math : fabsf; @@ -102,12 +103,17 @@ enum UIFlags ScrollY = 1<<6, ClampX = 1<<7, ClampY = 1<<8, - + Resizeable = 1<<9, + ResizeAdjacent = 1<<10, + FloatingWindow = 1<<11, + CenteredWindow = 1<<12, + TextInput = 1<<13, Clamp = UIFlags.ClampX | UIFlags.ClampY, } alias UIF = UIFlags; +const UIFlags AUTO_FLAGS = UIF.ResizeAdjacent; enum UISignal { @@ -159,35 +165,27 @@ UICtxParameter(T, string name) template CtxMemberInfo(int i) { - import std.string : endsWith; + import std.string : endsWith, startsWith; import std.traits : isInstanceOf, isPointer; struct MemberInfo { string id; - bool is_stack, is_top, is_stack_top; + bool is_stack, is_top; } enum string id = __traits(identifier, UICtx.tupleof[i]); - enum bool is_ptr = isPointer!(typeof(UICtx.tupleof[i])); enum bool is_top = endsWith(id, "_top"); - enum bool is_stack_top = isInstanceOf!(StackTop, typeof(UICtx.tupleof[i])); + enum bool is_stack = startsWith(typeof(UICtx.tupleof[i]).stringof, "StackTop!"); - static if(is_ptr) - { - enum bool is_stack = isInstanceOf!(Stack, typeof(*UICtx.tupleof[i])); - } - else - { - enum bool is_stack = false; - } - - enum MemberInfo CtxMemberInfo = MemberInfo(id: id, is_stack: is_ptr && is_stack, is_top: is_top, is_stack_top: is_stack_top); + enum MemberInfo CtxMemberInfo = MemberInfo(id: id, is_stack: is_stack, is_top: is_top); } struct UICtx { HashTable!(UIHash, UIItem*) items; + UIItem* free_items; + Arena arena; Arena temp_arena; Inputs* inputs; @@ -219,6 +217,7 @@ struct UICtx u32 tab_width; f32 text_size; + UIItem* window_root; UIItem* root; UIItem* drag_item; UIPanel* parent_panel; @@ -245,13 +244,13 @@ struct Stack(T) { Stack!(T)* next; T value; + bool auto_pop; } struct StackTop(T) { Stack!(T)* top; Stack!(T)* free; - bool auto_pop; } mixin template UIItemParameters() @@ -264,7 +263,7 @@ mixin template UIItemParameters() { { enum info = CtxMemberInfo!(i); - static if(info.is_stack_top) + static if(info.is_stack) { fields ~= typeof(UICtx.tupleof[i].top.value).stringof ~ " " ~ info.id ~ ";\n"; } @@ -279,17 +278,20 @@ mixin template UIItemParameters() struct UIItem { - UIKey key; - UIFlags flags; - u64 last_frame; - IVec2 dragged; + IVec2 dragged; + UIKey key; - UIItem* next, prev, first, last; // parent in mixin + u64 last_frame; + UISignal signal; + UIFlags flags; + + UIItem* next, prev, first, last; // parent in mixin - Rect rect; - Vec2 size; + Rect rect; + Vec2 size; - u8[] display_string; + f32 resize_pct; + u8[] display_string; mixin UIItemParameters!(); } @@ -417,6 +419,7 @@ InitUICtx(PlatformWindow* window) UICtx ctx = { items: CreateHashTable!(UIHash, UIItem*)(12), + free_items: Alloc!(UIItem)(&arena), arena: arena, temp_arena: CreateArena(MB(1)), drag_item: g_UI_NIL, @@ -426,6 +429,9 @@ InitUICtx(PlatformWindow* window) tab_width: 2, }; + UIItem* fi = ctx.free_items; + fi.first = fi.last = fi.next = fi.prev = fi.parent = g_UI_NIL; + ctx.atlas_buf = CreateAtlas(&arena, ctx.font, 16.0, 256); version(ENABLE_RENDERER) @@ -435,9 +441,10 @@ InitUICtx(PlatformWindow* window) for(u64 i = 0; i < FRAME_OVERLAP; i += 1) { ctx.buffers[i].m_vtx = CreateMappedBuffer!(Vertex)(&ctx.rd, BT.Vertex, VERTEX_MAX_COUNT); - ctx.buffers[i].m_idx = CreateMappedBuffer!(u32)(&ctx.rd, BT.Index, cast(u64)(ceil(VERTEX_MAX_COUNT*1.5))); + ctx.buffers[i].m_idx = CreateMappedBuffer!(u32)(&ctx.rd, BT.Index, 6); ctx.buffers[i].vtx = ctx.buffers[i].m_vtx.data; ctx.buffers[i].idx = ctx.buffers[i].m_idx.data; + ctx.buffers[i].idx[0 .. $] = [0, 1, 2, 2, 1, 3]; } DescLayoutBinding[2] layout_bindings = [ @@ -513,33 +520,54 @@ Set(UIItem* item, UICtx* ctx) } UIItem* -MakeItem(T)(T k) if(is(T: UIKey) || StringType!T) +MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T) { - UICtx* ctx = GetCtx(); + UICtx* ctx = GetCtx(); UIItem* item = Get(k); + item.flags = flags; Set(item, ctx); - if(!Nil(item.parent)) + if(item.flags & (UIF.CenteredWindow | UIF.FloatingWindow)) + { + item.parent = ctx.window_root; + DLLPush(item.parent, item, g_UI_NIL); + } + else if(!Nil(item.parent)) { DLLPush(item.parent, item, g_UI_NIL); } AutoPopStacks(ctx); + if(item.last_frame != ctx.frame-1 && item.flags & UIF.Resizeable) + { + if(item.size_info[item.parent.layout_axis].type != ST.Percentage) + { + Logf("Warning: Resizing not percentage SizeType item, disabling flag"); + item.flags &= ~UIF.Resizeable; + } + else + { + item.resize_pct = item.size_info[item.layout_axis].value; + } + } + + item.last_frame = ctx.frame; + return item; } -UISignal +void Signal(UIItem* item) { - UISignal signal; - UICtx* ctx = GetCtx(); + UICtx* ctx = GetCtx(); + i32 x = ctx.mouse_pos.x; + i32 y = ctx.mouse_pos.y; - i32 x = ctx.mouse_pos.x; - i32 y = ctx.mouse_pos.y; + item.signal = UIS.None; if(x >= item.rect.p0.x && x <= item.rect.p1.x && y >= item.rect.p0.y && y <= item.rect.p1.y) { - signal |= UIS.Hovered; + item.signal |= UIS.Hovered; } item.dragged = 0; @@ -548,21 +576,27 @@ Signal(UIItem* item) { bool taken; + Logf("%s", i.type); + if(item.flags & UIF.Clickable && i.type == UIE.Click && InBounds(ctx.mouse_pos, &item.rect)) { - signal |= UIS.Clicked; - taken = true; + Logf("clicked"); + item.signal |= UIS.Clicked; + taken = true; } if(Nil(ctx.drag_item) && item.flags & UIF.Draggable && i.type == UIE.DragStart && InBounds(i.pos, &item.rect)) { - signal |= UIS.Dragged; + Logf("dragged"); + item.signal |= UIS.Dragged; ctx.drag_item = item; taken = true; } if(ctx.drag_item == item && i.type == UIE.Drag) { + Logf("dragged"); + item.signal |= UIS.Dragged; item.dragged += i.rel; taken = true; } @@ -572,8 +606,6 @@ Signal(UIItem* item) DLLRemove(&ctx.events, i, g_UI_NIL_INPUT); } } - - return signal; } void @@ -593,8 +625,8 @@ BeginUI(Inputs* inputs) Reset(a); + // Convert Inputs ctx.events.first = ctx.events.last = null; - static bool dragging; static bool mouse_down; for(auto i = inputs.first; i; i = i.next) @@ -633,6 +665,21 @@ BeginUI(Inputs* inputs) } } + // Clean up items + KVPair!(UIHash, UIItem*)*[] items = GetAllNodes(&ctx.temp_arena, &ctx.items); + foreach(i; 0 .. items.length) + { + UIItem* item = items[i].value; + if(item.last_frame != ctx.frame) + { + Logf("discarding %s", cast(char[])item.key.hash_text); + item.first = item.last = item.parent = item.prev = item.next = g_UI_NIL; + DLLPush(ctx.free_items, item, g_UI_NIL); + } + } + + ctx.frame += 1; + // Ctx state ctx.f_idx = ctx.frame%FRAME_OVERLAP; ctx.inputs = inputs; @@ -660,13 +707,16 @@ BeginUI(Inputs* inputs) } memset(ctx.buffers[ctx.f_idx].vtx.ptr, 0, Vertex.sizeof * ctx.buffers[ctx.f_idx].count); - memset(ctx.buffers[ctx.f_idx].idx.ptr, 0, u32.sizeof * ctx.buffers[ctx.f_idx].count); ctx.buffers[ctx.f_idx].count = 0; // Root Item UISize[2] sizes = [UISize(ST.Pixels, ctx.res.x), UISize(ST.Pixels, ctx.res.y)]; - Push!("size_info", true)(ctx, sizes); - ctx.root = MakeItem("###root"); + Push!("size_info")(ctx, sizes); + + ctx.root = MakeItem("###root"); + ctx.window_root = MakeItem("###window_root"); + + Pop!("size_info")(ctx); Push!("parent")(ctx, ctx.root); } @@ -675,9 +725,45 @@ EndUI() { UICtx* ctx = GetCtx(); + // Automatic signals + for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) + { + if(item.flags & AUTO_FLAGS) + { + Signal(item); + + if(item.signal & UIS.Dragged) + { + Logf("1"); + } + + if(item.flags & UIF.ResizeAdjacent && item.dragged != IVec2(0)) + { + UIItem* prev = !Nil(item.prev) ? item.prev : g_UI_NIL; + UIItem* next = !Nil(item.next) ? item.next : g_UI_NIL; + + if(prev.flags & UIF.Resizeable && next.flags & UIF.Resizeable) + { + Axis2D axis = item.parent.layout_axis; + f32 mov_pct = Remap(item.dragged.v[axis], 0.0, item.parent.size.v[axis], 0.0, 1.0); + if(prev.resize_pct > 0.0 && prev.resize_pct < 1.0 && next.resize_pct > 0.0 && next.resize_pct < 1.0) + { + prev.resize_pct -= mov_pct; + next.resize_pct += mov_pct; + + prev.resize_pct = clamp(prev.resize_pct, 0.0, 1.0); + next.resize_pct = clamp(next.resize_pct, 0.0, 1.0); + } + } + } + } + } + + // Calculate item properties static foreach(axis; A2D.min .. A2D.max) { - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL)) + // Pixel sizes + for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) { if(item.size_info[axis].type == ST.Pixels) { @@ -685,10 +771,16 @@ EndUI() } } - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL)) + // Percentage sizes + for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) { if(item.size_info[axis].type == ST.Percentage) { + if(item.layout_axis == axis && item.flags & UIF.Resizeable) + { + item.size_info[axis].value = item.resize_pct; + } + for(UIItem* p = item.parent; !Nil(p); p = p.parent) { if(p.size_info[axis].type == ST.Pixels) @@ -700,7 +792,8 @@ EndUI() } } - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, ctx.root, g_UI_NIL)) + // Sum of children sizes + for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, g_UI_NIL)) { if(item.size_info[axis].type == ST.ChildrenSum) { @@ -712,7 +805,8 @@ EndUI() } } - for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL)) + // Violations + for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) { if(axis == item.layout_axis) { @@ -724,7 +818,6 @@ EndUI() if(children_size > item.size[axis]) { - Logf("%s %s %s %s", axis, cast(char[])item.key.hash_text, children_size, item.size[axis]); u64 child_count; for(UIItem* c = item.first; !Nil(c); c = c.next) { @@ -766,6 +859,7 @@ EndUI() } } + // Calculate final sizes { f32 pos = 0.0; for(UIItem* item = ctx.root; !Nil(item);) @@ -809,6 +903,11 @@ EndUI() } } + + // Render Items + RenderItems(ctx.root); + RenderItems(ctx.window_root); + version(ENABLE_RENDERER) with(ctx) { BindBuffers(&rd, &buffers[f_idx].m_idx, &buffers[f_idx].m_vtx); @@ -817,8 +916,39 @@ EndUI() FinishRendering(&rd); SubmitAndPresent(&rd); } +} - ctx.frame += 1; +void +RenderItems(UIItem* root) +{ + UICtx* ctx = GetCtx(); + for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL)) + { + if(item.flags & UIF.DrawBackground) + { + // DrawRect + Vertex* v = GetVertex(ctx); + v.dst_start = item.rect.p0 + item.border_thickness; + v.dst_end = item.rect.p1 - item.border_thickness; + v.cols = item.bg_col; + v.corner_radius = item.corner_radius; + + AddVertexCount(ctx); + } + + if(item.flags & UIF.DrawBorder) + { + Vertex* v = GetVertex(ctx); + v.dst_start = item.rect.p0; + v.dst_end = item.rect.p1; + v.cols = item.border_col; + v.corner_radius = item.corner_radius; + v.border_thickness = item.border_thickness; + v.edge_softness = item.edge_softness; + + AddVertexCount(ctx); + } + } } u32[2] @@ -862,11 +992,11 @@ Push(string stack_str, bool auto_pop = false, T)(UICtx* ctx, T value) node = Alloc!(Stack!(T))(&ctx.temp_arena); } - node.next = stack.top; - node.value = value; + node.next = stack.top; + node.value = value; + node.auto_pop = auto_pop; stack.top = node; - stack.auto_pop = auto_pop; } auto @@ -902,12 +1032,12 @@ AutoPopStacks(UICtx* ctx) { { enum member_info = CtxMemberInfo!(i); - static if(member_info.is_stack && !member_info.is_top) + static if(member_info.is_stack) { - if(ctx.tupleof[i].auto_pop) + if(ctx.tupleof[i].top.auto_pop) { + ctx.tupleof[i].top.auto_pop = false; Pop!(member_info.id)(ctx); - ctx.tupleof[i].auto_pop = false; } } } @@ -924,7 +1054,7 @@ InitStacks(UICtx* ctx) { { enum member_info = CtxMemberInfo!(i); - static if(member_info.is_stack && member_info.is_top) + static if(member_info.is_top) { enum global_default = replace("g_@_default", "@", chomp(member_info.id, "_top")); @@ -946,11 +1076,10 @@ ResetStacks(UICtx* ctx) { { enum member_info = CtxMemberInfo!(i); - static if(member_info.is_stack_top) + static if(member_info.is_stack) { enum top = replace("ctx.@_top", "@", member_info.id); ctx.tupleof[i].top = mixin(top); - ctx.tupleof[i].auto_pop = false; ctx.tupleof[i].free = null; assert(ctx.tupleof[i].top.next == null, "Top stack node next isn't null"); @@ -960,18 +1089,21 @@ ResetStacks(UICtx* ctx) } T* -Recurse(bool pre = true, T)(T* panel, T* root, T* nil) +Recurse(bool pre = true, T)(T* node, T* nil) { + T* child = pre ? node.first : node.last; + T* sibling = pre ? node.next : node.prev; + T* result = nil; - if(!Nil(panel.first)) + if(!Nil(child)) { - result = pre ? panel.first : panel.last; + result = child; } - else for(T* p = panel; !Nil(p) && p != root; p = p.parent) + else for(T* p = node; !Nil(p); p = p.parent) { - if(!Nil(pre ? p.next : p.prev)) + if(!Nil(sibling)) { - result = pre ? p.next : p.prev; + result = sibling; break; } } @@ -1115,16 +1247,26 @@ Get(T)(T k) if(is(T: UIKey) || StringType!T) UIKey key = MakeKey(k); } - Result!(UIItem*) result = g_ui_ctx.items[key.hash]; - if(!result.ok) + Result!(UIItem*) res = g_ui_ctx.items[key.hash]; + if(!res.ok) { - result.value = Alloc!(UIItem)(&g_ui_ctx.arena); - HTPush(&g_ui_ctx.items, key.hash, result.value); + if(!Nil(g_ui_ctx.free_items.first)) + { + res.value = DLLPop(g_ui_ctx.free_items, g_UI_NIL); + } + else + { + res.value = Alloc!(UIItem)(&g_ui_ctx.arena); + HTPush(&g_ui_ctx.items, key.hash, res.value); + } + + res.value.last_frame = g_ui_ctx.frame-2; } - result.value.key = key; + res.value.key = key; + res.value.next = res.value.prev = res.value.first = res.value.last = res.value.parent = g_UI_NIL; - return result.value; + return res.value; } f32 @@ -1283,7 +1425,7 @@ DrawGlyph(Glyph* glyph, f32 scale, f32* x_pos, f32 y) v.src_end.y = gb.atlas_b; } - AddUIIndices(ctx); + AddVertexCount(ctx); *x_pos += glyph.advance * scale; @@ -1382,7 +1524,7 @@ DrawRect(f32 x, f32 y, f32 w, f32 h, f32 corner_radius, f32 border, Vec4[4] cols v.dst_end -= border; } - AddUIIndices(ctx); + AddVertexCount(ctx); } void @@ -1399,21 +1541,13 @@ DrawBorder(Vec2 pos, Vec2 size, f32 border, f32 radius, f32 softness, Vec4[4] co v.edge_softness = softness; v.raised = 0.0; - AddUIIndices(ctx); + AddVertexCount(ctx); } pragma(inline) void -AddUIIndices(UICtx* ctx) +AddVertexCount(UICtx* ctx) { - ctx.buffers[ctx.f_idx].idx[0] = 0; - ctx.buffers[ctx.f_idx].idx[1] = 1; - ctx.buffers[ctx.f_idx].idx[2] = 2; - ctx.buffers[ctx.f_idx].idx[3] = 2; - ctx.buffers[ctx.f_idx].idx[4] = 1; - ctx.buffers[ctx.f_idx].idx[5] = 3; - ctx.buffers[ctx.f_idx].count += 1; - assert(ctx.buffers[ctx.f_idx].count < VERTEX_MAX_COUNT); }