diff --git a/src/editor/editor.d b/src/editor/editor.d index 5761766..5848aea 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -18,11 +18,61 @@ import core.stdc.stdio; f32 g_delta = 0.0; -struct EditorCtx +struct RenderCtx { - Arena arena; - PlatformWindow* window; - Panel* base_panel; + PlatformWindow* window; + Renderer rd; + Descriptor[FS][FO] font_descs; + Descriptor default_tex; + Descriptor sampler; + Pipeline pipeline; + DescSetLayout desc_set_layout; + DescSet[FO] desc_sets; + PipelineLayout pipeline_layout; + PushConst pc; + Vec2 res; +} + +struct Ctx +{ + RenderCtx rd_ctx; + + HashTable!(UIHash, UIItem*) items; + UIItem* free_items; + UIItem* last_item; + + Arena arena; + Arena temp_arena; + + Inputs* inputs; + u64 frame; + u64 f_idx; + + LinkedList!(UIInput) events; + IVec2 mouse_pos; + + FontFace font; + FontGlyphs[FS] glyph_sets; + u32 glyph_sets_used; + bool[FO] glyphs_to_load; + bool prev_has_tex; + u32 prev_atlas_index; + + UIBuffer[FO] buffers; + + u32 tab_width; + Vec4[TS.max][UISH.max] syntax_colors; + + UIKey drag_key; + UIKey hover_key; + UIKey focus_key; + + u64 last_hover_frame; + + f32 scroll_rate; + f32 animation_rate; + f32 fade_rate; + EditState state; u8[128] input_buf; u32 icount; @@ -32,7 +82,7 @@ struct EditorCtx string[] file_names; u64 panel_id; - Panel* focused_panel; + alias rd_ctx this; } struct CmdPalette @@ -60,6 +110,7 @@ struct Editor Vec2 select_start; Vec2 select_end; i64 line_offset; + i64 line_height; } struct ChangeStacks @@ -79,12 +130,19 @@ struct EditorChange EditorChange* next; } +alias CmdFn = bool function(Ctx* ctx); + struct Command { string name; string desc; string cmd; CmdType type; + union + { + CmdFn fn; + + } } struct Parameter @@ -119,11 +177,11 @@ alias ES = EditState; bool CmdModeActive() { - return g_ed_ctx.state == ES.CmdPalette; + return g_ctx.state == ES.CmdPalette; } -__gshared bool g_input_mode = false; -__gshared EditorCtx g_ed_ctx; +__gshared bool g_input_mode = false; +__gshared Ctx g_ctx; const Command NO_CMD = { name: [], @@ -166,7 +224,20 @@ const Command[] CMD_LIST = [ bool Active(EditState state) { - return g_ed_ctx.state == state; + return g_ctx.state == state; +} + +UIPanel* +MakePanel(UIPanel* parent = g_NIL_PANEL) +{ + UIPanel* p = Alloc!(UIPanel); + + static u32 id; + + p.id = id++; + p.item = MakeItem("###panel_%s", p.id); + + return p; } void @@ -174,33 +245,87 @@ Cycle(Inputs* inputs) { ResetScratch(MB(4)); - g_delta = DeltaTime(&g_ed_ctx.timer); + g_delta = DeltaTime(&g_ctx.timer); g_input_mode = Active(ES.InputMode); BeginUI(inputs); - UICtx* ui_ctx = GetCtx(); + Ctx* ctx = GetCtx(); + + static UIPanel* panel; + if(Nil(panel)) + { + panel = MakePanel(); + panel.pct = 1.0; + + UIPanel* p0 = MakePanel(); + UIPanel* p1 = MakePanel(); + UIPanel* p2 = MakePanel(); + UIPanel* p3 = MakePanel(); + UIPanel* p4 = MakePanel(); + UIPanel* p5 = MakePanel(); + UIPanel* p6 = MakePanel(); + + p0.pct = 0.3; + p1.pct = 0.7; + p0.parent = panel; + p1.parent = panel; + + DLLPush(panel, p0, g_NIL_PANEL); + DLLPush(panel, p1, g_NIL_PANEL); + + p0.axis = A2D.Y; + + p2.pct = 0.25; + p3.pct = 0.35; + p4.pct = 0.40; + p2.parent = p3.parent = p4.parent = p0; + + DLLPush(p0, p2, g_NIL_PANEL); + DLLPush(p0, p3, g_NIL_PANEL); + DLLPush(p0, p4, g_NIL_PANEL); + + p5.pct = 0.2; + p6.pct = 0.8; + p5.parent = p6.parent = p4; + + DLLPush(p4, p5, g_NIL_PANEL); + DLLPush(p4, p6, g_NIL_PANEL); + + p1.ed = CreateEditor(); + p2.ed = CreateEditor(); + p3.ed = CreateEditor(); + p5.ed = CreateEditor(); + p6.ed = CreateEditor(); + + OpenFile(p1.ed, "./src/VulkanRenderer/external/vma/vk_mem_alloc.h"); + OpenFile(p3.ed, "./src/VulkanRenderer/external/vma/vk_mem_alloc.h"); + OpenFile(p5.ed, "./src/VulkanRenderer/external/vma/vk_mem_alloc.h"); + OpenFile(p6.ed, "./src/VulkanRenderer/external/vma/vk_mem_alloc.h"); + } + + Panel(ctx, panel); EndUI(); } void -InitEditorCtx(PlatformWindow* window) +InitCtx(PlatformWindow* window) { - InitUICtx(window); + Ctx* ctx = &g_ctx; - EditorCtx* ctx = &g_ed_ctx; - ctx.window = window; ctx.arena = CreateArena(MB(2)); ctx.cmd.arena = CreateArena(MB(1)); ctx.cmd.cmd_arena = CreateArena(MB(1)); ctx.cmd.buffer = Alloc!(u8)(1024); - ctx.base_panel = CreatePanel(CreateEditor()); + //ctx.base_panel = CreatePanel(CreateEditor()); ctx.timer = CreateTimer(); - FocusEditor(ctx.base_panel); + InitUI(ctx); + + //FocusEditor(ctx.base_panel); if(getcwd() != "/") { @@ -234,26 +359,36 @@ InitEditorCtx(PlatformWindow* window) } } +Ctx* +GetCtx() +{ + return &g_ctx; +} + +/* void FocusEditor(Panel* p) { assert(p.ed); - g_ed_ctx.focused_panel = p; + g_ctx.focused_panel = p; } +*/ +/* Panel* CreatePanel(Editor* ed = null) { - Panel* p = Alloc!(Panel)(&g_ed_ctx.arena); + Panel* p = Alloc!(Panel)(&g_ctx.arena); p.layout_axis = A2D.Y; p.ed = ed; - p.id = g_ed_ctx.panel_id++; + p.id = g_ctx.panel_id++; p.text_size = 14; p.parent = p.first = p.last = p.next = p.prev = g_NIL_PANEL; return p; } +*/ bool EditModeActive() @@ -391,15 +526,16 @@ OpenFile(Editor* ed, string file_name) Editor* CreateEditor() { - Editor* ed = Alloc!(Editor)(&g_ed_ctx.arena); + Editor* ed = Alloc!(Editor)(&g_ctx.arena); ed.arena = CreateArena(MB(4)); ed.buf = CreateFlatBuffer([], []); - ed.editor_id = g_ed_ctx.editor_id_incr++; + ed.editor_id = g_ctx.editor_id_incr++; return ed; } +/* void AddEditor(Panel* target, Axis2D axis) { @@ -441,20 +577,15 @@ AddEditor(Panel* target, Axis2D axis) c.resize_pct = pct; } } - */ + * / FocusEditor(panel); } } - -UIItem* -GetEditorItem(Editor* ed) -{ - return Get("###ed_%s", ed.editor_id); -} +*/ pragma(inline) void -InsertInputToBuf(EditorCtx* ctx, Editor* ed) +InsertInputToBuf(Ctx* ctx, Editor* ed) { if(ctx.icount > 0) { @@ -466,18 +597,18 @@ InsertInputToBuf(EditorCtx* ctx, Editor* ed) void ResetCtx(Editor* ed) { - InsertInputToBuf(&g_ed_ctx, ed); + InsertInputToBuf(&g_ctx, ed); ResetCtx(); } void ResetCtx() { - g_ed_ctx.state = ES.NormalMode; - g_ed_ctx.cmd.icount = 0; - g_ed_ctx.cmd.commands = []; - g_ed_ctx.cmd.current = cast(Command)NO_CMD; - g_ed_ctx.cmd.selected = 0; + g_ctx.state = ES.NormalMode; + g_ctx.cmd.icount = 0; + g_ctx.cmd.commands = []; + g_ctx.cmd.current = cast(Command)NO_CMD; + g_ctx.cmd.selected = 0; } bool @@ -534,12 +665,12 @@ MovePanelFocus(A2D axis, bool prev)(UIPanel* panel) } void -HandleInputs(Panel* p, LinkedList!(UIInput)* inputs) +HandleInputs(UIPanel* p, LinkedList!(UIInput)* inputs, bool hovered, bool focused) { - Editor* ed = p.ed; - EditorCtx* ctx = &g_ed_ctx; - u8[] cb_text; - FlatBuffer* fb = &ed.buf; + Editor* ed = p.ed; + Ctx* ctx = &g_ctx; + FlatBuffer* fb = &ed.buf; + u8[] cb_text; for(auto node = inputs.first; node != null; node = node.next) { @@ -548,92 +679,121 @@ HandleInputs(Panel* p, LinkedList!(UIInput)* inputs) Input key = node.key; Modifier md = node.md; - if(key == Input.Escape) + switch(node.type) { - ResetCtx(ed); - taken = true; - } - else if(ctx.state == ES.InputMode) - { - taken = HandleInputMode(ctx, p, node); - } - else if(ctx.state == ES.SetPanelFocus) - { - switch(key) with(Input) - { - case Escape: + case UIE.Click: + { + if(hovered) { - ResetCtx(ed); + Focus(p); taken = true; - } break; - default: break; - } - } - else - { - switch(key) with(Input) - { - case a, i: + } + else { - if(key == a && Shift(md) && fb) - { - MoveToEOL(fb); - } - else if(key == a) - { - Move(fb, Right, MD.None); - } - else if(key == i && Shift(md)) - { - MoveToSOL(fb); - } - - ctx.state = ES.InputMode; - taken = true; - } break; - case Semicolon: + // TODO: set cursor pos + } + } break; + case UIE.Drag: + { + // TODO: did drag start on this panel and if so modify selection + } break; + case UIE.DragRelease: + { + // TODO: if dragged end selection + } break; + case UIE.DragStart: + { + if(hovered) { - if(Shift(node.md)) - { - ctx.state = ES.CmdPalette; - ctx.cmd.commands = cast(Command[])CMD_LIST; - ctx.cmd.icount = 0; - ctx.cmd.params = []; - taken = true; - } - } break; - case v: + // TODO: Selection + } + } break; + case UIE.Press: + { + if(focused) { - if(Ctrl(md)) + if(key == Input.Escape) { - cb_text = ClipboardText(ctx.window); + ResetCtx(ed); + taken = true; } - if(Shift(md)) + else if(ctx.state == ES.InputMode) { - ToggleSelection(fb, SM.Line); + taken = HandleInputMode(ctx, p, node); } else { - ToggleSelection(fb, SM.Normal); + switch(key) with(Input) + { + case a, i: + { + if(key == a && Shift(md) && fb) + { + MoveToEOL(fb); + } + else if(key == a) + { + Move(fb, Right, MD.None); + } + else if(key == i && Shift(md)) + { + MoveToSOL(fb); + } + + ctx.state = ES.InputMode; + taken = true; + } break; + case Semicolon: + { + if(Shift(node.md)) + { + ctx.state = ES.CmdPalette; + ctx.cmd.commands = cast(Command[])CMD_LIST; + ctx.cmd.icount = 0; + ctx.cmd.params = []; + taken = true; + } + } break; + case v: + { + if(Ctrl(md)) + { + cb_text = ClipboardText(ctx.window); + } + if(Shift(md)) + { + ToggleSelection(fb, SM.Line); + } + else + { + ToggleSelection(fb, SM.Normal); + } + } break; + case c: + { + if(Ctrl(md)) + { + // TODO: Copy + taken = true; + } + } break; + default: + { + taken = MoveCursor(ed, node); + } break; + } } - } break; - case c: + } + } break; + case UIE.Scroll: + { + if(hovered) { - if(Ctrl(md)) - { - // Copy - taken = true; - } - } break; - case w: - { - if(Ctrl(md)) - { - ctx.state = ES.SetPanelFocus; - } - } break; - default: taken = Move(fb, key, md); break; - } + SetOffset(ed, node.scroll*30); + taken = true; + } + } break; + default: break; } if(taken) @@ -651,17 +811,48 @@ HandleInputs(Panel* p, LinkedList!(UIInput)* inputs) } void +SetOffset(Editor* ed, i64 adjust) +{ + i64 max_offset = ed.buf.line_count-ed.line_height; + if(max_offset < 0) + { + max_offset = 0; + } + + ed.line_offset = clamp(ed.line_offset+adjust, 0, max_offset); +} + +bool MoveCursor(Editor* ed, UIInput* ev) { + bool taken; + switch(ev.key) with(Input) { - case Up, Down, Left, Right: Move(&ed.buf, ev.key, ev.md); break; + case Up, Down, Left, Right: taken = Move(&ed.buf, ev.key, ev.md); break; default: break; } + + if(taken) + { + I64Vec2 pos = VecPos(&ed.buf); + i64 min = ed.line_offset+2; + i64 max = clamp(ed.line_offset+ed.line_height-3, 0, ed.buf.line_count); + if(pos.y > max) + { + SetOffset(ed, pos.y-max); + } + else if(pos.y < min) + { + SetOffset(ed, -(min-pos.y)); + } + } + + return taken; } pragma(inline) void -InsertChar(EditorCtx* ctx, Input input, bool modified) +InsertChar(Ctx* ctx, Input input, bool modified) { ctx.input_buf[ctx.icount++] = InputToChar(input, modified); } @@ -684,7 +875,7 @@ CharCases() } bool -HandleInputMode(EditorCtx* ctx, Panel* p, UIInput* ev) +HandleInputMode(Ctx* ctx, UIPanel* p, UIInput* ev) { bool taken = false; @@ -794,11 +985,12 @@ StrContains(bool begins_with, T, U)(T str_param, U match_param) if(StringType!(T return count == match.length; } +/* bool HandleCmdMode(CmdPalette* cmd, UIInput* ev) { bool taken; - Panel* panel = g_ed_ctx.focused_panel; + Panel* panel = g_ctx.focused_panel; Editor* ed = panel.ed; u64 prev_count = cmd.icount; @@ -809,7 +1001,7 @@ HandleCmdMode(CmdPalette* cmd, UIInput* ev) if(cmd.current.type == CT.None && cmd.commands.length > 0) { cmd.current = cmd.commands[cmd.selected]; - g_ed_ctx.state = ES.RunCmd; + g_ctx.state = ES.RunCmd; } } goto CmdInputEnd; case Backspace: @@ -898,7 +1090,7 @@ HandleCmdMode(CmdPalette* cmd, UIInput* ev) { case OpenFile: { - PopulateParams(cmd, g_ed_ctx.file_names); + PopulateParams(cmd, g_ctx.file_names); } break; case SaveFile: { @@ -912,6 +1104,7 @@ CmdInputEnd: return taken; } +*/ pragma(inline) void Check(CmdPalette* cmd, u64 length) diff --git a/src/editor/main.d b/src/editor/main.d index 3ac0f20..41e677d 100644 --- a/src/editor/main.d +++ b/src/editor/main.d @@ -27,7 +27,7 @@ void main(string[] argv) StartPlatformThread(&window); - InitEditorCtx(&window); + InitCtx(&window); import ui; import vulkan; @@ -41,9 +41,9 @@ void main(string[] argv) break; } - if(g_ui_ctx.rd.res != Vec2(window.w, window.h).v) + if(g_ctx.rd.res != Vec2(window.w, window.h).v) { - SetExtent(&g_ui_ctx.rd, window.w, window.h); + SetExtent(&g_ctx.rd, window.w, window.h); } if(inputs.first == null) diff --git a/src/editor/ui.d b/src/editor/ui.d index 0cdfc20..96ba7f0 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -10,6 +10,7 @@ import editor; import views; import std.stdio; +import std.typecons : Tuple, tuple; import std.math.traits : isNaN; import std.math.rounding : ceil, floor; import std.math.exponential : pow; @@ -56,8 +57,7 @@ const u32 ATLAS_DIMENSION = 512; // TODO: add setting const f32 SCROLL_SPEED = 48.0; const f32 LINE_COUNT_PADDING = 4.0; - -__gshared UICtx g_ui_ctx; +const f32 CORNER_RADIUS = 8.0; //u8[] FONT_BYTES = Arr!(u8)(import("pc-9800.ttf")); //u8[] FONT_BYTES = Arr!(u8)(import("NuberNextCondensed-DemiBold.otf")); @@ -65,31 +65,17 @@ u8[] FONT_BYTES = cast(u8[])import("jetbrains-mono/JetBrainsMono-Regular.ttf u8[] VERTEX_BYTES = cast(u8[])import("gui.vert.spv"); u8[] FRAGMENT_BYTES = cast(u8[])import("gui.frag.spv"); -Vec4 g_corner_radius_default = 0.0; -f32 g_edge_softness_default = 0.8; -f32 g_border_thickness_default = 0.0; -Vec4 g_border_col_default = BORDER_COL; -Vec4 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); -u32 g_text_size_default = 18; -Vec2 g_scroll_target_default = Vec2(0.0); -Vec2 g_scroll_clamp_default = Vec2(0.0); -string g_display_string_default = null; -UISH g_syntax_highlight_default = UISH.None; -u8[] g_syntax_tokens_default = []; -Vec2 g_fixed_pos_default = Vec2(0.0); -alias g_parent_default = g_UI_NIL; +const SEP_FLAGS = UIF.Drag|UIF.Priority|UIF.TargetLeniency; +const PANEL_FLAGS = UIF.Click|UIF.Drag; const UI_COUNT = 5000; -__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 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 UIPanel g_nil_panel; +__gshared UIPanel* g_NIL_PANEL; enum Axis2D { @@ -98,22 +84,6 @@ enum Axis2D Max, } alias A2D = Axis2D; -enum UIFlags : u64 -{ - None = 0, - Parent = 1<<0, - Panel = 1<<1, - Window = 1<<2, -} alias UIF = UIFlags; - -enum UISignal -{ - None = 0, - Clicked = 1<<0, - Dragged = 1<<1, - Hovered = 1<<2, -} alias UIS = UISignal; - enum UIEvent { None = 0, @@ -134,8 +104,8 @@ struct UIInput { struct { - Input key; - string text; + Input key; + string text; }; IVec2 rel; IVec2 pos; @@ -167,63 +137,8 @@ struct PushConst u32 atlas_index; } -struct UICtx -{ - enum FO = FRAME_OVERLAP; - enum FS = FONT_SIZES; - - HashTable!(UIHash, UIItem*) items; - UIItem* free_items; - UIItem* transient_items; - - Arena arena; - Arena temp_arena; - Arena stack_arena; - - Inputs* inputs; - u64 frame; - u64 f_idx; - - LinkedList!(UIInput) events; - IVec2 mouse_pos; - - PlatformWindow* window; - Renderer rd; - Descriptor[FS][FO] font_descs; - Descriptor default_tex; - Descriptor sampler; - Pipeline pipeline; - DescSetLayout desc_set_layout; - DescSet[FO] desc_sets; - PipelineLayout pipeline_layout; - PushConst pc; - Vec2 res; - - FontFace font; - FontGlyphs[FS] glyph_sets; - u32 glyph_sets_used; - bool[FO] glyphs_to_load; - bool prev_has_tex; - u32 prev_atlas_index; - - UIBuffer[FO] buffers; - - u32 tab_width; - Vec4[TS.max][UISH.max] syntax_colors; - - UIItem* root_first; - UIItem* root_last; - UIKey drag_key; - UIKey hover_key; - u64 last_hover_frame; - - f32 scroll_rate; - f32 animation_rate; - f32 fade_rate; - - Stack!(UIItem*)* key_item_stack; - Stack!(UIItem*)* parent_stack; -} +enum FO = FRAME_OVERLAP; +enum FS = FONT_SIZES; struct FontGlyphs { @@ -246,47 +161,73 @@ struct StackTop(T) Stack!(T)* free; } +struct Style +{ + Vec4 col; + Vec4 hl_col; + Vec4 border_col; + Vec4 border_hl_col; + Vec4 corner_radius; + f32 border_thickness; + f32 edge_softness; +} + +Style SEP_STYLE = { + col: Vec4(0.0, 0.0, 0.0, 1.0), + hl_col: Vec4(0.2, 0.2, 0.2, 1.0), +}; + +Style PANEL_STYLE = { + col: BG_COL, + hl_col: BG_COL, + border_col: BORDER_COL, + border_hl_col: BORDER_COL, + corner_radius: Vec4(CORNER_RADIUS), + border_thickness: 1.0, + edge_softness: 1.0, +}; + +Style CMD_STYLE = { + col: BG_COL, + hl_col: BG_HL_COL, + border_col: CMD_BORDER_COL, + border_hl_col: BORDER_HL_COL, + corner_radius: Vec4(CORNER_RADIUS), + border_thickness: 1.0, + edge_softness: 1.0, +}; + +enum UIFlags +{ + None, + Ready = 1<<0, + Hot = 1<<1, + Active = 1<<2, + Click = 1<<3, + Drag = 1<<4, + Priority = 1<<5, + TargetLeniency = 1<<6, +} alias UIF = UIFlags; + struct UIItem { - IVec2 dragged; UIKey key; - UIItem* next, prev, first, last, parent; - + UIFlags flags; Rect rect; Vec2 size; - - Vec4 bg_col; - Vec4 border_col; - Vec4 corner_radius; Vec2 scroll_offset; + Vec2 scroll_target; Vec2 scroll_size; - UIFlags flags; + Axis2D axis; u64 last_frame; - Axis2D layout_axis; - f32 edge_softness; - f32 border_thickness; - f32 panel_pct; f32 ready_t; // Item visible f32 hot_t; // Item to be interacted with (e.g. hover) f32 active_t; // Item active (retained focus) -} -enum SizeType -{ - Pixels, - Percentage, - ChildrenSum, - TextSize, - Pct = ST.Percentage, - Px = ST.Pixels, -} + UIItem* draw_next; // Stacks + UIItem* free_next; -alias ST = SizeType; - -struct UISize -{ - SizeType type; - f32 value; + alias rect this; } struct UIBuffer @@ -326,6 +267,25 @@ struct Vertex u32 has_texture; } +struct UIPanel +{ + UIPanel* first, last, next, prev, parent; + UIItem* item; + f32 pct; + u32 id; + Editor* ed; + + alias item this; +} + +struct Window +{ + Window* next, prev; + string label; + u32 x, y, w, h; + Editor* ed; +} + enum SettingType { Value, @@ -350,7 +310,7 @@ Attribute[10] attributes = [ union Rect { Vec2[2] v; - struct { Vec2 p0, p1; }; + struct { Vec2 p0, p1; alias p0 pos; }; } alias UIHash = u64; @@ -361,24 +321,24 @@ struct UIKey { string text, hash_text; u64 hash; + + bool opEquals(UIKey key) => this.hash == key.hash; + bool opCast(T)() if(is(T == bool)) => this.hash != 0; } enum bool KeyType(T) = (StringType!(T) || is(T == UIKey) || is(T == const(UIKey))); enum bool ItemAndKeyType(T) = (KeyType!(T) || is(T == UIItem*)); void - - -void -InitUICtx(PlatformWindow* window) +InitUI(Ctx* ctx) { version(ENABLE_RENDERER) { version(linux) { PlatformHandles handles = { - display: window.display, - window: window.window, + display: ctx.window.display, + window: ctx.window.window, }; } @@ -388,30 +348,23 @@ InitUICtx(PlatformWindow* window) } } - g_UI_NIL = cast(UIItem*)&g_ui_nil_item; - g_UI_NIL_INPUT = cast(UIInput*)&g_ui_nil_input; + g_UI_NIL = cast(UIItem*)&g_ui_nil_item; + g_UI_NIL_INPUT = cast(UIInput*)&g_ui_nil_input; + g_NIL_PANEL = cast(UIPanel*)&g_nil_panel; Arena arena = CreateArena(MB(4)); UIBuffer[FRAME_OVERLAP] buffers; - UICtx* ctx = &g_ui_ctx; - ctx.items = CreateHashTable!(UIHash, UIItem*)(12); - ctx.free_items = Alloc!(UIItem)(&arena); ctx.arena = arena; ctx.temp_arena = CreateArena(MB(1)); - ctx.stack_arena = CreateArena(MB(1)); - ctx.transient_items = g_UI_NIL; ctx.font = OpenFont(cast(u8[])FONT_BYTES); ctx.tab_width = 2; ctx.syntax_colors = [DEFAULT_COLORS, SYNTAX_COLORS]; assert(ctx.font); - UIItem* fi = ctx.free_items; - fi.first = fi.last = fi.next = fi.prev = fi.parent = g_UI_NIL; - version(ENABLE_RENDERER) { ctx.rd = InitRenderer(handles, MB(16), MB(8)); @@ -458,7 +411,7 @@ InitUICtx(PlatformWindow* window) } } - FontAtlasBuf* abuf = GetFontAtlas(g_text_size_default); + FontAtlasBuf* abuf = GetFontAtlas(14); LoadFontGlyphs(); @@ -482,8 +435,6 @@ InitUICtx(PlatformWindow* window) SetClearColors(&ctx.rd, [0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]); } - - MakeItem("###root", UIF.Parent); } FontAtlasBuf* @@ -501,7 +452,7 @@ GetLineHeight(u32 size) FontGlyphs* GetFontGlyphs(u32 size) { - UICtx* ctx = GetCtx(); + Ctx* ctx = GetCtx(); FontGlyphs* fg = null; for(u64 i = 0; i < ctx.glyph_sets_used; i += 1) @@ -534,7 +485,7 @@ GetFontGlyphs(u32 size) void LoadFontGlyphs() { - UICtx* ctx = GetCtx(); + Ctx* ctx = GetCtx(); u64 fi = ctx.f_idx; for(u64 i = 0; i < ctx.glyph_sets_used; i += 1) @@ -552,36 +503,278 @@ LoadFontGlyphs() ctx.glyphs_to_load[fi] = false; } -UIItem* -MakeItem(T)(T k, f32 x, f32 y, f32 w, f32 h, UIFlags flags = UIF.None) if(KeyType!(T)) +void +Scissor(Ctx* ctx, Rect rect, Style* style) { - UICtx* ctx = GetCtx(); - UIItem* item = Get(k); + DrawUI(ctx); - item.flags = flags; - item.parent = item.parent_stack.value; - item.rect.p0 = Vec2(x, y); - item.rect.p1 = Vec2(x+w, y+h); + f32 b = style.border_thickness + style.border_thickness*style.edge_softness; - if(Nil(ctx.root_first)) + i32 x = cast(i32)clamp(floor(rect.p0.x+b), 0.0, ctx.res.x); + i32 y = cast(i32)clamp(floor(rect.p0.y+b), 0.0, ctx.res.y); + i32 w = cast(i32)clamp(floor(rect.p1.x-b) - x, 0.0, ctx.res.x); + i32 h = cast(i32)clamp(floor(rect.p1.y-b) - y, 0.0, ctx.res.y); + + SetScissor(&ctx.rd, x, y, w, h); +} + +void +EndScissor(Ctx* ctx) +{ + DrawUI(ctx); + ResetScissor(&ctx.rd); +} + +void +Panel(Ctx* ctx, UIPanel* panel) +{ + panel.item = MakeItem!(PANEL_FLAGS)("###panel_%s", panel.id); + Axis2D p_axis = Nil(panel.parent) ? A2D.X : panel.parent.axis; + Vec2 p_size = Nil(panel.parent) ? GetExtent() : panel.parent.size; + Vec2 p_bounds = Nil(panel.parent) ? GetExtent() : panel.parent.p1; + + const f32 SEP_SIZE = 3.0f; + + panel.pos = 0.0; + if(!Nil(panel.prev)) { - ctx.root_first = ctx.root_last = item; + panel.p0 = panel.prev.p0; + panel.p0.v[p_axis] = panel.prev.p1.v[p_axis]+SEP_SIZE; } - else if(!Nil(item.parent)) + else if(!Nil(panel.parent)) { - DLLPush(item.parent, item, g_UI_NIL); + panel.pos = panel.parent.rect.pos; } - if(flags & UIF.Parent) + panel.size = p_size; + panel.size.v[p_axis] *= panel.pct; + panel.p1 = Clamp(panel.pos+panel.size, Vec2(0.0), p_bounds); + + Axis2D axis = panel.axis; + if(panel.ed || Nil(panel.first)) { - Push(&ctx.parent_stack, item); + assert(Nil(panel.first) && Nil(panel.last), "Editor panel must have no children"); + + if(panel.ed) + { + FontGlyphs* fg = GetFontGlyphs(12); + FontAtlasBuf* abuf = &fg.abuf; + + f32 lheight = abuf.atlas.line_height; + panel.scroll_target.y = cast(f32)(panel.ed.line_offset)*lheight; + + f32 max_px = panel.ed.buf.line_count*lheight; + f32 offset = -(panel.item.scroll_offset.y%lheight); + u64 start_ln = cast(u64)(floor(panel.scroll_offset.y/lheight)); + const f32 PAD = 4.0f; + + // Line counter + + u64 lc_chars = panel.ed.buf.line_count.toChars().length; + f32 lc_width = PAD*2.0 + cast(f32)(lc_chars)*abuf.atlas.max_advance; + + Style style = PANEL_STYLE; + style.corner_radius.yw = 0.0; + + Rect[2] rects = Split(panel.rect, lc_width, A2D.X); + + DrawRect(ctx, rects[0], &style); + Scissor(ctx, rects[0], &style); + + Rect text_rect = Pad(rects[0], PAD); + text_rect.p0.y += offset; + + f32 panel_height = rects[0].p1.y - rects[0].p0.y; + panel.ed.line_height = cast(i64)ceil(panel_height/lheight); + u64 max_ln = Min(panel.ed.line_height + start_ln, panel.ed.buf.line_count); + + for(u64 i = start_ln; i < max_ln; i += 1) + { + string line_num = Scratchf("%s", i); + DrawText(ctx, line_num, fg, Vec4(1.0), text_rect, TA.Right); + text_rect.p0.y += lheight; + } + + EndScissor(ctx); + DrawBorder(ctx, rects[0], &style); + + // Editor + + style.corner_radius = Vec4(0.0, CORNER_RADIUS, 0.0, CORNER_RADIUS); + + DrawRect(ctx, rects[1], &style); + Scissor(ctx, rects[1], &style); + + text_rect = Pad(rects[1], PAD); + text_rect.p0.y += offset; + + u64 i = start_ln; + for(LineBuffer* lbuf = GetLine(&panel.ed.buf, i); i < max_ln && !CheckNil(g_NIL_LINE_BUF, lbuf); i += 1, lbuf = GetLine(&panel.ed.buf, i)) + { + DrawText(ctx, Str(lbuf.text), fg, cast(u8[])lbuf.style, text_rect, TA.Left); + text_rect.p0.y += lheight; + } + + ResetBuffer(&panel.ed.buf); + + EndScissor(ctx); + DrawBorder(ctx, rects[1], &style); + + // Inputs + HandleInputs(panel, &ctx.events, ctx.hover_key == panel.key, ctx.focus_key == panel.key); + } + else + { + DrawRect(ctx, panel.rect, &PANEL_STYLE); + DrawBorder(ctx, panel.rect, &PANEL_STYLE); + } + } + else for(UIPanel* p = panel.first; !Nil(p); p = p.next) + { + Panel(ctx, p); + if(!Nil(p.next)) + { + UIItem* sep = MakeItem!(SEP_FLAGS)("###sep_%s_%s", p.id, p.next.id); + + sep.size = panel.size; + sep.size.v[axis] = SEP_SIZE; + + sep.p0 = p.p0; + sep.p0.v[axis] = p.p1.v[panel.axis]; + sep.p1 = sep.p0+sep.size; + + DrawRect(ctx, sep.rect, &SEP_STYLE); + + IVec2 drag; + if(Dragged(ctx, sep, &drag)) + { + f32 mov_pct = Remap(drag.v[axis], 0.0, p_size.v[axis], 0.0, 1.0); + + if(p.pct - mov_pct >= 0.05 && p.next.pct + mov_pct >= 0.05) + { + p.pct -= mov_pct; + p.pct = clamp(p.pct, 0.0, 1.0); + p.next.pct += mov_pct; + p.next.pct = clamp(p.next.pct, 0.0, 1.0); + } + } + } + } +} + +void +CommandPalette(Ctx* ctx, CmdPalette* cmd) +{ + +} + +void +AddPanel(UIPanel* target, Axis2D axis) +{ + if(Nil(target.parent) || target.parent.axis != axis) + { + UIPanel* first = CreatePanel(target.ed); + UIPanel* second = CreatePanel(CreateEditor()); + + target.axis = axis; + target.ed = null; + + DLLPush(target, first, g_NIL_PANEL); + DLLPush(target, second, g_NIL_PANEL); + + first.parent = second.parent = target; + first.pct = second.pct = 0.5; + + g_ctx.focus_key = second.key; + } + else + { + UIPanel* panel = CreatePanel(CreateEditor()); + + DLLPush(target.parent, panel, g_NIL_PANEL); + panel.parent = target.parent; + + u32 count; + for(UIPanel* s = target.parent.first; !Nil(s); s = s.next) + { + count += 1; + } + + f32 reduce = 1.0/count; + f32 new_pct = 0.0; + for(UIPanel* s = target.parent.first; !Nil(s); s = s.next) + { + f32 item_reduce = s.pct * reduce; + new_pct += item_reduce; + s.pct -= item_reduce; + } + + panel.pct = new_pct; + + g_ctx.focus_key = panel.key; + } +} + +Rect[2] +Split(bool pct = false)(Rect rect, f32 value, Axis2D axis) +{ + Rect[2] result = rect; + + static if(pct) + { + f32 size = (rect.p1.v[axis]-rect.p0.v[axis])*value; + + result[0].p1.v[axis] = result[0].p0.v[axis]+size; + result[1].p0.v[axis] += size; + } + else + { + f32 size = Min(value, rect.p1.v[axis]-rect.p0.v[axis]); + + result[0].p1.v[axis] = result[0].p0.v[axis]+size; + result[1].p0.v[axis] += size; } - item.last_frame = ctx.frame; + static foreach(i; 0 .. result.length) + { + if(result[i].p1.v[axis] < result[i].p0.v[axis]) + { + result[i].p0.v[axis] = result[i].p1.v[axis]; + } + } - Push(&ctx.key_item_stack); + return result; +} - return item; +Rect +Pad(Rect rect, f32 px) +{ + f32 px_x = Min(px, (rect.p1.x-rect.p0.x)*0.5); + f32 px_y = Min(px, (rect.p1.y-rect.p0.y)*0.5); + + rect.p0 += Vec2(px_x, px_y); + rect.p1 -= Vec2(px_x, px_y); + + return rect; +} + +void +Focus(T)(T item) +{ + g_ctx.focus_key = item.key; +} + +UIPanel* +CreatePanel(Editor* ed) +{ + static u32 panel_id; + + UIPanel* p = Alloc!(UIPanel)(&g_ctx.arena); + + p.id = panel_id++; + p.item = MakeItem!(PANEL_FLAGS)("###panel_%s", p.id); + + return p; } static string @@ -650,7 +843,7 @@ Hovered(bool current_frame, T, U)(T param, U* ptr, U index) bool Hovered(bool current_frame, T)(T param) { - UICtx* ctx = GetCtx(); + Ctx* ctx = GetCtx(); bool result; UIKey key = mixin(AssignKey!(T, param)); @@ -671,7 +864,7 @@ Hovered(bool current_frame, T)(T param) } void -PushUIEvent(UICtx* ctx, UIInput input) +PushUIEvent(Ctx* ctx, UIInput input) { UIInput* ev = Alloc!(UIInput)(&ctx.temp_arena); *ev = input; @@ -680,7 +873,7 @@ PushUIEvent(UICtx* ctx, UIInput input) } void -DrawUI(UICtx* ctx, u32 atlas_index) +DrawUI(Ctx* ctx, u32 atlas_index) { if(ctx.pc.atlas_index != atlas_index) { @@ -691,7 +884,7 @@ DrawUI(UICtx* ctx, u32 atlas_index) } void -DrawUI(UICtx* ctx) +DrawUI(Ctx* ctx) { version(ENABLE_RENDERER) { @@ -709,7 +902,7 @@ DrawUI(UICtx* ctx) void BeginUI(Inputs* inputs) { - UICtx* ctx = GetCtx(); + Ctx* ctx = GetCtx(); Arena* a = &ctx.temp_arena; Reset(a); @@ -770,16 +963,13 @@ BeginUI(Inputs* inputs) u64 next_frame = ctx.frame+1; // Check current mouse target - if(mouse_moved && !Nil(ctx.root_last)) + if(mouse_moved && !Nil(ctx.last_item)) { - UIKey hovered = ZeroKey(); - for(Stack!(UIItem*)* node = ctx.key_item_stack; node; node = node.next) + UIKey hovered = FindHoverKey!(true)(ctx); + + if(ZeroKey(hovered)) { - if(InBounds(ctx.mouse_pos, &node.value.rect)) - { - hovered = node.value.key; - break; - } + hovered = FindHoverKey!(false)(ctx); } if(!ZeroKey(hovered)) @@ -791,15 +981,10 @@ BeginUI(Inputs* inputs) { ctx.hover_key = ZeroKey(); } + + Logf("hover key %s", ctx.hover_key.hash_text); } - - Reset(&ctx.stack_arena); - ctx.parent_stack = Alloc!(Stack!(UIItem*))(&ctx.stack_arena); - ctx.key_item_stack = Alloc!(Stack!(UIItem*))(&ctx.stack_arena); - - SetNil(&ctx.root_first, &ctx.root_last, &ctx.parent_stack.value, &ctx.key_item_stack.value); - // Clean up items KVPair!(UIHash, UIItem*)*[] items = GetAllNodes(&ctx.temp_arena, &ctx.items); foreach(i; 0 .. items.length) @@ -809,14 +994,16 @@ BeginUI(Inputs* inputs) { Delete(&ctx.items, items[i].key); memset(item, 0, UIItem.sizeof); - DLLPush(ctx.free_items, item, g_UI_NIL); + item.free_next = ctx.free_items; + ctx.free_items = item; } } // Ctx state - ctx.frame = next_frame; - ctx.f_idx = ctx.frame%FRAME_OVERLAP; - ctx.inputs = inputs; + ctx.frame = next_frame; + ctx.f_idx = ctx.frame%FRAME_OVERLAP; + ctx.inputs = inputs; + ctx.last_item = g_UI_NIL; assert(!mouse_moved || ZeroKey(ctx.hover_key) || (ctx.frame == ctx.last_hover_frame)); @@ -861,10 +1048,41 @@ BeginUI(Inputs* inputs) ctx.buffers[ctx.f_idx].vtx_offset = 0; } +UIKey +FindHoverKey(bool priority)(Ctx* ctx) +{ + UIKey hovered; + + for(UIItem* item = ctx.last_item; !Nil(item); item = item.draw_next) + { + if(item.flags & UIF.Click|UIF.Drag) + { + Rect rect = item.flags & UIF.TargetLeniency ? Rect(p0: item.p0-10.0f, p1: item.p1+10.0f) : item.rect; + + static if(priority) + { + bool in_bounds = item.flags & UIF.Priority && InBounds(ctx.mouse_pos, &rect); + } + else + { + bool in_bounds = InBounds(ctx.mouse_pos, &rect); + } + + if(in_bounds) + { + hovered = item.key; + break; + } + } + } + + return hovered; +} + void EndUI() { - UICtx* ctx = GetCtx(); + Ctx* ctx = GetCtx(); version(ENABLE_RENDERER) { @@ -875,7 +1093,7 @@ EndUI() if(!ZeroKey(ctx.drag_key)) { - for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i); i = i.next) + for(UIInput* i = ctx.events.first; !Nil(i); i = i.next) { if(i.type == UIE.DragRelease) { @@ -903,7 +1121,7 @@ SetNil(Args...)(Args args) } void -Clamp(UICtx* ctx, Vertex* v) +Clamp(Ctx* ctx, Vertex* v) { v.dst_start = Clamp(v.dst_start, Vec2(0.0), Vec2(ctx.res)); v.dst_end = Clamp(v.dst_end, Vec2(0.0), Vec2(ctx.res)); @@ -913,13 +1131,13 @@ bool Focused(T)(T param) if(ItemAndKeyType!(T)) { UIItem* item = mixin(AssignItem(T, param)); - return g_ui_ctx.focus_item == item; + return g_ctx.focus_item == item; } void UpdateState(f32* v, bool cond) { - *v += g_ui_ctx.animation_rate * (cast(f32)(cond) - *v); + *v += g_ctx.animation_rate * (cast(f32)(cond) - *v); f32 diff = fabsf((*v)-1.0); if(diff < 0.00009) { @@ -932,7 +1150,7 @@ UpdateState(f32* v, bool cond) } void -UpdateState(ISF flags)(UICtx* ctx, UIItem* item) +UpdateState(ISF flags)(Ctx* ctx, UIItem* item) { static if(flags & ISF.Hot) UpdateState(&item.hot_t, cast(bool)(flags & ISF.SetHot) || Hovered!(true)(item)); static if(flags & ISF.Ready) UpdateState(&item.ready_t, cast(bool)(flags & ISF.SetReady)); @@ -971,17 +1189,17 @@ AnimateReady(Args...)(f32 ready, Args cols) } void -DrawDropShadow(UICtx* ctx, UIItem* item) +DrawDropShadow(Ctx* ctx, Rect rect, f32 ready_t = 1.0) { - Vec2 size = item.rect.p1-item.rect.p0; + Vec2 size = Vec2(rect.p1.x-rect.p0.x, rect.p1.y-rect.p0.y); f32 px = clamp(((size.x+size.y)/2.0)*0.004, 1.0, f32.max); f32 alpha = 0.1; - AnimateReady(item.ready_t, &alpha); + AnimateReady(ready_t, &alpha); Vertex* v = GetVertex(ctx); - v.dst_start = item.rect.p0 - Vec2(px); - v.dst_end = item.rect.p1 + Vec2(px); + v.dst_start = rect.p0 - Vec2(px); + v.dst_end = rect.p1 + Vec2(px); v.cols = Vec4(0.0, 0.0, 0.0, alpha); v.corner_radius = 0.8f; v.edge_softness = 8.0f; @@ -990,36 +1208,38 @@ DrawDropShadow(UICtx* ctx, UIItem* item) } void -DrawBorder(UICtx* ctx, UIItem* item) +DrawBorder(Ctx* ctx, Rect rect, Style* style, f32 hot_t = 0.0, f32 ready_t = 1.0) { - Vec4 col = item.border_col; - AnimateHot(item.hot_t, BORDER_HL_COL, &col); - AnimateReady(item.ready_t, &col); + Vec4 col = style.border_col; + AnimateHot(hot_t, style.border_hl_col, &col); + AnimateReady(ready_t, &col); Vertex* v = GetVertex(ctx); - v.dst_start = item.rect.p0; - v.dst_end = item.rect.p1; + v.dst_start = rect.p0; + v.dst_end = rect.p1; v.cols = col; - v.corner_radius = item.corner_radius; - v.border_thickness = item.border_thickness; - v.edge_softness = item.edge_softness; + v.corner_radius = style.corner_radius; + v.border_thickness = style.border_thickness; + v.edge_softness = style.edge_softness; Clamp(ctx, v); } void -DrawBackground(UICtx* ctx, UIItem* item, bool bordered = false) +DrawRect(Ctx* ctx, Rect rect, Style* style, f32 hot_t = 0.0, f32 ready_t = 1.0) { - Vec4 col = item.bg_col; - AnimateHot(item.hot_t, BG_HL_COL, &col); - AnimateReady(item.ready_t, &col); + Vec4 col = style.col; + AnimateHot(hot_t, style.hl_col, &col); + AnimateReady(ready_t, &col); + + bool bordered = style.border_thickness > 0.0009; Vertex* v = GetVertex(ctx); - v.dst_start = item.rect.p0 + item.border_thickness; - v.dst_end = item.rect.p1 - item.border_thickness; + v.dst_start = rect.p0 + style.border_thickness; + v.dst_end = rect.p1 - style.border_thickness; v.cols = col; - v.corner_radius = item.corner_radius*(cast(f32)(bordered)*0.5); - v.edge_softness = item.edge_softness; + v.corner_radius = style.corner_radius*(cast(f32)(bordered)*0.5); + v.edge_softness = style.edge_softness; Clamp(ctx, v); } @@ -1032,11 +1252,9 @@ enum TextAlign } alias TA = TextAlign; void -DrawText(T, U)(UICtx* ctx, string text, T size_param, U col_param, Rect rect, TextAlign text_align, f32 ready = 1.0) - if((is(T == FontGlyphs*) || (is(T == u32)) && is(U == Vec4) || is(U == u8[]))) +DrawText(T, U)(Ctx* ctx, string text, T size_param, U col_param, Rect rect, TextAlign text_align, f32 ready = 1.0) + if((is(T == FontGlyphs*) || (is(T: u32)) && is(U == Vec4) || is(U == u8[]))) { - DrawUI(ctx, fg.index); - static if(is(T == FontGlyphs*)) { FontGlyphs* fg = size_param; @@ -1046,6 +1264,8 @@ DrawText(T, U)(UICtx* ctx, string text, T size_param, U col_param, Rect rect, Te FontGlyphs* fg = GetFontGlyphs(size_param); } + DrawUI(ctx, fg.index); + f32 x = rect.p0.x; if(text_align & TA.Right) { @@ -1055,17 +1275,24 @@ DrawText(T, U)(UICtx* ctx, string text, T size_param, U col_param, Rect rect, Te { x = ((rect.p1.x-rect.p0.x - CalcTextWidth(text, &fg.abuf)) / 2.0); } + + if(rect.p0.x > rect.p1.x) + { + Logf("assert failed %f %f", rect.p0.x, rect.p1.x); + assert(false); + } x = clamp(x, rect.p0.x, rect.p1.x); - Glyphs[] glyphs = fg.abuf.atlas.glyphs[0 .. $]; + f32 line_height = fg.abuf.atlas.line_height; + Glyph[] glyphs = fg.abuf.atlas.glyphs[0 .. $]; static if(is(U == u8[])) { u8[] tks = col_param; - foreach(j; 0 .. str.length) + foreach(j; 0 .. text.length) { u8 ch = text[j]; Glyph* g = ch < glyphs.length ? glyphs.ptr + ch : null; - Vec4 col = syntax_cols[tks[j]]; + Vec4 col = SYNTAX_COLORS[tks[j]]; AnimateReady(ready, &col); DrawGlyph(rect, g, &x, rect.p0.y, line_height, col); @@ -1076,17 +1303,17 @@ DrawText(T, U)(UICtx* ctx, string text, T size_param, U col_param, Rect rect, Te Vec4 col = col_param; AnimateReady(ready, &col); - foreach(j; 0 .. str.length) + foreach(j; 0 .. text.length) { - u8 ch = str[j]; + u8 ch = text[j]; Glyph* g = ch < glyphs.length ? glyphs.ptr + ch : null; - DrawGlyph(rect, g, &x, rect.p0.y, line_height, text_col); + DrawGlyph(rect, g, &x, rect.p0.y, line_height, col); } } } void -BeginScissor(UICtx* ctx, UIItem* item, bool scissor_x = true, bool scissor_y = true) +BeginScissor(Ctx* ctx, UIItem* item, bool scissor_x = true, bool scissor_y = true) { DrawUI(ctx); @@ -1100,44 +1327,10 @@ BeginScissor(UICtx* ctx, UIItem* item, bool scissor_x = true, bool scissor_y = t ResetScissor(&ctx.rd); } -void -EndScissor(UICtx* ctx) -{ - ResetScissor(&ctx.rd); -} - -u32[2] +Vec2 GetExtent() { - version(ENABLE_RENDERER) return RendererGetExtent(&g_ui_ctx.rd); else return [1280, 720]; -} - -void -Push(T)(Stack!(T)** stack, T value) -{ - UICtx* ctx = GetCtx(); - - Stack!(T)* node = Alloc!(Stack!(T))(&ctx.stack_arena); - - node.next = *stack; - node.value = value; - - *stack = node; -} - -T -Pop(T)(Stack!(T)** stack, T value) -{ - UICtx* ctx = GetCtx(); - - Stack!(T)* node = *stack; - - if((*stack).next != null) - { - *stack = (*stack).next; - } - - return node.value; + version(ENABLE_RENDERER) return Vec2(RendererGetExtent(&g_ctx.rd)); else return Vec2(1280, 720); } template @@ -1175,12 +1368,6 @@ Recurse(bool pre = true, T)(T* node, T* nil) return result; } -UICtx* -GetCtx() -{ - return &g_ui_ctx; -} - Vec2 RootSize() { @@ -1266,12 +1453,6 @@ HexCol(u64 col) ); } -bool -KeyEq(UIKey k0, UIKey k1) -{ - return k0.hash == k1.hash; -} - static UIKey ZeroKey() { @@ -1286,12 +1467,17 @@ ZeroKey(T)(T param) if(is(T == UIItem*) || is(T == UIKey)) } UIItem* -NewItem(UICtx* ctx) +NewItem(Ctx* ctx) { UIItem* item = g_UI_NIL; - item = DLLPop(ctx.free_items, g_UI_NIL); - if(Nil(item)) + item = ctx.free_items; + if(!Nil(item)) + { + ctx.free_items = item.free_next; + item.free_next = g_UI_NIL; + } + else { item = Alloc!(UIItem)(&ctx.arena); } @@ -1300,17 +1486,17 @@ NewItem(UICtx* ctx) } UIItem* -Get(Args...)(string str, Args args) +MakeItem(UIFlags flags = UIF.None, Args...)(string str, Args args) { char[] key = sformat(ScratchAlloc!(char)(cast(u64)(str.length*1.5)), str, args); - return Get(key); + return MakeItem(key, flags); } pragma(inline) UIItem* -Get(T)(T k) if(KeyType!(T)) +MakeItem(T)(T k, UIFlags flags = UIF.None) if(KeyType!(T)) { UIKey key = mixin(AssignKey!(T, k)); - UICtx* ctx = GetCtx(); + Ctx* ctx = GetCtx(); UIItem* item; Result!(UIItem*) res = ctx.items[key.hash]; @@ -1324,8 +1510,31 @@ Get(T)(T k) if(KeyType!(T)) HTPush(&ctx.items, key.hash, item); } - item.key = key; - item.next = item.prev = item.first = item.last = item.parent = g_UI_NIL; + if(item.last_frame != ctx.frame) + { + item.flags = flags; + item.draw_next = ctx.last_item; + ctx.last_item = item; + + static foreach(axis; A2D.min .. A2D.max) + { + if(fabsf(item.scroll_offset.v[axis] - item.scroll_target.v[axis]) > 0.0009f) + { + f32 v = ctx.scroll_rate * (item.scroll_target.v[axis] - item.scroll_offset.v[axis]); + item.scroll_offset.v[axis] += v; + + if(fabsf(item.scroll_offset.v[axis] - item.scroll_target.v[axis]) < 2.0f) + { + item.scroll_offset.v[axis] = item.scroll_target.v[axis]; + } + } + } + + + } + + item.key = key; + item.last_frame = ctx.frame; return item; } @@ -1351,7 +1560,7 @@ CalcTextWidth(T, U)(T text, U param) if((is(T == string) || StringType!T) && (is FontAtlasBuf* abuf = param; } - u32 tab_width = g_ui_ctx.tab_width; + u32 tab_width = g_ctx.tab_width; f32 width = 0.0; for(u64 i = 0; i < str.length; i += 1) @@ -1365,7 +1574,7 @@ CalcTextWidth(T, U)(T text, U param) if((is(T == string) || StringType!T) && (is pragma(inline) f32 GlyphWidth(Glyph* g, FontAtlasBuf* abuf) { - return g.ch == '\t' ? (abuf.atlas.glyphs[' '].advance*cast(f32)(g_ui_ctx.tab_width)) : g.advance; + return g.ch == '\t' ? (abuf.atlas.glyphs[' '].advance*cast(f32)(g_ctx.tab_width)) : g.advance; } pragma(inline) void @@ -1373,7 +1582,7 @@ DrawGlyph(Rect rect, Glyph* glyph, f32* x_pos, f32 y, f32 line_height, Vec4 col { if(glyph) { - UICtx* ctx = GetCtx(); + Ctx* ctx = GetCtx(); Vertex* v = null; f32 advance = glyph.advance; @@ -1434,16 +1643,71 @@ DrawGlyph(Rect rect, Glyph* glyph, f32* x_pos, f32 y, f32 line_height, Vec4 col } pragma(inline) Vertex* -GetVertex(UICtx* ctx) +GetVertex(Ctx* ctx) { assert(ctx.buffers[ctx.f_idx].count < VERTEX_MAX_COUNT); return ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count++; } + bool -Nil(UIItem* item) +Nil(T)(T item) { - return item == null || item == g_UI_NIL; + static if(is(T == UIItem*)) return item == null || item == g_UI_NIL; + else static if(is(T == UIInput*)) return item == null || item == g_UI_NIL_INPUT; + else static if(is(T == UIPanel*)) return item == null || item == g_NIL_PANEL; + else static assert(false, "Invalid nil type checked"); +} + +bool +Dragged(Ctx* ctx, UIItem* item, IVec2* dragged) +{ + if(item.key == ctx.drag_key || (!ctx.drag_key && item.key == ctx.hover_key)) + { + for(UIInput* i = ctx.events.first; !Nil(i); i = i.next) + { + bool taken; + + if(i.type == UIE.DragStart) + { + ctx.drag_key = item.key; + taken = true; + } + else if(i.type == UIE.Drag && ctx.drag_key == item.key) + { + *dragged += i.rel; + taken = true; + } + + if(taken) + { + DLLRemove(&ctx.events, i, g_UI_NIL_INPUT); + } + } + } + + return *dragged != IVec2(0); +} + +bool +Clicked(Ctx* ctx, UIItem* item) +{ + bool result; + + if(ctx.hover_key == item.key) + { + for(UIInput* i = ctx.events.first; !Nil(i); i = i.next) + { + if(i.type == UIE.Click) + { + result = true; + DLLRemove(&ctx.events, i, g_UI_NIL_INPUT); + break; + } + } + } + + return result; } pragma(inline) bool @@ -1529,49 +1793,5 @@ unittest assert(key.text == cast(u8[])r"Test String"); assert(key.hash == Hash(r"123123")); } - - { - Inputs inputs; - InitUICtx(null); - BeginUI(&inputs); - - UICtx* ctx = GetCtx(); - - Vec4 w = Vec4(1.0); - Vec4[4] col = w; - - Push!("bg_col")(col); - assert(ctx.bg_col.top.value == col); - - EndUI(); - - Vec2 extent = GetExtent(); - assert(extent == ctx.root.size); - - BeginUI(&inputs); - assert(ctx.bg_col.top.value == BG_COL); - EndUI(); - - // Layouts - { - BeginUI(&inputs); - - Push!("size_info")(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 0cc8831..e0817fc 100644 --- a/src/editor/views.d +++ b/src/editor/views.d @@ -9,11 +9,8 @@ import std.algorithm.comparison : clamp; import std.format; import std.conv; -__gshared const Panel g_nil_panel; -__gshared Panel* g_NIL_PANEL; __gshared const Editor g_nil_ed; __gshared Editor* g_NIL_ED; -__gshared const UIKey ZERO = ZeroKey(); /****** - Set up "themes" for different components (e.g. command palette window, editor view, status bar) then have the ability to create styles and apply them to different sections via a string lookup, e.g. start of function to draw cmd palette call ApplyTheme(selected_theme_name) then do code to draw things, get to options and call ApplyTheme(selected_theme_name_for_options), etc. @@ -27,15 +24,6 @@ const u32 CMD_SUB_PX = 10; shared static this() { - g_NIL_PANEL = cast(Panel* )&g_nil_panel; g_NIL_ED = cast(Editor*)&g_nil_ed; } -struct Panel -{ - Panel* first, last, next, prev, parent; - Axis2D layout_axis; - Editor* ed; - u64 id; - u32 text_size; -}