diff --git a/src/editor/buffer.d b/src/editor/buffer.d index 634b288..1d8ff1a 100644 --- a/src/editor/buffer.d +++ b/src/editor/buffer.d @@ -777,16 +777,19 @@ Move(FlatBuffer* fb, Input key, Modifier md) case Up: { MoveToEmptyLine!(true)(fb); + taken = true; } break; case Down: { MoveToEmptyLine!(false)(fb); + taken = true; } break; case Left: { if(fb.pos > 0) { MoveToWordEdge!(false)(fb); + taken = true; } } break; case Right: @@ -794,6 +797,7 @@ Move(FlatBuffer* fb, Input key, Modifier md) if(fb.pos < fb.length) { MoveToWordEdge!(true)(fb); + taken = true; } } break; case Home: fb.pos = 0; taken = true; break; @@ -816,6 +820,7 @@ Move(FlatBuffer* fb, Input key, Modifier md) if(fb.pos > 0) { MoveToNextWord!(false)(fb); + taken = true; } } break; case Right: @@ -823,6 +828,7 @@ Move(FlatBuffer* fb, Input key, Modifier md) if(fb.pos < fb.length) { MoveToNextWord!(true)(fb); + taken = true; } } break; default: break; @@ -871,8 +877,6 @@ Move(FlatBuffer* fb, Input key, Modifier md) UpdateSelection(fb); - Logf("selection %s", fb.selection.v); - return taken; } diff --git a/src/editor/editor.d b/src/editor/editor.d index 97854aa..bfbc899 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -1,4 +1,5 @@ import dlib; +import dlib.util : Str; import vulkan; import std.format : sformat; @@ -94,16 +95,23 @@ struct Ctx alias rd_ctx this; } +struct TextInputBuffer +{ + u8[] data; + u64 length; +} + struct CmdPalette { - Arena arena; - Arena cmd_arena; - u8[] buffer; - u32 icount; - i64 selected; - u64 visible_opts; - Command current; - Parameter[] params; + TextInputBuffer text_input; + i64 selected; + Command[CMD_LIST.length] cmd_arr; + Command[] cmds; + + Arena exec_cmd_arena; + TextInputBuffer exec_cmd_input; + i64 exec_cmd_selected; + string[] exec_cmd_opts; } struct Editor @@ -148,7 +156,6 @@ struct Command string desc; string cmd; CmdType type; - bool hidden; union { CmdFn fn; @@ -211,12 +218,6 @@ enum EditState 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; -struct Parameter -{ - string value; - bool visible; -} - __gshared bool g_input_mode = false; __gshared Ctx g_ctx; @@ -225,13 +226,13 @@ const Command NO_CMD = { type: CT.None, }; -Command[5] CMD_LIST = [ +Command[20] CMD_LIST = [ { name: "Open", cmd: "open", desc: "Open a file in the focused editor view.", type: CT.Callback, - fn: &NotImplementedCallback, + fn: &OpenFileWindow, }, { name: "Save", @@ -252,17 +253,137 @@ Command[5] CMD_LIST = [ cmd: "vsplit", desc: "Split the current editor view vertically.", type: CT.Callback, - fn: &NotImplementedCallback, + fn: &SplitVertically, }, { name: "Horizontal Split", cmd: "hsplit", desc: "Split the current editor view horizontally.", type: CT.Callback, + fn: &SplitHorizontally, + }, + { + name: "Test Option 1", + cmd: "testopt1", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 2", + cmd: "testopt2", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 3", + cmd: "testopt3", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 4", + cmd: "testopt4", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 5", + cmd: "testopt5", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 6", + cmd: "testopt6", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 7", + cmd: "testopt7", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 8", + cmd: "testopt8", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 9", + cmd: "testopt9", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 10", + cmd: "testopt10", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 11", + cmd: "testopt11", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 12", + cmd: "testopt12", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 13", + cmd: "testopt13", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 14", + cmd: "testopt14", + desc: "This is a test option", + type: CT.Callback, + fn: &NotImplementedCallback, + }, + { + name: "Test Option 15", + cmd: "testopt15", + desc: "This is a test option", + type: CT.Callback, fn: &NotImplementedCallback, }, ]; +bool +SplitVertically(Ctx* ctx) +{ + AddPanel(ctx.focused_panel, A2D.X); + return true; +} + +bool +SplitHorizontally(Ctx* ctx) +{ + AddPanel(ctx.focused_panel, A2D.Y); + return true; +} + + bool NotImplementedCallback(Ctx* ctx) { @@ -311,7 +432,7 @@ Cycle(Inputs* inputs) if(ctx.state == ES.RunCmd) { - if(ctx.cmd.current.fn(ctx)) + if(ctx.cmd.cmds[ctx.cmd.selected].fn(ctx)) { ctx.state = ES.NormalMode; } @@ -334,12 +455,12 @@ InitCtx(PlatformWindow* window) { Ctx* ctx = &g_ctx; - ctx.window = window; - ctx.arena = CreateArena(MB(2)); - ctx.cmd.arena = CreateArena(MB(1)); - ctx.cmd.cmd_arena = CreateArena(MB(1)); - ctx.cmd.buffer = Alloc!(u8)(1024); - ctx.timer = CreateTimer(); + ctx.window = window; + ctx.arena = CreateArena(MB(2)); + ctx.cmd.text_input.data = Alloc!(u8)(1024); + ctx.cmd.exec_cmd_input.data = Alloc!(u8)(1024); + ctx.cmd.exec_cmd_arena = CreateArena(MB(1)); + ctx.timer = CreateTimer(); InitUI(ctx); @@ -517,6 +638,12 @@ OpenFile(Editor* ed, string file_name) } } +string +Str(TextInputBuffer text_buffer) +{ + return Str(text_buffer.data[0 .. text_buffer.length]); +} + Editor* CreateEditor() { @@ -595,13 +722,26 @@ ResetCtx(Editor* ed) ResetCtx(); } +void +ResetCmd(Ctx* ctx) +{ + ctx.cmd.text_input.length = 0; + ctx.cmd.selected = 0; + ctx.cmd.cmds = ctx.cmd.cmd_arr[0 .. $]; + ctx.cmd.exec_cmd_input.length = 0; + ctx.cmd.exec_cmd_selected = 0; + ctx.cmd.exec_cmd_opts = []; + ctx.cmd.cmds = CMD_LIST[0 .. $]; + + Reset(&ctx.cmd.exec_cmd_arena); +} + void ResetCtx() { - g_ctx.state = ES.NormalMode; - g_ctx.cmd.icount = 0; - g_ctx.cmd.current = cast(Command)NO_CMD; - g_ctx.cmd.selected = 0; + g_ctx.state = ES.NormalMode; + g_ctx.cmd.text_input.length = 0; + g_ctx.cmd.selected = 0; } bool @@ -741,16 +881,8 @@ HandleInputs(UIPanel* p, LinkedList!(UIInput)* inputs, bool hovered, bool focuse { if(Shift(node.md)) { - ctx.state = ES.CmdPalette; - ctx.cmd.icount = 0; - ctx.cmd.selected = 0; - ctx.cmd.params = []; - ctx.cmd.visible_opts = CMD_LIST.length; - - static foreach(i; 0 .. CMD_LIST.length) - { - CMD_LIST[i].hidden = false; - } + ctx.state = ES.CmdPalette; + ResetCmd(ctx); taken = true; } @@ -890,20 +1022,22 @@ CharCases() bool HandleInputMode(Ctx* ctx, UIPanel* p, UIInput* ev) { - bool taken = false; + bool taken; - switch(ev.key) + if(!taken) switch(ev.key) { mixin(CharCases()); case Input.Backspace: { Backspace(&p.ed.buf); + taken = true; } break; case Input.Escape: { ctx.state = ES.NormalMode; + taken = true; } break; - default: MoveCursor(p.ed, ev); break; + default: taken = MoveCursor(p.ed, ev); break; } return taken; @@ -999,17 +1133,22 @@ StrContains(bool begins_with)(string str, string match) } void -HandleCmdMode(Ctx* ctx) +HandleCmdMode(Ctx* ctx, bool* enter_hit, bool* buffer_changed, bool* selection_changed) { - UIPanel* panel = ctx.focused_panel; - Editor* ed = panel.ed; - CmdPalette* cmd = &ctx.cmd; + UIPanel* panel = ctx.focused_panel; + Editor* ed = panel.ed; + TextInputBuffer* text_buffer = ctx.state == ES.CmdPalette ? &ctx.cmd.text_input : &ctx.cmd.exec_cmd_input; + i64* selected = ctx.state == ES.CmdPalette ? &ctx.cmd.selected : &ctx.cmd.exec_cmd_selected; - bool buffer_changed; - for(UIInput* ev = ctx.events.first; ev && ctx.state == ES.CmdPalette; ev = ev.next) + for(UIInput* ev = ctx.events.first; ev; ev = ev.next) { bool taken; + if(ctx.state != ES.CmdPalette && ctx.state != ES.RunCmd) + { + break; + } + switch(ev.type) { case UIE.Press: @@ -1018,11 +1157,9 @@ HandleCmdMode(Ctx* ctx) { case Enter: { - if(cmd.current.type != CT.None) + if(ctx.state == ES.CmdPalette) { - cmd.current = CMD_LIST[cmd.selected]; - - switch(cmd.current.type) with(CmdType) + switch(ctx.cmd.cmds[ctx.cmd.selected].type) with(CmdType) { case Config: { @@ -1034,45 +1171,31 @@ HandleCmdMode(Ctx* ctx) } break; case Callback: { - assert(cmd.current.fn, "Callback type doesn't have a function pointer set"); + assert(ctx.cmd.cmds[ctx.cmd.selected].fn, "Callback type doesn't have a function pointer set"); ctx.state = ES.RunCmd; } break; default: break; } } + else + { + *enter_hit = true; + } } break; case Backspace: { - if(cmd.icount > 0) + if(text_buffer.length) { - cmd.icount -= 1; - if(cmd.icount == cmd.current.name.length) - { - cmd.current = cast(Command)NO_CMD; - } - - buffer_changed = true; + text_buffer.length -= 1; + *buffer_changed = true; } } break; case Up, Down: { - i32 incr = ev.key == Up ? -1 : +1; - i64 selected = cmd.selected; - for(i64 i = selected+incr; true; i += incr) - { - if(i < 0 || i >= CMD_LIST.length) - { - break; - } + i32 incr = ev.key == Up ? -1 : +1; + *selected = (*selected)+incr; - if(!CMD_LIST[i].hidden) - { - selected = i; - break; - } - } - - cmd.selected = selected; + *selection_changed = true; } break; case Escape: { @@ -1082,11 +1205,11 @@ HandleCmdMode(Ctx* ctx) { if(ev.text.length) { - Check(cmd, ev.text.length); - cmd.buffer[cmd.icount .. cmd.icount+ev.text.length] = cast(u8[])ev.text[0 .. $]; - cmd.icount += ev.text.length; + Check(text_buffer, ev.text.length); + text_buffer.data[text_buffer.length .. text_buffer.length+ev.text.length] = cast(u8[])ev.text[0 .. $]; + text_buffer.length += ev.text.length; - buffer_changed = true; + *buffer_changed = true; } } break; } @@ -1099,67 +1222,14 @@ HandleCmdMode(Ctx* ctx) DLLRemove(&ctx.events, ev, null); } } - - if(buffer_changed) - { - if(cmd.icount) - { - cmd.visible_opts = 0; - string buffer_text = Str(cmd.buffer[0 .. cmd.icount]); - static foreach(i; 0 .. CMD_LIST.length) - { - CMD_LIST[i].hidden = !(StrContains!(false)(CMD_LIST[i].cmd, buffer_text) || StrContains!(false)(CMD_LIST[i].name, buffer_text)); - cmd.visible_opts += !CMD_LIST[i].hidden; - } - - if(CMD_LIST[cmd.selected].hidden) - { - i64 selected = 0; - foreach(i; 0 .. CMD_LIST.length) - { - if(!CMD_LIST[i].hidden) - { - selected = i; - break; - } - } - - cmd.selected = selected; - } - } - else - { - static foreach(i; 0 .. CMD_LIST.length) - { - CMD_LIST[i].hidden = false; - } - cmd.visible_opts = CMD_LIST.length; - } - } } pragma(inline) void -Check(CmdPalette* cmd, u64 length) +Check(TextInputBuffer* text_buffer, u64 length) { - if(cmd.icount+length >= cmd.buffer.length) + if(text_buffer.length+length >= text_buffer.data.length) { - cmd.buffer = Realloc!(u8)(cmd.buffer, cmd.buffer.length*2); + text_buffer.data = Realloc!(u8)(text_buffer.data, text_buffer.data.length*2); } } -string -GetParam(CmdPalette* cmd) -{ - string param; - for(u64 i = cmd.current.name.length; i < cmd.icount; i += 1) - { - if(cmd.buffer[i] != ' ') - { - param = Str(cmd.buffer[i .. cmd.icount]); - break; - } - } - - return param; -} - diff --git a/src/editor/ui.d b/src/editor/ui.d index a7ba0cf..9be3c5c 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -75,6 +75,13 @@ __gshared Style SEP_STYLE = { hl_col: Vec4(0.2, 0.2, 0.2, 1.0), }; +__gshared Style SCROLLBAR_STYLE = { + col: GREY, + hl_col: WHITE, + corner_radius: Vec4(4.0f), + edge_softness: 1.0, +}; + __gshared Style CURSOR_STYLE = { col: Vec4(1.0), hl_col: Vec4(1.0), @@ -639,19 +646,19 @@ Panel(Ctx* ctx, UIPanel* panel) Scissor(ctx, rects[1], &style); text_rect = Pad(rects[1], PAD); - text_rect.p0.y += offset; - I64Vec2 cursor_pos = VecPos(&panel.ed.buf); - UIItem* cursor = MakeItem("###cursor_%s", panel.id); + I64Vec2 cursor_coords = VecPos(&panel.ed.buf); + LineBuffer* current_lbuf = GetLine(&panel.ed.buf, cursor_coords.y); + UIItem* cursor = MakeItem("###cursor_%s", panel.id); if(ctx.focused_panel == panel) { - i64 y_pos = cursor_pos.y - panel.ed.line_offset; + i64 y_pos = cursor_coords.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); + cursor.target_pos.x = text_rect.p0.x + CalcTextWidth(current_lbuf.text[0 .. cursor_coords.x], &fg.abuf); AnimatePos(cursor); @@ -663,6 +670,8 @@ Panel(Ctx* ctx, UIPanel* panel) DrawRect(ctx, cursor.rect, &CURSOR_STYLE); } + text_rect.p0.y += offset; + u64 i = start_ln; if(ctx.focused_panel == panel) @@ -671,18 +680,18 @@ Panel(Ctx* ctx, UIPanel* panel) { 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) + if(i == cursor_coords.y) { - string str = Str(lbuf.text[0 .. cursor_pos.x]); - u8[] tks = cast(u8[])lbuf.style[0 .. cursor_pos.x]; + string str = Str(lbuf.text[0 .. cursor_coords.x]); + u8[] tks = cast(u8[])lbuf.style[0 .. cursor_coords.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 .. $]; + str = Str(lbuf.text[cursor_coords.x .. $]); + tks = cast(u8[])lbuf.style[cursor_coords.x .. $]; DrawText(ctx, str, tks, fg, split_rect, TA.Left); } @@ -698,7 +707,7 @@ Panel(Ctx* ctx, UIPanel* panel) { 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); + DrawText(ctx, Str(lbuf.text), cast(u8[])lbuf.style, fg, text_rect, TA.Left, cursor_coords.y == i ? cursor_coords.x : -1); text_rect.p0.y += lheight; } } @@ -754,65 +763,94 @@ Panel(Ctx* ctx, UIPanel* panel) } void -CommandPalette(Ctx* ctx) +SearchInputWindow(T)(string hash_text, TextInputBuffer* text_input, T[] options, bool ready_cond, i64* selected, bool scroll_to_selected) if(is(T == Command) || is(T == string)) { - Vec2 extent = GetExtent(); - CmdPalette* cmd = &ctx.cmd; + Ctx* ctx = GetCtx(); + Vec2 extent = GetExtent(); + f32 x = extent.x * 0.1; + f32 y = extent.y * 0.2; + f32 w = extent.x * 0.8; + f32 h = extent.y * 0.6; - f32 x = extent.x * 0.1; - f32 y = extent.y * 0.2; - f32 w = extent.x * 0.8; - 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) + UIItem* window_item = MakeItem(hash_text); + SetReady(window_item, ready_cond); + if(window_item.ready_t > 0.0) { const f32 PAD = 2.0f; - FontGlyphs* fg = GetFontGlyphs(14); - FontGlyphs* sub_fg = GetFontGlyphs(12); + FontGlyphs* fg = GetFontGlyphs(14); + f32 lheight = fg.abuf.atlas.line_height; + static if(is(T == Command)) + { + FontGlyphs* sub_fg = GetFontGlyphs(12); + f32 sub_lheight = sub_fg.abuf.atlas.line_height; + } - 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); + window_item.p0 = Vec2(x, y); + window_item.p1 = Vec2(x+w, y+h); + window_item.size = Vec2(w, h); Style style = CMD_STYLE; - Rect[2] rects = Split(cmd_item.rect, lheight+PAD*4.0, A2D.Y); + Rect[2] rects = Split(window_item.rect, lheight+PAD*4.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); + DrawRect(ctx, rects[0], &style, 0.0, window_item.ready_t); + DrawBorder(ctx, rects[0], &style, 0.0, window_item.ready_t); Rect text_input_rect = rects[0]; text_input_rect.p0.y += PAD*2.0f; text_input_rect.p0.x += PAD*4.0f; - string input_str = cmd.icount ? Str(cmd.buffer[0 .. cmd.icount]) : "Type to search..."; - Vec4 input_col = cmd.icount ? WHITE : GREY; - DrawText(ctx, input_str, input_col, fg, text_input_rect, TA.Left, -1, cmd_item.ready_t); + string input_str = text_input.length ? Str(text_input.data[0 .. text_input.length]) : "Type to search..."; + Vec4 input_col = text_input.length ? WHITE : GREY; + DrawText(ctx, input_str, input_col, fg, text_input_rect, TA.Left, -1, window_item.ready_t); style.corner_radius.zw = CORNER_RADIUS; style.corner_radius.xy = 0.0; - DrawRect(ctx, rects[1], &style, 0.0, cmd_item.ready_t); + DrawRect(ctx, rects[1], &style, 0.0, window_item.ready_t); Rect border_rect = rects[1]; + Scissor(ctx, rects[1], &CMD_STYLE); + rects[1].p0 += style.border_thickness; rects[1].p1 -= style.border_thickness; - Rect opt_rect = rects[1]; - foreach(i; 0 .. CMD_LIST.length) + f32 full_opt_height = lheight+PAD*3.0f; + static if(is(T == Command)) { - if(CMD_LIST[i].hidden) continue; + full_opt_height += sub_lheight; + } - Rect[2] opt_rects = Split(opt_rect, lheight+sub_lheight+PAD*3.0f, A2D.Y); + f32 opt_section_height = rects[1].p1.y-rects[1].p0.y; + i64 start_opt_index = cast(i64)floor(window_item.scroll_offset.y/full_opt_height); + f32 opt_start_offset = window_item.scroll_offset.y%full_opt_height; + + if(scroll_to_selected) + { + f32 opt_start_pos = (*selected) * full_opt_height; + f32 opt_end_pos = opt_start_pos + full_opt_height; + + if(window_item.scroll_target.y > opt_start_pos) + { + window_item.scroll_target.y = opt_start_pos; + } + else if(window_item.scroll_target.y+opt_section_height < opt_end_pos) + { + window_item.scroll_target.y = opt_end_pos-opt_section_height; + } + } + + Rect opt_rect = rects[1]; + opt_rect.p0.y -= opt_start_offset; + + UIItem* selected_opt_item = g_UI_NIL; + if(start_opt_index < options.length) foreach(i; start_opt_index .. options.length) + { + Rect[2] opt_rects = Split(opt_rect, full_opt_height, A2D.Y); if(opt_rects[0].p0.y == opt_rects[0].p1.y) { break; @@ -821,32 +859,204 @@ CommandPalette(Ctx* ctx) 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.size = Vec2(window_item.size.x, opt_height); opt_item.rect = opt_rects[0]; - bool hovered = Hovered!(true)(opt_item, &cmd.selected, i) || cmd.selected == i; + bool hovered = Hovered!(true)(opt_item, selected, i) || *selected == i; SetHot(opt_item, hovered); - DrawRect(ctx, opt_rects[0], &CMD_OPT_STYLES[i%2], opt_item.hot_t, cmd_item.ready_t); + DrawRect(ctx, opt_rects[0], &CMD_OPT_STYLES[i%2], opt_item.hot_t, window_item.ready_t); if(opt_rects[0].p1.y - opt_rects[0].p0.y > PAD) { opt_rects[0].p0.y += PAD; opt_rects[0].p0.x += PAD*2.0f; - 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); + static if(is(T == Command)) + { + DrawText(ctx, options[i].name, WHITE, fg, opt_rects[0], TA.Left, -1, window_item.ready_t); + opt_rects[0].p0.y += lheight+PAD; + DrawText(ctx, options[i].desc, GREY, sub_fg, opt_rects[0], TA.Left, -1, window_item.ready_t); + } + static if(is(T == string)) + { + DrawText(ctx, options[i], WHITE, fg, opt_rects[0], TA.Left, -1, window_item.ready_t); + } } opt_rect = opt_rects[1]; } - DrawBorder(ctx, border_rect, &style, 0.0, cmd_item.ready_t); + f32 total_cmd_height = options.length*full_opt_height; + f32 scrollable_height = clamp(total_cmd_height-opt_section_height, 0.0, f32.max); - HandleCmdMode(ctx); + window_item.scroll_target.y = clamp(window_item.scroll_target.y, 0.0, scrollable_height); + + if(options.length && total_cmd_height > opt_section_height) + { + UIItem* scrollbar_item = MakeItem!(UIF.Drag|UIF.Priority|UIF.TargetLeniency)("%s_scroll", hash_text); + + f32 scrollbar_pct = opt_section_height/total_cmd_height; + f32 scrollbar_height = opt_section_height*scrollbar_pct; + f32 offset_pct = Remap(window_item.scroll_target.y, 0.0, scrollable_height, 0.0, 1.0); + f32 scrollable_range = opt_section_height-scrollbar_height; + f32 scrollbar_offset = offset_pct*scrollable_range; + f32 inner_width = rects[1].p1.x-rects[1].p0.x; + + Rect[2] scrollbar_rects = Split(rects[1], inner_width-10.0f, A2D.X); + + scrollbar_item.rect = scrollbar_rects[1]; + scrollbar_item.rect.p0.y += scrollbar_offset; + scrollbar_item.rect.p1.y = scrollbar_item.rect.p0.y+scrollbar_height; + scrollbar_item.rect.p1.x -= 2.0f; + + i32 scrolled; + for(UIInput* i = ctx.events.first; !Nil(i) && ready_cond; i = i.next) + { + if(i.type == UIE.Scroll) + { + scrolled += i.scroll; + DLLRemove(&ctx.events, i, g_UI_NIL_INPUT); + } + } + + IVec2 drag; + if(Dragged(ctx, scrollbar_item, &drag) || scrolled != 0) + { + drag.y -= scrolled*40; + + f32 mov_pct = Remap(drag.y, 0.0, scrollable_range, 0.0, 1.0); + offset_pct -= mov_pct; + offset_pct = clamp(offset_pct, 0.0, 1.0); + window_item.scroll_target.y = scrollable_height * offset_pct; + } + + DrawRect(ctx, scrollbar_item.rect, &SCROLLBAR_STYLE, 0.0f, window_item.ready_t); + } + + EndScissor(ctx); + + DrawBorder(ctx, border_rect, &style, 0.0, window_item.ready_t); } + else + { + window_item.scroll_target.y = 0.0; + } +} + +bool +OpenFileWindow(Ctx* ctx) +{ + bool exit; + CmdPalette* cmd = &ctx.cmd; + + if(!cmd.exec_cmd_opts.length && !cmd.exec_cmd_input.length) + { + cmd.exec_cmd_opts = ctx.file_names[0 .. $]; + } + + bool enter_hit, buffer_changed, selection_changed; + if(ctx.state == ES.RunCmd) + { + HandleCmdMode(ctx, &enter_hit, &buffer_changed, &selection_changed); + if(buffer_changed) + { + if(cmd.exec_cmd_input.length) + { + Reset(&cmd.exec_cmd_arena); + cmd.exec_cmd_opts = Alloc!(string)(ctx.file_names.length); + + u64 option_count; + string buffer_text = Str(cmd.exec_cmd_input); + foreach(i; 0 .. ctx.file_names.length) + { + if(StrContains!(false)(ctx.file_names[i], buffer_text)) + { + cmd.exec_cmd_opts[option_count++] = ctx.file_names[i]; + } + } + + cmd.exec_cmd_opts = cmd.exec_cmd_opts[0 .. option_count]; + } + else + { + cmd.exec_cmd_opts = ctx.file_names[0 .. $]; + } + } + + if(buffer_changed || selection_changed) + { + cmd.exec_cmd_selected = clamp(cmd.exec_cmd_selected, 0, cmd.exec_cmd_opts.length ? cmd.exec_cmd_opts.length-1 : 0); + } + + if(cmd.exec_cmd_opts.length) + { + string file_name = cmd.exec_cmd_opts[cmd.exec_cmd_selected]; + if(enter_hit && file_name.length) + { + OpenFile(ctx.focused_panel.ed, file_name); + exit = true; + } + } + } + SearchInputWindow( + "###open_file", + &cmd.exec_cmd_input, + cmd.exec_cmd_opts, + ctx.state == ES.RunCmd, + &cmd.exec_cmd_selected, + selection_changed + ); + + return exit; +} + +void +CommandPalette(Ctx* ctx) +{ + CmdPalette* cmd = &ctx.cmd; + + bool enter_hit, buffer_changed, selection_changed; + if(ctx.state == ES.CmdPalette) + { + HandleCmdMode(ctx, &enter_hit, &buffer_changed, &selection_changed); + + if(buffer_changed) + { + if(cmd.text_input.length) + { + u64 list_count; + + string buffer_text = Str(cmd.text_input); + static foreach(i; 0 .. CMD_LIST.length) + { + if(StrContains!(false)(CMD_LIST[i].cmd, buffer_text) || StrContains!(false)(CMD_LIST[i].name, buffer_text)) + { + cmd.cmd_arr[list_count++] = CMD_LIST[i]; + } + } + + cmd.cmds = cmd.cmd_arr[0 .. list_count]; + } + else + { + cmd.cmds = CMD_LIST[0 .. $]; + } + } + + if(buffer_changed || selection_changed) + { + cmd.selected = clamp(cmd.selected, 0, cmd.cmds.length ? cmd.cmds.length-1 : 0); + } + } + SearchInputWindow( + "###cmd_palette", + &cmd.text_input, + cmd.cmds, + ctx.state == ES.CmdPalette, + &cmd.selected, + selection_changed + ); } void @@ -866,7 +1076,7 @@ AddPanel(UIPanel* target, Axis2D axis) first.parent = second.parent = target; first.pct = second.pct = 0.5; - g_ctx.focus_key = second.key; + Focus(second); } else { @@ -892,7 +1102,7 @@ AddPanel(UIPanel* target, Axis2D axis) panel.pct = new_pct; - g_ctx.focus_key = panel.key; + Focus(panel); } } @@ -1096,7 +1306,9 @@ BeginUI(Inputs* inputs) ctx.events.first = ctx.events.last = null; bool mouse_moved; + static f32 mouse_down_elapsed = 0.0; static bool dragging; + static i32 final_y; static bool mouse_down; for(InputEvent* i = inputs.first; i; i = i.next) { @@ -1105,26 +1317,35 @@ BeginUI(Inputs* inputs) case Input.LeftClick: { mouse_down = i.pressed; - if(!mouse_down) + if(mouse_down) { - PushUIEvent(ctx, UIInput(dragging ? UIE.DragRelease : UIE.Click)); + PushUIEvent(ctx, UIInput(UIE.Click)); + } + else + { + if(dragging) + { + PushUIEvent(ctx, UIInput(UIE.DragRelease)); + final_y = 0; + } + mouse_down_elapsed = 0.0; + dragging = false; } - dragging = false; } break; case Input.MouseMotion: { mouse_moved = true; ctx.mouse_pos = IVec2(i.x, i.y); - if(!dragging && mouse_down) + if(!dragging && mouse_down_elapsed > 0.06f) { PushUIEvent(ctx, UIInput(type: UIE.DragStart, pos: IVec2(i.x, i.y))); + dragging = true; } - - dragging = mouse_down; if(dragging) { + final_y += i.rel_y; PushUIEvent(ctx, UIInput(type: UIE.Drag, rel: IVec2(i.rel_x, i.rel_y))); } } break; @@ -1145,6 +1366,11 @@ BeginUI(Inputs* inputs) } } + if(mouse_down) + { + mouse_down_elapsed += g_delta; + } + u64 next_frame = ctx.frame+1; // Check current mouse target @@ -1435,19 +1661,19 @@ DrawBorder(Ctx* ctx, Rect rect, Style* style, f32 hot_t = 0.0, f32 ready_t = 1.0 } void -DrawRect(Ctx* ctx, Rect rect, Style* style, f32 hot_t = 0.0, f32 ready_t = 1.0) +DrawRect(Ctx* ctx, Rect rect, Style* style, f32 hot_t = 0.0f, f32 ready_t = 1.0f) { Vec4 col = style.col; AnimateHot(hot_t, style.hl_col, &col); AnimateReady(ready_t, &col); - bool bordered = style.border_thickness > 0.0009; + bool bordered = style.border_thickness > 0.0009f; Vertex* v = GetVertex(ctx); v.dst_start = rect.p0 + style.border_thickness; v.dst_end = rect.p1 - style.border_thickness; v.cols = col; - v.corner_radius = style.corner_radius*(cast(f32)(bordered)*0.5); + v.corner_radius = style.corner_radius * (bordered ? 0.5f : 1.0f); v.edge_softness = style.edge_softness; Clamp(ctx, v); @@ -1707,7 +1933,16 @@ NewItem(Ctx* ctx) UIItem* MakeItem(UIFlags flags = UIF.None, Args...)(string str, Args args) { - char[] key = sformat(Alloc!(char)(&g_ctx.str_arenas[g_ctx.f_idx], cast(u64)(str.length*1.5)), str, args); + u64 len = cast(u64)(str.length*1.5); + static foreach(i; 0 .. Args.length) + { + static if(StringType!(Args[i])) + { + len += args[i].length; + } + } + + char[] key = sformat(Alloc!(char)(&g_ctx.str_arenas[g_ctx.f_idx], len), str, args); return MakeItem(key, flags); } @@ -1861,7 +2096,7 @@ DrawGlyph(Rect rect, Glyph* glyph, f32* x_pos, f32 y, f32 line_height, Vec4 col } else if(glyph.ch == '\t') { - *x_pos += advance * (ctx.tab_width - 1); + *x_pos += advance*ctx.tab_width; } else {