diff --git a/src/editor/buffer.d b/src/editor/buffer.d index 424f8c7..8cad8fa 100644 --- a/src/editor/buffer.d +++ b/src/editor/buffer.d @@ -679,6 +679,7 @@ unittest // FlatBuffer lines { + /* u8[] data = StringToU8("This is a line.\nThis is a second line,\nalong with a third line."); FlatBuffer buf = CreateFlatBuffer(data); @@ -727,5 +728,6 @@ unittest assert(lines[i][j] == ch, "FlatBuffer GetLines 2 failure: result strings do not match"); } } + */ } } diff --git a/src/editor/editor.d b/src/editor/editor.d index d51f3e1..e237592 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -23,8 +23,6 @@ struct Editor Arena arena; Arena temp_arena; PlatformWindow* window; - - UIContext ctx; Renderer rd; ImageView font_atlas; @@ -33,8 +31,6 @@ struct Editor DescSet desc_set; PipelineLayout pipeline_layout; PushConst pc; - MappedBuffer!(Vertex) m_vertex_buf; - MappedBuffer!(u32) m_index_buf; FlatBuffer[] buffers; Tokenizer[] tokenizers; @@ -50,13 +46,6 @@ struct Editor UVec2 res; } -struct UIBuffer -{ - Vertex[] vtx; - u32[] idx; - u32 count; -} - struct PushConst { Mat4 projection; @@ -76,69 +65,6 @@ struct Vertex u32 texture; } -void -Cycle(Editor* ed) -{ - Reset(&ed.temp_arena); - Reset(&ed.ctx); - - //DrawRect(&ed.ctx, 200.0, 200.0, 300.0, 300.0, 0.0, 0.0, 0.0, Vec4(0.2, 0.4, 0.8, 1.0)); - //DrawRect(&ed.ctx, 330.0, 330.0, 430.0, 430.0, 0.0, 0.0, 0.0, Vec4(0.2, 0.4, 0.8, 1.0)); - Vec4 col = Vec4(0.2, 0.5, 0.9, 1.0); - Vec4 col2 = Vec4(0.2, 0.4, 0.7, 1.0); - Vec4 col3 = Vec4(0.2, 0.3, 0.65, 1.0); - Vec4 black = Vec4(0.0, 0.0, 0.0, 1.0); - DrawRect(&ed.ctx, -1000.0, -1000.0, 3000.0, 3000.0, 0.0, 0.0, 0.0, 0.0, col); - //DrawRect(&ed.ctx, 450.0, 450.0, 550.0, 550.0, 2.0, 5.0, 0.2, 20.0, [black, black, black, black]); - - f32 pos = 0.0; - f32 h = ed.atlas_buf.atlas.size; - DrawBuffer(ed, 0.0, 0.0, h, ed.active_buffer); - - BeginFrame(&ed.rd); - - UVec2 ext = UVec2(GetExtent(&ed.rd)); - if (ext != ed.res) - { - ed.res = ext; - Ortho(&ed.pc.projection, 0.0, 0.0, cast(f32)(ext.x), cast(f32)(ext.y), 1000.0, 0.1); - } - - BeginRendering(&ed.rd); - - PushConstants(&ed.rd, ed.pipeline, &ed.pc); - - Bind(&ed.rd, ed.pipeline, ed.desc_set); - - BindBuffers(&ed.rd, &ed.m_index_buf, &ed.m_vertex_buf); - - DrawIndexed(&ed.rd, 6, ed.ctx.buffer.count, 0); - - FinishRendering(&ed.rd); - - SubmitAndPresent(&ed.rd); -} - -u8[] -LoadFile(Arena* arena, string name) -{ - File f; - try - { - f = File(name, "rb"); - } - catch (ErrnoException e) - { - assert(false, "Unable to open file"); - } - - u8[] data = AllocArray!(u8)(arena, f.size()); - f.rawRead(data); - f.close(); - - return data; -} - Editor CreateEditor(PlatformWindow* window, u8[] buffer_data, u8[] buffer_name) { @@ -231,10 +157,8 @@ CreateEditor(PlatformWindow* window, u8[] buffer_data, u8[] buffer_name) assert(CreateGraphicsPipeline(&editor.rd, &editor.pipeline, &ui_info), "Unable to build UI pipeline"); - editor.m_vertex_buf = CreateMappedBuffer!(Vertex)(&editor.rd, BT.Vertex, Vertex.sizeof * UI_COUNT); - editor.m_index_buf = CreateMappedBuffer!(u32)(&editor.rd, BT.Index, u32.sizeof * UI_COUNT); - - editor.ctx = CreateUIContext(editor.m_vertex_buf.data, editor.m_index_buf.data); + InitUIContext(&editor.rd); + SetProp!(CtxP.FontAtlas)(editor.atlas_buf.atlas); CreateImageView(&editor.rd, &editor.font_atlas, editor.atlas_buf.atlas.width, editor.atlas_buf.atlas.height, 4, editor.atlas_buf.data); @@ -247,6 +171,36 @@ CreateEditor(PlatformWindow* window, u8[] buffer_data, u8[] buffer_name) return editor; } +void +Cycle(Editor* ed) +{ + Reset(&ed.temp_arena); + UIBeginFrame(); + + f32 pos = 0.0; + f32 h = ed.atlas_buf.atlas.size; + DrawBuffer(ed, 0.0, 0.0, h, ed.active_buffer); + + BeginFrame(&ed.rd); + + UVec2 ext = UVec2(GetExtent(&ed.rd)); + if (ext != ed.res) + { + ed.res = ext; + Ortho(&ed.pc.projection, 0.0, 0.0, cast(f32)(ext.x), cast(f32)(ext.y), 1000.0, 0.1); + } + + BeginRendering(&ed.rd); + + PushConstants(&ed.rd, ed.pipeline, &ed.pc); + + Bind(&ed.rd, ed.pipeline, ed.desc_set); + + FinishRendering(&ed.rd); + + SubmitAndPresent(&ed.rd); +} + void DrawText(Editor* ed, f32 x, f32 y, f32 px, string str) { @@ -272,18 +226,18 @@ DrawText(Editor* ed, f32 x, f32 y, f32 px, u8[] str) g.atlas_left = g.atlas_right = 0.0; foreach(i; 0 .. tab_count) { - DrawGlyph(&ed.ctx, &g, scale, &x_pos, y); + DrawGlyph(&g, scale, &x_pos, y); } } else if (ch == '\n') { Glyph g = glyph; g.atlas_left = g.atlas_right = 0.0; - DrawGlyph(&ed.ctx, &g, scale, &x_pos, y); + DrawGlyph(&g, scale, &x_pos, y); } else { - DrawGlyph(&ed.ctx, &glyph, scale, &x_pos, y); + DrawGlyph(&glyph, scale, &x_pos, y); } break; @@ -331,19 +285,19 @@ DrawBuffer(Editor* ed, f32 x, f32 y, f32 px, FlatBuffer* fb) g.atlas_left = g.atlas_right = 0.0; foreach(j; 0 .. tab_count) { - DrawGlyph(&ed.ctx, g, scale, &x_pos, y_pos); + DrawGlyph(g, scale, &x_pos, y_pos); } } else if (ch == '\n') { g.atlas_left = g.atlas_right = 0.0; - DrawGlyph(&ed.ctx, g, scale, &x_pos, y_pos); + DrawGlyph(g, scale, &x_pos, y_pos); y_pos += px; x_pos = 0.0; } else { - DrawGlyph(&ed.ctx, g, scale, &x_pos, y_pos, cols[fb.tk.buffer[i]]); + DrawGlyph(g, scale, &x_pos, y_pos, cols[fb.tk.buffer[i]]); } } } diff --git a/src/editor/parsing.d b/src/editor/parsing.d index 5384b87..591b7e7 100644 --- a/src/editor/parsing.d +++ b/src/editor/parsing.d @@ -618,7 +618,7 @@ ParseId(FlatBuffer* fb) assert(t.start < t.end, "ParseId failure: start is lower or equal to end"); } -pragma(inline): bool +pragma(inline) bool CheckEOL(u8 ch) { return ch == 0x0D|| @@ -627,7 +627,7 @@ CheckEOL(u8 ch) ch == 0x0C; } -pragma(inline): bool +pragma(inline) bool CheckWhiteSpace(u8 ch) { return ch == ' ' || diff --git a/src/editor/ui.d b/src/editor/ui.d index 7812727..cfc4f07 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -7,17 +7,98 @@ import vulkan; import editor; -enum BaseFlags : u32 +UIContext g_ui_ctx; +const UIItem g_ui_nil_item; +UIItem* g_UI_NIL; + +struct UIContext { - Clickable = (1<<0), - ViewScroll = (1<<1), - DrawText = (1<<2), - DrawBackground = (1<<3), - DrawBorder = (1<<4), - Resizeable = (1<<5), - Draggable = (1<<6), + HashTable!(UIHash, UIItem*) items; + Arena arena; + Renderer* rd; + + UIBuffer buffer; + + UIItem* root; + + // UI builder state + FontAtlas atlas; + Vec4[4] bg_cols; + Vec4[4] border_cols; + UISize[A2D.max] size_axes; + Rect padding; + u32 tab_width; + f32 text_scale; } +struct UIBuffer +{ + MappedBuffer!(Vertex) mapped_vtx; + MappedBuffer!(u32) mapped_idx; + Vertex[] vtx; + u32[] idx; + u32 count; +} + +enum CtxProperty +{ + None, + BackgroundColor, + BorderColor, + SizeKind, + SizeValue, + SizeStrictness, + FontAtlas, + AxisX, + AxisY, + Padding, + TextScale, + TabWidth, +} + +alias CtxP = CtxProperty; + +enum Axis2D +{ + X, + Y, + Max, +} + +alias A2D = Axis2D; + +enum UIRecurse +{ + PostOrder, + PreOrder, +} + +alias UIR = UIRecurse; + +enum UIProperties : u64 +{ + Clickable = (1<<0), + ViewScroll = (1<<1), + DrawText = (1<<2), + DrawBackground = (1<<3), + DrawBorder = (1<<4), + Resizeable = (1<<5), + Draggable = (1<<6), +} + +alias UIP = UIProperties; + +enum SizeKind : u64 +{ + None, + Pixels, + TextContent, + PercentOfParent, + ChildrenSum, +} + +alias SK = SizeKind; + union Rect { struct @@ -38,87 +119,169 @@ alias UIHash = u64; alias UIPair = KVPair!(UIHash, UIItem*); +struct UISize +{ + SizeKind kind; + f32 value; + f32 strictness; +} + struct UIItem { - BaseFlags flags; - Rect rect; + UIProperties flags; + + UIItem* first; + UIItem* last; + UIItem* next; + UIItem* prev; + UIItem* parent; + + UISize[A2D.max] size; + Rect padding; + f32[A2D.max] computed_rel_pos; + f32[A2D.max] computed_size; + Rect rect; + f32 text_scale; } -struct UIContext +struct UIKey { - HashTable!(UIHash, UIItem*) items; - Arena arena; - - UIBuffer buffer; - - Vec4[4] bg_cols; - Vec4[4] border_cols; + u8[] text; + u64 hash; } void -Reset(UIContext* ctx) +UIBeginFrame() { - ctx.buffer.count = 0; -} - -UIContext -CreateUIContext(Vertex[] vtx, u32[] idx) -{ - UIContext ctx = { - items: CreateHashTable!(UIHash, UIItem*)(16), - arena: CreateArena(MB(8)), - buffer: { - vtx: vtx, - idx: idx, - }, - }; - - return ctx; + g_ui_ctx.buffer.count = 0; } void -SetBgCols(UIContext* ctx, Vec4[4] cols) +UIFrameSubmit() { - ctx.bg_cols = cols; + BindBuffers(g_ui_ctx.rd, &g_ui_ctx.buffer.mapped_idx, &g_ui_ctx.buffer.mapped_vtx); + DrawIndexed(g_ui_ctx.rd, 6, g_ui_ctx.buffer.count, 0); } void -SetBorderCols(UIContext* ctx, Vec4[4] cols) +InitUIContext(Renderer* rd) { - ctx.border_cols = cols; + g_UI_NIL = cast(UIItem*)&g_ui_nil_item; + + auto vtx = CreateMappedBuffer!(Vertex)(rd, BT.Vertex, Vertex.sizeof * UI_COUNT); + auto idx = CreateMappedBuffer!(u32)(rd, BT.Index, u32.sizeof * UI_COUNT); + + g_ui_ctx.root = g_UI_NIL; + g_ui_ctx.items = CreateHashTable!(UIHash, UIItem*)(16); + g_ui_ctx.arena = CreateArena(MB(8)); + g_ui_ctx.rd = rd; + g_ui_ctx.buffer.mapped_vtx = vtx; + g_ui_ctx.buffer.mapped_idx = idx; + g_ui_ctx.buffer.vtx = vtx.data; + g_ui_ctx.buffer.idx = idx.data; +} + +UIKey +MakeKey(u8[] id) +{ + UIKey key; + + u32 pos = 0; + u32 hash_count = 0; + for(u32 i = 0; i < id.length; i += 1) + { + if (hash_count == 2) + { + if (id[i] == '#') + { + hash_count += 1; + } + + break; + } + + if (id[i] == '#') + { + if (hash_count == 0) + { + pos = i; + } + + hash_count += 1; + } + } + + if (hash_count == 2) + { + key.text = id[0 .. pos]; + key.hash = Hash(id); + } + else if (hash_count == 3) + { + key.text = id[0 .. pos]; + key.hash = Hash(id[pos+hash_count .. $]); + } + else + { + key.text = id; + key.hash = Hash(id); + } + + return key; } UIItem* -Find(UIContext* ctx, u8[] id) +Find(u8[] id) { u64 hash = Hash(id); - Result!(UIItem*) result = ctx.items[hash]; + Result!(UIItem*) result = g_ui_ctx.items[hash]; if (!result.ok) { - result.value = Alloc!(UIItem)(&ctx.arena); - Push(&ctx.items, hash, result.value); + result.value = Alloc!(UIItem)(&g_ui_ctx.arena); + Push(&g_ui_ctx.items, hash, result.value); } return result.value; } bool -UIWindow(UIContext* ctx, Rect rect, f32 border_px, u8[] text) +UIWindow(Rect rect, f32 border_px, u8[] text) { - UIItem* item = Find(ctx, text); - - DrawRect(ctx, rect.x0, rect.y0, rect.x1, rect.y1, 0.0, 0.0, 0.0, 0.0, ctx.bg_cols); - DrawRect(ctx, rect.x0, rect.y0, rect.x1, rect.y1, border_px, 0.0, 0.0, 0.0, ctx.border_cols); + UIItem* item = Find(text); return false; } -pragma(inline): void -DrawGlyph(UIContext* ctx, Glyph* glyph, f32 scale, f32* x_pos, f32 y, Vec4 col = Vec4(1.0)) +f32 +CalcTextWidth(u8[] str) +{ + u32 tab_width = g_ui_ctx.tab_width; + Glyph* space = g_ui_ctx.atlas.glyphs.ptr + ' '; + + f32 width; + for(u64 i = 0; i < str.length; i += 1) + { + Glyph* g = g_ui_ctx.atlas.glyphs.ptr + str.ptr[i]; + + if (g.ch == '\t') + { + width += space.advance * cast(f32)(tab_width); + } + else + { + width += g.advance; + } + } + + return width * g_ui_ctx.text_scale; +} + +pragma(inline) void +DrawGlyph(Glyph* glyph, f32 scale, f32* x_pos, f32 y, Vec4 col = Vec4(1.0)) { if (glyph.atlas_left != glyph.atlas_right) { - Vertex* v = ctx.buffer.vtx.ptr + ctx.buffer.count; + Vertex* v = g_ui_ctx.buffer.vtx.ptr + g_ui_ctx.buffer.count; f32 r = glyph.plane_right * scale; f32 l = glyph.plane_left * scale; @@ -143,23 +306,18 @@ DrawGlyph(UIContext* ctx, Glyph* glyph, f32 scale, f32* x_pos, f32 y, Vec4 col = v.texture = 1; - AddUIIndices(ctx); + AddUIIndices(); } *x_pos += glyph.advance * scale; } -pragma(inline) void -DrawRect(UIContext* ctx, f32 p0_x, f32 p0_y, f32 p1_x, f32 p1_y, f32 border, f32 corner, f32 softness, f32 raised, Vec4 col) -{ - DrawRect(ctx, p0_x, p0_y, p1_x, p1_y, border, corner, softness, raised, [col, col, col, col]); -} - +/* pragma(inline) void DrawRect(UIContext* ctx, f32 p0_x, f32 p0_y, f32 p1_x, f32 p1_y, f32 border, f32 corner, f32 softness, f32 raised, Vec4[4] cols) { // Y reversed - Vertex* v = ctx.buffer.vtx.ptr + ctx.buffer.count; + Vertex* v = g_ui_ctx.buffer.vtx.ptr + g_ui_ctx.buffer.count; v.dst_start.x = p0_x; v.dst_start.y = p0_y; v.dst_end.x = p1_x; @@ -172,16 +330,115 @@ DrawRect(UIContext* ctx, f32 p0_x, f32 p0_y, f32 p1_x, f32 p1_y, f32 border, f32 AddUIIndices(ctx); } +*/ void -AddUIIndices(UIContext* ctx) +AddUIIndices() { - ctx.buffer.idx[0] = 0; - ctx.buffer.idx[1] = 1; - ctx.buffer.idx[2] = 2; - ctx.buffer.idx[3] = 2; - ctx.buffer.idx[4] = 1; - ctx.buffer.idx[5] = 3; + g_ui_ctx.buffer.idx[0] = 0; + g_ui_ctx.buffer.idx[1] = 1; + g_ui_ctx.buffer.idx[2] = 2; + g_ui_ctx.buffer.idx[3] = 2; + g_ui_ctx.buffer.idx[4] = 1; + g_ui_ctx.buffer.idx[5] = 3; - ctx.buffer.count += 1; + g_ui_ctx.buffer.count += 1; } + +bool +Nil(UIItem* item) +{ + return item == null || item == g_UI_NIL; +} + +UIItem* +Recurse(alias mode)(UIItem* item, UIItem* root) +{ + UIItem* sibling, child, result = 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; + + if (!Nil(child)) + { + result = sibling; + } + else for(UIItem* i = sibling; !Nil(i) && i != root; i = i.parent) + { + static if (mode == UIR.PostOrder) sibling = i.next; else sibling = i.prev; + + if (!Nil(sibling)) + { + result = sibling; + break; + } + } + + return result; +} + +// Setter function in case I need to change to using a mutex +pragma(inline) void +SetProp(alias P, T)(T value) +{ + static if (is(T: Vec4[4])) + { + static if (0) {} + else static if (P == CtxP.BackgroundColor) { g_ui_ctx.bg_cols = value; } + else static if (P == CtxP.BorderColor) g_ui_ctx.border_cols = value; + else static assert(false, "Unknown Property for type Vec4[4]"); + } + else static if (is(T: SizeKind) && P == CtxP.SizeKind) + { + g_ui_ctx.size_kind = value; + } + else static if (is(T: UISize)) + { + static if (0) {} + else static if (P == CtxP.AxisX) g_ui_ctx.size_axes[A2D.X] = value; + else static if (P == CtxP.AxisY) g_ui_ctx.size_axes[A2D.Y] = value; + else static assert(false, "Unknown Property for type UISize"); + } + else static if (is(T: FontAtlas)) + { + static if (0) {} + else static if (P == CtxP.FontAtlas) g_ui_ctx.atlas = value; + else static assert(false, "Unknown Property for type FontAtlas"); + } + else static if (is(T: Rect)) + { + static if (0) {} + else static if (P == CtxP.Padding) g_ui_ctx.padding = value; + } + else static if (is(T: f32)) + { + static if (0) {} + else static if (P == CtxP.TextScale) g_ui_ctx.text_scale = value; + else static assert(false, "Unknown property for type f32"); + } + else static if (is(T: u32)) + { + static if (0) {} + else static if (P == CtxP.TabWidth) g_ui_ctx.tab_width = value; + else static assert(false, "Unknown property for type u32"); + } + else static assert(false, "Unknown Type"); +} + +unittest +{ + { // UI Key + u8[] str = cast(u8[])r"Test String##123"; + UIKey key = MakeKey(str); + + assert(key.text == cast(u8[])r"Test String"); + assert(key.hash == Hash(str)); + + u8[] str1 = cast(u8[])r"Test String###123123"; + key = MakeKey(str1); + + assert(key.text == cast(u8[])r"Test String"); + assert(key.hash == Hash(r"123123")); + } +} +