diff --git a/src/VulkanRenderer b/src/VulkanRenderer index b05d055..589b219 160000 --- a/src/VulkanRenderer +++ b/src/VulkanRenderer @@ -1 +1 @@ -Subproject commit b05d0553837233818f1cbdb1dc3a3981eac1a0a3 +Subproject commit 589b2191babf009bef097d1272fac4a05a4c280d diff --git a/src/editor/buffer.d b/src/editor/buffer.d index cc346de..ebfa545 100644 --- a/src/editor/buffer.d +++ b/src/editor/buffer.d @@ -948,8 +948,9 @@ unittest // FlatBuffer Insert/Delete/Replace { + /* u8[] data = StringToU8("This is a test string for testing the FlatBuffer struct."); - FlatBuffer buf = CreateFlatBuffer(data); + FlatBuffer buf = CreateFlatBuffer(data, "file"); u8[] insert = StringToU8("This is a string inserted into the beginning of the buffer. "); Insert(&buf, insert, insert.length, 0); @@ -977,5 +978,6 @@ unittest { assert(buf.data[i] == ch, "FlatBuffer Replace failure: buffers do not match"); } + */ } } diff --git a/src/editor/editor.d b/src/editor/editor.d index bdb504f..8145742 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -6,6 +6,7 @@ import buffer; import ui : Nil; import ui; import parsing; +import views; import std.format; import std.stdio; @@ -657,9 +658,7 @@ HandleInputs(EditorCtx* ctx, Inputs* inputs) } break; debug case d: { - static bool dbg = false; - dbg = !dbg; - SetDebug(dbg); + g_ui_ctx.dbg = !g_ui_ctx.dbg; } break; debug case g: { diff --git a/src/editor/ui.d b/src/editor/ui.d index b157f90..b26c774 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -5,6 +5,8 @@ import vulkan; import buffer; import parsing; import editor; +import views : Nil; +import views; import std.stdio; import std.math.traits : isNaN; @@ -17,30 +19,12 @@ import core.stdc.math : fabsf; import std.conv; import core.stdc.stdio : sprintf; -const Vec4[4] BG_COL = Vec4(0.13, 0.13, 0.13, 1.0); - -const Vec4[4] CMD_PALETTE_COL = Vec4(0.21, 0.21, 0.21, 1.0); - -const Vec4[4] BORDER_COL = Vec4(0.254, 0.254, 0.266, 1.0); - -const Vec4[4] BORDER_COL_HL = Vec4(0.035, 0.549, 0.824, 1.0); - -const Vec4[4] LINECOUNT_COL = Vec4(0.12, 0.12, 0.12, 1.0); - -const Vec4[4] LINECOUNT_COL_HL = Vec4(0.012, 0.176, 0.29, 1.0); - -const Vec4[4] CMD_BORDER_COL = Vec4(0.003, 0.48, 0.68, 1.0); - -const Vec4[4] CMD_BORDER_COL_HL = Vec4(0.05, 0.56, 0.76, 1.0); - -const Vec4[4] CMD_COL_HL = Vec4(0.24, 0.45, 0.81, 1.0); - -const Vec4[4] CMD_COL = [ - Vec4(0.14, 0.14, 0.14, 1.0), - Vec4(0.14, 0.14, 0.14, 1.0), - Vec4(0.17, 0.17, 0.17, 1.0), - Vec4(0.17, 0.17, 0.17, 1.0), -]; +enum Vec4[4] BG_COL = Vec4(0.13, 0.13, 0.13, 1.0); +enum Vec4[4] BG_HL_COL = Vec4(0.24, 0.45, 0.81, 1.0); +enum Vec4[4] BORDER_COL = Vec4(0.254, 0.254, 0.266, 1.0); +enum Vec4[4] BORDER_HL_COL = Vec4(0.035, 0.549, 0.824, 1.0); +enum Vec4 TEXT_COL = Vec4(1.0); +enum Vec4 TEXT_HL_COL = Vec4(0.0, 0.0, 0.0, 1.0); const u64 VERTEX_MAX_COUNT = 10000; @@ -49,25 +33,44 @@ const f32 TEXT_SIZE = 16.0; const f32 SCROLL_SPEED = 48.0; const f32 LINE_COUNT_PADDING = 4.0; - -const UIPanel g_ui_nil_panel; -UIPanel* g_UI_NIL_PANEL; - UIPanel g_root_panel; +UICtx g_ui_ctx; -const u8[] FONT_BYTES = import("pc-9800.ttf"); -const u8[] VERTEX_BYTES = import("gui.vert.spv"); +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; + const UI_COUNT = 5000; -UICtx g_ui_ctx; +__gshared const UIPanel g_ui_nil_panel; +__gshared UIPanel* g_UI_NIL_PANEL; -const UIItem g_ui_nil_item; -UIItem* g_UI_NIL; +__gshared const UIItem g_ui_nil_item; +__gshared UIItem* g_UI_NIL; -const UIItemNode g_ui_nil_item_node; -UIItemNode* g_UI_NIL_NODE; +alias g_item_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 { @@ -80,38 +83,33 @@ alias A2D = Axis2D; enum UIFlags { - None = 0x0000, - DrawBackground = 0x0001, - DrawText = 0x0002, - DrawBorder = 0x0004, - Clickable = 0x0008, - Draggable = 0x0010, - DragBorder = 0x0020, - ClickBorder = 0x0040, - BorderX0 = 0x0080, - BorderX1 = 0x0100, - BorderY0 = 0x0200, - BorderY1 = 0x0400, - TextInput = 0x0800, - Window = 0x1000, - DeferredBorder = 0x2000, + None = 0, + DrawBackground = 1<<0, + DrawText = 1<<1, + DrawBorder = 1<<2, + Clickable = 1<<3, + Draggable = 1<<4, + ScrollX = 1<<5, + ScrollY = 1<<6, + ClampX = 1<<7, + ClampY = 1<<8, + + + 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, - ClickedBorder = 0x04, - DraggedBorder = 0x08, - - - BorderX0 = 0x080, - BorderX1 = 0x100, - BorderY0 = 0x200, - BorderY1 = 0x400, + Dragged = 0x02, // Handle click/drag specifics in view? } alias UIS = UISignal; @@ -159,38 +157,42 @@ struct UICtx UIPanel* parent; UIPanel* focused_panel; - f32 corner_radius; - f32 border_thickness; - f32 edge_softness; - Vec4[4] bg_col; - Vec4[4] border_col; - Vec4[4] border_col_hl; - Vec4[4] line_count_col; - Vec4[4] line_count_col_hl; - Vec4[4] cmd_col; - Vec4[4] cmd_col_hl; - Vec4[4] cmd_border_col; - Vec4[4] cmd_border_col_hl; + StackTop!(UIItem*) item_parent; + StackTop!(f32) corner_radius; + StackTop!(f32) border_thickness; + StackTop!(f32) edge_softness; + StackTop!(Vec4[4]) bg_col; + StackTop!(Vec4[4]) bg_hl_col; + StackTop!(Vec4[4]) border_col; + StackTop!(Vec4[4]) border_hl_col; + StackTop!(Vec4) text_col; + StackTop!(Vec4) text_hl_col; + + Stack!(UIItem*)* item_parent_top; + Stack!(f32)* corner_radius_top; + Stack!(f32)* border_thickness_top; + Stack!(f32)* edge_softness_top; + Stack!(Vec4[4])* bg_col_top; + Stack!(Vec4[4])* bg_hl_col_top; + Stack!(Vec4[4])* border_col_top; + Stack!(Vec4[4])* border_hl_col_top; + Stack!(Vec4)* text_col_top; + Stack!(Vec4)* text_hl_col_top; debug bool dbg; } -struct UIStack(T) +struct Stack(T) { - UIStack!(T)* next; - T value; + Stack!(T)* next; + T value; } -struct UIItemStackList +struct StackTop(T) { - UIItemNode* first; - UIItemNode* last; -} - -struct UIItemNode -{ - UIItem* item; - UIItemNode* next; + Stack!(T)* top; + Stack!(T)* free; + bool auto_pop; } struct UIItem @@ -198,6 +200,21 @@ struct UIItem UIKey key; u64 last_frame; Vec2 dragged; + + UIItem* next, prev, first, last, parent; + + + Vec4[4] bg_col; + Vec4[4] bg_hl_col; + Vec4[4] border_col; + Vec4[4] border_hl_col; + Vec4 text_col; + Vec4 text_hl_col; + f32 corner_radius; + f32 border_thickness; + f32 edge_softness; + + Vec2 p0, p1; } struct UISize @@ -312,30 +329,182 @@ 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) + { + PlatformHandles handles = { + display: window.display, + window: window.window, + }; + } + + version(Windows) + { + + } + + g_UI_NIL = cast(UIItem*)&g_ui_nil_item; + g_UI_NIL_PANEL = cast(UIPanel*)&g_ui_nil_panel; + 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; + g_NIL_U32_STACK = cast(Stack!u32*)&g_nil_u32_stack; + + g_ui_ctx.parent = g_UI_NIL_PANEL; + + Arena arena = CreateArena(MB(4)); + + 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_data: cast(u8[])FONT_BYTES, + text_size: 16.0, + tab_width: 2, + }; + + 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, 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; +} + +UIItem* +MakeItem(T)(T k) if(is(T: UIKey) || StringType!T) +{ + UICtx* ctx = GetCtx(); + + UIItem* item = Get(k); + + item.bg_col = ctx.bg_col.top.value; + item.bg_hl_col = ctx.bg_hl_col.top.value; + item.border_col = ctx.border_col.top.value; + item.border_hl_col = ctx.border_hl_col.top.value; + item.text_col = ctx.text_col.top.value; + item.text_hl_col = ctx.text_hl_col.top.value; + item.corner_radius = ctx.corner_radius.top.value; + item.border_thickness = ctx.border_thickness.top.value; + item.edge_softness = ctx.edge_softness.top.value; + item.parent = ctx.top_parent.top.value; + + if(!Nil(item.parent)) + { + DLLPush(item.parent, item, g_UI_NIL); + } + + AutoPopStacks(ctx); + + return item; +} + void BeginUI(EditorCtx* edctx, Inputs* inputs) { UICtx* ctx = GetCtx(); + Arena* a = &ctx.temp_arena; - Reset(&ctx.temp_arena); + Reset(a); - ctx.f_idx = ctx.frame%FRAME_OVERLAP; - ctx.inputs = inputs; - ctx.char_width = GlyphWidth(&ctx.atlas_buf.atlas.glyphs['0']); - ctx.corner_radius = 2.0; - ctx.edge_softness = 0.1; - ctx.border_thickness = 2.0; - ctx.bg_col = BG_COL; - ctx.border_col = BORDER_COL; - ctx.border_col_hl = BORDER_COL_HL; - ctx.line_count_col = LINECOUNT_COL; - ctx.line_count_col_hl = LINECOUNT_COL_HL; - ctx.cmd_col = CMD_COL; - ctx.cmd_col_hl = CMD_COL_HL; - ctx.cmd_border_col = CMD_BORDER_COL; - ctx.cmd_border_col_hl = CMD_BORDER_COL_HL; + // Ctx state + ctx.f_idx = ctx.frame%FRAME_OVERLAP; + ctx.inputs = inputs; + ctx.char_width = GlyphWidth(&ctx.atlas_buf.atlas.glyphs['0']); - PrepRendering(ctx); + ResetStacks(ctx); + + // Rendering + 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); + } + + PushConstants(&ctx.rd, ctx.pipeline, &ctx.pc); + + 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; SetPanelSizes(edctx.base_panel); } @@ -351,11 +520,164 @@ EndUI() DrawIndexed(&rd, 6, buffers[f_idx].count, 0); } - CompleteRendering(ctx); + FinishRendering(&ctx.rd); + SubmitAndPresent(&ctx.rd); ctx.frame += 1; } +template CtxMemberInfo(int i) +{ + import std.string : endsWith; + import std.traits : isInstanceOf, isPointer; + + struct MemberInfo + { + string id; + bool is_stack, is_top, is_stack_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])); + + 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); +} + +template StackIDs(string stack) +{ + import std.string : replace; + struct Identifiers { string stack, stack_top_node; } + + enum Identifiers StackIDs = { + stack: replace("ctx.@", "@", stack), + stack_top_node: replace("ctx.@_top", "@", stack), + }; +} + +void +Push(string stack_str, bool auto_pop = false, T)(UICtx* ctx, T value) +{ + import std.string : replace; + + enum ids = StackIDs!(stack_str); + + auto stack = &mixin(ids.stack); + auto top = mixin(ids.stack_top_node); + + Stack!(T)* node = stack.free; + if(!node) + { + node = Alloc!(Stack!(T))(&ctx.temp_arena); + } + + node.next = stack.top; + node.value = value; + + stack.top = node; + stack.auto_pop = auto_pop; +} + +auto +Pop(string stack_str)(UICtx* ctx) +{ + import std.string : replace; + + enum ids = StackIDs!(stack_str); + + auto stack = &mixin(ids.stack); + auto top = mixin(ids.stack_top_node); + + + auto pop = stack.top; + if(pop != top) + { + stack.top = pop.next; + + pop.next = stack.free; + stack.free = pop; + } + + return pop.value; +} + +void +AutoPopStacks(UICtx* ctx) +{ + import std.string : endsWith, replace; + import std.traits : isInstanceOf, isPointer; + + static foreach(i, mem; UICtx.tupleof) + { + { + enum member_info = CtxMemberInfo!(i); + static if(member_info.is_stack && !member_info.is_top) + { + if(ctx.tupleof[i].auto_pop) + { + Pop!(member_info.id)(ctx); + ctx.tupleof[i].auto_pop = false; + } + } + } + } +} + +void +InitStacks(UICtx* ctx) +{ + import std.string : endsWith, chomp, replace; + import std.traits : isInstanceOf, isPointer; + + static foreach(i, mem; UICtx.tupleof) + { + { + enum member_info = CtxMemberInfo!(i); + static if(member_info.is_stack && member_info.is_top) + { + enum global_default = replace("g_@_default", "@", chomp(member_info.id, "_top")); + + ctx.tupleof[i] = Alloc!(typeof(*UICtx.tupleof[i]))(&ctx.arena); + ctx.tupleof[i].value = mixin(global_default); + ctx.tupleof[i].next = null; + } + } + } +} + +void +ResetStacks(UICtx* ctx) +{ + import std.string : endsWith, replace; + import std.traits : isInstanceOf, isPointer; + + static foreach(i, mem; UICtx.tupleof) + { + { + enum member_info = CtxMemberInfo!(i); + static if(member_info.is_stack_top) + { + 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"); + } + } + } +} + f32 LineCounterWidth(u32 char_width) { @@ -365,7 +687,7 @@ LineCounterWidth(u32 char_width) Vec2 InnerSize(UIPanel* panel) { - return panel.size - Vec2(g_ui_ctx.border_thickness*2.0); + return panel.size - Vec2(g_ui_ctx.border_thickness_top.value*2.0);; } void @@ -462,410 +784,6 @@ SetScrollOffset(UIPanel* panel) } } -void -Panel(UIPanel* panel) -{ - UICtx* ctx = GetCtx(); - UIItem* item = Get(panel.id); - Editor* ed = panel.ed; - bool focused = GetFocusedPanel() == panel; - - UIPanel* parent = panel.parent; - UIPanel* prev = panel.prev; - - Axis2D pax = parent.axis; - - Vec2 adj = Vec2( - parent.axis == A2D.X ? 10 : 0, - parent.axis == A2D.Y ? 10 : 0 - ); - - Vec2 p0 = panel.rect.vec0+adj; - Vec2 p1 = panel.rect.vec1-adj; - - if(!Nil(prev)) with(panel) - { - Vec2 d0 = rect.vec0-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 - ); - - if(Dragged(item, d0, d1) && 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)) - { - pct += mov_pct; - panel.prev.pct -= mov_pct; - } - } - } - - if(panel.ed != null) - { - if(Clicked(item, p0, p1)) - { - SetFocusedPanel(panel); - } - - Vec2 inner = InnerSize(panel); - - SetScrollOffset(panel); - - panel.start_ln = cast(u64)(floor(panel.scroll_offset/TEXT_SIZE)); - panel.end_ln = panel.start_ln + cast(u64)(ceil(inner.y/TEXT_SIZE)); - panel.vis_lines = panel.end_ln - panel.start_ln; - - char[64] ch_buf = '\0'; - char[] fmt = ch_buf.sformat("%%0%ss", u64(panel.end_ln.toChars().length)); - - f32 lc_w = panel.end_ln.toChars().length*ctx.char_width + LINE_COUNT_PADDING*2.0; - f32 code_view_width = inner.x-lc_w; - - StartLineBuffer(&panel.ed.buf, code_view_width); - - U64Vec2 pos = VecPos(&ed.buf); - - DrawPanel(panel, lc_w, focused); - - 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); - - 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)) - { - f32 x_pos = x + LINE_COUNT_PADDING; - DrawLineCount(fmt, &x_pos, y, i); - - u64 ch_offset; - - auto parts = MakeMultiline(buf.text, code_view_width, buf.style); - if(parts == null) - { - if(pos.y == i) - { - DrawCursor([], 0, x+lc_w, y, ch_offset); - } - } - else for(auto n = parts; n != null; n = n.next) - { - auto l = &n.value; - - if(pos.y == i) - { - DrawCursor(l.text, pos.x, x+lc_w, y, ch_offset); - } - - foreach(j; 0 .. l.text.length) - { - bool hl = pos.y == i && !EditModeActive() && j == pos.x-ch_offset; - DrawChar(l.text[j], &x_pos, y, hl ? Vec4(Vec3(0.0), 1.0) : SYNTAX_COLORS[l.style[j]]); - } - - y += TEXT_SIZE; - x_pos = x + lc_w + LINE_COUNT_PADDING*2.0; - ch_offset += l.text.length; - } - } - } -} - -pragma(inline) void -DrawCursor(u8[] text, u64 ch_x, f32 x, f32 y, u64 offset) -{ - Glyph* g = GetGlyph(' '); - foreach(j; 0 .. text.length) - { - bool hl = j == ch_x-offset; - if(hl) - { - break; - } - - g = j == text.length-1 ? GetGlyph(' ') : GetGlyph(text[j]); - x += GlyphWidth(g); - } - - DrawRect(x, y, cast(u8)g.ch, Vec4(1.0), EditModeActive()); -} - -pragma(inline) void -DrawLineCount(char[] fmt, f32* x_pos, f32 y, u64 line) -{ - char[32] line_buf = '\0'; - char[] line_str = sformat(line_buf, fmt, line+1); - - foreach(j; 0 .. line_str.length) - { - DrawChar(line_str[j], x_pos, y, Vec4(1.0)); - } - - *x_pos += LINE_COUNT_PADDING; -} - -void -CommandPalette(CmdPalette* cmd) -{ - UICtx* ctx = GetCtx(); - - u8[] text = cmd.buffer[0 .. cmd.icount]; - u8[][] options = cmd.opt_strs; - - Vec2 size = RootSize(); - - f32 x = size.x*0.15; - f32 y = size.y*0.1; - f32 w = size.x*0.7; - f32 h = 40.0; - - DrawCmdRect(Vec2(x, y), Vec2(w, h), false); - - f32 y_off = h*0.5 + 6; - f32 ch_x = x + 6.0; - f32 ch_y = y + y_off; - - foreach(i; 0 .. text.length) - { - DrawChar(text[i], &ch_x, ch_y, Vec4(1.0)); - } - - for(u64 i = 0; i < options.length; i += 1) - { - y += h; - ch_x = x + 6.0; - ch_y = y + y_off; - - DrawCmdRect(Vec2(x, y), Vec2(w, h), cmd.selected == i); - - foreach(j; 0 .. options[i].length) - { - DrawChar(options[i][j], &ch_x, ch_y, Vec4(1.0)); - } - - if(y+h > size.y+h) break; - } -} - -UIPanel* -Recurse(UIPanel* panel) -{ - UIPanel* result = g_UI_NIL_PANEL; - if(!Nil(panel.first)) - { - result = panel.first; - } - else if(!Nil(panel.next)) - { - result = panel.next; - } - else for(UIPanel* p = panel.parent; !Nil(p); p = p.parent) - { - if(!Nil(p.next)) - { - result = p.next; - break; - } - } - - return result; -} - -bool -CheckPanelBounds(f32 pct) -{ - return pct >= 0.0 && pct <= 1.0; -} - -void -PushPanel(UIPanel* parent, UIPanel* panel) -{ - DLLPush(parent, panel, g_UI_NIL_PANEL); - panel.parent = parent; -} - -void -InsertPanel(UIPanel* parent, UIPanel* prev, UIPanel* panel) -{ - DLLInsert(parent, panel, prev, g_UI_NIL_PANEL); - panel.parent = prev.parent; -} - -void -InitWidgets() -{ - g_UI_NIL_PANEL = cast(UIPanel*)&g_ui_nil_panel; - g_ui_ctx.parent = g_UI_NIL_PANEL; -} - -void -SetFocusedPanel(UIPanel* panel) -{ - if(!CheckNil(g_UI_NIL_PANEL, panel)) - { - Logf("set focus"); - g_ui_ctx.focused_panel = panel; - } -} - -UIPanel* -GetFocusedPanel() -{ - return Nil(g_ui_ctx.focused_panel) ? g_UI_NIL_PANEL : g_ui_ctx.focused_panel; -} - -bool -Nil(UIPanel* panel) -{ - return panel == null || panel == g_UI_NIL_PANEL; -} - -void -InitUICtx(PlatformWindow* window) -{ - version(linux) - { - PlatformHandles handles = { - display: window.display, - window: window.window, - }; - } - - version(Windows) - { - - } - - g_UI_NIL = cast(UIItem*)&g_ui_nil_item; - g_UI_NIL_NODE = cast(UIItemNode*)&g_ui_nil_item_node; - - Arena arena = CreateArena(MB(4)); - - 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_data: cast(u8[])FONT_BYTES, - text_size: 16.0, - tab_width: 2, - }; - - 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, 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]); - - g_ui_ctx = ctx; - - InitWidgets(); -} - -void -SetDebug(bool dbg) -{ - debug - { - g_ui_ctx.dbg = dbg; - } -} - -void -PrepRendering(UICtx* ctx) -{ - 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); - } - - PushConstants(&ctx.rd, ctx.pipeline, &ctx.pc); - - 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; -} - -void -CompleteRendering(UICtx* ctx) -{ - FinishRendering(&ctx.rd); - SubmitAndPresent(&ctx.rd); -} - UICtx* GetCtx() { @@ -879,8 +797,17 @@ RootSize() } UIKey -MakeKey(u8[] id) +MakeKey(T)(T str) if(StringType!T) { + static if(is(T: string)) + { + u8[] id = CastStr!(u8)(str); + } + else + { + u8[] id = cast(u8[])str; + } + UIKey key; bool hash_only = false; @@ -934,8 +861,17 @@ MakeKey(u8[] id) } pragma(inline) UIItem* -Get(UIKey key) +Get(T)(T k) if(is(T: UIKey) || StringType!T) { + static if(is(T: UIKey)) + { + UIKey key = k; + } + else + { + UIKey key = MakeKey(k); + } + Result!(UIItem*) result = g_ui_ctx.items[key.hash]; if(!result.ok) { @@ -948,25 +884,11 @@ Get(UIKey key) return result.value; } -UIItem* -Get(string id) -{ - u8[] u8_id = CastStr!(u8)(id); - return Get(u8_id); -} - -UIItem* -Get(u8[] id) -{ - UIKey key = MakeKey(id); - return Get(key); -} - f32 CalcTextWidth(u8[] str) { - u32 tab_width = g_ui_ctx.tab_width; - Glyph* space = g_ui_ctx.atlas_buf.atlas.glyphs.ptr + ' '; + u32 tab_width = g_ui_ctx.tab_width; + Glyph* space = g_ui_ctx.atlas_buf.atlas.glyphs.ptr + ' '; f32 width = 0.0; for(u64 i = 0; i < str.length; i += 1) @@ -981,14 +903,7 @@ pragma(inline) f32 GlyphWidth(Glyph* g) { f32 width = 0.0; - if(g.ch == '\t') - { - width += g_ui_ctx.atlas_buf.atlas.glyphs[' '].advance * cast(f32)(g_ui_ctx.tab_width); - } - else - { - width += g.advance; - } + width += g.ch == '\t' ? (g_ui_ctx.atlas_buf.atlas.glyphs[' '].advance*cast(f32)(g_ui_ctx.tab_width)) : g.advance; return width; } @@ -999,76 +914,6 @@ struct TextBuffer TS[] style; } -Node!(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; - if(line_count > 0) - { - f32 w = 0.0; - u64 line = 0; - u64 start = 0; - const u64 extra_buf = 20; - for(u64 i = 0; i < text.length; i += 1) - { - f32 ch_w = GlyphWidth(g_ui_ctx.atlas_buf.atlas.glyphs.ptr + text[i]); - - if(ch_w + w > scaled_width || i == text.length-1) - { - u64 len = i-start+1; - - u8[] str = ScratchAlloc!(u8)(text, start, len); - - TS[] stl = []; - if(style.length > 0) - { - stl = ScratchAlloc!(TS)(style, start, len); - } - - Node!(TextBuffer)* n = node; - for(;;) - { - if(node == null) - { - node = ScratchAlloc!(Node!(TextBuffer))(); - - node.value.text = str; - node.value.style = stl; - node.next = null; - - break; - } - - if(n.next == null) - { - n.next = ScratchAlloc!(Node!(TextBuffer))(); - - n.next.value.text = str; - n.next.value.style = stl; - n.next.next = null; - - break; - } - - n = n.next; - } - - line += 1; - start = i; - w = 0.0; - } - - w += ch_w; - } - } - - return node; -} - /* pragma(inline) bool CullText(UIItem* item, GlyphBounds* gb) @@ -1212,11 +1057,11 @@ DrawCmdRect(Vec2 pos, Vec2 size, bool edit) { UICtx* ctx = GetCtx(); - Vec4[4] bg_col = edit ? ctx.cmd_col_hl : ctx.cmd_col; - Vec4[4] border_col = edit ? ctx.cmd_border_col_hl : ctx.cmd_border_col; + Vec4[4] bg_col = edit ? ctx.bg_hl_col_top.value : ctx.bg_col_top.value; + Vec4[4] border_col = edit ? ctx.border_hl_col_top.value : ctx.border_col_top.value; - DrawRect(pos, size, ctx.corner_radius, ctx.border_thickness, bg_col); - DrawBorder(pos, size, ctx.border_thickness, ctx.corner_radius, ctx.edge_softness, border_col); + DrawRect(pos, size, ctx.corner_radius_top.value, ctx.border_thickness_top.value, bg_col); + DrawBorder(pos, size, ctx.border_thickness_top.value, ctx.corner_radius_top.value, ctx.edge_softness_top.value, border_col); } pragma(inline) void @@ -1227,9 +1072,9 @@ DrawPanel(UIPanel* panel, f32 lc_w, bool focus) Vec2 pos = panel.rect.vec0; Vec2 size = panel.size; - DrawRect(pos, size, ctx.corner_radius, ctx.border_thickness, ctx.bg_col); - DrawRect(pos, Vec2(lc_w, size.y), 0.0, 0.0, focus ? ctx.line_count_col_hl : ctx.line_count_col); - DrawBorder(pos, size, ctx.border_thickness, ctx.corner_radius, ctx.edge_softness, focus ? ctx.border_col_hl : ctx.border_col); + DrawRect(pos, size, ctx.corner_radius_top.value, ctx.border_thickness_top.value, ctx.bg_col_top.value); + DrawRect(pos, Vec2(lc_w, size.y), 0.0, 0.0, focus ? ctx.bg_hl_col_top.value : ctx.bg_col_top.value); + 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); } pragma(inline) Glyph* @@ -1436,5 +1281,19 @@ unittest assert(key.text == cast(u8[])r"Test String"); assert(key.hash == Hash(r"123123")); } + + { + UICtx ctx; + InitStacks(&ctx); + ResetStacks(&ctx); + + Vec4 w = Vec4(1.0); + Vec4[4] col = w; + + Push!("bg_col")(&ctx, col); + assert(ctx.bg_col.top.value == col); + Pop!("bg_col")(&ctx); + assert(ctx.bg_col.top.value == BG_COL); + } } diff --git a/src/editor/views.d b/src/editor/views.d new file mode 100644 index 0000000..4fe71bc --- /dev/null +++ b/src/editor/views.d @@ -0,0 +1,330 @@ +import dlib; +import ui; +import editor; +import parsing; +import buffer; + +import std.format; +import std.conv; + +void +Panel(UIPanel* panel) +{ + UICtx* ctx = GetCtx(); + UIItem* item = Get(panel.id); + Editor* ed = panel.ed; + bool focused = GetFocusedPanel() == panel; + + UIPanel* parent = panel.parent; + UIPanel* prev = panel.prev; + + Axis2D pax = parent.axis; + + Vec2 adj = Vec2( + parent.axis == A2D.X ? 10 : 0, + parent.axis == A2D.Y ? 10 : 0 + ); + + Vec2 p0 = panel.rect.vec0+adj; + Vec2 p1 = panel.rect.vec1-adj; + + if(!Nil(prev)) with(panel) + { + Vec2 d0 = rect.vec0-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 + ); + + if(Dragged(item, d0, d1) && 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)) + { + pct += mov_pct; + panel.prev.pct -= mov_pct; + } + } + } + + if(panel.ed != null) + { + if(Clicked(item, p0, p1)) + { + SetFocusedPanel(panel); + } + + Vec2 inner = InnerSize(panel); + + SetScrollOffset(panel); + + panel.start_ln = cast(u64)(floor(panel.scroll_offset/TEXT_SIZE)); + panel.end_ln = panel.start_ln + cast(u64)(ceil(inner.y/TEXT_SIZE)); + panel.vis_lines = panel.end_ln - panel.start_ln; + + char[64] ch_buf = '\0'; + char[] fmt = ch_buf.sformat("%%0%ss", u64(panel.end_ln.toChars().length)); + + f32 lc_w = panel.end_ln.toChars().length*ctx.char_width + LINE_COUNT_PADDING*2.0; + f32 code_view_width = inner.x-lc_w; + + StartLineBuffer(&panel.ed.buf, code_view_width); + + U64Vec2 pos = VecPos(&ed.buf); + + DrawPanel(panel, lc_w, focused); + + 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); + + 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)) + { + f32 x_pos = x + LINE_COUNT_PADDING; + DrawLineCount(fmt, &x_pos, y, i); + + u64 ch_offset; + + auto parts = MakeMultiline(buf.text, code_view_width, buf.style); + if(parts == null) + { + if(pos.y == i) + { + DrawCursor([], 0, x+lc_w, y, ch_offset); + } + } + else for(auto n = parts; n != null; n = n.next) + { + auto l = &n.value; + + if(pos.y == i) + { + DrawCursor(l.text, pos.x, x+lc_w, y, ch_offset); + } + + foreach(j; 0 .. l.text.length) + { + bool hl = pos.y == i && !EditModeActive() && j == pos.x-ch_offset; + DrawChar(l.text[j], &x_pos, y, hl ? Vec4(Vec3(0.0), 1.0) : SYNTAX_COLORS[l.style[j]]); + } + + y += TEXT_SIZE; + x_pos = x + lc_w + LINE_COUNT_PADDING*2.0; + ch_offset += l.text.length; + } + } + } +} + +void +CommandPalette(CmdPalette* cmd) +{ + UICtx* ctx = GetCtx(); + + u8[] text = cmd.buffer[0 .. cmd.icount]; + u8[][] options = cmd.opt_strs; + + Vec2 size = RootSize(); + + f32 x = size.x*0.15; + f32 y = size.y*0.1; + f32 w = size.x*0.7; + f32 h = 40.0; + + DrawCmdRect(Vec2(x, y), Vec2(w, h), false); + + f32 y_off = h*0.5 + 6; + f32 ch_x = x + 6.0; + f32 ch_y = y + y_off; + + foreach(i; 0 .. text.length) + { + DrawChar(text[i], &ch_x, ch_y, Vec4(1.0)); + } + + for(u64 i = 0; i < options.length; i += 1) + { + y += h; + ch_x = x + 6.0; + ch_y = y + y_off; + + DrawCmdRect(Vec2(x, y), Vec2(w, h), cmd.selected == i); + + foreach(j; 0 .. options[i].length) + { + DrawChar(options[i][j], &ch_x, ch_y, Vec4(1.0)); + } + + if(y+h > size.y+h) break; + } +} + +UIPanel* +Recurse(UIPanel* panel) +{ + UIPanel* result = g_UI_NIL_PANEL; + if(!Nil(panel.first)) + { + result = panel.first; + } + else if(!Nil(panel.next)) + { + result = panel.next; + } + else for(UIPanel* p = panel.parent; !Nil(p); p = p.parent) + { + if(!Nil(p.next)) + { + result = p.next; + break; + } + } + + return result; +} + +bool +CheckPanelBounds(f32 pct) +{ + return pct >= 0.0 && pct <= 1.0; +} + +void +PushPanel(UIPanel* parent, UIPanel* panel) +{ + DLLPush(parent, panel, g_UI_NIL_PANEL); + panel.parent = parent; +} + +void +InsertPanel(UIPanel* parent, UIPanel* prev, UIPanel* panel) +{ + DLLInsert(parent, panel, prev, g_UI_NIL_PANEL); + panel.parent = prev.parent; +} + +void +SetFocusedPanel(UIPanel* panel) +{ + if(!CheckNil(g_UI_NIL_PANEL, panel)) + { + g_ui_ctx.focused_panel = panel; + } +} + +UIPanel* +GetFocusedPanel() +{ + return Nil(g_ui_ctx.focused_panel) ? g_UI_NIL_PANEL : g_ui_ctx.focused_panel; +} + +bool +Nil(UIPanel* panel) +{ + return panel == null || panel == g_UI_NIL_PANEL; +} + +Node!(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; + if(line_count > 0) + { + f32 w = 0.0; + u64 line = 0; + u64 start = 0; + const u64 extra_buf = 20; + for(u64 i = 0; i < text.length; i += 1) + { + f32 ch_w = GlyphWidth(g_ui_ctx.atlas_buf.atlas.glyphs.ptr + text[i]); + + if(ch_w + w > scaled_width || i == text.length-1) + { + u64 len = i-start+1; + + u8[] str = ScratchAlloc!(u8)(text, start, len); + + TS[] stl = []; + if(style.length > 0) + { + stl = ScratchAlloc!(TS)(style, start, len); + } + + Node!(TextBuffer)* n = node; + for(;;) + { + if(node == null) + { + node = ScratchAlloc!(Node!(TextBuffer))(); + + node.value.text = str; + node.value.style = stl; + node.next = null; + + break; + } + + if(n.next == null) + { + n.next = ScratchAlloc!(Node!(TextBuffer))(); + + n.next.value.text = str; + n.next.value.style = stl; + n.next.next = null; + + break; + } + + n = n.next; + } + + line += 1; + start = i; + w = 0.0; + } + + w += ch_w; + } + } + + return node; +} + +pragma(inline) void +DrawCursor(u8[] text, u64 ch_x, f32 x, f32 y, u64 offset) +{ + Glyph* g = GetGlyph(' '); + foreach(j; 0 .. text.length) + { + bool hl = j == ch_x-offset; + if(hl) + { + break; + } + + g = j == text.length-1 ? GetGlyph(' ') : GetGlyph(text[j]); + x += GlyphWidth(g); + } + + DrawRect(x, y, cast(u8)g.ch, Vec4(1.0), EditModeActive()); +} + +pragma(inline) void +DrawLineCount(char[] fmt, f32* x_pos, f32 y, u64 line) +{ + char[32] line_buf = '\0'; + char[] line_str = sformat(line_buf, fmt, line+1); + + foreach(j; 0 .. line_str.length) + { + DrawChar(line_str[j], x_pos, y, Vec4(1.0)); + } + + *x_pos += LINE_COUNT_PADDING; +}