diff --git a/src/editor/buffer.d b/src/editor/buffer.d index e05c4ab..4e5c934 100644 --- a/src/editor/buffer.d +++ b/src/editor/buffer.d @@ -8,13 +8,15 @@ struct FlatBuffer { Tokenizer tk; Arena arena; - I64Vec2 pos; u8[] data; + Arena ls_arena; + u64[] line_starts; u64 length; u64 lf_count; i64 buf_pos; i64 offset; i64 rows; + bool dirty; } struct LineBuffers @@ -57,6 +59,7 @@ CreateFlatBuffer(u8[] data) FlatBuffer fb = { arena: CreateArena(MB(1)), + ls_arena: CreateArena(KB(512)), data: buf, length: cast(u64)data.length, lf_count: lf_count, @@ -64,11 +67,37 @@ CreateFlatBuffer(u8[] data) fb.tk = CreateTokenizer(&fb); - Tokenize(&fb); + Fix(&fb); return fb; } +void +Fix(FlatBuffer* buffer) +{ + with(buffer) + { + Reset(&ls_arena); + line_starts = AllocArray!(u64)(&ls_arena, lf_count+1); + line_starts[0] = 0; + + u64 ls_idx = 1; + for(u64 i = 0; i < length; i += 1) + { + if(data[i] == '\n') + { + line_starts[ls_idx] = i+1; + ls_idx += 1; + } + + } + + dirty = false; + + Tokenize(buffer); + } +} + LineBuffers CreateLineBuffers(u64 arena_size) { @@ -78,42 +107,41 @@ CreateLineBuffers(u64 arena_size) } void -Insert(FlatBuffer* buffer, u8[] insert, u64 length, u64 pos) +Insert(FlatBuffer* fb, u8[] insert, u64 length, u64 pos) { - Logf("%s %s %s", buffer.length, length, buffer.data.length); - if(buffer.length + length > buffer.data.length) + if(fb.length + length > fb.data.length) { - FlatBuffer new_buf = CreateFlatBuffer(buffer.data); - MFreeArray(buffer.data); - *buffer = new_buf; + FlatBuffer new_buf = CreateFlatBuffer(fb.data); + MFreeArray(fb.data); + *fb = new_buf; } + u64 prev_lf = fb.lf_count; for(u64 i = 0; i < length; i += 1) { - buffer.buf_pos += 1; - buffer.pos.x += 1; + fb.buf_pos += 1; if(insert.ptr[i] == '\n') { - buffer.lf_count += 1; - buffer.pos.y += 1; - buffer.pos.x = 0; + fb.lf_count += 1; } } - u64 temp_len = buffer.length-pos; - u8[] temp = AllocArray!(u8)(&buffer.arena, temp_len); + u64 temp_len = fb.length-pos; + u8[] temp = AllocArray!(u8)(&fb.arena, temp_len); - temp[0 .. temp_len] = buffer.data[pos .. pos+temp_len]; - buffer.data[pos .. pos+length] = insert[0 .. length]; + temp[0 .. temp_len] = fb.data[pos .. pos+temp_len]; + fb.data[pos .. pos+length] = insert[0 .. length]; pos += length; - buffer.data[pos .. pos+temp_len] = temp[0 .. temp_len]; + fb.data[pos .. pos+temp_len] = temp[0 .. temp_len]; - buffer.length += length; + fb.length += length; - Reset(&buffer.arena); + Fix(fb); - AdjustOffset(buffer); + Reset(&fb.arena); + + AdjustOffset(fb); } void @@ -122,12 +150,60 @@ Insert(FlatBuffer* buffer, u8[] insert, u64 length) Insert(buffer, insert, length, buffer.buf_pos); } -void -Insert(FlatBuffer* buffer, u8[] insert, u64 length, I64Vec2 pos) +pragma(inline) u64 +CurrentLine(FlatBuffer* fb) { - assert(pos.y <= buffer.lf_count, "Insert failure: y provided is greater than lf_count"); + return LineFromPos(fb, fb.buf_pos); +} - Insert(buffer, insert, length, VecToPos(buffer, pos)); +pragma(inline) u64 +CurrentCol(FlatBuffer* fb) +{ + return LinePos(fb, fb.buf_pos); +} + +pragma(inline) u64 +LinePos(FlatBuffer* fb, u64 pos) +{ + u64 line = LineFromPos(fb, pos); + return pos - fb.line_starts[line]; +} + +pragma(inline) U64Vec2 +VecPos(FlatBuffer* fb) +{ + return U64Vec2(CurrentCol(fb), CurrentLine(fb)); +} + +pragma(inline) u64 +LineFromPos(FlatBuffer* fb, u64 pos) +{ + u64 line = 0; + for(u64 i = 0; i < fb.line_starts.length; i += 1) + { + if(fb.line_starts[i] > pos) + { + break; + } + + line = i; + } + + return line; +} + +pragma(inline) u64 +LineLength(FlatBuffer* fb, u64 line) +{ + u64 len = 0; + if(line < fb.line_starts.length) + { + u64 start = fb.line_starts[line]; + u64 end = fb.line_starts.length-1 > line ? fb.line_starts[line+1] : fb.length; + len = end-start; + } + + return len; } void @@ -145,89 +221,94 @@ GetLines(FlatBuffer* buffer, LineBuffers* linebufs, u64 start_line, u64 length) linebufs.first = Alloc!(LineBuffer)(&linebufs.arena); linebufs.count = 0; - i64 start = -1; - u64 line = 0; + u64 total_lines = buffer.line_starts.length; + start_line = Min(start_line, total_lines); + u64 end_line = Min(start_line + length, total_lines); + if(buffer.length == 0) { linebufs.first.text = AllocArray!(u8)(&linebufs.arena, 1); linebufs.first.text[0] = 0; } + else if(start_line == end_line) with(buffer) + { + u64 start_of_line = line_starts[start_line]; + u64 len = LineLength(buffer, start_line); + + linebufs.first.text = AllocArray!(u8)(&linebufs.arena, len); + linebufs.first.text = data[start_of_line .. start_of_line+len]; + linebufs.count += 1; + } else with(linebufs) { LineBuffer* current = first; - for(u64 i = 0; i < buffer.length; i += 1) + for(u64 i = start_line; i < end_line; i += 1) { - if(count == length) + u64 start = buffer.line_starts[i]; + u64 len = LineLength(buffer, i); + + if(len > 0) { - break; + current.text = AllocArray!(u8)(&arena, len); + current.text[0 .. len] = buffer.data[start .. start+len]; + count += 1; + + current.next = Alloc!(LineBuffer)(&arena); + current = current.next; } - bool new_line = (buffer.data.ptr[i] == '\n'); - if(line < start_line && new_line) - { - line += 1; - continue; - } - - if(line < start_line) - { - continue; - } - - if(start < 0 && new_line) + if(i == buffer.lf_count && buffer.data[buffer.length-1] == '\n') { current.text = AllocArray!(u8)(&arena, 1); - current.text[0] = '\n'; + current.text[0] = 0; count += 1; current.next = Alloc!(LineBuffer)(&arena); - current = current.next; - continue; } - - if(start < 0) - { - start = cast(i64)i; - } - - if(new_line) - { - current.text = AllocArray!(u8)(&arena, i-start); - current.text[0 .. i-start] = buffer.data[start .. i]; - count += 1; - start = -1; - - current.next = Alloc!(LineBuffer)(&arena); - current = current.next; - - continue; - } - - if(i == buffer.length-1) - { - current.text = AllocArray!(u8)(&arena, (buffer.length-start)); - current.text[0 .. buffer.length-start] = buffer.data[start .. buffer.length]; - - current.next = Alloc!(LineBuffer)(&arena); - current = current.next; - - count += 1; - } - } - - if(buffer.length > 0 && buffer.data[buffer.length-1] == '\n') - { - current.text = AllocArray!(u8)(&linebufs.arena, 1); - current.text[0] = 0; - count += 1; } } } void -Move(FlatBuffer* buffer, Input key, Modifier md) +MoveUp(FlatBuffer* fb) +{ + MoveUp(fb, LinePos(fb, fb.buf_pos)); +} + +void +MoveUp(FlatBuffer* fb, u64 col) +{ + u64 line = CurrentLine(fb); + if(line > 0) + { + u64 end = fb.line_starts[line]-1; + line -= 1; + fb.buf_pos = Min(fb.line_starts[line]+col, end); + } +} + +void +MoveDown(FlatBuffer* fb) +{ + MoveDown(fb, LinePos(fb, fb.buf_pos)); +} + +void +MoveDown(FlatBuffer* fb, u64 col) +{ + u64 line = CurrentLine(fb); + if(line+1 < fb.line_starts.length) + { + line += 1; + fb.buf_pos = fb.line_starts[line]; + u64 end = fb.line_starts.length > line+1 ? fb.line_starts[line+1]-1 : fb.length; + fb.buf_pos = Min(fb.buf_pos+col, end); + } +} + +void +Move(FlatBuffer* fb, Input key, Modifier md) { - Logf("key: %s", key); if(md & (MD.LeftShift | MD.RightShift)) { switch(key) @@ -278,336 +359,62 @@ Move(FlatBuffer* buffer, Input key, Modifier md) { case Input.Down: { - if(buffer.pos.y < buffer.lf_count) - { - i64 x = buffer.pos.x; - for(;;) - { - if(buffer.data[buffer.buf_pos] == '\n') - { - Walk!(WalkDir.Forward)(buffer); - break; - } - - Walk!(WalkDir.Forward)(buffer); - } - - for(;;) - { - if(buffer.data[buffer.buf_pos] == '\n') - { - break; - } - - if(buffer.pos.x == x) - { - break; - } - - if(buffer.buf_pos == buffer.length) - { - break; - } - - Walk!(WalkDir.Forward)(buffer); - } - } + MoveDown(fb); } break; case Input.Up: { - if(buffer.pos.y > 0) - { - i64 new_l = 0; - i64 x = buffer.pos.x; - for(;;) - { - if(buffer.buf_pos == 0) - { - break; - } - - Walk!(WalkDir.Backward)(buffer); - - if(buffer.data[buffer.buf_pos] == '\n') - { - new_l += 1; - if(new_l == 2) - { - Walk!(WalkDir.Forward)(buffer); - break; - } - } - } - - Logf("sol reached %s", buffer.pos.v); - - for(;;) - { - if(buffer.data[buffer.buf_pos] == '\n') - { - break; - } - - if(buffer.pos.x >= x) - { - break; - } - - if(buffer.buf_pos == buffer.length) - { - break; - } - - Walk!(WalkDir.Forward)(buffer); - } - } + MoveUp(fb); } break; case Input.Left: { - if(buffer.pos.x != 0) + if(fb.buf_pos > 0 && fb.data[fb.buf_pos-1] != '\n') { - Walk!(WalkDir.Backward)(buffer); + fb.buf_pos -= 1; } } break; case Input.Right: { - if(buffer.buf_pos < buffer.length && buffer.data[buffer.buf_pos] != '\n') + if(fb.buf_pos < fb.length && fb.data[fb.buf_pos] != '\n') { - Walk!(WalkDir.Forward)(buffer); + fb.buf_pos += 1; } } break; default: break; } } - AdjustOffset(buffer); - Logf("buf_pos %s pos %s", buffer.buf_pos, buffer.pos.v); - - //VerifyPosition(buffer, key, md); + AdjustOffset(fb); + U64Vec2 p = VecPos(fb); + Logf("buf_pos %s pos %s", fb.buf_pos, p.v); } void -AdjustOffset(FlatBuffer* buffer) +AdjustOffset(FlatBuffer* fb) { - with(buffer) + with(fb) { - i64 screen_pos = pos.y - offset; + i64 screen_pos = CurrentLine(fb) - offset; i64 start = 1; i64 end = rows-1; if(offset > 0 && screen_pos < start) { - Logf("rows %s offset %s adjusting by %s - %s", rows, offset, screen_pos, start); offset += screen_pos - start; } else if(screen_pos > end) { - Logf("rows %s offset %s adjusting by %s - %s", rows, offset, screen_pos, end); offset += screen_pos - end; } } } -u64 -VecToPos(FlatBuffer* buffer, I64Vec2 vec) -{ - u64 pos, line, col; - bool closer_to_start = abs(buffer.pos.y - vec.x) > vec.x; - if(closer_to_start) - { - for(; pos < buffer.length; pos += 1) - { - if(vec.y == line) - { - break; - } - - if(buffer.data.ptr[pos] == '\n') - { - line += 1; - } - } - - for(; pos < buffer.length; pos += 1) - { - if(buffer.data.ptr[pos] == '\n') - { - break; - } - - if(col == vec.x) - { - break; - } - - col += 1; - } - } - else - { - u64 move = buffer.pos.y < vec.y || (buffer.pos.y == vec.y && buffer.pos.x < vec.x) ? -1 : +1; - for(pos = buffer.buf_pos; pos < buffer.length && pos > 0; pos += move) - { - if(vec.y == line) - { - break; - } - - if(buffer.data.ptr[pos] == '\n') - { - line += 1; - } - } - - for(; pos < buffer.length; pos += move) - { - if(col == vec.x) - { - break; - } - - if(buffer.data.ptr[pos] == '\n') - { - break; - } - - col += move; - } - } - - return pos; -} - void -Backspace(FlatBuffer* buffer, u64 length) +Backspace(FlatBuffer* buffer) { - if(buffer.buf_pos-length > 0) - { - u8[] del = buffer.data[buffer.buf_pos-length .. buffer.buf_pos]; - u8 ch = buffer.data[buffer.buf_pos]; - buffer.buf_pos -= 1; - Delete(buffer, length, buffer.buf_pos); - if(ch == '\n') - { - - } - } -} - -void -AdjustLinePos(FlatBuffer* buffer, i64 adj) -{ - if(buffer.buf_pos+adj <= 0) - { - buffer.buf_pos = 0; - buffer.pos = 0; - } - else if(buffer.buf_pos+adj >= buffer.length) - { - buffer.buf_pos = buffer.length; - buffer.pos.y = buffer.lf_count; - buffer.pos.x = PosInLine(buffer, buffer.buf_pos); - } -} - -pragma(inline) void -Walk(alias dir)(FlatBuffer* buffer) -{ - static if(dir == WalkDir.Backward) + if(buffer.buf_pos-1 >= 0) { buffer.buf_pos -= 1; - if(buffer.data[buffer.buf_pos] == '\n') - { - buffer.pos.y -= 1; - buffer.pos.x = PosInLine(buffer, buffer.buf_pos); - } - else - { - buffer.pos.x -= 1; - } - - if(buffer.pos.x < 0 || buffer.pos.y < 0) - { - Logf("walked %s buf_pos %s pos %s", dir, buffer.buf_pos, buffer.pos.v); - assert(pos.x >= 0 && pos.y >= 0); - } + Delete(buffer, 1, buffer.buf_pos); } - else static if(dir == WalkDir.Forward) - { - buffer.buf_pos += 1; - if(buffer.data[buffer.buf_pos-1] == '\n') - { - buffer.pos.y += 1; - buffer.pos.x = 0; - } - else - { - buffer.pos.x += 1; - } - } -} - -pragma(inline) void -VerifyPosition(FlatBuffer* buffer, Input input, Modifier md) -{ - debug - { - with(buffer) - { - I64Vec2 p = 0; - i64 i = 0; - for(; i < length && p != pos; i += 1) - { - if(data[i] == '\n') - { - p.x = 0; - p.y += 1; - } - else - { - p.x += 1; - } - - assert(p.y <= pos.y); - } - - if(i != buf_pos || p != pos) - { - Logf("Buffer positions not in sync: expected: %s %s %s result: %s %s %s", i, p.v, cast(char)data[i], buf_pos, pos.v, cast(char)data[buf_pos]); - Logf("Input: %s Shift: %s Ctrl: %s", input, md & (MD.LeftShift|MD.RightShift), md & (MD.LeftCtrl|MD.RightCtrl)); - assert(false); - } - } - } -} - -pragma(inline) i64 -PosInLine(FlatBuffer* buffer, i64 pos) -{ - assert(pos >= 0 && pos <= buffer.length); - - i64 p = pos; - i64 line_pos = 0; - i64 skip_lf = buffer.data[pos] == '\n'; - - i64 lf_count = 0; - for(; p > 0; p -= 1, line_pos += 1) - { - if(buffer.data[p] == '\n') - { - if(skip_lf) - { - skip_lf = false; - } - else - { - break; - } - } - } - - Logf("line_pos %s", line_pos); - - return line_pos; } void @@ -616,6 +423,14 @@ Delete(FlatBuffer* buffer, u64 length, u64 pos) u64 end = pos+length; assert(end <= buffer.length, "Delete failure: pos+length is not in range"); + for(u64 i = pos; i < buffer.length && i < pos+length; i += 1) + { + if(buffer.data[i] == '\n') + { + buffer.lf_count -= 1; + } + } + u8[] temp; if(end != buffer.length) { @@ -625,6 +440,8 @@ Delete(FlatBuffer* buffer, u64 length, u64 pos) } buffer.length -= length; + + Fix(buffer); } void diff --git a/src/editor/editor.d b/src/editor/editor.d index 13ca52a..9968f40 100644 --- a/src/editor/editor.d +++ b/src/editor/editor.d @@ -25,6 +25,7 @@ struct EditorCtx EditState state; u8[128] input_buf; u32 icount; + Timer timer; } struct Editor @@ -35,7 +36,6 @@ struct Editor FlatBuffer buf; Tokenizer tk; LineBuffers linebufs; - i64 offset; Vec2 cursor_pos; Vec2 select_start; @@ -62,6 +62,8 @@ Cycle(EditorCtx* ctx, Inputs* inputs) { ResetScratch(MB(4)); + g_delta = DeltaTime(&ctx.timer); + assert(Nil(ctx.base_panel.next)); HandleInputs(ctx, inputs); @@ -88,6 +90,7 @@ InitEditorCtx(PlatformWindow* window) ctx.base_panel = CreatePanel(&ctx); ctx.base_panel.ed = CreateEditor(&ctx); + ctx.timer = CreateTimer(); SetFocusedPanel(ctx.base_panel); return ctx; @@ -330,7 +333,11 @@ HandleInputMode(EditorCtx* ctx, InputEvent ev) mixin(CharCases()); case Input.Backspace: { - + UIPanel* p = GetFocusedPanel(); + if(!Nil(p)) + { + Backspace(&p.ed.buf); + } } break; case Input.Escape: { diff --git a/src/editor/ui.d b/src/editor/ui.d index e05d39b..df61b26 100644 --- a/src/editor/ui.d +++ b/src/editor/ui.d @@ -106,6 +106,7 @@ struct UICtx Vec4 text_color; Axis2D layout_axis; Vec2 adjustment; + Vec2 offset; u32 tab_width; f32 text_size; f32 border_thickness; @@ -161,6 +162,7 @@ struct UIItem f32 corner_radius; f32 edge_softness; Vec2 last_click_pos; + Vec2 offset; } struct UISize @@ -483,6 +485,12 @@ SetAdjustment(Vec2 adj) g_ui_ctx.adjustment = adj; } +void +SetOffset(Vec2 offset) +{ + g_ui_ctx.offset = offset; +} + void SetHighlightedChar(i64 pos) { @@ -718,7 +726,7 @@ CalcPositions(alias axis)(UIItem* item) for(UIItem* i = item; !Nil(i);) { f32 end_pos = pos + i.size.v[axis]; - i.rect.vec0.v[axis] = pos; + i.rect.vec0.v[axis] = pos + i.offset.v[axis]; i.rect.vec1.v[axis] = end_pos; assert(!isNaN(i.rect.vec0.v[axis])); @@ -833,6 +841,7 @@ BuildItem(UIItem* item, UISize size_x, UISize size_y, UIFlags properties) item.edge_softness = ctx.edge_softness; item.last_frame = ctx.frame; item.highlighted_char = ctx.highlighted_char; + item.offset = ctx.offset; item.parent = ctx.top_parent == g_UI_NIL_NODE ? g_UI_NIL : ctx.top_parent.item; if(!Nil(item.parent)) diff --git a/src/editor/widgets.d b/src/editor/widgets.d index beac213..078bb08 100644 --- a/src/editor/widgets.d +++ b/src/editor/widgets.d @@ -6,6 +6,8 @@ import ui; import editor; import std.format : sformat; import std.math.rounding : ceil, floor; +import std.math.exponential : pow; +import core.stdc.math : fabsf; const UIPanel g_ui_nil_panel; UIPanel* g_UI_NIL_PANEL; @@ -22,8 +24,9 @@ struct WidgetCtx struct UIPanel { - u8[] id; - Editor* ed; + AnimState anim; + u8[] id; + Editor* ed; UIPanel* parent; UIPanel* next; @@ -36,6 +39,18 @@ struct UIPanel Axis2D axis; f32 pct; Vec4 color; + + i64 prev_offset; + i64 start_row; + i64 end_row; +} + +struct AnimState +{ + f32 start_value; + f32 end_value; + f32 start_time; + f32 remaining_time; } struct TextPart @@ -171,6 +186,45 @@ TextLine(u8[] text) UIItem* item = Get(text); } +void +SetPanelScroll(UIPanel* panel, i64 rows, f32 text_size) +{ + i64 low = panel.prev_offset < panel.ed.buf.offset ? panel.prev_offset : panel.ed.buf.offset; + i64 high = panel.prev_offset < panel.ed.buf.offset ? panel.ed.buf.offset + rows : panel.prev_offset + rows; + + panel.start_row = low; + panel.end_row = high; + + f32 start = panel.anim.start_value; + f32 end = panel.anim.end_value; + if(start == 0.0 && end == 0.0) + { + f32 offset = (panel.prev_offset-panel.ed.buf.offset) * text_size; + if(offset > 0.0) + { + end = -offset; + } + else + { + start = offset; + } + } + + panel.anim.start_value = start; + panel.anim.end_value = end; + + panel.anim.start_time = 0.1; + panel.anim.remaining_time = 0.1; + + panel.prev_offset = panel.ed.buf.offset; +} + +f32 +EaseOutExp(f32 v) +{ + return fabsf(1.0 - v) <= 0.00009 ? 1.0 : 1.0 - pow(2, -10 * v); +} + void EditorView(UIPanel* panel) { @@ -186,20 +240,50 @@ EditorView(UIPanel* panel) i64 rows = cast(i64)ceil(item.size.y/text_size); if(ed.buf.rows != rows) { - Logf("editor height %s text_size %s rows %s", item.size.y, text_size, rows); ed.buf.rows = rows; } - GetLines(&ed.buf, &ed.linebufs, rows); + if(panel.prev_offset != ed.buf.offset) + { + SetPanelScroll(panel, rows, text_size); + } + + i64 line_offset = ed.buf.offset; + if(panel.anim.remaining_time > 0.0) with(panel.anim) + { + remaining_time -= g_delta; + if(remaining_time < 0.0) + { + remaining_time = 0.0; + } + + f32 offset = Remap(remaining_time, 0.0, start_time, 0.0, 1.0); + offset = Remap(EaseOutExp(offset), 0.0, 1.0, start_value, end_value); + SetOffset(Vec2(0.0, offset)); + + if(remaining_time == 0.0) + { + start_value = end_value = start_time = 0.0; + } + + line_offset = panel.start_row; + GetLines(&ed.buf, &ed.linebufs, panel.start_row, panel.end_row-panel.start_row); + } + else + { + GetLines(&ed.buf, &ed.linebufs, rows); + } + + U64Vec2 pos = VecPos(&ed.buf); u64 i = 0; for(LineBuffer* buf = ed.linebufs.first; buf != null; buf = buf.next, i += 1) { if(buf.text.length > 0) { - i64 line_no = i+ed.buf.offset; - if(ed.buf.pos.y == line_no && focused) + i64 line_no = i+line_offset; + if(pos.y == line_no && focused) { - SetHighlightedChar(ed.buf.pos.x); + SetHighlightedChar(pos.x); } TextPart* tp = WrappedTextLine(buf.text, panel.id, text_size, line_no); @@ -221,6 +305,8 @@ EditorView(UIPanel* panel) SetFocusedPanel(panel); } + SetOffset(Vec2(0.0)); + EndPanel(); }