reimplement most of command palette

This commit is contained in:
Matthew 2026-01-25 18:29:28 +11:00
parent a2c39e320f
commit b45da79331
2 changed files with 291 additions and 256 deletions

View File

@ -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()
{
@ -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,25 +976,45 @@ 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)
{
UIPanel* panel = ctx.focused_panel;
Editor* ed = panel.ed;
CmdPalette* cmd = &ctx.cmd;
for(InputEvent* ev = ctx.inputs.first; ev && ctx.state == ES.CmdPalette; ev = ev.next)
{
bool taken;
Panel* panel = g_ctx.focused_panel;
Editor* ed = panel.ed;
if(!ev.pressed) continue;
u64 prev_count = cmd.icount;
switch(ev.key) with(Input)
{
case Enter:
{
if(cmd.current.type == CT.None && cmd.commands.length > 0)
if(cmd.current.type != CT.None)
{
cmd.current = cmd.commands[cmd.selected];
g_ctx.state = ES.RunCmd;
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;
}
} goto CmdInputEnd;
}
} break;
case Backspace:
{
if(cmd.icount > 0)
@ -1027,9 +1033,9 @@ HandleCmdMode(CmdPalette* cmd, UIInput* ev)
} goto case Tab;
case Tab:
{
if(cmd.commands.length > 0)
if(cmd.current.type == CT.None)
{
cmd.current = cmd.commands[cmd.selected];
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++] = ' ';
@ -1038,19 +1044,12 @@ HandleCmdMode(CmdPalette* cmd, UIInput* ev)
case Up, Down:
{
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);
}
cmd.selected = clamp(cmd.selected+incr, 0, CMD_LIST.length-1);
} break;
case Escape:
{
ResetCtx();
} goto CmdInputEnd;
} break;
default:
{
if(ev.text.length)
@ -1061,56 +1060,13 @@ HandleCmdMode(CmdPalette* cmd, UIInput* ev)
} break;
}
if(cmd.current.type == CT.None)
if(taken)
{
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;
DLLRemove(ctx.inputs, ev, null);
}
}
}
cmd.commands = count ? cmd.commands[0 .. count] : cast(Command[])CMD_LIST;
}
}
else if(prev_count != cmd.icount)
{
switch(cmd.current.type) with(CT)
{
case OpenFile:
{
PopulateParams(cmd, g_ctx.file_names);
} break;
case SaveFile:
{
string param = GetParam(cmd);
} break;
default: break;
}
}
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);
}
}

View File

@ -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;
}
}
}
@ -1620,6 +1692,8 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(KeyType!(T))
item.draw_next = ctx.last_item;
ctx.last_item = item;
if(item.scroll_offset != item.scroll_target)
{
static foreach(axis; A2D.min .. A2D.max)
{
if(fabsf(item.scroll_offset.v[axis] - item.scroll_target.v[axis]) > 0.0009f)
@ -1633,8 +1707,7 @@ MakeItem(T)(T k, UIFlags flags = UIF.None) if(KeyType!(T))
}
}
}
}
}
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*) ))
{