From b45da79331010db80f412df7edf3bde7dc61381a Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 25 Jan 2026 18:29:28 +1100 Subject: [PATCH] reimplement most of command palette --- src/editor/editor.d | 275 +++++++++++++++----------------------------- src/editor/ui.d | 272 +++++++++++++++++++++++++++++++------------ 2 files changed, 291 insertions(+), 256 deletions(-) diff --git a/src/editor/editor.d b/src/editor/editor.d index 9fbac76..adcfe24 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -97,8 +97,6 @@ struct CmdPalette Arena cmd_arena; u8[] buffer; u32 icount; - Command[] commands; - string[] opt_strs; i64 selected; Command current; Parameter[] params; @@ -222,37 +220,41 @@ const Command NO_CMD = { type: CT.None, }; -const Command[] CMD_LIST = [ +Command[5] CMD_LIST = [ { name: "Open", cmd: "open", desc: "Open a file in the focused editor view.", type: CT.Callback, - //fn: &NotImplementedCallback, + fn: &NotImplementedCallback, }, { name: "Save", cmd: "save", desc: "Save the current file in the focused editor view.", type: CT.Callback, + fn: &NotImplementedCallback, }, { name: "New File", cmd: "new file", desc: "Create a new file with a specified name and location.", type: CT.Callback, + fn: &NotImplementedCallback, }, { name: "Vertical Split", cmd: "vsplit", desc: "Split the current editor view vertically.", type: CT.Callback, + fn: &NotImplementedCallback, }, { name: "Horizontal Split", cmd: "hsplit", desc: "Split the current editor view horizontally.", type: CT.Callback, + fn: &NotImplementedCallback, }, ]; @@ -302,6 +304,16 @@ Cycle(Inputs* inputs) Panel(ctx, ctx.base_panel); + if(ctx.state == ES.RunCmd) + { + if(ctx.cmd.current.fn(ctx)) + { + ctx.state = ES.NormalMode; + } + } + + CommandPalette(ctx); + EndUI(); } @@ -367,31 +379,6 @@ GetCtx() return &g_ctx; } -/* -void -FocusEditor(Panel* p) -{ - assert(p.ed); - g_ctx.focused_panel = p; -} -*/ - -/* -Panel* -CreatePanel(Editor* ed = null) -{ - Panel* p = Alloc!(Panel)(&g_ctx.arena); - - p.layout_axis = A2D.Y; - p.ed = ed; - 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() { @@ -464,9 +451,9 @@ SaveFile(Editor* ed, string file_name) } } - u64 tab_width = GetCtx().tab_width; - u64 buf_size = ed.buf.length + ((tab_width-1) * tab_count); - u8[] temp_buf = ScratchAlloc!(u8)(buf_size); + u64 tab_width = GetCtx().tab_width; + u64 buf_size = ed.buf.length + ((tab_width-1) * tab_count); + u8[] temp_buf = ScratchAlloc!(u8)(buf_size); u64 buf_pos; for(u64 i = 0; i < ed.buf.length; i += 1) @@ -608,7 +595,6 @@ ResetCtx() { 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; } @@ -751,8 +737,8 @@ HandleInputs(UIPanel* p, LinkedList!(UIInput)* inputs, bool hovered, bool focuse if(Shift(node.md)) { ctx.state = ES.CmdPalette; - ctx.cmd.commands = cast(Command[])CMD_LIST; ctx.cmd.icount = 0; + ctx.cmd.selected = 0; ctx.cmd.params = []; taken = true; } @@ -990,126 +976,96 @@ 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) +void +HandleCmdMode(Ctx* ctx) { - bool taken; - Panel* panel = g_ctx.focused_panel; - Editor* ed = panel.ed; + UIPanel* panel = ctx.focused_panel; + Editor* ed = panel.ed; + CmdPalette* cmd = &ctx.cmd; - u64 prev_count = cmd.icount; - switch(ev.key) with(Input) + for(InputEvent* ev = ctx.inputs.first; ev && ctx.state == ES.CmdPalette; ev = ev.next) { - case Enter: - { - if(cmd.current.type == CT.None && cmd.commands.length > 0) + bool taken; + if(!ev.pressed) continue; + + switch(ev.key) with(Input) + { + case Enter: { - cmd.current = cmd.commands[cmd.selected]; - g_ctx.state = ES.RunCmd; - } - } goto CmdInputEnd; - case Backspace: - { - if(cmd.icount > 0) - { - cmd.icount -= 1; - if(cmd.icount == cmd.current.name.length) + if(cmd.current.type != CT.None) { - cmd.current = cast(Command)NO_CMD; + cmd.current = CMD_LIST[cmd.selected]; + + switch(cmd.current.type) with(CmdType) + { + case Config: + { + // TODO: implement + } break; + case Hotkey: + { + // TODO: implement + } break; + case Callback: + { + assert(cmd.current.fn, "Callback type doesn't have a function pointer set"); + ctx.state = ES.RunCmd; + } break; + default: break; + } } - } - } break; - case Space: - { - Check(cmd, 1); - cmd.buffer[cmd.icount++] = ' '; - } goto case Tab; - case Tab: - { - if(cmd.commands.length > 0) + } break; + case Backspace: { - cmd.current = cmd.commands[cmd.selected]; - cmd.buffer[0 .. cmd.current.name.length] = Arr!(u8)(cmd.current.name[0 .. $]); - cmd.icount = cast(u32)cmd.current.name.length; + if(cmd.icount > 0) + { + cmd.icount -= 1; + if(cmd.icount == cmd.current.name.length) + { + cmd.current = cast(Command)NO_CMD; + } + } + } break; + case Space: + { + Check(cmd, 1); cmd.buffer[cmd.icount++] = ' '; - } - } break; - case Up, Down: - { - i32 incr = ev.key == Up ? -1 : +1; - if(cmd.opt_strs.length) + } goto case Tab; + case Tab: { - 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) - { - cmd.buffer[cmd.icount .. cmd.icount+ev.text.length] = cast(u8[])ev.text[0 .. $]; - cmd.icount += ev.text.length; - } - } break; - } - - if(cmd.current.type == CT.None) - { - 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)) + if(cmd.current.type == CT.None) { - cmd.commands[count] = cast(Command)CMD_LIST[i]; - count += 1; + cmd.current = CMD_LIST[cmd.selected]; + cmd.buffer[0 .. cmd.current.name.length] = Arr!(u8)(cmd.current.name[0 .. $]); + cmd.icount = cast(u32)cmd.current.name.length; + cmd.buffer[cmd.icount++] = ' '; } - } - } - - cmd.commands = count ? cmd.commands[0 .. count] : cast(Command[])CMD_LIST; + } break; + case Up, Down: + { + i32 incr = ev.key == Up ? -1 : +1; + cmd.selected = clamp(cmd.selected+incr, 0, CMD_LIST.length-1); + } break; + case Escape: + { + ResetCtx(); + } break; + default: + { + if(ev.text.length) + { + cmd.buffer[cmd.icount .. cmd.icount+ev.text.length] = cast(u8[])ev.text[0 .. $]; + cmd.icount += ev.text.length; + } + } break; } - } - else if(prev_count != cmd.icount) - { - switch(cmd.current.type) with(CT) + + if(taken) { - case OpenFile: - { - PopulateParams(cmd, g_ctx.file_names); - } break; - case SaveFile: - { - string param = GetParam(cmd); - } break; - default: break; + DLLRemove(ctx.inputs, ev, null); } } - -CmdInputEnd: - - return taken; } -*/ pragma(inline) void Check(CmdPalette* cmd, u64 length) @@ -1136,46 +1092,3 @@ GetParam(CmdPalette* cmd) return param; } -void -PopulateParams(CmdPalette* cmd, string[] strs) -{ - string param = GetParam(cmd); - if(cmd.params.length == 0 || !param.length) - { - cmd.params = Alloc!(Parameter)(&cmd.cmd_arena, strs.length); - cmd.opt_strs = Alloc!(string)(&cmd.cmd_arena, strs.length); - - for(u64 i = 0; i < cmd.params.length; i += 1) - { - cmd.params[i].value = strs[i]; - cmd.params[i].visible = true; - cmd.opt_strs[i] = cmd.params[i].value; - } - } - - if(param.length) - { - cmd.opt_strs = Alloc!(string)(&cmd.cmd_arena, strs.length); - - u64 matches; - for(u64 i = 0; i < cmd.params.length; i += 1) - { - bool contains = StrContains!(false)(cmd.params[i].value, param); - if(contains) - { - cmd.opt_strs[matches] = cmd.params[i].value; - matches += 1; - } - - cmd.params[i].visible = contains; - } - - cmd.opt_strs = cmd.opt_strs[0 .. matches]; - } - - if(cmd.selected >= cmd.opt_strs.length) - { - cmd.selected = Max(cmd.opt_strs.length, 0); - } -} - diff --git a/src/editor/ui.d b/src/editor/ui.d index 78a0c21..b4f1e07 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -70,6 +70,51 @@ const PANEL_FLAGS = UIF.Click|UIF.Drag; const UI_COUNT = 5000; +__gshared Style SEP_STYLE = { + col: Vec4(0.0, 0.0, 0.0, 1.0), + hl_col: Vec4(0.2, 0.2, 0.2, 1.0), +}; + +__gshared Style CURSOR_STYLE = { + col: Vec4(1.0), + hl_col: Vec4(1.0), +}; + +__gshared 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, +}; + +__gshared 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 Style CMD_OPT_STYLE_0 = { + col: OPT_COL, + hl_col: BG_HL_COL, +}; + +enum Style CMD_OPT_STYLE_1 = { + col: OPT_ALT_COL, + hl_col: BG_HL_COL, +}; + +__gshared Style[2] CMD_OPT_STYLES = [ + CMD_OPT_STYLE_0, + CMD_OPT_STYLE_1, +]; + __gshared const UIItem g_ui_nil_item; __gshared UIItem* g_UI_NIL; __gshared const UIInput g_ui_nil_input; @@ -93,6 +138,7 @@ enum UIEvent DragStart = 1<<3, Press = 1<<4, Scroll = 1<<5, + AnimatePos = 1<<6, } alias UIE = UIEvent; struct UIInput @@ -172,46 +218,13 @@ struct Style f32 edge_softness = 0.0; } -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 CURSOR_STYLE = { - col: Vec4(1.0), - hl_col: Vec4(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 +enum UIFlags : u64 { None, - Ready = 1<<0, - Hot = 1<<1, - Active = 1<<2, - Click = 1<<3, - Drag = 1<<4, - Priority = 1<<5, - TargetLeniency = 1<<6, + Click = 1<<0, + Drag = 1<<1, + Priority = 1<<2, + TargetLeniency = 1<<3, } alias UIF = UIFlags; struct UIItem @@ -255,7 +268,7 @@ struct VPos struct Vertex { Vec4 cols; - Vec4 corner_radius; + Vec4 corner_radius; // x = top left, y = top right, z = bottom left, w = bottom right union { VPos[2] pos; @@ -532,25 +545,6 @@ EndScissor(Ctx* ctx) ResetScissor(&ctx.rd); } -void -AnimatePos(UIItem* item) -{ - if(item.p0 != item.target_pos) - { - item.p0 += g_ctx.pos_rate * (item.target_pos - item.p0); - - static foreach(i; 0 .. 2) - { - if(fabsf(item.p0.v[i] - item.target_pos.v[i]) < 2.0f) - { - item.p0.v[i] = item.target_pos.v[i]; - } - } - } - - item.p1 = item.p0+item.size; -} - void Panel(Ctx* ctx, UIPanel* panel) { @@ -752,9 +746,87 @@ Panel(Ctx* ctx, UIPanel* panel) } void -CommandPalette(Ctx* ctx, CmdPalette* cmd) +CommandPalette(Ctx* ctx) { - + Vec2 extent = GetExtent(); + CmdPalette* cmd = &ctx.cmd; + + f32 x = extent.x * 0.3; + f32 y = extent.y * 0.2; + f32 w = extent.x * 0.4; + f32 h = extent.y * 0.6; + + UIItem* cmd_item = MakeItem("###cmd_palette"); + SetReady(cmd_item, ctx.state == ES.CmdPalette); + if(cmd_item.ready_t > 0.0) + { + const f32 PAD = 2.0f; + + FontGlyphs* fg = GetFontGlyphs(14); + FontGlyphs* sub_fg = GetFontGlyphs(12); + + f32 lheight = fg.abuf.atlas.line_height; + f32 sub_lheight = sub_fg.abuf.atlas.line_height; + + cmd_item.p0 = Vec2(x, y); + cmd_item.p1 = Vec2(x+w, y+h); + cmd_item.size = Vec2(w, h); + + Style style = CMD_STYLE; + + Rect[2] rects = Split(cmd_item.rect, lheight+PAD*2.0, A2D.Y); + + style.corner_radius.zw = 0.0; + + DrawRect(ctx, rects[0], &style, 0.0, cmd_item.ready_t); + DrawBorder(ctx, rects[0], &style, 0.0, cmd_item.ready_t); + + style.corner_radius.zw = 1.0; + style.corner_radius.xy = 0.0; + + DrawRect(ctx, rects[1], &style, 0.0, cmd_item.ready_t); + + rects[1].p0 += style.border_thickness; + rects[1].p1 -= style.border_thickness; + + Rect opt_rect = rects[1]; + foreach(i; 0 .. CMD_LIST.length) + { + Rect[2] opt_rects = Split(opt_rect, lheight+sub_lheight+PAD*3.0f, A2D.Y); + if(opt_rects[0].p0.y == opt_rects[0].p1.y) + { + break; + } + + + UIItem* opt_item = MakeItem!(UIF.Click|UIF.Priority)("###opt_%s", i); + + f32 opt_height = opt_rects[0].p1.y-opt_rects[1].p0.y; + opt_item.size = Vec2(cmd_item.size.x, opt_height); + opt_item.rect = opt_rects[0]; + + bool hovered = Hovered!(true)(opt_item, &cmd.selected, i) || cmd.selected == i; + SetHot(opt_item, hovered); + + DrawRect(ctx, opt_rects[0], &CMD_OPT_STYLES[i%2], opt_item.hot_t, cmd_item.ready_t); + + if(opt_rects[0].p1.y - opt_rects[0].p0.y > PAD) + { + opt_rects[0].p0 += PAD; + DrawText(ctx, CMD_LIST[i].name, WHITE, fg, opt_rects[0], TA.Left, -1, cmd_item.ready_t); + + opt_rects[0].p0.y += lheight+PAD; + + DrawText(ctx, CMD_LIST[i].desc, GREY, sub_fg, opt_rects[0], TA.Left, -1, cmd_item.ready_t); + } + + opt_rect = opt_rects[1]; + } + + DrawBorder(ctx, rects[1], &style, 0.0, cmd_item.ready_t); + + HandleCmdMode(ctx); + } } void @@ -1149,7 +1221,7 @@ FindHoverKey(bool priority)(Ctx* ctx) for(UIItem* item = ctx.last_item; !Nil(item); item = item.draw_next) { - if(item.flags & UIF.Click|UIF.Drag) + 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; @@ -1270,13 +1342,13 @@ AnimateReady(Args...)(f32 ready, Args cols) { static foreach(i; 0 .. Args.length) { - static if(is(typeof(Args[i]) == f32*)) + static if(is(Args[i] == f32*)) { - *(cols[i]) *= item.ready_t; + *(cols[i]) *= ready; } - else static if(is(typeof(Args[i]) == Vec4*)) + else static if(is(Args[i] == Vec4*)) { - cols[i].a *= item.ready_t; + cols[i].a *= ready; } } } @@ -1600,7 +1672,7 @@ pragma(inline) UIItem* MakeItem(T)(T k, UIFlags flags = UIF.None) if(KeyType!(T)) { UIKey key = mixin(AssignKey!(T, k)); - Ctx* ctx = GetCtx(); + Ctx* ctx = GetCtx(); UIItem* item; Result!(UIItem*) res = ctx.items[key.hash]; @@ -1620,21 +1692,22 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(KeyType!(T)) item.draw_next = ctx.last_item; ctx.last_item = item; - static foreach(axis; A2D.min .. A2D.max) + if(item.scroll_offset != item.scroll_target) { - if(fabsf(item.scroll_offset.v[axis] - item.scroll_target.v[axis]) > 0.0009f) + static foreach(axis; A2D.min .. A2D.max) { - 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) + if(fabsf(item.scroll_offset.v[axis] - item.scroll_target.v[axis]) > 0.0009f) { - item.scroll_offset.v[axis] = item.scroll_target.v[axis]; + 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; @@ -1643,6 +1716,55 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(KeyType!(T)) return item; } +void +AnimatePos(UIItem* item) +{ + if(item.p0 != item.target_pos) + { + item.p0 += g_ctx.pos_rate * (item.target_pos - item.p0); + + static foreach(axis; A2D.min .. A2D.max) + { + if(fabsf(item.p0.v[axis] - item.target_pos.v[axis]) < 2.0f) + { + item.p0.v[axis] = item.target_pos.v[axis]; + } + } + } + + item.p1 = item.p0+item.size; +} + +void +SetReady(UIItem* item, bool ready) +{ + item.ready_t += g_ctx.fade_rate * (cast(f32)(ready) - item.ready_t); + + if(ready && item.ready_t > 0.9998) + { + item.ready_t = 1.0; + } + else if(item.ready_t < 0.0009) + { + item.ready_t = 0.0; + } +} + +void +SetHot(UIItem* item, bool hot) +{ + item.hot_t += g_ctx.animation_rate * (cast(f32)(hot) - item.hot_t); + + if(hot && item.hot_t > 0.9998) + { + item.hot_t = 1.0; + } + else if(item.hot_t < 0.0009) + { + item.hot_t = 0.0; + } +} + f32 CalcTextWidth(T, U)(T text, U param) if((is(T == string) || StringType!T) && (is(U: u32) || is(U == FontAtlasBuf*) )) {