diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index d02bdca..8ea50b7 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -9,6 +9,12 @@ void InitBuffer(Allocator allocator, Buffer *buffer) { Add(&buffer->line_starts, (Int)0); } +Buffer *CreateBuffer(Allocator allocator) { + Buffer *result = AllocType(allocator, Buffer); + InitBuffer(allocator, result); + return result; +} + void Grow(Buffer *buffer, Int change_size) { Int new_size = buffer->len + change_size; if (new_size > buffer->cap) { @@ -33,16 +39,44 @@ void OffsetAllLinesForward(Buffer *buffer, Int line, Int *_offset) { } } -Range GetLine(Buffer &buffer, Int line) { - Range result = {0, buffer.len}; - result.min = buffer.line_starts[line]; - if (buffer.line_starts.len < line + 1) { +Int LastLine(Buffer &buffer) { + Int result = buffer.line_starts.len - 1; + return result; +} + +const Int LAST_LINE = INT64_MAX; +Range GetLine(Buffer &buffer, Int line) { + Range result = {buffer.line_starts[line], buffer.len}; + if (line + 1 < buffer.line_starts.len) { result.max = buffer.line_starts[line + 1]; } return result; } -Int GetLineNumber(Buffer &buffer, Int pos) { +Int CalculateLongestLine(Buffer &buffer) { + // @optimize: this needs to be cached and then only updated when + // we modify the buffer. Maybe some modification ID or something. + Int index_max = 0; + Int size_max = 0; + for (Int i = 0; i < buffer.line_starts.len; i += 1) { + Range range = GetLine(buffer, i); + Int size = GetSize(range); + if (size > size_max) { + index_max = i; + size_max = size; + } + } + return index_max; +} + +Int GetCharCountOfLongestLine(Buffer &buffer) { + Int line = CalculateLongestLine(buffer); + Range range = GetLine(buffer, line); + Int result = GetSize(range); + return result; +} + +Int PosToLine(Buffer &buffer, Int pos) { Add(&buffer.line_starts, buffer.len + 1); // binary search @@ -69,10 +103,25 @@ Int GetLineNumber(Buffer &buffer, Int pos) { return result; } +XY PosToXY(Buffer &buffer, Int pos) { + Int line = PosToLine(buffer, pos); + Range line_range = GetLine(buffer, line); + Int col = pos - line_range.min; + XY result = {col, line}; + return result; +} + +Int XYToPos(Buffer &buffer, XY xy) { + xy.line = Clamp(xy.line, (Int)0, buffer.line_starts.len - 1); + Range line_range = GetLine(buffer, xy.line); + Int pos = Clamp(xy.col + line_range.min, line_range.min, line_range.max); + return pos; +} + void ValidateLineStarts(Buffer *buffer) { Int line = 0; for (Int i = 0; i < buffer->len; i += 1) { - Int l = GetLineNumber(*buffer, i); + Int l = PosToLine(*buffer, i); Assert(l == line); if (buffer->data[i] == L'\n') line += 1; } @@ -80,7 +129,7 @@ void ValidateLineStarts(Buffer *buffer) { void UpdateLines(Buffer *buffer, Range range, String16 string) { Array &ls = buffer->line_starts; - Int min_line_number = GetLineNumber(*buffer, range.min); + Int min_line_number = PosToLine(*buffer, range.min); Assert(min_line_number < ls.len); // Update lines remove { @@ -161,7 +210,7 @@ void TestBuffer() { Assert(a == test_string); Assert(buffer.line_starts.len == 1); Assert(buffer.line_starts[0] == 0); - Assert(GetLineNumber(buffer, 4) == 0); + Assert(PosToLine(buffer, 4) == 0); } { ReplaceText(&buffer, {0, 5}, L""); @@ -171,7 +220,7 @@ void TestBuffer() { Assert(a == L" itself"); Assert(buffer.line_starts.len == 1); Assert(buffer.line_starts[0] == 0); - Assert(GetLineNumber(buffer, 4) == 0); + Assert(PosToLine(buffer, 4) == 0); } { ReplaceText(&buffer, GetEndAsRange(buffer), L" and"); @@ -181,7 +230,7 @@ void TestBuffer() { Assert(a == L" itself and"); Assert(buffer.line_starts.len == 1); Assert(buffer.line_starts[0] == 0); - Assert(GetLineNumber(buffer, 4) == 0); + Assert(PosToLine(buffer, 4) == 0); } { ReplaceText(&buffer, GetRange(buffer), L""); @@ -195,31 +244,31 @@ void TestBuffer() { { ReplaceText(&buffer, GetEndAsRange(buffer), L"Memes and other\nthings"); Assert(buffer.line_starts.len == 2); - Assert(GetLineNumber(buffer, 17) == 1); - Assert(GetLineNumber(buffer, 16) == 1); - Assert(GetLineNumber(buffer, 15) == 0); + Assert(PosToLine(buffer, 17) == 1); + Assert(PosToLine(buffer, 16) == 1); + Assert(PosToLine(buffer, 15) == 0); Assert(buffer.data[15] == L'\n'); Assert(buffer.data[16] == L't'); ReplaceText(&buffer, GetBeginAsRange(buffer), L"Things as is\nand stuff\n"); Assert(buffer.line_starts.len == 4); - Assert(GetLineNumber(buffer, 12) == 0); + Assert(PosToLine(buffer, 12) == 0); Assert(buffer.data[12] == L'\n'); - Assert(GetLineNumber(buffer, 13) == 1); - Assert(GetLineNumber(buffer, 21) == 1); - Assert(GetLineNumber(buffer, 22) == 1); + Assert(PosToLine(buffer, 13) == 1); + Assert(PosToLine(buffer, 21) == 1); + Assert(PosToLine(buffer, 22) == 1); Assert(buffer.data[22] == L'\n'); - Assert(GetLineNumber(buffer, 23) == 2); - Assert(GetLineNumber(buffer, 37) == 2); - Assert(GetLineNumber(buffer, 38) == 2); + Assert(PosToLine(buffer, 23) == 2); + Assert(PosToLine(buffer, 37) == 2); + Assert(PosToLine(buffer, 38) == 2); Assert(buffer.data[38] == L'\n'); - Assert(GetLineNumber(buffer, 39) == 3); + Assert(PosToLine(buffer, 39) == 3); Assert(buffer.data[39] == L't'); ReplaceText(&buffer, GetBeginAsRange(buffer), L"a"); Assert(buffer.line_starts.len == 4); - Assert(GetLineNumber(buffer, 13) == 0); - Assert(GetLineNumber(buffer, 14) == 1); + Assert(PosToLine(buffer, 13) == 0); + Assert(PosToLine(buffer, 14) == 1); } } @@ -227,11 +276,11 @@ void TestBuffer() { Buffer buffer = {}; InitBuffer(scratch, &buffer); ReplaceText(&buffer, {}, L"Thing\nmeme"); - Assert(GetLineNumber(buffer, 5) == 0); - Assert(GetLineNumber(buffer, 6) == 1); + Assert(PosToLine(buffer, 5) == 0); + Assert(PosToLine(buffer, 6) == 1); ReplaceText(&buffer, {0, 1}, L""); - Assert(GetLineNumber(buffer, 4) == 0); - Assert(GetLineNumber(buffer, 5) == 1); + Assert(PosToLine(buffer, 4) == 0); + Assert(PosToLine(buffer, 5) == 1); ReplaceText(&buffer, {4, 5}, L""); Assert(buffer.line_starts.len == 1); ValidateLineStarts(&buffer); @@ -243,8 +292,8 @@ void TestBuffer() { ReplaceText(&buffer, {}, L"Thing\nmeme"); ReplaceText(&buffer, {0, 5}, L"per\ncop"); Assert(buffer.line_starts.len == (Int)3); - Assert(GetLineNumber(buffer, 3) == 0); - Assert(GetLineNumber(buffer, 4) == 1); + Assert(PosToLine(buffer, 3) == 0); + Assert(PosToLine(buffer, 4) == 1); ValidateLineStarts(&buffer); ReplaceText(&buffer, {0, 8}, L"Thing\nmeme"); diff --git a/src/text_editor/buffer.h b/src/text_editor/buffer.h index 8c7c50d..cc7e8b9 100644 --- a/src/text_editor/buffer.h +++ b/src/text_editor/buffer.h @@ -21,9 +21,9 @@ struct Cursor { Int ifront; }; -struct Pos { - Int line; +struct XY { Int col; + Int line; }; struct Edit { diff --git a/src/text_editor/buffer_helpers.cpp b/src/text_editor/buffer_helpers.cpp index d2c76b9..8c9e522 100644 --- a/src/text_editor/buffer_helpers.cpp +++ b/src/text_editor/buffer_helpers.cpp @@ -104,3 +104,14 @@ bool InRange(Int a, Range b) { bool result = a >= b.min && a < b.max; return result; } + +Range operator-(Range a, Int value) { + a.min -= value; + a.max -= value; + return a; +} + +Range operator-=(Range &range, Int value) { + range = range - value; + return range; +} \ No newline at end of file diff --git a/src/text_editor/math.cpp b/src/text_editor/math.cpp new file mode 100644 index 0000000..2c7fd83 --- /dev/null +++ b/src/text_editor/math.cpp @@ -0,0 +1,146 @@ +struct Vec2 { + float x; + float y; +}; + +struct Vec2I { + Int x; + Int y; +}; + +struct Rect2 { + Vec2 min; + Vec2 max; +}; + +struct Rect2I { + Vec2I min; + Vec2I max; +}; + +Vec2 GetSize(Rect2 r) { + Vec2 result = {r.max.x - r.min.x, r.max.y - r.min.y}; + return result; +} + +// clang-format off +Rect2 operator-(Rect2 r, Rect2 value) { return { r.min.x - value.min.x, r.min.y - value.min.y, r.max.x - value.max.x, r.max.y - value.max.y }; } +Rect2 operator+(Rect2 r, Rect2 value) { return { r.min.x + value.min.x, r.min.y + value.min.y, r.max.x + value.max.x, r.max.y + value.max.y }; } +Rect2 operator*(Rect2 r, Rect2 value) { return { r.min.x * value.min.x, r.min.y * value.min.y, r.max.x * value.max.x, r.max.y * value.max.y }; } +Rect2 operator/(Rect2 r, Rect2 value) { return { r.min.x / value.min.x, r.min.y / value.min.y, r.max.x / value.max.x, r.max.y / value.max.y }; } + +Rect2 operator-(Rect2 r, Vec2 value) { return { r.min.x - value.x, r.min.y - value.y, r.max.x - value.x, r.max.y - value.y }; } +Rect2 operator+(Rect2 r, Vec2 value) { return { r.min.x + value.x, r.min.y + value.y, r.max.x + value.x, r.max.y + value.y }; } +Rect2 operator*(Rect2 r, Vec2 value) { return { r.min.x * value.x, r.min.y * value.y, r.max.x * value.x, r.max.y * value.y }; } +Rect2 operator/(Rect2 r, Vec2 value) { return { r.min.x / value.x, r.min.y / value.y, r.max.x / value.x, r.max.y / value.y }; } + +Rect2 operator-(Rect2 r, float value) { return { r.min.x - value, r.min.y - value, r.max.x - value, r.max.y - value }; } +Rect2 operator+(Rect2 r, float value) { return { r.min.x + value, r.min.y + value, r.max.x + value, r.max.y + value }; } +Rect2 operator*(Rect2 r, float value) { return { r.min.x * value, r.min.y * value, r.max.x * value, r.max.y * value }; } +Rect2 operator/(Rect2 r, float value) { return { r.min.x / value, r.min.y / value, r.max.x / value, r.max.y / value }; } + +Rect2 operator-=(Rect2 &r, Rect2 value) { r = r - value; return r; } +Rect2 operator+=(Rect2 &r, Rect2 value) { r = r + value; return r; } +Rect2 operator*=(Rect2 &r, Rect2 value) { r = r * value; return r; } +Rect2 operator/=(Rect2 &r, Rect2 value) { r = r / value; return r; } +Rect2 operator-=(Rect2 &r, Vec2 value) { r = r - value; return r; } +Rect2 operator+=(Rect2 &r, Vec2 value) { r = r + value; return r; } +Rect2 operator*=(Rect2 &r, Vec2 value) { r = r * value; return r; } +Rect2 operator/=(Rect2 &r, Vec2 value) { r = r / value; return r; } +Rect2 operator-=(Rect2 &r, float value) { r = r - value; return r; } +Rect2 operator+=(Rect2 &r, float value) { r = r + value; return r; } +Rect2 operator*=(Rect2 &r, float value) { r = r * value; return r; } +Rect2 operator/=(Rect2 &r, float value) { r = r / value; return r; } + +Vec2 operator-(Vec2 a, Vec2 b) { return {a.x - b.x, a.y - b.y}; } +Vec2 operator+(Vec2 a, Vec2 b) { return {a.x + b.x, a.y + b.y}; } +Vec2 operator*(Vec2 a, Vec2 b) { return {a.x * b.x, a.y * b.y}; } +Vec2 operator/(Vec2 a, Vec2 b) { return {a.x / b.x, a.y / b.y}; } + +Vec2 operator-(Vec2 a, float b) { return {a.x - b, a.y - b}; } +Vec2 operator+(Vec2 a, float b) { return {a.x + b, a.y + b}; } +Vec2 operator*(Vec2 a, float b) { return {a.x * b, a.y * b}; } +Vec2 operator/(Vec2 a, float b) { return {a.x / b, a.y / b}; } + +Vec2 operator-=(Vec2 &a, Vec2 b) { a = a - b; return a; } +Vec2 operator+=(Vec2 &a, Vec2 b) { a = a + b; return a; } +Vec2 operator*=(Vec2 &a, Vec2 b) { a = a * b; return a; } +Vec2 operator/=(Vec2 &a, Vec2 b) { a = a / b; return a; } +Vec2 operator-=(Vec2 &a, float b) { a = a - b; return a; } +Vec2 operator+=(Vec2 &a, float b) { a = a + b; return a; } +Vec2 operator*=(Vec2 &a, float b) { a = a * b; return a; } +Vec2 operator/=(Vec2 &a, float b) { a = a / b; return a; } + +// clang-format on + +Rect2 Rect2FromSize(Vec2 pos, Vec2 size) { + Rect2 result = {}; + result.min = pos; + result.max = pos + size; + return result; +} + +Rect2 Shrink(Rect2 result, float v) { + result.min.x += v; + result.max.x -= v; + result.min.y += v; + result.max.y -= v; + return result; +} + +Vec2 GetMid(Rect2 r) { + Vec2 size = GetSize(r); + size.x /= 2.f; + size.y /= 2.f; + Vec2 result = r.min + size; + return result; +} + +Rect2 CutLeft(Rect2 *r, float value) { + float minx = r->min.x; + r->min.x = Min(r->min.x + value, r->max.x); + Rect2 result = { + { minx, r->min.y}, + {r->min.x, r->max.y} + }; + return result; +} + +Rect2 CutRight(Rect2 *r, float value) { + float maxx = r->max.x; + r->max.x = Max(r->min.x, r->max.x - value); + Rect2 result = { + {r->max.x, r->min.y}, + { maxx, r->max.y}, + }; + return result; +} + +// Y is up +Rect2 CutBottom(Rect2 *r, float value) { + float maxy = r->max.y; + r->max.y = Max(r->min.y, r->max.y - value); + Rect2 result = { + {r->min.x, r->max.y}, + {r->max.x, maxy}, + }; + return result; +} + +// Y is up +Rect2 CutTop(Rect2 *r, float value) { + float miny = r->min.y; + r->min.y = Min(r->min.y + value, r->max.y); + Rect2 result = { + {r->min.x, miny}, + {r->max.x, r->min.y}, + }; + return result; +} + +float SafeDivide(float a, float b) { + if (b == 0.f) { + return 0.f; + } + return a / b; +} \ No newline at end of file diff --git a/src/text_editor/raylib_utils.cpp b/src/text_editor/raylib_utils.cpp new file mode 100644 index 0000000..25a7069 --- /dev/null +++ b/src/text_editor/raylib_utils.cpp @@ -0,0 +1,69 @@ +Rectangle ToRectangle(Rect2 r) { + Rectangle result = {r.min.x, r.min.y, r.max.x - r.min.x, r.max.y - r.min.y}; + return result; +} + +Rect2 GetScreenRect() { + Rect2 result = {0, 0, (float)GetRenderWidth(), (float)GetRenderHeight()}; + return result; +} + +float GetCharSpacing(Font font, float font_size, float spacing) { + int index = GetGlyphIndex(font, '_'); + + float textOffsetX = 0; + float scaleFactor = font_size / font.baseSize; // Character quad scaling factor + if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width * scaleFactor + spacing); + else textOffsetX += ((float)font.glyphs[index].advanceX * scaleFactor + spacing); + return textOffsetX; +} + +void DrawString(Font font, String16 text, Vector2 position, float fontSize, float spacing, Color tint) { + if (font.texture.id == 0) font = GetFontDefault(); // Security check in case of not valid font + + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scaleFactor = fontSize / font.baseSize; // Character quad scaling factor + + for (int i = 0; i < text.len; i += 1) { + // Get next codepoint from byte string and glyph index in font + int codepoint = text[i]; + int index = GetGlyphIndex(font, codepoint); + + if ((codepoint != '\n') && (codepoint != ' ') && (codepoint != '\t')) { + DrawTextCodepoint(font, codepoint, {position.x + textOffsetX, position.y}, fontSize, tint); + } + + if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width * scaleFactor + spacing); + else textOffsetX += ((float)font.glyphs[index].advanceX * scaleFactor + spacing); + } +} + +Vector2 MeasureString(Font font, String16 text, float fontSize, float spacing) { + Vector2 textSize = {0}; + if ((font.texture.id == 0) || (text.data == NULL)) return textSize; // Security check + + int size = (int)text.len; // Get size in bytes of text + int tempByteCounter = 0; // Used to count longer text line num chars + int byteCounter = 0; + + float textWidth = 0.0f; + float tempTextWidth = 0.0f; // Used to count longer text line width + + float textHeight = fontSize; + float scaleFactor = fontSize / (float)font.baseSize; + + for (int i = 0; i < size; i += 1) { + int index = GetGlyphIndex(font, text[i]); + + if (font.glyphs[index].advanceX != 0) textWidth += font.glyphs[index].advanceX; + else textWidth += (font.recs[index].width + font.glyphs[index].offsetX); + } + + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + + textSize.x = tempTextWidth * scaleFactor + (float)((tempByteCounter - 1) * spacing); + textSize.y = textHeight; + + return textSize; +} \ No newline at end of file diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index fd8f541..142efbb 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -2,6 +2,8 @@ #include "../basic/basic.h" #include "new_basic.cpp" #include "string16.cpp" +#include +#include "math.cpp" #include "buffer.h" #include "buffer_helpers.cpp" @@ -10,20 +12,63 @@ #include "buffer_history.cpp" #include "raylib.h" +#include "raylib_utils.cpp" +#include "view.cpp" +#include "view_draw.cpp" int main(void) { InitScratch(); Test(); TestBuffer(); TestBufferMultiCursor(); - return 0; + Arena Perm = {}; + InitArena(&Perm); + + SetWindowState(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT); InitWindow(1280, 720, "hello :)"); + SetExitKey(KEY_F5); SetTargetFPS(60); + { + uint32_t data = 0xffdddddd; + Image window_icon_image = {(void *)&data, 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8}; + SetWindowIcon(window_icon_image); + } + + View view = {}; + { + float font_size = 32; + float font_spacing = 1; + float line_spacing = font_size; + Font font = LoadFontEx("c:\\Windows\\Fonts\\consola.ttf", (int)font_size, NULL, 250); + + view.font = font; + view.font_size = font_size; + view.font_spacing = font_spacing; + view.line_spacing = line_spacing; + view.char_spacing = GetCharSpacing(font, font_size, font_spacing); + Add(&view.cursors, {0, 0}); + view.buffer = CreateBuffer(Perm); + { + Scratch scratch; + for (int i = 0; i < 150; i += 1) { + String s = Format(scratch, "line1: %d line2: %d line3: %d line4: %d line5: %d line6: %d line1: %d line2: %d line3: %d line4: %d line5: %d line6: %d\n", i, i, i, i, i, i, i, i, i, i, i, i); + String16 s16 = ToString16(scratch, s); + ReplaceText(view.buffer, GetEndAsRange(*view.buffer), s16); + } + } + } + while (!WindowShouldClose()) { + view.rect = GetScreenRect(); + HandleKeybindings(&view); + BeginDrawing(); ClearBackground(RAYWHITE); - DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY); + { + DrawVisibleCells(view); + For(view.cursors) DrawCursor(view, it); + } EndDrawing(); } CloseWindow(); diff --git a/src/text_editor/view.cpp b/src/text_editor/view.cpp new file mode 100644 index 0000000..91889b5 --- /dev/null +++ b/src/text_editor/view.cpp @@ -0,0 +1,50 @@ +struct View { + Font font; + float font_size; + float font_spacing; + float line_spacing; + float char_spacing; + + Vec2 scroll; + Buffer *buffer; + Array cursors; + Rect2 rect; +}; + +Int MovePos(Buffer &buffer, Int pos, XY offset) { + XY xy = PosToXY(buffer, pos); + Int result = XYToPos(buffer, {xy.col + offset.col, xy.line + offset.line}); + return result; +} + +void HandleKeybindings(View *_view) { + View &view = *_view; + Buffer &buf = *view.buffer; + + if (IsKeyDown(KEY_F1)) { + view.scroll.x -= GetMouseWheelMove() * 48; + } else { + view.scroll.y -= GetMouseWheelMove() * 48; + } + + if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) { + For(view.cursors) { + if (IsKeyDown(KEY_LEFT_SHIFT)) { + Int front = GetFront(it); + Int new_front = MovePos(buf, front, {0, 1}); + it = ChangeFront(it, new_front); + } else { + it.range.max = it.range.min = MovePos(buf, it.range.max, {0, 1}); + } + } + } + + // Clip scroll + { + Int last_line = LastLine(view.buffer[0]); + view.scroll.y = Clamp(view.scroll.y, 0.f, Max(0.f, (last_line - 1) * view.line_spacing)); + + Int line_chars = GetCharCountOfLongestLine(view.buffer[0]); + view.scroll.x = Clamp(view.scroll.x, 0.f, Max(0.f, (line_chars - 2) * view.char_spacing)); + } +} \ No newline at end of file diff --git a/src/text_editor/view_draw.cpp b/src/text_editor/view_draw.cpp new file mode 100644 index 0000000..a44c3e1 --- /dev/null +++ b/src/text_editor/view_draw.cpp @@ -0,0 +1,70 @@ + +Rect2I GetVisibleCells(const View &view) { + Vec2 size = GetSize(view.rect); + float _cx = size.x / view.char_spacing; + float _cy = size.y / view.line_spacing; + int cx = (int)ceilf(_cx) + 1; + int cy = (int)ceilf(_cy) + 1; + + Vec2 pos = {SafeDivide(view.scroll.x, view.char_spacing), SafeDivide(view.scroll.y, view.line_spacing)}; + int x = (int)floorf(pos.x); + int y = (int)floorf(pos.y); + Rect2I result = {x, y, x + cx, y + cy}; + return result; +} + +void DrawVisibleCells(const View &view) { + Rect2I visible = GetVisibleCells(view); + for (Int line_index = visible.min.y; line_index < visible.max.y && line_index >= 0 && line_index < view.buffer->line_starts.len; line_index += 1) { + Range line_range = GetLine(*view.buffer, line_index); + String16 line_string = GetString(*view.buffer, line_range); + line_string = Skip(line_string, visible.min.x); + line_string = GetPrefix(line_string, visible.max.x - visible.min.x); + + Vec2 pos = {visible.min.x * view.char_spacing, line_index * view.line_spacing}; + pos -= view.scroll; + DrawString(view.font, line_string, pos, view.font_size, view.font_spacing, BLACK); + } +} + +void DrawCursor(const View &view, XY xy, float size, Color color) { + Rect2 _rect = Rect2FromSize({(float)xy.col * view.char_spacing, (float)xy.line * view.line_spacing}, {view.char_spacing, view.line_spacing}); + Rect2 rect = CutLeft(&_rect, size); + rect -= view.scroll; + DrawRectangleRec(ToRectangle(rect), color); +} + +void DrawCursor(const View &view, Cursor it) { + Buffer &buf = *view.buffer; + Int front = GetFront(it); + XY fxy = PosToXY(buf, front); + DrawCursor(view, fxy, 2, RED); + + Int back = GetBack(it); + XY bxy = PosToXY(buf, back); + DrawCursor(view, bxy, 1, GREEN); + + // Draw selection + { + XY min = PosToXY(buf, it.range.min); + XY max = PosToXY(buf, it.range.max); + + Rect2I vlines = GetVisibleCells(view); + vlines.min.y = Clamp(vlines.min.y, min.line, max.line); + vlines.max.y = Clamp(vlines.max.y, min.line, max.line); + + for (Int line = vlines.min.y; line < vlines.max.y; line += 1) { + Range range = GetLine(buf, line); + range -= range.min; + range.min = Clamp(range.min, vlines.min.x, vlines.max.x); + range.max = Clamp(range.max, vlines.min.x, vlines.max.x); + + for (Int col = range.min; col < range.max; col += 1) { + Vec2 pos = {col * view.char_spacing, line * view.line_spacing}; + pos -= view.scroll; + Rectangle rectangle = {pos.x, pos.y, view.char_spacing, view.line_spacing}; + DrawRectangleRec(rectangle, {0, 50, 150, 20}); + } + } + } +}