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,20 +67,26 @@ struct Ctx
UIKey hover_key;
UIKey focus_key;
UIPanel* base_panel;
UIPanel* focused_panel;
u64 last_hover_frame;
f32 pos_rate;
f32 scroll_rate;
f32 animation_rate;
f32 fade_rate;
EditState state;
u8[128] input_buf;
u32 icount;
u64 editor_id_incr;
Timer timer;
CmdPalette cmd;
string[] file_names;
u64 panel_id;
ConfigValue[CO.max] config_values;
EditState state;
u8[128] input_buf;
u32 icount;
u64 editor_id_incr;
Timer timer;
CmdPalette cmd;
string[] file_names;
u64 panel_id;
alias rd_ctx this;
}
@ -111,6 +117,8 @@ struct Editor
Vec2 select_end;
i64 line_offset;
i64 line_height;
alias buf this;
}
struct ChangeStacks
@ -130,7 +138,7 @@ struct EditorChange
EditorChange* next;
}
alias CmdFn = bool function(Ctx* ctx);
alias CmdFn = bool function(Ctx* ctx); // return true when completed
struct Command
{
@ -140,28 +148,56 @@ struct Command
CmdType type;
union
{
CmdFn fn;
CmdFn fn;
ConfigOpt opt;
}
}
struct Parameter
union ConfigValue
{
string value;
bool visible;
f32 _f32;
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
{
None,
OpenFile,
SaveFile,
CreateFile,
VSplit,
HSplit,
}
alias CT = CmdType;
Config,
Callback,
Hotkey,
} alias CT = CmdType;
enum EditState
{
@ -170,14 +206,12 @@ enum EditState
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;
alias ES = EditState;
bool
CmdModeActive()
struct Parameter
{
return g_ctx.state == ES.CmdPalette;
string value;
bool visible;
}
__gshared bool g_input_mode = false;
@ -193,34 +227,48 @@ const Command[] CMD_LIST = [
name: "Open",
cmd: "open",
desc: "Open a file in the focused editor view.",
type: CT.OpenFile,
type: CT.Callback,
//fn: &NotImplementedCallback,
},
{
name: "Save",
cmd: "save",
desc: "Save the current file in the focused editor view.",
type: CT.SaveFile,
type: CT.Callback,
},
{
name: "New File",
cmd: "new file",
desc: "Create a new file with a specified name and location.",
type: CT.CreateFile,
type: CT.Callback,
},
{
name: "Vertical Split",
cmd: "vsplit",
desc: "Split the current editor view vertically.",
type: CT.VSplit,
type: CT.Callback,
},
{
name: "Horizontal Split",
cmd: "hsplit",
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
Active(EditState state)
{
@ -245,71 +293,25 @@ Cycle(Inputs* inputs)
{
ResetScratch(MB(4));
g_delta = DeltaTime(&g_ctx.timer);
g_delta = DeltaTime(&g_ctx.timer);
g_input_mode = Active(ES.InputMode);
BeginUI(inputs);
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);
Panel(ctx, ctx.base_panel);
EndUI();
}
void
Focus(UIPanel* panel)
{
g_ctx.focused_panel = panel;
g_ctx.focus_key = panel.key;
}
void
InitCtx(PlatformWindow* window)
{
@ -320,12 +322,12 @@ InitCtx(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(CreateEditor());
ctx.timer = CreateTimer();
InitUI(ctx);
//FocusEditor(ctx.base_panel);
ctx.base_panel = CreatePanel(CreateEditor());
Focus(ctx.base_panel);
if(getcwd() != "/")
{
@ -664,13 +666,14 @@ MovePanelFocus(A2D axis, bool prev)(UIPanel* panel)
return result;
}
void
bool
HandleInputs(UIPanel* p, LinkedList!(UIInput)* inputs, bool hovered, bool focused)
{
Editor* ed = p.ed;
Ctx* ctx = &g_ctx;
FlatBuffer* fb = &ed.buf;
u8[] cb_text;
u64 initial_len = p.ed.buf.length;
u8[] cb_text;
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);
}
return initial_len != p.ed.buf.length;
}
void

View File

@ -168,8 +168,8 @@ struct Style
Vec4 border_col;
Vec4 border_hl_col;
Vec4 corner_radius;
f32 border_thickness;
f32 edge_softness;
f32 border_thickness = 0.0;
f32 edge_softness = 0.0;
}
Style SEP_STYLE = {
@ -177,6 +177,11 @@ Style SEP_STYLE = {
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,
@ -200,13 +205,13 @@ Style CMD_STYLE = {
enum UIFlags
{
None,
Ready = 1<<0,
Hot = 1<<1,
Active = 1<<2,
Click = 1<<3,
Drag = 1<<4,
Priority = 1<<5,
TargetLeniency = 1<<6,
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
@ -218,6 +223,7 @@ struct UIItem
Vec2 scroll_offset;
Vec2 scroll_target;
Vec2 scroll_size;
Vec2 target_pos;
Axis2D axis;
u64 last_frame;
f32 ready_t; // Item visible
@ -274,6 +280,7 @@ struct UIPanel
f32 pct;
u32 id;
Editor* ed;
bool move_text_with_cursor;
alias item this;
}
@ -525,6 +532,25 @@ 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)
{
@ -560,6 +586,11 @@ Panel(Ctx* ctx, UIPanel* panel)
FontGlyphs* fg = GetFontGlyphs(12);
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;
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)
{
string line_num = Scratchf("%s", i);
DrawText(ctx, line_num, fg, Vec4(1.0), text_rect, TA.Right);
string line_num = Scratchf("%s", i+1);
DrawText(ctx, line_num, Vec4(1.0), fg, text_rect, TA.Right);
text_rect.p0.y += lheight;
}
@ -608,20 +639,78 @@ Panel(Ctx* ctx, UIPanel* panel)
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))
I64Vec2 cursor_pos = VecPos(&panel.ed.buf);
UIItem* cursor = MakeItem("###cursor_%s", panel.id);
if(ctx.focused_panel == panel)
{
DrawText(ctx, Str(lbuf.text), fg, cast(u8[])lbuf.style, text_rect, TA.Left);
text_rect.p0.y += lheight;
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;
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))
{
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;
}
}
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);
EndScissor(ctx);
DrawBorder(ctx, rects[1], &style);
// Inputs
HandleInputs(panel, &ctx.events, ctx.hover_key == panel.key, ctx.focus_key == panel.key);
}
else
{
@ -773,7 +862,11 @@ CreatePanel(Editor* ed)
p.id = panel_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;
}
@ -1010,6 +1103,7 @@ BeginUI(Inputs* inputs)
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.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)
{
@ -1252,7 +1346,7 @@ enum TextAlign
} alias TA = TextAlign;
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[])))
{
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];
Glyph* g = ch < glyphs.length ? glyphs.ptr + ch : null;
Vec4 col = SYNTAX_COLORS[tks[j]];
if(hl_char == j)
{
col = InvertCol(col);
}
AnimateReady(ready, &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];
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
BeginScissor(Ctx* ctx, UIItem* item, bool scissor_x = true, bool scissor_y = true)
{