From ad3ababe9785c6b690817f15cdfc3a927582fe4e Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 12 Dec 2025 20:13:12 +1100 Subject: [PATCH] ui code almost ready to replace current code --- dub.json | 19 +- src/VulkanRenderer | 2 +- src/dlib | 2 +- src/editor/editor.d | 34 +-- src/editor/main.d | 2 +- src/editor/ui.d | 689 ++++++++++++++++++++++++++++++-------------- src/editor/views.d | 40 +-- 7 files changed, 537 insertions(+), 251 deletions(-) diff --git a/dub.json b/dub.json index 868a74e..1e8488f 100644 --- a/dub.json +++ b/dub.json @@ -13,11 +13,28 @@ "sourcePaths": ["src/editor", "src/dlib", "src/dlib/external/xxhash", "src/VulkanRenderer"], "libs-linux": ["X11", "vulkan", "stdc++", "xfixes", "freetype"], "libs-windows": [], - "versions": ["VULKAN_DEBUG"], + "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"] }, + { + "name": "editor-test", + "targetType": "executable", + "targetName": "Editor", + "targetPath": "build", + "sourceFiles-linux": ["build/libvma.a", "build/libstb.a", "build/libm3d.a", "build/libcglm.a"], + "sourceFiles-windows": [], + "importPaths": ["src/editor", "src/dlib", "src/dlib/external/xxhash", "src/VulkanRenderer"], + "sourcePaths": ["src/editor", "src/dlib", "src/dlib/external/xxhash", "src/VulkanRenderer"], + "libs-linux": ["X11", "vulkan", "stdc++", "xfixes", "freetype"], + "libs-windows": [], + "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"] + }, ] } diff --git a/src/VulkanRenderer b/src/VulkanRenderer index 589b219..b3639d9 160000 --- a/src/VulkanRenderer +++ b/src/VulkanRenderer @@ -1 +1 @@ -Subproject commit 589b2191babf009bef097d1272fac4a05a4c280d +Subproject commit b3639d9c884c09fb98b750ba613118d633470312 diff --git a/src/dlib b/src/dlib index 60b4e0c..e5f91ca 160000 --- a/src/dlib +++ b/src/dlib @@ -1 +1 @@ -Subproject commit 60b4e0cb27ba304c7b9444a2da8a581211c21ca9 +Subproject commit e5f91cae6d90c40e458e1208c2ab8f8eb917c99a diff --git a/src/editor/editor.d b/src/editor/editor.d index 8c914c6..c1f5120 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -139,25 +139,25 @@ Cycle(EditorCtx* ctx, Inputs* inputs) g_input_mode = ctx.state == ES.InputMode; - BeginUI(ctx, inputs); + BeginUI(inputs); UIPanel* root = ctx.base_panel; root.size.x = g_ui_ctx.res.x; root.size.y = g_ui_ctx.res.y; - root.rect.vec0 = 0.0; - root.rect.vec1 = 0.0; + root.rect.p0 = 0.0; + root.rect.p1 = 0.0; static foreach(axis; A2D.min .. A2D.max) { for(UIPanel* p = root; !Nil(p); p = Recurse(p, root, g_UI_NIL_PANEL)) { - f32 pos = p.rect.vec0.v[axis]; + f32 pos = p.rect.p0.v[axis]; for(UIPanel* c = p.first; !Nil(c); c = c.next) { c.size.v[axis] = p.axis == axis ? c.pct * p.size.v[axis] : p.size.v[axis]; - c.rect.vec0.v[axis] = pos; - c.rect.vec1.v[axis] = pos + c.size.v[axis]; + c.rect.p0.v[axis] = pos; + c.rect.p1.v[axis] = pos + c.size.v[axis]; if(axis == p.axis) { @@ -571,13 +571,13 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs) UIPanel* panel = GetFocusedPanel(); FlatBuffer* fb = !Nil(panel) ? &panel.ed.buf : null; - for(auto node = inputs.list.first; node != null; node = node.next) + for(auto node = inputs.first; node != null; node = node.next) { bool taken = false; - Input key = node.value.key; - Modifier md = node.value.md; - bool pressed = node.value.pressed; + Input key = node.key; + Modifier md = node.md; + bool pressed = node.pressed; if (pressed) { @@ -588,11 +588,11 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs) } else if(ctx.state == ES.InputMode) { - taken = HandleInputMode(ctx, node.value); + taken = HandleInputMode(ctx, node); } else if(ctx.state == ES.CmdOpen) { - taken = HandleCmdMode(ctx, node.value); + taken = HandleCmdMode(ctx, node); } else if(ctx.state == ES.SetPanelFocus) { @@ -646,7 +646,7 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs) } break; case Semicolon: { - if(Shift(node.value.md)) + if(Shift(node.md)) { ctx.state = ES.CmdOpen; taken = true; @@ -701,7 +701,7 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs) if(taken) { - DLLRemove(&inputs.list, node, null); + DLLRemove(inputs, node, null); } } @@ -714,7 +714,7 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs) } void -MoveCursor(InputEvent ev) +MoveCursor(InputEvent* ev) { UIPanel* p = GetFocusedPanel(); @@ -754,7 +754,7 @@ CharCases() } bool -HandleInputMode(EditorCtx* ctx, InputEvent ev) +HandleInputMode(EditorCtx* ctx, InputEvent* ev) { bool taken = false; @@ -912,7 +912,7 @@ GetCommands(CmdPalette* cmd) } bool -HandleCmdMode(EditorCtx* ctx, InputEvent ev) +HandleCmdMode(EditorCtx* ctx, InputEvent* ev) { u8 result = 0; bool taken = false; diff --git a/src/editor/main.d b/src/editor/main.d index eeb8d92..e5fc67d 100644 --- a/src/editor/main.d +++ b/src/editor/main.d @@ -47,7 +47,7 @@ void main(string[] argv) SetExtent(&g_ui_ctx.rd, window.w, window.h); } - if(inputs.list.first == null) + if(inputs.first == null) { no_ev_count += 1; Pause(); diff --git a/src/editor/ui.d b/src/editor/ui.d index e1f0926..0cc438e 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -2,6 +2,7 @@ import dlib; import dlib.util : HTPush = Push; import vulkan; +import vulkan : RendererGetExtent = GetExtent; import buffer; import parsing; import editor; @@ -10,7 +11,7 @@ import views; import std.stdio; import std.math.traits : isNaN; -import std.math.rounding : ceil; +import std.math.rounding : ceil, floor; import std.math.exponential : pow; import std.math.remainder : fmod; import std.format : sformat; @@ -48,39 +49,38 @@ const u8[] FONT_BYTES = import("pc-9800.ttf"); const u8[] VERTEX_BYTES = import("gui.vert.spv"); const u8[] FRAGMENT_BYTES = import("gui.frag.spv"); -f32 g_corner_radius_default = 2.0; -f32 g_edge_softness_default = 0.1; -f32 g_border_thickness_default = 2.0; -Vec4[4] g_bg_col_default = BG_COL; -Vec4[4] g_bg_hl_col_default = BG_HL_COL; -Vec4[4] g_border_col_default = BORDER_COL; -Vec4[4] g_border_hl_col_default = BORDER_HL_COL; -Vec4 g_text_col_default = TEXT_COL; -Vec4 g_text_hl_col_default = TEXT_HL_COL; -UISize[2] g_size_info_default = [UISize(ST.Percentage, 1.0), UISize(ST.Percentage, 1.0)]; +f32 g_corner_radius_default = 2.0; +f32 g_edge_softness_default = 0.1; +f32 g_border_thickness_default = 2.0; +Vec4[4] g_bg_col_default = BG_COL; +Vec4[4] g_bg_hl_col_default = BG_HL_COL; +Vec4[4] g_border_col_default = BORDER_COL; +Vec4[4] g_border_hl_col_default = BORDER_HL_COL; +Vec4 g_text_col_default = TEXT_COL; +Vec4 g_text_hl_col_default = TEXT_HL_COL; +UISize[2] g_size_info_default = [UISize(ST.Percentage, 1.0), UISize(ST.Percentage, 1.0)]; +Axis2D g_layout_axis_default = A2D.X; +Vec2 g_padding_default = Vec2(0.0); const UI_COUNT = 5000; -__gshared const UIPanel g_ui_nil_panel; -__gshared UIPanel* g_UI_NIL_PANEL; - -__gshared const UIItem g_ui_nil_item; -__gshared UIItem* g_UI_NIL; +__gshared const UIPanel g_ui_nil_panel; +__gshared UIPanel* g_UI_NIL_PANEL; +__gshared const UIItem g_ui_nil_item; +__gshared UIItem* g_UI_NIL; +__gshared const UIInput g_ui_nil_input; +__gshared UIInput* g_UI_NIL_INPUT; +__gshared const Stack!f32 g_nil_f32_stack; +__gshared Stack!f32* g_NIL_F32_STACK; +__gshared const Stack!Vec4[4] g_nil_gradient_col_stack; +__gshared Stack!Vec4[4]* g_NIL_GRAD_COL_STACK; +__gshared const Stack!Vec4 g_nil_vec4_stack; +__gshared Stack!Vec4* g_NIL_VEC4_STACK; +__gshared const Stack!u32 g_nil_u32_stack; +__gshared Stack!u32* g_NIL_U32_STACK; alias g_parent_default = g_UI_NIL; -__gshared const Stack!f32 g_nil_f32_stack; -__gshared Stack!f32* g_NIL_F32_STACK; - -__gshared const Stack!Vec4[4] g_nil_gradient_col_stack; -__gshared Stack!Vec4[4]* g_NIL_GRAD_COL_STACK; - -__gshared const Stack!Vec4 g_nil_vec4_stack; -__gshared Stack!Vec4* g_NIL_VEC4_STACK; - -__gshared const Stack!u32 g_nil_u32_stack; -__gshared Stack!u32* g_NIL_U32_STACK; - enum Axis2D { X, @@ -106,31 +106,47 @@ enum UIFlags Clamp = UIFlags.ClampX | UIFlags.ClampY, } - alias UIF = UIFlags; -enum UIEventFlag -{ - None, // Events e.g. pressing/releasing/scrolling -} enum UISignal { - None = 0x00, - Clicked = 0x01, - Dragged = 0x02, // Handle click/drag specifics in view? + None = 0, + Clicked = 1<<0, + Dragged = 1<<1, + Hovered = 1<<2, } - alias UIS = UISignal; +enum UIEvent +{ + None = 0, + Click = 1<<0, + Drag = 1<<1, + DragStart = 1<<2, + Press = 1<<3, +} +alias UIE = UIEvent; + +struct UIInput +{ + UIEvent type; + UIInput* next, prev; + union + { + Input key; + IVec2 rel; + IVec2 pos; + } +} + mixin template UICtxParameter(T, string name) { static string CtxParameterGen(T, string name)() { - import std.traits; - import std.array; + import std.traits, std.array; string stack_top = "\tStackTop!("~T.stringof~") "~name~";\n"; string stack = "\tStack!("~T.stringof~")* "~name~"_top;\n"; @@ -178,7 +194,11 @@ struct UICtx u64 frame; u64 f_idx; -PlatformWindow* window; + LinkedList!(UIInput) events; + IVec2 mouse_pos; + IVec2 drag_start; + + PlatformWindow* window; Renderer rd; Descriptor font_atlas; Descriptor sampler; @@ -186,7 +206,7 @@ PlatformWindow* window; DescSetLayout desc_set_layout; DescSet desc_set; PipelineLayout pipeline_layout; - PushConst pc; + Mat4 projection; Vec2 res; u8[] font_data; @@ -215,6 +235,8 @@ PlatformWindow* window; mixin UICtxParameter!(Vec4, "text_col"); mixin UICtxParameter!(Vec4, "text_hl_col"); mixin UICtxParameter!(UISize[2], "size_info"); + mixin UICtxParameter!(Axis2D, "layout_axis"); + mixin UICtxParameter!(Vec2, "padding"); debug bool dbg; } @@ -257,14 +279,17 @@ mixin template UIItemParameters() struct UIItem { - UIKey key; - u64 last_frame; - Vec2 dragged; + UIKey key; + UIFlags flags; + u64 last_frame; + IVec2 dragged; - UIItem* next, prev, first, last; // parent in mixin + UIItem* next, prev, first, last; // parent in mixin + + Rect rect; + Vec2 size; - Rect pref_size; - Rect size; + u8[] display_string; mixin UIItemParameters!(); } @@ -273,7 +298,7 @@ enum SizeType { Pixels, Percentage, - FitChild, + ChildrenSum, } alias ST = SizeType; @@ -294,23 +319,10 @@ struct UIBuffer u32 count; } -struct PushConst -{ - Mat4 projection; -} - struct GlyphBounds { - f32 r = 0.0; - f32 l = 0.0; - f32 t = 0.0; - f32 b = 0.0; - f32 w = 0.0; - f32 h = 0.0; - f32 atlas_r = 0.0; - f32 atlas_l = 0.0; - f32 atlas_t = 0.0; - f32 atlas_b = 0.0; + f32 r = 0.0, l = 0.0, t = 0.0, b = 0.0, w = 0.0, h = 0.0; + f32 atlas_r = 0.0, atlas_l = 0.0, atlas_t = 0.0, atlas_b = 0.0; } struct Vertex @@ -331,18 +343,7 @@ struct Vertex union Rect { Vec2[2] v; - struct - { - Vec2 vec0; - Vec2 vec1; - }; - struct - { - f32 x0; - f32 y0; - f32 x1; - f32 y1; - }; + struct { Vec2 p0, p1; }; } alias UIHash = u64; @@ -351,8 +352,7 @@ alias UIPair = KVPair!(UIHash, UIItem*); struct UIKey { - u8[] text; - u8[] hash_text; + u8[] text, hash_text; u64 hash; } @@ -361,14 +361,7 @@ struct UIPanel u8[] id; Editor* ed; - UIPanel* parent; - UIPanel* next; - UIPanel* prev; - UIPanel* first; - UIPanel* last; - - UIPanel* list_next; - + UIPanel* parent, next, prev, first, last, list_next; Axis2D axis; f32 pct; Vec4 color; @@ -376,12 +369,8 @@ struct UIPanel Rect rect; Vec2 size; - i64 start_ln; - i64 end_ln; - i64 vis_lines; - i64 prev_start_ln; - f32 scroll_offset; - f32 scroll_target; + i64 start_ln, end_ln, vis_lines, prev_start_ln; + f32 scroll_offset, scroll_target; } struct TextPart @@ -391,28 +380,30 @@ struct TextPart u32 count; } -// Keep defaults, pop anything used once - enum bool StringType(T) = (is(T: string) || is(T: u8[]) || is(T: char[])); void InitUICtx(PlatformWindow* window) { - version(linux) + version(ENABLE_RENDERER) { - PlatformHandles handles = { - display: window.display, - window: window.window, - }; - } + version(linux) + { + PlatformHandles handles = { + display: window.display, + window: window.window, + }; + } - version(Windows) - { + version(Windows) + { + } } g_UI_NIL = cast(UIItem*)&g_ui_nil_item; g_UI_NIL_PANEL = cast(UIPanel*)&g_ui_nil_panel; + g_UI_NIL_INPUT = cast(UIInput*)&g_ui_nil_input; g_NIL_F32_STACK = cast(Stack!f32*)&g_nil_f32_stack; g_NIL_GRAD_COL_STACK = cast(Stack!Vec4[4]*)&g_nil_gradient_col_stack; g_NIL_VEC4_STACK = cast(Stack!Vec4*)&g_nil_vec4_stack; @@ -424,83 +415,79 @@ InitUICtx(PlatformWindow* window) UIBuffer[FRAME_OVERLAP] buffers; - FontFace font = OpenFont(cast(u8[])FONT_BYTES); - FontAtlasBuf atlas_buf = CreateAtlas(&arena, font, 16.0, 256); - - Vec4 b = Vec4(Vec3(0.0), 1.0); - UICtx ctx = { - rd: InitRenderer(handles, MB(16), MB(8)), items: CreateHashTable!(UIHash, UIItem*)(12), arena: arena, temp_arena: CreateArena(MB(1)), drag_item: g_UI_NIL, - atlas_buf: atlas_buf, - font: font, + font: OpenFont(cast(u8[])FONT_BYTES), font_data: cast(u8[])FONT_BYTES, text_size: 16.0, tab_width: 2, }; - for(u64 i = 0; i < FRAME_OVERLAP; i += 1) + ctx.atlas_buf = CreateAtlas(&arena, ctx.font, 16.0, 256); + + version(ENABLE_RENDERER) { - 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].vtx = ctx.buffers[i].m_vtx.data; - ctx.buffers[i].idx = ctx.buffers[i].m_idx.data; + ctx.rd = InitRenderer(handles, MB(16), MB(8)); + + 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].vtx = ctx.buffers[i].m_vtx.data; + ctx.buffers[i].idx = ctx.buffers[i].m_idx.data; + } + + DescLayoutBinding[2] layout_bindings = [ + { binding: 0, descriptorType: DT.Image, descriptorCount: 1, stageFlags: SS.All }, + { binding: 1, descriptorType: DT.Sampler, descriptorCount: 1, stageFlags: SS.All }, + ]; + + ctx.desc_set_layout = CreateDescSetLayout(&ctx.rd, layout_bindings); + ctx.desc_set = AllocDescSet(&ctx.rd, ctx.desc_set_layout); + ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, Mat4.sizeof); + + Attribute[14] attributes = [ + { binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 0}, + { binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 1}, + { binding: 0, location: 2, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 2}, + { binding: 0, location: 3, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 3}, + { binding: 0, location: 4, format: FMT.RG_F32, offset: Vertex.dst_start.offsetof }, + { binding: 0, location: 5, format: FMT.RG_F32, offset: Vertex.dst_end.offsetof }, + { binding: 0, location: 6, format: FMT.RG_F32, offset: Vertex.src_start.offsetof }, + { binding: 0, location: 7, format: FMT.RG_F32, offset: Vertex.src_end.offsetof }, + { binding: 0, location: 8, format: FMT.R_F32, offset: Vertex.border_thickness.offsetof }, + { binding: 0, location: 9, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof }, + { binding: 0, location: 10, format: FMT.R_F32, offset: Vertex.edge_softness.offsetof }, + { binding: 0, location: 11, format: FMT.R_F32, offset: Vertex.raised.offsetof }, + { binding: 0, location: 12, format: FMT.R_F32, offset: Vertex.z_index.offsetof }, + { binding: 0, location: 13, format: FMT.R_U32, offset: Vertex.texture.offsetof }, + ]; + + GfxPipelineInfo ui_info = { + vertex_shader: cast(u8[])VERTEX_BYTES, + frag_shader: cast(u8[])FRAGMENT_BYTES, + input_rate: IR.Instance, + input_rate_stride: Vertex.sizeof, + layout: ctx.pipeline_layout, + vertex_attributes: attributes, + src_color: BF.SrcAlpha, + dst_color: BF.OneMinusSrcAlpha, + color_op: BO.Add, + src_alpha: BF.One, + dst_alpha: BF.Zero, + alpha_op: BO.Add, + }; + + CreateGraphicsPipeline(&ctx.rd, &ctx.pipeline, &ui_info); + CreateImageView(&ctx.rd, &ctx.font_atlas, ctx.atlas_buf.atlas.width, ctx.atlas_buf.atlas.height, 4, ctx.atlas_buf.data, DT.Image, 0); + ctx.sampler = CreateSampler(&ctx.rd, MipmapMode.Nearest, 1); + Write(&ctx.rd, ctx.desc_set, [ctx.font_atlas, ctx.sampler]); + SetClearColors(&ctx.rd, [0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]); } - DescLayoutBinding[2] layout_bindings = [ - { binding: 0, descriptorType: DT.Image, descriptorCount: 1, stageFlags: SS.All }, - { binding: 1, descriptorType: DT.Sampler, descriptorCount: 1, stageFlags: SS.All }, - ]; - - ctx.desc_set_layout = CreateDescSetLayout(&ctx.rd, layout_bindings); - ctx.desc_set = AllocDescSet(&ctx.rd, ctx.desc_set_layout); - ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, PushConst.sizeof); - - Attribute[14] attributes = [ - { binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 0}, - { binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 1}, - { binding: 0, location: 2, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 2}, - { binding: 0, location: 3, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 3}, - { binding: 0, location: 4, format: FMT.RG_F32, offset: Vertex.dst_start.offsetof }, - { binding: 0, location: 5, format: FMT.RG_F32, offset: Vertex.dst_end.offsetof }, - { binding: 0, location: 6, format: FMT.RG_F32, offset: Vertex.src_start.offsetof }, - { binding: 0, location: 7, format: FMT.RG_F32, offset: Vertex.src_end.offsetof }, - { binding: 0, location: 8, format: FMT.R_F32, offset: Vertex.border_thickness.offsetof }, - { binding: 0, location: 9, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof }, - { binding: 0, location: 10, format: FMT.R_F32, offset: Vertex.edge_softness.offsetof }, - { binding: 0, location: 11, format: FMT.R_F32, offset: Vertex.raised.offsetof }, - { binding: 0, location: 12, format: FMT.R_F32, offset: Vertex.z_index.offsetof }, - { binding: 0, location: 13, format: FMT.R_U32, offset: Vertex.texture.offsetof }, - ]; - - GfxPipelineInfo ui_info = { - vertex_shader: cast(u8[])VERTEX_BYTES, - frag_shader: cast(u8[])FRAGMENT_BYTES, - input_rate: IR.Instance, - input_rate_stride: Vertex.sizeof, - layout: ctx.pipeline_layout, - vertex_attributes: attributes, - src_color: BF.SrcAlpha, - dst_color: BF.OneMinusSrcAlpha, - color_op: BO.Add, - src_alpha: BF.One, - dst_alpha: BF.Zero, - alpha_op: BO.Add, - }; - - CreateGraphicsPipeline(&ctx.rd, &ctx.pipeline, &ui_info); - - CreateImageView(&ctx.rd, &ctx.font_atlas, ctx.atlas_buf.atlas.width, ctx.atlas_buf.atlas.height, 4, ctx.atlas_buf.data, DT.Image, 0); - - ctx.sampler = CreateSampler(&ctx.rd, MipmapMode.Nearest, 1); - - Write(&ctx.rd, ctx.desc_set, [ctx.font_atlas, ctx.sampler]); - - SetClearColors(&ctx.rd, [0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]); - InitStacks(&ctx); g_ui_ctx = ctx; @@ -529,29 +516,123 @@ UIItem* MakeItem(T)(T k) if(is(T: UIKey) || StringType!T) { UICtx* ctx = GetCtx(); - UIItem* item = Get(k); Set(item, ctx); - if(!Nil(item.parent)) { DLLPush(item.parent, item, g_UI_NIL); } - AutoPopStacks(ctx); return item; } +UISignal +Signal(UIItem* item) +{ + UISignal signal; + UICtx* ctx = GetCtx(); + + i32 x = ctx.mouse_pos.x; + i32 y = ctx.mouse_pos.y; + + 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.dragged = 0; + + for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i); i = i.next) + { + bool taken; + + if(item.flags & UIF.Clickable && i.type == UIE.Click && InBounds(ctx.mouse_pos, &item.rect)) + { + 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; + ctx.drag_item = item; + taken = true; + } + + if(ctx.drag_item == item && i.type == UIE.Drag) + { + item.dragged += i.rel; + taken = true; + } + + if(taken) + { + DLLRemove(&ctx.events, i, g_UI_NIL_INPUT); + } + } + + return signal; +} + void -BeginUI(EditorCtx* edctx, Inputs* inputs) +PushUIEvent(UICtx* ctx, UIInput input) +{ + UIInput* ev = Alloc!(UIInput)(&ctx.temp_arena); + *ev = input; + + DLLPush(&ctx.events, ev, null); +} + +void +BeginUI(Inputs* inputs) { UICtx* ctx = GetCtx(); Arena* a = &ctx.temp_arena; Reset(a); + ctx.events.first = ctx.events.last = null; + + static bool dragging; + static bool mouse_down; + for(auto i = inputs.first; i; i = i.next) + { + switch(i.key) + { + case Input.LeftClick: + { + mouse_down = i.pressed; + if(!mouse_down && !dragging) + { + PushUIEvent(ctx, UIInput(UIE.Click)); + } + dragging = false; + } break; + case Input.MouseMotion: + { + ctx.mouse_pos = IVec2(i.x, i.y); + + if(!dragging && mouse_down) + { + PushUIEvent(ctx, UIInput(type: UIE.DragStart, pos: IVec2(i.x, i.y))); + } + + dragging = mouse_down; + + if(dragging) + { + PushUIEvent(ctx, UIInput(type: UIE.Drag, rel: IVec2(i.rel_x, i.rel_y))); + } + } break; + default: + { + PushUIEvent(ctx, UIInput(type: UIE.Press, key: i.key)); + } break; + } + } + // Ctx state ctx.f_idx = ctx.frame%FRAME_OVERLAP; ctx.inputs = inputs; @@ -559,26 +640,34 @@ BeginUI(EditorCtx* edctx, Inputs* inputs) ResetStacks(ctx); - // Rendering - BeginFrame(&ctx.rd); - BeginRendering(&ctx.rd); + version(ENABLE_RENDERER) + { + BeginFrame(&ctx.rd); + BeginRendering(&ctx.rd); + } Vec2 ext = GetExtent(&ctx.rd); if(ext != ctx.res) { ctx.res = ext; - Ortho(&ctx.pc.projection, 0.0, 0.0, ext.x, ext.y, -10.0, 10.0); + Ortho(&ctx.projection, 0.0, 0.0, ext.x, ext.y, -10.0, 10.0); } - PushConstants(&ctx.rd, ctx.pipeline, &ctx.pc); - - Bind(&ctx.rd, ctx.pipeline, ctx.desc_set); + version(ENABLE_RENDERER) + { + PushConstants(&ctx.rd, ctx.pipeline, &ctx.projection); + Bind(&ctx.rd, ctx.pipeline, ctx.desc_set); + } 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; - ctx.root = Root(ctx); + // 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!("parent")(ctx, ctx.root); } void @@ -592,32 +681,161 @@ EndUI() { if(item.size_info[axis].type == ST.Pixels) { - item.size.v[axis] = item.size_info[axis].value; + item.size.v[axis] = item.size_info[axis].value + item.padding.v[axis]; + } + } + + for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL)) + { + if(item.size_info[axis].type == ST.Percentage) + { + for(UIItem* p = item.parent; !Nil(p); p = p.parent) + { + if(p.size_info[axis].type == ST.Pixels) + { + item.size.v[axis] = floor(item.size_info[axis].value * item.parent.size[axis]); + break; + } + } + } + } + + for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, ctx.root, g_UI_NIL)) + { + if(item.size_info[axis].type == ST.ChildrenSum) + { + f32 size = 0.0; + for(UIItem* c = item.first; !Nil(c); c = c.next) + { + size += c.size.v[axis]; + } + } + } + + for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, ctx.root, g_UI_NIL)) + { + if(axis == item.layout_axis) + { + f32 children_size = 0.0; + for(UIItem* c = item.first; !Nil(c); c = c.next) + { + children_size += c.size.v[axis]; + } + + 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) + { + f32 reduced = c.size.v[axis] - c.size.v[axis]*c.size_info[axis].strictness; + + children_size -= reduced; + c.size.v[axis] -= reduced; + + child_count += 1; + } + + if(children_size > 0.0) + { + for(UIItem* c = item.last; !Nil(c); c = c.prev) + { + f32 reduced = Min(children_size, c.size[axis]); + children_size -= reduced; + c.size.v[axis] -= reduced; + + if(children_size < 0.0009) + { + break; + } + } + + assert(children_size < 0.0009); + } + } + } + else + { + for(UIItem* c = item.first; !Nil(c); c = c.next) + { + if(c.size.v[axis] > item.size.v[axis]) + { + c.size.v[axis] = item.size.v[axis]; + } + } + } + } + + { + f32 pos = 0.0; + for(UIItem* item = ctx.root; !Nil(item);) + { + f32 next_pos = 0.0; + f32 end_pos = pos + item.size.v[axis]; + + item.rect.p0.v[axis] = pos; + item.rect.p1.v[axis] = end_pos; + + assert(!isNaN(item.rect.p0.v[axis])); + assert(!isNaN(item.rect.p1.v[axis])); + + next_pos = item.parent.layout_axis == axis ? end_pos : pos; + + if(!Nil(item.first)) + { + item = item.first; + } + else if(!Nil(item.next)) + { + item = item.next; + pos = next_pos; + } + else for(UIItem* p = item.parent;; p = p.parent) + { + if(!Nil(p.next)) + { + item = p.next; + pos = item.parent.layout_axis == axis ? item.prev.rect.p1.v[axis] : item.prev.rect.p0.v[axis]; + break; + } + + if(Nil(p)) + { + item = g_UI_NIL; + break; + } + } } } } - with(ctx) + version(ENABLE_RENDERER) with(ctx) { BindBuffers(&rd, &buffers[f_idx].m_idx, &buffers[f_idx].m_vtx); DrawIndexed(&rd, 6, buffers[f_idx].count, 0); - } - FinishRendering(&ctx.rd); - SubmitAndPresent(&ctx.rd); + FinishRendering(&rd); + SubmitAndPresent(&rd); + } ctx.frame += 1; } -UIItem* -Root(UICtx* ctx) +u32[2] +GetExtent(Renderer* rd) { - UISize[2] sizes = [UISize(ST.Pixels, ctx.res.x), UISize(ST.Pixels, ctx.res.y)]; - Push!("size_info")(ctx, sizes); - return MakeItem("###root"); + version(ENABLE_RENDERER) + { + return RendererGetExtent(rd); + } + else + { + return [1280, 720]; + } } -template StackIDs(string stack) +template +StackIDs(string stack) { import std.string : replace; struct Identifiers { string stack, stack_top_node; } @@ -935,8 +1153,9 @@ GlyphWidth(Glyph* g) struct TextBuffer { - u8[] text; - TS[] style; + u8[] text; + TS[] style; + TextBuffer* next; } /* @@ -1094,7 +1313,7 @@ DrawPanel(UIPanel* panel, f32 lc_w, bool focus) { UICtx* ctx = GetCtx(); - Vec2 pos = panel.rect.vec0; + Vec2 pos = panel.rect.p0; Vec2 size = panel.size; DrawRect(pos, size, ctx.corner_radius_top.value, ctx.border_thickness_top.value, ctx.bg_col_top.value); @@ -1102,6 +1321,24 @@ DrawPanel(UIPanel* panel, f32 lc_w, bool focus) DrawBorder(pos, size, ctx.border_thickness_top.value, ctx.corner_radius_top.value, ctx.edge_softness_top.value, focus ? ctx.border_hl_col_top.value : ctx.border_col_top.value); } +static UISize[2] +MakeUISize(UISize x, UISize y) +{ + return [x, y]; +} + +static UISize[2] +MakeUISizeX(SizeType type, f32 value, f32 strictness = 1.0) +{ + return [UISize(type, value, strictness), UISize(ST.Percentage, 1.0)]; +} + +static UISize[2] +MakeUISizeY(SizeType type, f32 value, f32 strictness = 1.0) +{ + return [UISize(ST.Percentage, 1.0), UISize(type, value, strictness)]; +} + pragma(inline) Glyph* GetGlyph(u8 ch) { @@ -1187,25 +1424,25 @@ Nil(UIItem* item) } pragma(inline) bool -InBounds(InputEvent* ev, Vec2 p0, Vec2 p1) +InBounds(T)(T pos, Rect* rect) { - return ev.x >= p0.x && ev.x <= p1.x && ev.y >= p0.y && ev.y <= p1.y; + return pos.x >= rect.p0.x && pos.x <= rect.p1.x && pos.y >= rect.p0.y && pos.y <= rect.p1.y; } bool -Clicked(UIItem* item, Vec2 p0, Vec2 p1) +Clicked(UIItem* item, Rect* rect) { bool result; UICtx* ctx = GetCtx(); - for(auto n = ctx.inputs.list.first; !CheckNil(null, n) && Nil(ctx.drag_item); n = n.next) + for(auto n = ctx.inputs.first; !CheckNil(null, n) && Nil(ctx.drag_item); n = n.next) { - InputEvent* ev = &n.value; + InputEvent* ev = n; - if(ev.key == Input.LeftClick && ev.pressed && InBounds(ev, p0, p1)) + if(ev.key == Input.LeftClick && ev.pressed && InBounds(ev, rect)) { result = true; - DLLRemove(&ctx.inputs.list, n, null); + DLLRemove(ctx.inputs, n, null); break; } } @@ -1214,27 +1451,27 @@ Clicked(UIItem* item, Vec2 p0, Vec2 p1) } bool -Dragged(UIItem* item, Vec2 p0, Vec2 p1) +Dragged(UIItem* item, Rect* rect) { bool result; UICtx* ctx = GetCtx(); - item.dragged = 0.0; + item.dragged = 0; - if(Nil(ctx.drag_item) && Clicked(item, p0, p1)) + if(Nil(ctx.drag_item) && Clicked(item, rect)) { ctx.drag_item = item; } - for(auto n = ctx.inputs.list.first; !CheckNil(null, n) && ctx.drag_item == item; n = n.next) + for(auto n = ctx.inputs.first; !CheckNil(null, n) && ctx.drag_item == item; n = n.next) { - InputEvent* ev = &n.value; + InputEvent* ev = n; bool taken; if(ev.key == Input.MouseMotion) { - item.dragged.x += cast(f32)ev.rel_x; - item.dragged.y += cast(f32)ev.rel_y; + item.dragged.x += ev.rel_x; + item.dragged.y += ev.rel_y; result = true; taken = true; } @@ -1246,7 +1483,7 @@ Dragged(UIItem* item, Vec2 p0, Vec2 p1) if(taken) { - DLLRemove(&ctx.inputs.list, n, null); + DLLRemove(ctx.inputs, n, null); } } @@ -1270,17 +1507,47 @@ unittest } { - UICtx ctx; - InitStacks(&ctx); - ResetStacks(&ctx); + Inputs inputs; + InitUICtx(null); + BeginUI(&inputs); + + UICtx* ctx = GetCtx(); Vec4 w = Vec4(1.0); Vec4[4] col = w; - Push!("bg_col")(&ctx, col); + Push!("bg_col")(ctx, col); assert(ctx.bg_col.top.value == col); - Pop!("bg_col")(&ctx); + + EndUI(); + + Vec2 extent = GetExtent(null); + assert(extent == ctx.root.size); + + BeginUI(&inputs); assert(ctx.bg_col.top.value == BG_COL); + EndUI(); + + // Layouts + { + BeginUI(&inputs); + + Push!("size_info")(ctx, MakeUISizeX(ST.Percentage, 0.5)); + UIItem* root = ctx.root; + + UIItem* i0 = MakeItem("###i0"); + UIItem* i1 = MakeItem("###i1"); + + assert(i0.parent == root); + assert(i1.parent == root); + + EndUI(); + + Vec2 expected = Vec2(floor(root.size.x * 0.5), root.size.y); + + assert(i0.size == expected); + assert(i1.size == expected); + } } } diff --git a/src/editor/views.d b/src/editor/views.d index 4db7f1d..b4fc206 100644 --- a/src/editor/views.d +++ b/src/editor/views.d @@ -25,18 +25,20 @@ Panel(UIPanel* panel) parent.axis == A2D.Y ? 10 : 0 ); - Vec2 p0 = panel.rect.vec0+adj; - Vec2 p1 = panel.rect.vec1-adj; + Vec2 p0 = panel.rect.p0+adj; + Vec2 p1 = panel.rect.p1-adj; + Rect r = Rect(p0: p0, p1: p1); if(!Nil(prev)) with(panel) { - Vec2 d0 = rect.vec0-adj; + Vec2 d0 = rect.p0-adj; Vec2 d1 = Vec2( - pax == A2D.X ? rect.vec0.x+adj.x : rect.vec1.x, - pax == A2D.Y ? rect.vec0.y+adj.y : rect.vec1.y + pax == A2D.X ? rect.p0.x+adj.x : rect.p1.x, + pax == A2D.Y ? rect.p0.y+adj.y : rect.p1.y ); - if(Dragged(item, d0, d1) && item.dragged.v[pax] != 0.0) + Rect dr = Rect(p0: d0, p1: d1); + if(Dragged(item, &dr) && item.dragged.v[pax] != 0.0) { f32 mov_pct = Remap(item.dragged.v[pax], 0.0, panel.parent.size.v[pax], 0.0, 1.0); if(CheckPanelBounds(pct + mov_pct) && CheckPanelBounds(panel.prev.pct - mov_pct)) @@ -49,7 +51,7 @@ Panel(UIPanel* panel) if(panel.ed != null) { - if(Clicked(item, p0, p1)) + if(Clicked(item, &r)) { SetFocusedPanel(panel); } @@ -76,8 +78,8 @@ Panel(UIPanel* panel) f32 y_rem = fmod(panel.scroll_offset, TEXT_SIZE); - f32 x = panel.rect.x0; - f32 y = panel.rect.y0 + TEXT_SIZE - fmod(panel.scroll_offset, TEXT_SIZE); + f32 x = panel.rect.p0.x; + f32 y = panel.rect.p1.y + TEXT_SIZE - fmod(panel.scroll_offset, TEXT_SIZE); u64 i = panel.start_ln; for(auto buf = GetLine(&ed.buf, i); !CheckNil(g_NIL_LINE_BUF, buf) && i < panel.end_ln; i += 1, buf = GetLine(&ed.buf, i)) @@ -97,7 +99,7 @@ Panel(UIPanel* panel) } else for(auto n = parts; n != null; n = n.next) { - auto l = &n.value; + auto l = n; if(pos.y == i) { @@ -202,14 +204,14 @@ Nil(UIPanel* panel) return panel == null || panel == g_UI_NIL_PANEL; } -Node!(TextBuffer)* +TextBuffer* MakeMultiline(u8[] text, f32 width, TS[] style = []) { f32 scaled_width = width * (g_ui_ctx.atlas_buf.atlas.size/g_ui_ctx.text_size); f32 text_width = CalcTextWidth(text); u64 line_count = cast(u64)(ceil(text_width/scaled_width)); - Node!(TextBuffer)* node = null; + TextBuffer* node = null; if(line_count > 0) { f32 w = 0.0; @@ -232,15 +234,15 @@ MakeMultiline(u8[] text, f32 width, TS[] style = []) stl = ScratchAlloc!(TS)(style, start, len); } - Node!(TextBuffer)* n = node; + TextBuffer* n = node; for(;;) { if(node == null) { - node = ScratchAlloc!(Node!(TextBuffer))(); + node = ScratchAlloc!(TextBuffer)(); - node.value.text = str; - node.value.style = stl; + node.text = str; + node.style = stl; node.next = null; break; @@ -248,10 +250,10 @@ MakeMultiline(u8[] text, f32 width, TS[] style = []) if(n.next == null) { - n.next = ScratchAlloc!(Node!(TextBuffer))(); + n.next = ScratchAlloc!(TextBuffer)(); - n.next.value.text = str; - n.next.value.style = stl; + n.next.text = str; + n.next.style = stl; n.next.next = null; break;