From dcb0d620c79746a79682ab1602b2cf9584d0066f Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 4 Jan 2026 20:46:26 +1100 Subject: [PATCH] more work --- assets/gui.frag.spv | Bin 7144 -> 7376 bytes assets/gui.vert.spv | Bin 9956 -> 9520 bytes src/dlib | 2 +- src/editor/editor.d | 199 ++++++++++++------------ src/editor/ui.d | 320 +++++++++++++++++++++++++------------- src/editor/views.d | 274 ++++++++++++++++++++------------ src/shaders/gui.frag.glsl | 2 +- src/shaders/gui.layout | 8 +- src/shaders/gui.vert.glsl | 33 ++-- 9 files changed, 508 insertions(+), 330 deletions(-) diff --git a/assets/gui.frag.spv b/assets/gui.frag.spv index 8794c328a5e47009bdd6bdefa2b25c57b3c5d821..bd666ad8d604f5a2668a4a601bfc72d6240c8f85 100644 GIT binary patch delta 1541 zcmZ9LPe@cz6vpqJaYo0PG4sFk(likgBeN6{2{IQMVQFD)iW+R9DIG;eawDTeL`%7g zwkfsCg--U*O3f54lrn-$6tMsgB<%a1@4?`cpXc1~oO|wh=iT{P_OaY;wdBPL zn-osr7JGW!rG+jLBZRPnCa*sLBTB$SFaUrjUr2Q9T#VXxz`bTTjK>j<{D`c!{>mMm5u;+MjYor5yfF8WZD zPju2QmxyC<8SH5h@1! zV%bK2)OtY<)=yv^fag~jYkom$g;vPQiYfNOz5vuKQHK{|7z_aF%;9%_1*mh)OEL<0!OvsQ80=fXoKe6!b+GS1 z2^a_1m#q(b!U$YIQZf8Vz|9kCEK_r*p#29}b%ROuD^tswf;SCV=n-H8-1i<#t6Yy( zIcu!A7q6{fs~Tv0>Tn82GY6Q*k=`-1&xW3+Iz3v}SFETcqsm!)e4dd6pZcsC^6brA zgvV=p#<(_1@To1TRZm`x1G!WRfO&c1q4C6cz_@v`6MjB*STolZmFrZOG_8CY8T{EW eQj0U$(F$1J0k08b-vK*bQ3G1KEkZ@jXxe`;Zs>Rb delta 1365 zcmYjQO-K}B82-N5ADvxyMVC=_2g;PFMMKsfDi#GJjEKw>iy)|__OPH5x{@KWN^}V7 zAm5==kP$(L4mSPUk75!YiXs#(vORW?ND2>8+4BrDvL8IWf6x2QGvB=Pe($&4A&*v5 zBz#f?MM#+Aq2?Vy;T1yofxc9xA4W6*w}DCE8L$ZC$NPHZ(&TK!Z+-Hg*3@CW&eOiX z-5SvwWOX(@oE;iSCj zHF|CCHca8919(5aMRh^UHwpr!0PA_{TIsSZ^4gD|P?@r+TrJYgDBlM-rV8;xSe|=8 zOlaZ>Eb~v9#9^FtVm^|`6dv&$mObvwm+)~9cJC}K=O+Nxi(wapXcc3lqe%`UfqF^e z!@k0L05-7BI1eyaha1shV^$)vA?2z{7?rv2NUQ=Ufli0;1O6EYV@_@jF}bXjh$4H? zPxu_X6JR|C6(zWIs{948whtih66P?!4zR}g#loxJ7;(9-)(lgYt58L~EVo)J4r#gH zD6^g0cNJhewf_NFy8^g!|G_`+z?Hj!m|WIexf1eRxvUq;)tnmTf*0P@>qS9RMzm3S z)xl`JG}MKt%H09Y0{A0v2U;L>0`>vGd>iC)A07dd0Q2PVJ3j`PXUv_L0=N&y(8FDL z0g%Hy=NyH71vCNN4fJiX?_wGO3&%l9z|H_{o>q5bW+(#THxZ2iGwMUk^v%M1t=3`@ z-<<8~rm4@vV-!)lOta@LJZ5-Hmob$e>>a?ofqD2~_NH|p=5aY6;geSmP8Sw{YRcd< zKd)|>Tcb%-_YjVZlG7|^f1=5kDjsU>A`>X)`?Z~n2a)Sk2)?J;Q z;PJS_5l9@ D>PpGi diff --git a/assets/gui.vert.spv b/assets/gui.vert.spv index 26ecb60989657c4a9b685e64fa18267d293ac4f5..25c67fbdbe4139e2cbbb98314f492cc7f7f1c009 100644 GIT binary patch literal 9520 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 Au>b%7 literal 9956 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 diff --git a/src/dlib b/src/dlib index 10c7fdc..9f70b89 160000 --- a/src/dlib +++ b/src/dlib @@ -1 +1 @@ -Subproject commit 10c7fdcaedfdd98af47c640e00f1fad8705f476d +Subproject commit 9f70b8982c71d6d24031a08c6f2780be2dd402e0 diff --git a/src/editor/editor.d b/src/editor/editor.d index d263956..137a542 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -32,7 +32,7 @@ struct EditorCtx string[] file_names; u64 panel_id; - Editor* focused_editor; + Panel* focused_panel; } struct CmdPalette @@ -109,12 +109,19 @@ enum EditState { NormalMode, InputMode, - CmdOpen, + CmdPalette, + RunCmd, SetPanelFocus, // if moving left/right move up parent tree until one is found with a2d.x, same thing for up/down a2d.y } alias ES = EditState; +bool +CmdModeActive() +{ + return g_ed_ctx.state == ES.CmdPalette; +} + __gshared bool g_input_mode = false; __gshared EditorCtx g_ed_ctx; @@ -200,12 +207,17 @@ Cycle(Inputs* inputs) UIItem* cmd_item = Get("###cmd_palette"); - bool cmd_active = Active(ES.CmdOpen); + bool cmd_active = Active(ES.CmdPalette); if(cmd_active || Ready(cmd_item)) { CommandPalette(&g_ed_ctx.cmd, cmd_active); } + if(Active(ES.RunCmd)) + { + + } + EndUI(); } @@ -221,10 +233,10 @@ InitEditorCtx(PlatformWindow* window) ctx.cmd.arena = CreateArena(MB(1)); ctx.cmd.cmd_arena = CreateArena(MB(1)); ctx.cmd.buffer = Alloc!(u8)(1024); - ctx.base_panel = CreatePanel(ctx, CreateEditor(ctx)); + ctx.base_panel = CreatePanel(CreateEditor()); ctx.timer = CreateTimer(); - FocusEditor(ctx.base_panel.ed); + FocusEditor(ctx.base_panel); if(getcwd() != "/") { @@ -259,21 +271,20 @@ InitEditorCtx(PlatformWindow* window) } void -FocusEditor(Editor* ed) +FocusEditor(Panel* p) { - assert(ed); - g_ed_ctx.focused_editor = ed; - Logf("editor %s", ed.editor_id); + assert(p.ed); + g_ed_ctx.focused_panel = p; } Panel* -CreatePanel(EditorCtx* ctx, Editor* ed = null) +CreatePanel(Editor* ed = null) { - Panel* p = Alloc!(Panel)(&ctx.arena); + Panel* p = Alloc!(Panel)(&g_ed_ctx.arena); p.layout_axis = A2D.Y; p.ed = ed; - p.id = ctx.panel_id++; + p.id = g_ed_ctx.panel_id++; p.text_size = 14; p.parent = p.first = p.last = p.next = p.prev = g_NIL_PANEL; @@ -414,24 +425,24 @@ OpenFile(Editor* ed, string file_name) } Editor* -CreateEditor(EditorCtx* ctx) +CreateEditor() { - Editor* ed = Alloc!(Editor)(&ctx.arena); + Editor* ed = Alloc!(Editor)(&g_ed_ctx.arena); ed.arena = CreateArena(MB(4)); ed.buf = CreateFlatBuffer([], []); - ed.editor_id = ctx.editor_id_incr++; + ed.editor_id = g_ed_ctx.editor_id_incr++; return ed; } void -AddEditor(EditorCtx* ctx, Panel* target, Axis2D axis) +AddEditor(Panel* target, Axis2D axis) { if(CheckNil(g_NIL_PANEL, target.parent) || target.parent.layout_axis != axis) { - Panel* first = CreatePanel(ctx, target.ed); - Panel* second = CreatePanel(ctx, CreateEditor(ctx)); + Panel* first = CreatePanel(target.ed); + Panel* second = CreatePanel(CreateEditor()); target.layout_axis = axis; target.ed = null; @@ -441,11 +452,11 @@ AddEditor(EditorCtx* ctx, Panel* target, Axis2D axis) first.parent = second.parent = target; - FocusEditor(second.ed); + FocusEditor(second); } else if(target.parent.layout_axis == axis) { - Panel* panel = CreatePanel(ctx, CreateEditor(ctx)); + Panel* panel = CreatePanel(CreateEditor()); DLLPush(target.parent, target, panel); panel.parent = target.parent; @@ -466,7 +477,7 @@ AddEditor(EditorCtx* ctx, Panel* target, Axis2D axis) } } - FocusEditor(panel.ed); + FocusEditor(panel); } } @@ -487,15 +498,20 @@ InsertInputToBuf(EditorCtx* ctx, Editor* ed) } void -ResetCtx(EditorCtx* ctx, Editor* ed) +ResetCtx(Editor* ed) { - InsertInputToBuf(ctx, ed); + InsertInputToBuf(&g_ed_ctx, ed); + ResetCtx(); +} - ctx.state = ES.NormalMode; - ctx.cmd.icount = 0; - ctx.cmd.commands = []; - ctx.cmd.current = cast(Command)NO_CMD; - ctx.cmd.selected = 0; +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; } bool @@ -568,24 +584,20 @@ HandleInputs(Panel* p, LinkedList!(UIInput)* inputs) if(key == Input.Escape) { - ResetCtx(ctx, ed); + ResetCtx(ed); taken = true; } else if(ctx.state == ES.InputMode) { taken = HandleInputMode(ctx, p, node); } - else if(ctx.state == ES.CmdOpen) - { - taken = HandleCmdMode(ctx, p, node); - } else if(ctx.state == ES.SetPanelFocus) { switch(key) with(Input) { case Escape: { - ResetCtx(ctx, ed); + ResetCtx(ed); taken = true; } break; default: break; @@ -617,7 +629,7 @@ HandleInputs(Panel* p, LinkedList!(UIInput)* inputs) { if(Shift(node.md)) { - ctx.state = ES.CmdOpen; + ctx.state = ES.CmdPalette; ctx.cmd.commands = cast(Command[])CMD_LIST; ctx.cmd.icount = 0; ctx.cmd.params = []; @@ -816,37 +828,12 @@ StrContains(bool begins_with, T, U)(T str_param, U match_param) if(StringType!(T return count == match.length; } -void -GetCommands(CmdPalette* cmd) -{ - Reset(&cmd.arena); - - cmd.commands = Alloc!(Command)(&cmd.arena, CMD_LIST.length); - cmd.params = []; - u8[] str = cmd.buffer[0 .. cmd.icount]; - - u64 count; - if(str.length > 0) - { - for(u64 i = 0; i < CMD_LIST.length; i += 1) - { - if(StrContains!(true)(CMD_LIST[i].cmd, str) || StrContains!(true)(CMD_LIST[i].name, str)) - { - cmd.commands[count] = cast(Command)CMD_LIST[i]; - count += 1; - } - } - } - - cmd.commands = count ? cmd.commands[0 .. count] : cast(Command[])CMD_LIST; -} - bool -HandleCmdMode(EditorCtx* ctx, Panel* panel, UIInput* ev) +HandleCmdMode(CmdPalette* cmd, UIInput* ev) { - bool taken; - CmdPalette* cmd = &ctx.cmd; - Editor* ed = panel.ed; + bool taken; + Panel* panel = g_ed_ctx.focused_panel; + Editor* ed = panel.ed; u64 prev_count = cmd.icount; switch(ev.key) with(Input) @@ -855,43 +842,19 @@ HandleCmdMode(EditorCtx* ctx, Panel* panel, UIInput* ev) { if(cmd.current.type == CT.None && cmd.commands.length > 0) { - if(cmd.commands[cmd.selected].type == CT.OpenFile) - { - goto case Tab; - } - else - { - cmd.current = cmd.commands[cmd.selected]; - } + cmd.current = cmd.commands[cmd.selected]; + g_ed_ctx.state = ES.RunCmd; } - - switch(cmd.current.type) - { - case CT.OpenFile: - { - if(cmd.selected >= 0 && cmd.selected < cmd.opt_strs.length) - { - OpenFile(ed, cmd.opt_strs[cmd.selected]); - } - } break; - case CT.SaveFile: - { - SaveFile(ed, GetParam(cmd)); - } break; - case CT.VSplit, CT.HSplit: - { - AddEditor(ctx, panel, cmd.current.type == CT.VSplit ? A2D.X : A2D.Y); - } break; - default: break; - } - - ResetCtx(ctx, ed); } goto CmdInputEnd; case Backspace: { - if(cmd.icount > 0 && cmd.buffer[--cmd.icount] == ' ') + if(cmd.icount > 0) { - cmd.current = cast(Command)NO_CMD; + cmd.icount -= 1; + if(cmd.icount == cmd.current.name.length) + { + cmd.current = cast(Command)NO_CMD; + } } } break; case Space: @@ -911,8 +874,20 @@ HandleCmdMode(EditorCtx* ctx, Panel* panel, UIInput* ev) } break; case Up, Down: { - cmd.selected = clamp(cmd.selected+(ev.key == Up ? -1 : +1), 0, cmd.opt_strs.length-1); + i32 incr = ev.key == Up ? -1 : +1; + if(cmd.opt_strs.length) + { + cmd.selected = clamp(cmd.selected+incr, 0, cmd.opt_strs.length-1); + } + else + { + cmd.selected = clamp(cmd.selected+incr, 0, cmd.commands.length); + } } break; + case Escape: + { + ResetCtx(); + } goto CmdInputEnd; default: { if(ev.text.length) @@ -923,9 +898,33 @@ HandleCmdMode(EditorCtx* ctx, Panel* panel, UIInput* ev) } break; } - if(cmd.current.type == CT.None && prev_count != cmd.icount) + if(cmd.current.type == CT.None) { - GetCommands(cmd); + cmd.opt_strs = []; + + if(prev_count != cmd.icount) + { + Reset(&cmd.arena); + + cmd.commands = Alloc!(Command)(&cmd.arena, CMD_LIST.length); + cmd.params = []; + u8[] str = cmd.buffer[0 .. cmd.icount]; + + u64 count; + if(str.length > 0) + { + for(u64 i = 0; i < CMD_LIST.length; i += 1) + { + if(StrContains!(true)(CMD_LIST[i].cmd, str) || StrContains!(true)(CMD_LIST[i].name, str)) + { + cmd.commands[count] = cast(Command)CMD_LIST[i]; + count += 1; + } + } + } + + cmd.commands = count ? cmd.commands[0 .. count] : cast(Command[])CMD_LIST; + } } else if(prev_count != cmd.icount) { @@ -933,7 +932,7 @@ HandleCmdMode(EditorCtx* ctx, Panel* panel, UIInput* ev) { case OpenFile: { - PopulateParams(cmd, ctx.file_names); + PopulateParams(cmd, g_ed_ctx.file_names); } break; case SaveFile: { diff --git a/src/editor/ui.d b/src/editor/ui.d index 15335a3..82fa5be 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -31,7 +31,7 @@ import core.stdc.stdio : sprintf; *********************************/ enum Vec4 BG_COL = SRGBVec4(0.13, 0.13, 0.13, 1.0); -enum Vec4 BG_HL_COL = SRGBVec4(0.24, 0.45, 0.81, 1.0); +enum Vec4 BG_HL_COL = SRGBVec4(0.12, 0.30, 0.63, 1.0); enum Vec4 BORDER_COL = SRGBVec4(0.254, 0.254, 0.266, 1.0); enum Vec4 BORDER_HL_COL = SRGBVec4(0.035, 0.549, 0.824, 1.0); enum Vec4 TEXT_COL = SRGBVec4(1.0); @@ -48,8 +48,8 @@ const f32 LINE_COUNT_PADDING = 4.0; __gshared UICtx g_ui_ctx; -//const u8[] FONT_BYTES = import("pc-9800.ttf"); -//const u8[] FONT_BYTES = import("NuberNextCondensed-DemiBold.otf"); +//u8[] FONT_BYTES = Arr!(u8)(import("pc-9800.ttf")); +//u8[] FONT_BYTES = Arr!(u8)(import("NuberNextCondensed-DemiBold.otf")); 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"); @@ -230,6 +230,12 @@ CtxMemberInfo(int i) enum MemberInfo CtxMemberInfo = MemberInfo(id: id, is_stack: is_stack, is_top: is_top); } +struct PushConst +{ + Mat4 projection; + u32 atlas_index; +} + struct UICtx { enum FO = FRAME_OVERLAP; @@ -241,6 +247,8 @@ struct UICtx Arena arena; Arena temp_arena; + Arena key_stack_arena; + Inputs* inputs; u64 frame; u64 f_idx; @@ -257,13 +265,15 @@ struct UICtx DescSetLayout desc_set_layout; DescSet[FO] desc_sets; PipelineLayout pipeline_layout; - Mat4 projection; + 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; @@ -280,6 +290,8 @@ struct UICtx f32 animation_rate; f32 fade_rate; + Stack!(UIItem*)* key_item_stack; + mixin UICtxParameter!(Vec4, "bg_col"); mixin UICtxParameter!(Vec4, "bg_col_end"); mixin UICtxParameter!(Vec4, "bg_hl_col"); @@ -440,8 +452,7 @@ struct Vertex f32 border_thickness; f32 edge_softness; f32 raised; - u32 texture; - u32 atlas_index; + u32 has_texture; } enum SettingType @@ -452,22 +463,18 @@ enum SettingType Toggle, } -Attribute[15] attributes = [ - { binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof }, - { binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.cols_end.offsetof }, - { binding: 0, location: 2, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof + f32.sizeof*0 }, - { binding: 0, location: 3, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof + f32.sizeof*1 }, - { binding: 0, location: 4, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof + f32.sizeof*2 }, - { binding: 0, location: 5, format: FMT.R_F32, offset: Vertex.corner_radius.offsetof + f32.sizeof*3 }, - { binding: 0, location: 6, format: FMT.RG_F32, offset: Vertex.dst_start.offsetof }, - { binding: 0, location: 7, format: FMT.RG_F32, offset: Vertex.dst_end.offsetof }, - { binding: 0, location: 8, format: FMT.RG_F32, offset: Vertex.src_start.offsetof }, - { binding: 0, location: 9, format: FMT.RG_F32, offset: Vertex.src_end.offsetof }, - { binding: 0, location: 10, format: FMT.R_F32, offset: Vertex.border_thickness.offsetof }, - { binding: 0, location: 11, format: FMT.R_F32, offset: Vertex.edge_softness.offsetof }, - { binding: 0, location: 12, format: FMT.R_F32, offset: Vertex.raised.offsetof }, - { binding: 0, location: 13, format: FMT.R_U32, offset: Vertex.texture.offsetof }, - { binding: 0, location: 14, format: FMT.R_U32, offset: Vertex.atlas_index.offsetof }, +Attribute[11] attributes = [ + { binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof }, + { binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.cols_end.offsetof }, + { binding: 0, location: 2, format: FMT.RGBA_F32, offset: Vertex.corner_radius.offsetof }, + { binding: 0, location: 3, format: FMT.RG_F32, offset: Vertex.dst_start.offsetof }, + { binding: 0, location: 4, format: FMT.RG_F32, offset: Vertex.dst_end.offsetof }, + { binding: 0, location: 5, format: FMT.RG_F32, offset: Vertex.src_start.offsetof }, + { binding: 0, location: 6, format: FMT.RG_F32, offset: Vertex.src_end.offsetof }, + { binding: 0, location: 7, format: FMT.R_F32, offset: Vertex.border_thickness.offsetof }, + { binding: 0, location: 8, format: FMT.R_F32, offset: Vertex.edge_softness.offsetof }, + { binding: 0, location: 9, format: FMT.R_F32, offset: Vertex.raised.offsetof }, + { binding: 0, location: 10, format: FMT.R_U32, offset: Vertex.has_texture.offsetof }, ]; union Rect @@ -486,7 +493,8 @@ struct UIKey u64 hash; } -enum bool StringType(T) = (is(T: string) || is(T: u8[]) || is(T: char[])); +enum bool KeyType(T) = (StringType!(T) || is(T == UIKey) || is(T == const(UIKey))); +enum bool ItemAndKeyType(T) = (KeyType!(T) || is(T == UIItem*)); void InitUICtx(PlatformWindow* window) @@ -524,6 +532,7 @@ InitUICtx(PlatformWindow* window) ctx.free_items = Alloc!(UIItem)(&arena); ctx.arena = arena; ctx.temp_arena = CreateArena(MB(1)); + ctx.key_stack_arena = CreateArena(MB(1)); ctx.transient_items = g_UI_NIL; ctx.font = OpenFont(cast(u8[])FONT_BYTES); ctx.tab_width = 2; @@ -544,7 +553,7 @@ InitUICtx(PlatformWindow* window) ]; ctx.desc_set_layout = CreateDescSetLayout(&ctx.rd, layout_bindings); - ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, Mat4.sizeof); + ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, PushConst.sizeof); for(u64 i = 0; i < FRAME_OVERLAP; i += 1) { @@ -614,6 +623,12 @@ GetFontAtlas(u32 size) return &GetFontGlyphs(size).abuf; } +f32 +GetLineHeight(u32 size) +{ + return GetFontAtlas(size).atlas.line_height; +} + FontGlyphs* GetFontGlyphs(u32 size) { @@ -636,12 +651,10 @@ GetFontGlyphs(u32 size) assert(ctx.font); - fg.abuf = CreateAtlas(&ctx.arena, ctx.font, cast(f32)size, ATLAS_DIMENSION); + fg.abuf = CreateAtlas(&ctx.arena, ctx.font, size, UVec2(ATLAS_DIMENSION)); fg.size = size; fg.loaded = false; - Logf("setting size %s to %s", size, i); - ctx.glyphs_to_load = true; ctx.glyph_sets_used += 1; } @@ -660,7 +673,6 @@ LoadFontGlyphs() FontGlyphs* fg = ctx.glyph_sets.ptr + i; if(!fg.loaded[fi]) { - Logf("loading %s frame index %s", i, fi); Transfer(&ctx.rd, &ctx.font_descs[fi][i].view, fg.abuf.data, ATLAS_DIMENSION, ATLAS_DIMENSION); fg.loaded[fi] = true; } @@ -712,7 +724,7 @@ MakeItem(Args...)(string str, Args args) } UIItem* -MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T) +MakeItem(T)(T k, UIFlags flags = UIF.None) if(KeyType!(T)) { UICtx* ctx = GetCtx(); UIItem* item = Get(k); @@ -724,7 +736,7 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T) if(Nil(ctx.root_first)) { ctx.root_first = ctx.root_last = item; - Push!("parent")(item); + Push!("parent")(item, false); } if(item.flags & (UIF.Window | UIF.FloatingWindow) || Nil(ctx.root_first)) { @@ -856,9 +868,54 @@ SetColorTransparency(UIItem* item, f32 v) item.border_col.a *= v; } -bool -Ready(UIItem* item) +static string +AssignItem(T, alias p)() { + import std.format : format; + + enum string id = __traits(identifier, p); + string result = ""; + static if(is(T == UIItem*)) + { + result = id; + } + else static if(is(T == UIKey) || StringType!(T)) + { + result = format("Get(%s)", id); + } + else static assert(false, "Unknown type for AssignItem"); + + return result; +} + +static string +AssignKey(T, alias p)() +{ + import std.format : format; + + enum string id = __traits(identifier, p); + string result = ""; + static if(is(T == UIItem*)) + { + result = format("%s.key", id); + } + else static if(is(T == UIKey) || is(T == const(UIKey))) + { + result = id; + } + else static if(StringType!(T)) + { + result = format("MakeKey(%s)", id); + } + else static assert(false, "Unknown type for AssignKey"); + + return result; +} + +bool +Ready(T)(T param) if(ItemAndKeyType!(T)) +{ + UIItem* item = mixin(AssignItem!(T, param)); return item.ready_t > 0.0001; } @@ -879,15 +936,8 @@ Hovered(bool current_frame, T)(T param) { UICtx* ctx = GetCtx(); bool result; - - static if(is(T == UIKey)) - { - UIKey key = param; - } - else static if(is(T == UIItem*)) - { - UIKey key = param.key; - } + + UIKey key = mixin(AssignKey!(T, param)); if(!ZeroKey(ctx.hover_key)) { @@ -909,9 +959,11 @@ Signal(UIItem* item) { UICtx* ctx = GetCtx(); - if(item.signal != UIS.None) + assert(!ZeroKey(item.key), "Zero key passed to Signal"); + + if(item.signal == UIS.None && !ZeroKey(item.key)) { - bool mouse_over = InBounds(ctx.mouse_pos, &item.rect); + bool mouse_over = KeyEq(ctx.hover_key, item.key); if(mouse_over) { @@ -924,13 +976,13 @@ Signal(UIItem* item) { bool taken; - if(item.flags & UIF.Clickable && i.type == UIE.Click && InBounds(ctx.mouse_pos, &item.rect)) + if(item.flags & UIF.Clickable && i.type == UIE.Click && mouse_over) { item.signal |= UIS.Clicked; taken = true; } - if(ZeroKey(ctx.drag_key) && item.flags & UIF.Draggable && i.type == UIE.DragStart && InBounds(i.pos, &item.rect)) + if(ZeroKey(ctx.drag_key) && item.flags & UIF.Draggable && i.type == UIE.DragStart && mouse_over) { item.signal |= UIS.Dragged; ctx.drag_key = item.key; @@ -967,6 +1019,17 @@ PushUIEvent(UICtx* ctx, UIInput input) DLLPush(&ctx.events, ev, null); } +void +DrawUI(UICtx* ctx, u32 atlas_index) +{ + if(ctx.pc.atlas_index != atlas_index) + { + DrawUI(ctx); + ctx.pc.atlas_index = atlas_index; + PushConstants(&ctx.rd, ctx.pipeline, &ctx.pc); + } +} + void DrawUI(UICtx* ctx) { @@ -1055,18 +1118,12 @@ BeginUI(Inputs* inputs) // Check current mouse target if(mouse_moved && !Nil(ctx.root_last)) { - UIItem* last = ctx.root_last; - while(!Nil(last.last)) - { - last = last.last; - } - UIKey hovered = ZeroKey(); - for(UIItem* item = last; !Nil(item); item = Recurse!(false)(item, g_UI_NIL)) + for(Stack!(UIItem*)* node = ctx.key_item_stack; node; node = node.next) { - if(InBounds(ctx.mouse_pos, &item.rect) && !ZeroKey(item.key)) + if(InBounds(ctx.mouse_pos, &node.value.rect)) { - hovered = item.key; + hovered = node.value.key; break; } } @@ -1082,7 +1139,8 @@ BeginUI(Inputs* inputs) } } - ctx.root_first = ctx.root_last = g_UI_NIL; + ctx.key_item_stack = null; + ctx.root_first = ctx.root_last = g_UI_NIL; // Clean up items KVPair!(UIHash, UIItem*)*[] items = GetAllNodes(&ctx.temp_arena, &ctx.items); @@ -1105,7 +1163,6 @@ BeginUI(Inputs* inputs) item = next; } - // Ctx state ctx.transient_items = g_UI_NIL; ctx.frame = next_frame; @@ -1145,10 +1202,10 @@ EndUI() if(ext != ctx.res) { ctx.res = ext; - Ortho(&ctx.projection, 0.0, 0.0, ext.x, ext.y, -10.0, 10.0); + Ortho(&ctx.pc.projection, 0.0, 0.0, ext.x, ext.y, -10.0, 10.0); } - PushConstants(&ctx.rd, ctx.pipeline, &ctx.projection); + PushConstants(&ctx.rd, ctx.pipeline, &ctx.pc); Bind(&ctx.rd, ctx.pipeline, ctx.desc_sets[ctx.f_idx]); } else @@ -1206,12 +1263,17 @@ EndUI() { if(item.size_info[axis].type == ST.Pixels) { - item.size.v[axis] = AxisPadding!(axis)(item) + item.size_info[axis].value; + item.size.v[axis] = item.size_info[axis].value; } else if(item.size_info[axis].type == ST.TextSize) { - f32 size = axis == A2D.X ? item.max_text_width : GetFontAtlas(item.text_size).atlas.line_height*item.text_lines.length; - item.size.v[axis] = size + AxisPadding!(axis)(item); + f32 size = axis == A2D.X ? item.max_text_width : GetFontAtlas(item.text_size).atlas.line_height * item.text_lines.length; + if(axis == A2D.Y) + { + // Logf("line height %f %f %f", size, floor(size), AxisPadding!(axis)(item)); + + } + item.size.v[axis] = floor(size) + AxisPadding!(axis)(item); } } @@ -1396,6 +1458,11 @@ EndUI() } } + if(!ZeroKey(item.key)) + { + PushKeyedItem(item); + } + RenderItem(ctx, item); } @@ -1434,17 +1501,9 @@ Clamp(UICtx* ctx, Vertex* v) } void -FocusItem(T)(T focus) if(is(T == UIKey) || StringType!T || is(T == UIItem*)) +FocusItem(T)(T focus) if(ItemAndKeyType!(T)) { - static if(is(T == UIItem*)) - { - assert(!Nil(focus)); - g_ui_ctx.focus_item = focus; - } - else - { - g_ui_ctx.focus_item = Get(focus); - } + g_ui_ctx.focus_item = mixin(AssignItem(T, focus)); } pragma(inline) void @@ -1513,9 +1572,16 @@ RenderItem(UICtx* ctx, UIItem* item) Vec4[] syntax_cols = ctx.syntax_colors[item.syntax_highlight]; f32 line_height = fg.abuf.atlas.line_height; + DrawUI(ctx, fg.index); + if(item.flags & UIF.VerticalAlignText) { - y_pos += (item.rect.p1.y - item.rect.p0.y - line_height) / 2.0; + f32 adj = floor((item.rect.p1.y - item.rect.p0.y - line_height) / 2.0); + y_pos += adj; + } + else + { + y_pos += item.padding[A2D.Y].x; } foreach(i; 0 .. item.text_lines.length) @@ -1530,7 +1596,6 @@ RenderItem(UICtx* ctx, UIItem* item) else if(item.flags & UIF.CenterAlignText) { x_pos = ((item.rect.p1.x-item.rect.p0.x - CalcTextWidth(str, &fg.abuf)) / 2.0) + item.padding[A2D.X].x; - Logf("%f", x_pos); } else { @@ -1545,7 +1610,7 @@ RenderItem(UICtx* ctx, UIItem* item) { u8 ch = str[j]; Glyph* g = ch < fg.abuf.atlas.glyphs.length ? fg.abuf.atlas.glyphs.ptr + ch : null; - DrawGlyph(item, g, fg.index, &x_pos, y_pos, line_height, fg.index, syntax_cols[tks[j]]); + DrawGlyph(item, g, &x_pos, y_pos, line_height, syntax_cols[tks[j]]); } } else @@ -1554,7 +1619,7 @@ RenderItem(UICtx* ctx, UIItem* item) { u8 ch = str[j]; Glyph* g = ch < fg.abuf.atlas.glyphs.length ? fg.abuf.atlas.glyphs.ptr + ch : null; - DrawGlyph(item, g, fg.index, &x_pos, y_pos, line_height, fg.index, item.text_col); + DrawGlyph(item, g, &x_pos, y_pos, line_height, item.text_col); } } @@ -1569,13 +1634,10 @@ RenderItem(UICtx* ctx, UIItem* item) { DrawUI(ctx); - Vec2 p0 = Clamp(item.rect.p0 - item.padding[A2D.X], Vec2(0.0), Vec2(ctx.res.x, ctx.res.y)); - Vec2 p1 = Clamp(item.rect.p1 + item.padding[A2D.Y], Vec2(0.0), Vec2(ctx.res.x, ctx.res.y)); - - u32 x = cast(u32)(scissor_x ? floor(p0.x) : 0); - u32 y = cast(u32)(scissor_y ? floor(p0.y) : 0); - u32 w = cast(u32)(scissor_x ? floor(p1.x) - x : ctx.res.x); - u32 h = cast(u32)(scissor_y ? floor(p1.y) - y : ctx.res.y); + i32 x = cast(i32)clamp(scissor_x ? floor(item.rect.p0.x) : 0, 0.0, ctx.res.x); + i32 y = cast(i32)clamp(scissor_y ? floor(item.rect.p0.y) : 0, 0.0, ctx.res.y); + i32 w = cast(i32)clamp(scissor_x ? floor(item.rect.p1.x) - x : ctx.res.x, 0.0, ctx.res.x); + i32 h = cast(i32)clamp(scissor_y ? floor(item.rect.p1.y) - y : ctx.res.y, 0.0, ctx.res.y); SetScissor(&ctx.rd, x, y, w, h); @@ -1607,6 +1669,41 @@ GetExtent() version(ENABLE_RENDERER) return RendererGetExtent(&g_ui_ctx.rd); else return [1280, 720]; } +void +PushKeyedItem(UIItem* item) +{ + UICtx* ctx = GetCtx(); + + assert(!Nil(item)); + assert(!ZeroKey(item.key)); + + Stack!(UIItem*)* node = Alloc!(Stack!(UIItem*))(&ctx.key_stack_arena); + + node.next = ctx.key_item_stack; + node.value = item; + + ctx.key_item_stack = node; +} + +void +PushDisplayStringCopy(T)(T param, bool auto_pop = true) +{ + PushDisplayString(ScratchAlloc(mixin(AssignStr!(T, param))), auto_pop); +} + +void +PushDisplayString(Args...)(string fmt, Args args) +{ + static if(is(Args[Args.length-1] == bool)) + { + PushDisplayString(Scratchf(fmt, args[0 .. Args.length-1]), args[Args.length-1]); + } + else + { + PushDisplayString(Scratchf(fmt, args)); + } +} + template StackIDs(string stack, string ctx = "ctx") { @@ -1623,7 +1720,13 @@ static string PushScope(string stack, string value)() { import std.conv; - return i"Push!(\"$(stack)\")($(value)); scope(exit) Pop!(\"$(stack)\")();".text; + return i"Push!(\"$(stack)\")($(value), false); scope(exit) Pop!(\"$(stack)\")();".text; +} + +struct UIPushInfo +{ + string s; // stack + string v; // value } static string @@ -1640,14 +1743,22 @@ PushOnce(UIPushInfo[] info)() return result; } -struct UIPushInfo +void +PushOnce(Args...)() { - string s; // stack - string v; // value + static assert(Args.length%2 == 0, "Must provide key/value pairs in the form of string/value."); + + static foreach(idx; 0 .. Args.length/2) + { + { + enum i = idx*2; + Push!(Args[i+0])(Args[i+1], true); + } + } } void -Push(string stack_str, T)(T value, bool auto_pop = false) +Push(string stack_str, T)(T value, bool auto_pop = true) { import std.string : replace; @@ -1668,10 +1779,17 @@ Push(string stack_str, T)(T value, bool auto_pop = false) } node.next = stack.top; - node.value = value; node.auto_pop = auto_pop; + static if(is(ST == string) && StringType!(T)) + { + node.value = Str(value); + } + else + { + node.value = value; + } - stack.top = node; + stack.top = node; } static string @@ -1750,7 +1868,7 @@ GenPushFuncs() } } - funcs ~= "pragma(inline) void " ~ fn_name ~ "(T)(T value, bool auto_pop = false){ Push!(\"" ~ info.id ~ "\")(value, auto_pop); }\n"; + funcs ~= "pragma(inline) void " ~ fn_name ~ "(T)(T value, bool auto_pop = true){ Push!(\"" ~ info.id ~ "\")(value, auto_pop); }\n"; } } } @@ -1953,8 +2071,9 @@ ZeroKey() } bool -ZeroKey(UIKey key) +ZeroKey(T)(T param) if(is(T == UIItem*) || is(T == UIKey)) { + UIKey key = mixin(AssignKey!(T, param)); return key.hash == 0 && key.hash_text.length == 0; } @@ -1982,19 +2101,11 @@ Get(Args...)(string str, Args args) } pragma(inline) UIItem* -Get(T)(T k) if(is(T: UIKey) || StringType!T) +Get(T)(T k) if(KeyType!(T)) { - static if(is(T: UIKey)) - { - UIKey key = k; - } - else - { - UIKey key = MakeKey(k); - } - - UIItem* item; + UIKey key = mixin(AssignKey!(T, k)); UICtx* ctx = GetCtx(); + UIItem* item; if(ZeroKey(key)) { @@ -2062,7 +2173,7 @@ GlyphWidth(Glyph* g, FontAtlasBuf* abuf) } pragma(inline) void -DrawGlyph(UIItem* item, Glyph* glyph, u32 atlas_index, f32* x_pos, f32 y, f32 line_height, u32 index, Vec4 col = Vec4(1.0)) +DrawGlyph(UIItem* item, Glyph* glyph, f32* x_pos, f32 y, f32 line_height, Vec4 col = Vec4(1.0)) { if(glyph) { @@ -2091,10 +2202,9 @@ DrawGlyph(UIItem* item, Glyph* glyph, u32 atlas_index, f32* x_pos, f32 y, f32 li v.dst_end = Vec2(*x_pos+glyph.plane_left+w, y_pos+h); v.cols = col; v.cols_end = col; - v.texture = true; - v.atlas_index = index; v.src_start = Vec2(glyph.atlas_left, glyph.atlas_top); v.src_end = Vec2(glyph.atlas_right, glyph.atlas_bottom); + v.has_texture = true; f32 end_x = *x_pos + advance; f32 width = end_x - *x_pos; diff --git a/src/editor/views.d b/src/editor/views.d index 4142afb..10f7a81 100644 --- a/src/editor/views.d +++ b/src/editor/views.d @@ -14,18 +14,28 @@ __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. + - Move scrolling behaviour into ui.d and externally be oblivious to it, e.g. just feed it all lines of text in a text buffer, will have to specifically handle extreme cases later but initially i think it should be fine +******/ + Vec4 BG_COL = Vec4(0.18, 0.18, 0.18, 1.0); Vec4 HL_BG_COL = Vec4(0.160, 0.533, 0.803, 1.0); Vec4 HL_BORDER_COL = Vec4(0.172, 0.643, 0.988, 1.0); Vec4 CMD_COL = Vec4(0.22, 0.22, 0.22, 1.0); Vec4 CMD_BORDER_COL = Vec4(0.48, 0.48, 0.48, 1.0); Vec4 CMD_INPUT_COL = Vec4(0.130, 0.420, 0.640, 1.0); -Vec4 GREY = Vec4(0.45, 0.45, 0.45, 1.0); +Vec4 GREY = Vec4(0.80, 0.80, 0.80, 1.0); Vec4 WHITE = HexCol(0xFFFFFF); Vec4 BLUE = HexCol(0x4EA9FF); Vec4 RED = HexCol(0xFF3268); Vec4 YELLOW = HexCol(0xF7C443); +const f32 CMD_X_PAD = 8.0; +const f32 CMD_Y_PAD = 4.0; +const u32 CMD_TITLE_PX = 14; +const u32 CMD_SUB_PX = 10; + shared static this() { g_NIL_PANEL = cast(Panel* )&g_nil_panel; @@ -70,7 +80,7 @@ LineCounterView(FontAtlasBuf* abuf, u64 max_line, u64 lines, i64 line_offset, f3 for(u64 i = line_offset; i < end_line && i < max_line; i += 1) { char[] buf = ScratchAlloc!(char)(ch_width); - Push!("display_string")(Scratchf("%s", i), true); + Push!("display_string")(Scratchf("%s", i)); MakeItem(ZERO, UIF.DrawText); } } @@ -80,7 +90,7 @@ EditorTextView(UIItem* editor, Panel* p, FontAtlasBuf* abuf, u64 lines, i64 line { Editor* ed = p.ed; - PushLayoutAxis(A2D.Y, true); + PushLayoutAxis(A2D.Y); f32 text_size = cast(f32)p.text_size; f32 clamp_y = cast(f32)(ed.buf.line_count-lines)*text_size; @@ -129,16 +139,12 @@ EditorTextView(UIItem* editor, Panel* p, FontAtlasBuf* abuf, u64 lines, i64 line mixin(PushScope!("size_info", q{ UISY(ST.TextSize) } )); mixin(PushScope!("syntax_highlight", q{ UISH.D } )); - // Do a thing like the bottom 3 colours on lost trail (arknights) on highlighting but make it slide to the right one colour every time you scroll down - u64 i = line_offset; for(LineBuffer* lb = GetLine(&ed.buf, i); !CheckNil(g_NIL_LINE_BUF, lb) && i < line_offset+lines; i += 1, lb = GetLine(&ed.buf, i)) { - enum UIPushInfo[] lc_info = [ - { "display_string", q{ Str(lb.text) } }, - { "syntax_tokens", q{ cast(u8[])(lb.style) } }, - ]; - mixin(PushOnce!(lc_info)); + string txt = Str(lb.text); + u8[] tokens = cast(u8[])(lb.style); + PushOnce!("display_string", txt, "syntax_tokens", tokens); UIItem* line = MakeItem(zero, UIF.DrawText); } @@ -163,6 +169,8 @@ EditorView(Panel* p) } else { + assert(ed); + UIKey ed_key = MakeKey("###ed_%s", ed.editor_id); UIItem* editor = Get(ed_key); @@ -202,7 +210,7 @@ EditorView(Panel* p) } } - for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i) && g_ed_ctx.focused_editor == p.ed; i = i.next) + for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i) && g_ed_ctx.focused_panel == p && !CmdModeActive(); i = i.next) { bool taken; @@ -229,6 +237,9 @@ EditorView(Panel* p) { DLLRemove(&ctx.events, i, g_UI_NIL_INPUT); } + + if(g_ed_ctx.focused_panel != p) break; + if(g_ed_ctx.state == ES.CmdPalette) break; } ed.cursor_pos = VecPos(&ed.buf); @@ -241,19 +252,12 @@ EditorView(Panel* p) ResetBuffer(&ed.buf); } -void -CommandPalette(CmdPalette* cmd, bool active) +UIItem* +CommandWindow(f32 w, f32 h, f32 x, f32 y, bool active) { - UICtx* ctx = GetCtx(); - Vec2 ext = GetExtent(); - FontAtlasBuf* abuf = &g_ui_ctx.glyph_sets[0].abuf; - - f32 w = ext.x*0.6; - f32 h = ext.y*0.7; - enum UIPushInfo[] cmd_params = [ { "layout_axis", q{ A2D.Y } }, - { "fixed_pos", q{ Vec2(ext.x*0.2, ext.y*0.1) } }, + { "fixed_pos", q{ Vec2(x, y) } }, { "bg_col", q{ BG_COL } }, { "border_col", q{ HL_BORDER_COL } }, { "border_thickness", q{ 4.0 } }, @@ -268,109 +272,179 @@ CommandPalette(CmdPalette* cmd, bool active) cmd_flags |= UIF.SetReady; } - UIItem* cmd_item = MakeItem("###cmd_palette", cmd_flags); + UIItem* window = MakeItem("###cmd_palette", cmd_flags); + Push!("parent")(window, false); - f32 padding_y = 4.0; + return window; +} - mixin(PushScope!("parent", q{ cmd_item } )); - mixin(PushScope!("padding", q{ Vec2(4.0, padding_y) })); +UIItem* +CommandInput(CmdPalette* cmd, string placeholder = "") +{ + string input_str = cmd.icount ? Str(cmd.buffer[0 .. cmd.icount]) : "Search..."; + Vec4 input_text_col = cmd.icount ? WHITE : GREY; enum UIPushInfo[] cmd_input_params = [ { "bg_col", q{ CMD_COL } }, { "border_col", q{ CMD_BORDER_COL } }, { "border_thickness", q{ 1.0 }}, - { "text_size", q{ 18 }}, - { "corner_radius", q{ Vec4(12.0, 12.0, 0.0, 0.0)}}, - { "edge_softness", q{ 1.0 }}, + { "text_size", q{ 14 }}, + { "corner_radius", q{ Vec4(12.0, 12.0, 0.0, 0.0) } }, + { "edge_softness", q{ 1.0 } }, { "size_info", q{ UISY(ST.TextSize) } }, - { "padding", q{ Vec2A2(6.0, 6.0, 10.0, 10.0)} }, - { "display_string", q{ Str(cmd.buffer[0 .. cmd.icount]) } }, + { "padding", q{ Vec2A2(6.0, 6.0, 6.0, 6.0)} }, + { "display_string", q{ input_str } }, + { "text_col", q{ input_text_col } }, ]; mixin(PushOnce!(cmd_input_params)); + return MakeItem("###cmd_input", UIF.DrawBorder|UIF.DrawBackground|UIF.DrawText|UIF.Overflow); +} - MakeItem(ZERO, UIF.DrawBorder|UIF.DrawBackground|UIF.DrawText|UIF.Overflow|UIF.VerticalAlignText); - - // the two sizes seems to fuck this up somehow - u32 title_px = 16; - u32 sub_px = 16; - f32 title_pad = 2.0f; - f32 opt_y_pad = 4.0; - f32 opt_x_pad = 8.0; - f32 opt_height = cast(f32)(title_px + sub_px) + opt_y_pad*2.0 + title_pad; - - enum UIPushInfo[] cmd_opts_box_params = [ - { "layout_axis", q{ A2D.Y }}, - { "border_col", q{ CMD_BORDER_COL } }, - { "border_thickness", q{ 1.0 } }, - { "corner_radius", q{ Vec4(0.0, 0.0, 12.0, 12.0) } }, - { "edge_softness", q{ 1.0 }}, - { "size_info", q{ UISY(ST.Pixels, h-opt_height) } }, - ]; - - mixin(PushOnce!(cmd_opts_box_params)); - - UIItem* opt_box = MakeItem(ZERO, UIF.DrawBorder); - - mixin(PushScope!("parent", q{ opt_box })); - mixin(PushScope!("padding", q{ Vec2A2(opt_x_pad, opt_x_pad, opt_y_pad, opt_y_pad) } )); - mixin(PushScope!("size_info", q{ UISY(ST.Pixels, opt_height) } )); - - u64 max_opts = cast(u64)ceil(h/opt_height); - +UIItem* +CommandOpt(T)(CmdPalette* cmd, T opt, u64 i) +{ Vec4[2] opt_cols = [ Vec4(0.22, 0.22, 0.22, 1.0), - Vec4(0.35, 0.35, 0.35, 1.0), + Vec4(0.31, 0.31, 0.31, 1.0), ]; - // Active should be highlights around the item like File Pilot (maybe not) + UIFlags flags = UIF.DrawBackground|UIF.AnimateHot|UIF.Clickable; - bool opts = cast(bool)cmd.opt_strs.length; - if(opts) + PushBgCol(opt_cols[i%2]); + PushLayoutAxis(A2D.Y); + + static if(is(T == string)) { - for(u64 i = 0; i < cmd.opt_strs.length && i < max_opts; i += 1) + PushDisplayString(cmd.opt_strs[i]); + + UIKey k = MakeKey("###opt_%s", i); + Hovered!(true)(k, &cmd.selected, i); + + UIItem* item = MakeItem(k, flags|UIF.DrawText|SetHot(cmd.selected == i)); + + Signal(item); + + return item; + } + else if(is(T == Command*)) + { + UIKey k = MakeKey("###optc_%s", i); + Hovered!(true)(k, &cmd.selected, i); + + UIItem* optc = MakeItem(k, flags|SetHot(cmd.selected == i)); + + Signal(optc); + + PushParent(optc, false); + PushSizeInfo(UISY(ST.TextSize), false); + + PushTextSize(CMD_TITLE_PX); + PushPadding(Vec2A2(CMD_X_PAD, CMD_X_PAD, CMD_Y_PAD, 2.0)); + PushDisplayStringCopy(cmd.commands[i].name); + + MakeItem(ZERO, UIF.DrawText); + + PushTextSize(CMD_SUB_PX); + PushTextCol(GREY); + PushPadding(Vec2A2(CMD_X_PAD, CMD_X_PAD, 0.0, 0.0)); + PushDisplayStringCopy(cmd.commands[i].desc); + + MakeItem(ZERO, UIF.DrawText); + + Pop!("parent", "size_info"); + + return optc; + } +} + +void +CommandPalette(CmdPalette* cmd, bool active) +{ + UICtx* ctx = GetCtx(); + Vec2 ext = GetExtent(); + + f32 w = ext.x*0.6; + f32 h = ext.y*0.7; + + UIItem* cmd_item = CommandWindow(w, h, ext.x*0.2, ext.y*0.1, active); + + UIItem* input = CommandInput(cmd, "Search..."); + + // Command Palette Options + { + u32 title_px = 14; + u32 sub_px = 10; + f32 title_size = GetLineHeight(title_px); + f32 sub_size = GetLineHeight(sub_px); + f32 title_pad = 2.0f; + f32 y_pad = 4.0; + f32 x_pad = 8.0; + f32 opt_height = cast(f32)(title_size + sub_size) + y_pad*2.0 + title_pad; + + enum UIPushInfo[] cmd_opts_box_params = [ + { "layout_axis", q{ A2D.Y }}, + { "border_col", q{ CMD_BORDER_COL } }, + { "border_thickness", q{ 1.0 } }, + { "corner_radius", q{ Vec4(0.0, 0.0, 12.0, 12.0) } }, + { "edge_softness", q{ 1.0 }}, + { "size_info", q{ UISY(ST.Pixels, h-opt_height) } }, + ]; + + mixin(PushOnce!(cmd_opts_box_params)); + + UIItem* opt_box = MakeItem(ZERO, UIF.DrawBorder); + + mixin(PushScope!("parent", q{ opt_box })); + mixin(PushScope!("size_info", q{ UISY(ST.Pixels, opt_height) } )); + + u64 max_opts = cast(u64)ceil(h/opt_height); + + bool opts = cast(bool)cmd.opt_strs.length; + if(opts) { - PushDisplayString(Str(cmd.opt_strs[i]), true); - PushBgCol(opt_cols[i%2], true); + mixin(PushScope!("padding", q{ Vec2A2(CMD_X_PAD, CMD_X_PAD, CMD_Y_PAD, CMD_Y_PAD) } )); - UIKey k = MakeKey("###opt_%s", i); - Hovered!(true)(k, &cmd.selected, i); - - MakeItem(k, UIF.DrawBackground|UIF.DrawText|UIF.AnimateHot|(cmd.selected == i ? UIF.SetHot : UIF.None)); + for(u64 i = 0; i < cmd.opt_strs.length && i < max_opts; i += 1) + { + UIItem* opt = CommandOpt(cmd, cmd.opt_strs[i], i); + } + } + else + { + for(u64 i = 0; i < cmd.commands.length && i < max_opts; i += 1) + { + UIItem* opt = CommandOpt(cmd, &cmd.commands[i], i); + } } } - else + + for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i); i = i.next) { - for(u64 i = 0; i < cmd.commands.length && i < max_opts; i += 1) + bool taken = HandleCmdMode(cmd, i); + + if(taken) { - PushBgCol(opt_cols[i%2], true); - PushLayoutAxis(A2D.Y, true); - - UIKey k = MakeKey("###optc_%s", i); - Hovered!(true)(k, &cmd.selected, i); - - UIItem* optc = MakeItem(k, UIF.DrawBackground|UIF.AnimateHot|(cmd.selected == i ? UIF.SetHot : UIF.None)); - PushParent(optc); - - PushSizeInfo(UISY(ST.TextSize)); - - PushTextSize(title_px, true); - PushTextCol(WHITE, true); - PushPadding(Vec2A2(opt_x_pad, opt_x_pad, 0.0, title_pad), true); - PushDisplayString(ScratchAlloc(cmd.commands[i].name), true); - - MakeItem(ZERO, UIF.DrawText); - - PushTextSize(sub_px, true); - PushTextCol(GREY, true); - PushPadding(Vec2A2(opt_x_pad, opt_x_pad, 0.0, 0.0), true); - PushDisplayString(ScratchAlloc(cmd.commands[i].desc), true); - - MakeItem(ZERO, UIF.DrawText); - - Pop!("parent", "size_info"); + DLLRemove(&ctx.events, i, g_UI_NIL_INPUT); } } + + Pop!("parent"); +} + +void +CommandTextInput(CmdPalette* cmd, string[] opts, void function(string) callback) +{ + UICtx* ctx = GetCtx(); + Vec2 ext = GetExtent(); + + +} + +UIFlags +SetHot(bool cond) +{ + return (cond ? UIF.SetHot : UIF.None); } UIItem* @@ -387,7 +461,7 @@ Container(bool push = true)(Axis2D axis, UISize[2] size_info) static if(push) { - Push!("parent")(item); + Push!("parent")(item, false); } return item; @@ -411,7 +485,7 @@ StatusBar(EditorCtx* ed_ctx) status = "Input"; status_col = YELLOW; } - else if(ed_ctx.state == ES.CmdOpen) + else if(ed_ctx.state == ES.CmdPalette) { status = "Command"; status_col = RED; diff --git a/src/shaders/gui.frag.glsl b/src/shaders/gui.frag.glsl index eb51179..43da360 100644 --- a/src/shaders/gui.frag.glsl +++ b/src/shaders/gui.frag.glsl @@ -66,7 +66,7 @@ void main() vec4 gamma = vec4(1.0/1.4); vec4 tex_color = vec4(1.0); - if(FDF.texture != 0) + if(FDF.has_texture != 0) { tex_color = texture(sampler2D(SpriteAtlas, SamplerNearest), FD.uv); } diff --git a/src/shaders/gui.layout b/src/shaders/gui.layout index 4c1c269..6e464eb 100644 --- a/src/shaders/gui.layout +++ b/src/shaders/gui.layout @@ -6,9 +6,10 @@ layout (set = 1, binding = 1) uniform sampler SamplerNearest; layout (push_constant) uniform Constants { mat4 projection; + uint atlas_index; } PC; -#define SpriteAtlas SpriteAtlasArray[FDF.atlas_index] +#define SpriteAtlas SpriteAtlasArray[PC.atlas_index] #ifdef VERT_SHADER # define FragData out struct FragDataOut @@ -22,11 +23,10 @@ layout (push_constant) uniform Constants { layout (location = 0) FragDataFlat { - uint texture; - uint atlas_index; + uint has_texture; } FDF; -layout (location = 2) FragData +layout (location = 1) FragData { vec4 color; vec4 color_end; diff --git a/src/shaders/gui.vert.glsl b/src/shaders/gui.vert.glsl index 62813b7..9911138 100644 --- a/src/shaders/gui.vert.glsl +++ b/src/shaders/gui.vert.glsl @@ -8,19 +8,15 @@ layout (location = 0) in vec4 in_col_start; layout (location = 1) in vec4 in_col_end; -layout (location = 2) in float in_corner_radius_x0y0; -layout (location = 3) in float in_corner_radius_x1y0; -layout (location = 4) in float in_corner_radius_x0y1; -layout (location = 5) in float in_corner_radius_x1y1; -layout (location = 6) in vec2 in_dst_start; -layout (location = 7) in vec2 in_dst_end; -layout (location = 8) in vec2 in_src_start; -layout (location = 9) in vec2 in_src_end; -layout (location = 10) in float border_thickness; -layout (location = 11) in float edge_softness; -layout (location = 12) in float raised; -layout (location = 13) in uint in_has_texture; -layout (location = 14) in uint in_atlas_index; +layout (location = 2) in vec4 in_corner_radius; +layout (location = 3) in vec2 in_dst_start; +layout (location = 4) in vec2 in_dst_end; +layout (location = 5) in vec2 in_src_start; +layout (location = 6) in vec2 in_src_end; +layout (location = 7) in float border_thickness; +layout (location = 8) in float edge_softness; +layout (location = 9) in float raised; +layout (location = 10) in uint has_texture; vec2 Vertices[4] = vec2[4]( vec2(-1.0, -1.0), @@ -57,10 +53,10 @@ void main() ); float corner_radius[4] = float[4]( - in_corner_radius_x0y0, - in_corner_radius_x0y1, - in_corner_radius_x1y0, - in_corner_radius_x1y1 + in_corner_radius.x, + in_corner_radius.z, + in_corner_radius.y, + in_corner_radius.w ); vec2 dst_verts_pct = vec2(bool(gl_VertexIndex >> 1) ? 1.0f : 0.0f, @@ -77,8 +73,7 @@ void main() FD.raised = raised; FD.border_thickness = border_thickness; FD.sdf_sample_pos = (2.0f * dst_verts_pct - 1.0f) * half_size; - FDF.texture = in_has_texture; - FDF.atlas_index = in_atlas_index; + FDF.has_texture = has_texture; vec4 v_pos = PC.projection * vec4(pos.x, pos.y, 0, 1); gl_Position = vec4(v_pos.x, v_pos.y, v_pos.z, 1);