start work on command palette rework, cursor animations

This commit is contained in:
Matthew 2026-01-21 07:40:40 +11:00
parent f13daded03
commit a2c39e320f
2 changed files with 222 additions and 113 deletions

View File

@ -67,12 +67,18 @@ struct Ctx
UIKey hover_key; UIKey hover_key;
UIKey focus_key; UIKey focus_key;
UIPanel* base_panel;
UIPanel* focused_panel;
u64 last_hover_frame; u64 last_hover_frame;
f32 pos_rate;
f32 scroll_rate; f32 scroll_rate;
f32 animation_rate; f32 animation_rate;
f32 fade_rate; f32 fade_rate;
ConfigValue[CO.max] config_values;
EditState state; EditState state;
u8[128] input_buf; u8[128] input_buf;
u32 icount; u32 icount;
@ -111,6 +117,8 @@ struct Editor
Vec2 select_end; Vec2 select_end;
i64 line_offset; i64 line_offset;
i64 line_height; i64 line_height;
alias buf this;
} }
struct ChangeStacks struct ChangeStacks
@ -130,7 +138,7 @@ struct EditorChange
EditorChange* next; EditorChange* next;
} }
alias CmdFn = bool function(Ctx* ctx); alias CmdFn = bool function(Ctx* ctx); // return true when completed
struct Command struct Command
{ {
@ -141,27 +149,55 @@ struct Command
union union
{ {
CmdFn fn; CmdFn fn;
ConfigOpt opt;
} }
} }
struct Parameter union ConfigValue
{ {
string value; f32 _f32;
bool visible; u64 _u64;
Vec4 _vec4;
} }
enum ValueType
{
F32,
U64,
Vec4,
Max,
} alias VT = ValueType;
enum ConfigInputType
{
Text,
Slider,
Dropdown,
Max,
} alias CIT = ConfigInputType;
struct ConfigInfo
{
ValueType value_type;
ConfigInputType input_type;
}
enum ConfigOpt
{
CornerRadius,
TabWidth,
ScrollSpeed,
EditorPadding,
Max,
} alias CO = ConfigOpt;
enum CmdType enum CmdType
{ {
None, None,
OpenFile, Config,
SaveFile, Callback,
CreateFile, Hotkey,
VSplit, } alias CT = CmdType;
HSplit,
}
alias CT = CmdType;
enum EditState enum EditState
{ {
@ -170,14 +206,12 @@ enum EditState
CmdPalette, CmdPalette,
RunCmd, RunCmd,
SetPanelFocus, // if moving left/right move up parent tree until one is found with a2d.x, same thing for up/down a2d.y 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;
alias ES = EditState; struct Parameter
bool
CmdModeActive()
{ {
return g_ctx.state == ES.CmdPalette; string value;
bool visible;
} }
__gshared bool g_input_mode = false; __gshared bool g_input_mode = false;
@ -193,34 +227,48 @@ const Command[] CMD_LIST = [
name: "Open", name: "Open",
cmd: "open", cmd: "open",
desc: "Open a file in the focused editor view.", desc: "Open a file in the focused editor view.",
type: CT.OpenFile, type: CT.Callback,
//fn: &NotImplementedCallback,
}, },
{ {
name: "Save", name: "Save",
cmd: "save", cmd: "save",
desc: "Save the current file in the focused editor view.", desc: "Save the current file in the focused editor view.",
type: CT.SaveFile, type: CT.Callback,
}, },
{ {
name: "New File", name: "New File",
cmd: "new file", cmd: "new file",
desc: "Create a new file with a specified name and location.", desc: "Create a new file with a specified name and location.",
type: CT.CreateFile, type: CT.Callback,
}, },
{ {
name: "Vertical Split", name: "Vertical Split",
cmd: "vsplit", cmd: "vsplit",
desc: "Split the current editor view vertically.", desc: "Split the current editor view vertically.",
type: CT.VSplit, type: CT.Callback,
}, },
{ {
name: "Horizontal Split", name: "Horizontal Split",
cmd: "hsplit", cmd: "hsplit",
desc: "Split the current editor view horizontally.", desc: "Split the current editor view horizontally.",
type: CT.HSplit, type: CT.Callback,
}, },
]; ];
bool
NotImplementedCallback(Ctx* ctx)
{
Logf("Not yet implemented!");
return true;
}
bool
CmdModeActive()
{
return g_ctx.state == ES.CmdPalette;
}
bool bool
Active(EditState state) Active(EditState state)
{ {
@ -246,70 +294,24 @@ Cycle(Inputs* inputs)
ResetScratch(MB(4)); ResetScratch(MB(4));
g_delta = DeltaTime(&g_ctx.timer); g_delta = DeltaTime(&g_ctx.timer);
g_input_mode = Active(ES.InputMode); g_input_mode = Active(ES.InputMode);
BeginUI(inputs); BeginUI(inputs);
Ctx* ctx = GetCtx(); Ctx* ctx = GetCtx();
static UIPanel* panel; Panel(ctx, ctx.base_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(); EndUI();
} }
void
Focus(UIPanel* panel)
{
g_ctx.focused_panel = panel;
g_ctx.focus_key = panel.key;
}
void void
InitCtx(PlatformWindow* window) InitCtx(PlatformWindow* window)
{ {
@ -320,12 +322,12 @@ InitCtx(PlatformWindow* window)
ctx.cmd.arena = CreateArena(MB(1)); ctx.cmd.arena = CreateArena(MB(1));
ctx.cmd.cmd_arena = CreateArena(MB(1)); ctx.cmd.cmd_arena = CreateArena(MB(1));
ctx.cmd.buffer = Alloc!(u8)(1024); ctx.cmd.buffer = Alloc!(u8)(1024);
//ctx.base_panel = CreatePanel(CreateEditor());
ctx.timer = CreateTimer(); ctx.timer = CreateTimer();
InitUI(ctx); InitUI(ctx);
//FocusEditor(ctx.base_panel); ctx.base_panel = CreatePanel(CreateEditor());
Focus(ctx.base_panel);
if(getcwd() != "/") if(getcwd() != "/")
{ {
@ -664,12 +666,13 @@ MovePanelFocus(A2D axis, bool prev)(UIPanel* panel)
return result; return result;
} }
void bool
HandleInputs(UIPanel* p, LinkedList!(UIInput)* inputs, bool hovered, bool focused) HandleInputs(UIPanel* p, LinkedList!(UIInput)* inputs, bool hovered, bool focused)
{ {
Editor* ed = p.ed; Editor* ed = p.ed;
Ctx* ctx = &g_ctx; Ctx* ctx = &g_ctx;
FlatBuffer* fb = &ed.buf; FlatBuffer* fb = &ed.buf;
u64 initial_len = p.ed.buf.length;
u8[] cb_text; u8[] cb_text;
for(auto node = inputs.first; node != null; node = node.next) for(auto node = inputs.first; node != null; node = node.next)
@ -808,6 +811,8 @@ HandleInputs(UIPanel* p, LinkedList!(UIInput)* inputs, bool hovered, bool focuse
{ {
Insert(fb, cb_text, cb_text.length); Insert(fb, cb_text, cb_text.length);
} }
return initial_len != p.ed.buf.length;
} }
void void

View File

@ -168,8 +168,8 @@ struct Style
Vec4 border_col; Vec4 border_col;
Vec4 border_hl_col; Vec4 border_hl_col;
Vec4 corner_radius; Vec4 corner_radius;
f32 border_thickness; f32 border_thickness = 0.0;
f32 edge_softness; f32 edge_softness = 0.0;
} }
Style SEP_STYLE = { Style SEP_STYLE = {
@ -177,6 +177,11 @@ Style SEP_STYLE = {
hl_col: Vec4(0.2, 0.2, 0.2, 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 = { Style PANEL_STYLE = {
col: BG_COL, col: BG_COL,
hl_col: BG_COL, hl_col: BG_COL,
@ -218,6 +223,7 @@ struct UIItem
Vec2 scroll_offset; Vec2 scroll_offset;
Vec2 scroll_target; Vec2 scroll_target;
Vec2 scroll_size; Vec2 scroll_size;
Vec2 target_pos;
Axis2D axis; Axis2D axis;
u64 last_frame; u64 last_frame;
f32 ready_t; // Item visible f32 ready_t; // Item visible
@ -274,6 +280,7 @@ struct UIPanel
f32 pct; f32 pct;
u32 id; u32 id;
Editor* ed; Editor* ed;
bool move_text_with_cursor;
alias item this; alias item this;
} }
@ -525,6 +532,25 @@ EndScissor(Ctx* ctx)
ResetScissor(&ctx.rd); 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 void
Panel(Ctx* ctx, UIPanel* panel) Panel(Ctx* ctx, UIPanel* panel)
{ {
@ -560,6 +586,11 @@ Panel(Ctx* ctx, UIPanel* panel)
FontGlyphs* fg = GetFontGlyphs(12); FontGlyphs* fg = GetFontGlyphs(12);
FontAtlasBuf* abuf = &fg.abuf; FontAtlasBuf* abuf = &fg.abuf;
// Inputs
bool hovered = ctx.hover_key == panel.key;
bool focused = ctx.focus_key == panel.key && ctx.focused_panel == panel;
panel.move_text_with_cursor |= HandleInputs(panel, &ctx.events, hovered, focused);
f32 lheight = abuf.atlas.line_height; f32 lheight = abuf.atlas.line_height;
panel.scroll_target.y = cast(f32)(panel.ed.line_offset)*lheight; panel.scroll_target.y = cast(f32)(panel.ed.line_offset)*lheight;
@ -590,8 +621,8 @@ Panel(Ctx* ctx, UIPanel* panel)
for(u64 i = start_ln; i < max_ln; i += 1) for(u64 i = start_ln; i < max_ln; i += 1)
{ {
string line_num = Scratchf("%s", i); string line_num = Scratchf("%s", i+1);
DrawText(ctx, line_num, fg, Vec4(1.0), text_rect, TA.Right); DrawText(ctx, line_num, Vec4(1.0), fg, text_rect, TA.Right);
text_rect.p0.y += lheight; text_rect.p0.y += lheight;
} }
@ -608,20 +639,78 @@ Panel(Ctx* ctx, UIPanel* panel)
text_rect = Pad(rects[1], PAD); text_rect = Pad(rects[1], PAD);
text_rect.p0.y += offset; text_rect.p0.y += offset;
I64Vec2 cursor_pos = VecPos(&panel.ed.buf);
UIItem* cursor = MakeItem("###cursor_%s", panel.id);
if(ctx.focused_panel == panel)
{
i64 y_pos = cursor_pos.y - panel.ed.line_offset;
cursor.size.x = Active(ES.InputMode) ? 2.0 : fg.abuf.atlas.max_advance;
cursor.size.y = lheight;
cursor.target_pos.y = text_rect.p0.y + (y_pos*lheight);
cursor.target_pos.x = text_rect.p0.x + (cursor_pos.x*fg.abuf.atlas.max_advance);
AnimatePos(cursor);
if(cursor.p0 == cursor.target_pos)
{
panel.move_text_with_cursor = false;
}
DrawRect(ctx, cursor.rect, &CURSOR_STYLE);
}
u64 i = start_ln; u64 i = start_ln;
if(ctx.focused_panel == panel)
{
if(Active(ES.InputMode) && panel.move_text_with_cursor)
{
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)) 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); if(i == cursor_pos.y)
{
string str = Str(lbuf.text[0 .. cursor_pos.x]);
u8[] tks = cast(u8[])lbuf.style[0 .. cursor_pos.x];
DrawText(ctx, str, tks, fg, text_rect, TA.Left);
Rect split_rect = text_rect;
split_rect.p0.x = cursor.p0.x;
str = Str(lbuf.text[cursor_pos.x .. $]);
tks = cast(u8[])lbuf.style[cursor_pos.x .. $];
DrawText(ctx, str, tks, fg, split_rect, TA.Left);
}
else
{
DrawText(ctx, Str(lbuf.text), cast(u8[])lbuf.style, fg, text_rect, TA.Left);
}
text_rect.p0.y += lheight; text_rect.p0.y += lheight;
} }
}
else if(!Active(ES.InputMode))
{
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), cast(u8[])lbuf.style, fg, text_rect, TA.Left, cursor_pos.y == i ? cursor_pos.x : -1);
text_rect.p0.y += lheight;
}
}
else 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), cast(u8[])lbuf.style, fg, text_rect, TA.Left);
text_rect.p0.y += lheight;
}
}
ResetBuffer(&panel.ed.buf); ResetBuffer(&panel.ed.buf);
EndScissor(ctx); EndScissor(ctx);
DrawBorder(ctx, rects[1], &style); DrawBorder(ctx, rects[1], &style);
// Inputs
HandleInputs(panel, &ctx.events, ctx.hover_key == panel.key, ctx.focus_key == panel.key);
} }
else else
{ {
@ -773,6 +862,10 @@ CreatePanel(Editor* ed)
p.id = panel_id++; p.id = panel_id++;
p.item = MakeItem!(PANEL_FLAGS)("###panel_%s", p.id); p.item = MakeItem!(PANEL_FLAGS)("###panel_%s", p.id);
p.ed = ed;
p.pct = 1.0;
p.first = p.last = p.next = p.prev = p.parent = g_NIL_PANEL;
return p; return p;
} }
@ -1010,6 +1103,7 @@ BeginUI(Inputs* inputs)
ctx.animation_rate = 1.0 - pow(2.0, (-30.0f * g_delta)); ctx.animation_rate = 1.0 - pow(2.0, (-30.0f * g_delta));
ctx.fade_rate = 1.0 - pow(2.0, (-50.0f * g_delta)); ctx.fade_rate = 1.0 - pow(2.0, (-50.0f * g_delta));
ctx.scroll_rate = 1.0 - pow(2.0, (-60.0f * g_delta)); ctx.scroll_rate = 1.0 - pow(2.0, (-60.0f * g_delta));
ctx.pos_rate = 1.0 - pow(2.0, (-90.0f * g_delta));
version(ENABLE_RENDERER) version(ENABLE_RENDERER)
{ {
@ -1252,7 +1346,7 @@ enum TextAlign
} alias TA = TextAlign; } alias TA = TextAlign;
void void
DrawText(T, U)(Ctx* ctx, string text, T size_param, U col_param, Rect rect, TextAlign text_align, f32 ready = 1.0) DrawText(T, U)(Ctx* ctx, string text, U col_param, T size_param, Rect rect, TextAlign text_align, i64 hl_char = -1, f32 ready = 1.0)
if((is(T == FontGlyphs*) || (is(T: u32)) && is(U == Vec4) || is(U == u8[]))) if((is(T == FontGlyphs*) || (is(T: u32)) && is(U == Vec4) || is(U == u8[])))
{ {
static if(is(T == FontGlyphs*)) static if(is(T == FontGlyphs*))
@ -1293,6 +1387,10 @@ DrawText(T, U)(Ctx* ctx, string text, T size_param, U col_param, Rect rect, Text
u8 ch = text[j]; u8 ch = text[j];
Glyph* g = ch < glyphs.length ? glyphs.ptr + ch : null; Glyph* g = ch < glyphs.length ? glyphs.ptr + ch : null;
Vec4 col = SYNTAX_COLORS[tks[j]]; Vec4 col = SYNTAX_COLORS[tks[j]];
if(hl_char == j)
{
col = InvertCol(col);
}
AnimateReady(ready, &col); AnimateReady(ready, &col);
DrawGlyph(rect, g, &x, rect.p0.y, line_height, col); DrawGlyph(rect, g, &x, rect.p0.y, line_height, col);
@ -1307,11 +1405,17 @@ DrawText(T, U)(Ctx* ctx, string text, T size_param, U col_param, Rect rect, Text
{ {
u8 ch = text[j]; u8 ch = text[j];
Glyph* g = ch < glyphs.length ? glyphs.ptr + ch : null; Glyph* g = ch < glyphs.length ? glyphs.ptr + ch : null;
DrawGlyph(rect, g, &x, rect.p0.y, line_height, col); DrawGlyph(rect, g, &x, rect.p0.y, line_height, hl_char == j ? InvertCol(col) : col);
} }
} }
} }
pragma(inline) Vec4
InvertCol(Vec4 col)
{
return Vec4(1.0-col.r, 1.0-col.g, 1.0-col.b, col.a);
}
void void
BeginScissor(Ctx* ctx, UIItem* item, bool scissor_x = true, bool scissor_y = true) BeginScissor(Ctx* ctx, UIItem* item, bool scissor_x = true, bool scissor_y = true)
{ {