Cursor movement, draw selection and fixing bugs

This commit is contained in:
Krzosa Karol
2024-07-20 07:41:02 +02:00
parent cd48094a18
commit 0071136146
8 changed files with 473 additions and 33 deletions

View File

@@ -9,6 +9,12 @@ void InitBuffer(Allocator allocator, Buffer *buffer) {
Add(&buffer->line_starts, (Int)0); 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) { void Grow(Buffer *buffer, Int change_size) {
Int new_size = buffer->len + change_size; Int new_size = buffer->len + change_size;
if (new_size > buffer->cap) { if (new_size > buffer->cap) {
@@ -33,16 +39,44 @@ void OffsetAllLinesForward(Buffer *buffer, Int line, Int *_offset) {
} }
} }
Range GetLine(Buffer &buffer, Int line) { Int LastLine(Buffer &buffer) {
Range result = {0, buffer.len}; Int result = buffer.line_starts.len - 1;
result.min = buffer.line_starts[line]; return result;
if (buffer.line_starts.len < line + 1) { }
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]; result.max = buffer.line_starts[line + 1];
} }
return result; 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); Add(&buffer.line_starts, buffer.len + 1);
// binary search // binary search
@@ -69,10 +103,25 @@ Int GetLineNumber(Buffer &buffer, Int pos) {
return result; 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) { void ValidateLineStarts(Buffer *buffer) {
Int line = 0; Int line = 0;
for (Int i = 0; i < buffer->len; i += 1) { for (Int i = 0; i < buffer->len; i += 1) {
Int l = GetLineNumber(*buffer, i); Int l = PosToLine(*buffer, i);
Assert(l == line); Assert(l == line);
if (buffer->data[i] == L'\n') line += 1; if (buffer->data[i] == L'\n') line += 1;
} }
@@ -80,7 +129,7 @@ void ValidateLineStarts(Buffer *buffer) {
void UpdateLines(Buffer *buffer, Range range, String16 string) { void UpdateLines(Buffer *buffer, Range range, String16 string) {
Array<Int> &ls = buffer->line_starts; Array<Int> &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); Assert(min_line_number < ls.len);
// Update lines remove // Update lines remove
{ {
@@ -161,7 +210,7 @@ void TestBuffer() {
Assert(a == test_string); Assert(a == test_string);
Assert(buffer.line_starts.len == 1); Assert(buffer.line_starts.len == 1);
Assert(buffer.line_starts[0] == 0); Assert(buffer.line_starts[0] == 0);
Assert(GetLineNumber(buffer, 4) == 0); Assert(PosToLine(buffer, 4) == 0);
} }
{ {
ReplaceText(&buffer, {0, 5}, L""); ReplaceText(&buffer, {0, 5}, L"");
@@ -171,7 +220,7 @@ void TestBuffer() {
Assert(a == L" itself"); Assert(a == L" itself");
Assert(buffer.line_starts.len == 1); Assert(buffer.line_starts.len == 1);
Assert(buffer.line_starts[0] == 0); Assert(buffer.line_starts[0] == 0);
Assert(GetLineNumber(buffer, 4) == 0); Assert(PosToLine(buffer, 4) == 0);
} }
{ {
ReplaceText(&buffer, GetEndAsRange(buffer), L" and"); ReplaceText(&buffer, GetEndAsRange(buffer), L" and");
@@ -181,7 +230,7 @@ void TestBuffer() {
Assert(a == L" itself and"); Assert(a == L" itself and");
Assert(buffer.line_starts.len == 1); Assert(buffer.line_starts.len == 1);
Assert(buffer.line_starts[0] == 0); Assert(buffer.line_starts[0] == 0);
Assert(GetLineNumber(buffer, 4) == 0); Assert(PosToLine(buffer, 4) == 0);
} }
{ {
ReplaceText(&buffer, GetRange(buffer), L""); ReplaceText(&buffer, GetRange(buffer), L"");
@@ -195,31 +244,31 @@ void TestBuffer() {
{ {
ReplaceText(&buffer, GetEndAsRange(buffer), L"Memes and other\nthings"); ReplaceText(&buffer, GetEndAsRange(buffer), L"Memes and other\nthings");
Assert(buffer.line_starts.len == 2); Assert(buffer.line_starts.len == 2);
Assert(GetLineNumber(buffer, 17) == 1); Assert(PosToLine(buffer, 17) == 1);
Assert(GetLineNumber(buffer, 16) == 1); Assert(PosToLine(buffer, 16) == 1);
Assert(GetLineNumber(buffer, 15) == 0); Assert(PosToLine(buffer, 15) == 0);
Assert(buffer.data[15] == L'\n'); Assert(buffer.data[15] == L'\n');
Assert(buffer.data[16] == L't'); Assert(buffer.data[16] == L't');
ReplaceText(&buffer, GetBeginAsRange(buffer), L"Things as is\nand stuff\n"); ReplaceText(&buffer, GetBeginAsRange(buffer), L"Things as is\nand stuff\n");
Assert(buffer.line_starts.len == 4); Assert(buffer.line_starts.len == 4);
Assert(GetLineNumber(buffer, 12) == 0); Assert(PosToLine(buffer, 12) == 0);
Assert(buffer.data[12] == L'\n'); Assert(buffer.data[12] == L'\n');
Assert(GetLineNumber(buffer, 13) == 1); Assert(PosToLine(buffer, 13) == 1);
Assert(GetLineNumber(buffer, 21) == 1); Assert(PosToLine(buffer, 21) == 1);
Assert(GetLineNumber(buffer, 22) == 1); Assert(PosToLine(buffer, 22) == 1);
Assert(buffer.data[22] == L'\n'); Assert(buffer.data[22] == L'\n');
Assert(GetLineNumber(buffer, 23) == 2); Assert(PosToLine(buffer, 23) == 2);
Assert(GetLineNumber(buffer, 37) == 2); Assert(PosToLine(buffer, 37) == 2);
Assert(GetLineNumber(buffer, 38) == 2); Assert(PosToLine(buffer, 38) == 2);
Assert(buffer.data[38] == L'\n'); Assert(buffer.data[38] == L'\n');
Assert(GetLineNumber(buffer, 39) == 3); Assert(PosToLine(buffer, 39) == 3);
Assert(buffer.data[39] == L't'); Assert(buffer.data[39] == L't');
ReplaceText(&buffer, GetBeginAsRange(buffer), L"a"); ReplaceText(&buffer, GetBeginAsRange(buffer), L"a");
Assert(buffer.line_starts.len == 4); Assert(buffer.line_starts.len == 4);
Assert(GetLineNumber(buffer, 13) == 0); Assert(PosToLine(buffer, 13) == 0);
Assert(GetLineNumber(buffer, 14) == 1); Assert(PosToLine(buffer, 14) == 1);
} }
} }
@@ -227,11 +276,11 @@ void TestBuffer() {
Buffer buffer = {}; Buffer buffer = {};
InitBuffer(scratch, &buffer); InitBuffer(scratch, &buffer);
ReplaceText(&buffer, {}, L"Thing\nmeme"); ReplaceText(&buffer, {}, L"Thing\nmeme");
Assert(GetLineNumber(buffer, 5) == 0); Assert(PosToLine(buffer, 5) == 0);
Assert(GetLineNumber(buffer, 6) == 1); Assert(PosToLine(buffer, 6) == 1);
ReplaceText(&buffer, {0, 1}, L""); ReplaceText(&buffer, {0, 1}, L"");
Assert(GetLineNumber(buffer, 4) == 0); Assert(PosToLine(buffer, 4) == 0);
Assert(GetLineNumber(buffer, 5) == 1); Assert(PosToLine(buffer, 5) == 1);
ReplaceText(&buffer, {4, 5}, L""); ReplaceText(&buffer, {4, 5}, L"");
Assert(buffer.line_starts.len == 1); Assert(buffer.line_starts.len == 1);
ValidateLineStarts(&buffer); ValidateLineStarts(&buffer);
@@ -243,8 +292,8 @@ void TestBuffer() {
ReplaceText(&buffer, {}, L"Thing\nmeme"); ReplaceText(&buffer, {}, L"Thing\nmeme");
ReplaceText(&buffer, {0, 5}, L"per\ncop"); ReplaceText(&buffer, {0, 5}, L"per\ncop");
Assert(buffer.line_starts.len == (Int)3); Assert(buffer.line_starts.len == (Int)3);
Assert(GetLineNumber(buffer, 3) == 0); Assert(PosToLine(buffer, 3) == 0);
Assert(GetLineNumber(buffer, 4) == 1); Assert(PosToLine(buffer, 4) == 1);
ValidateLineStarts(&buffer); ValidateLineStarts(&buffer);
ReplaceText(&buffer, {0, 8}, L"Thing\nmeme"); ReplaceText(&buffer, {0, 8}, L"Thing\nmeme");

View File

@@ -21,9 +21,9 @@ struct Cursor {
Int ifront; Int ifront;
}; };
struct Pos { struct XY {
Int line;
Int col; Int col;
Int line;
}; };
struct Edit { struct Edit {

View File

@@ -104,3 +104,14 @@ bool InRange(Int a, Range b) {
bool result = a >= b.min && a < b.max; bool result = a >= b.min && a < b.max;
return result; 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;
}

146
src/text_editor/math.cpp Normal file
View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -2,6 +2,8 @@
#include "../basic/basic.h" #include "../basic/basic.h"
#include "new_basic.cpp" #include "new_basic.cpp"
#include "string16.cpp" #include "string16.cpp"
#include <math.h>
#include "math.cpp"
#include "buffer.h" #include "buffer.h"
#include "buffer_helpers.cpp" #include "buffer_helpers.cpp"
@@ -10,20 +12,63 @@
#include "buffer_history.cpp" #include "buffer_history.cpp"
#include "raylib.h" #include "raylib.h"
#include "raylib_utils.cpp"
#include "view.cpp"
#include "view_draw.cpp"
int main(void) { int main(void) {
InitScratch(); InitScratch();
Test(); Test();
TestBuffer(); TestBuffer();
TestBufferMultiCursor(); TestBufferMultiCursor();
return 0;
Arena Perm = {};
InitArena(&Perm);
SetWindowState(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT);
InitWindow(1280, 720, "hello :)"); InitWindow(1280, 720, "hello :)");
SetExitKey(KEY_F5);
SetTargetFPS(60); 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()) { while (!WindowShouldClose()) {
view.rect = GetScreenRect();
HandleKeybindings(&view);
BeginDrawing(); BeginDrawing();
ClearBackground(RAYWHITE); ClearBackground(RAYWHITE);
DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY); {
DrawVisibleCells(view);
For(view.cursors) DrawCursor(view, it);
}
EndDrawing(); EndDrawing();
} }
CloseWindow(); CloseWindow();

50
src/text_editor/view.cpp Normal file
View File

@@ -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<Cursor> 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));
}
}

View File

@@ -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});
}
}
}
}