Cursor movement, draw selection and fixing bugs
This commit is contained in:
@@ -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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 GetLine(Buffer &buffer, Int line) {
|
||||||
Range result = {0, buffer.len};
|
Range result = {buffer.line_starts[line], buffer.len};
|
||||||
result.min = buffer.line_starts[line];
|
if (line + 1 < buffer.line_starts.len) {
|
||||||
if (buffer.line_starts.len < line + 1) {
|
|
||||||
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");
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
146
src/text_editor/math.cpp
Normal 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;
|
||||||
|
}
|
||||||
69
src/text_editor/raylib_utils.cpp
Normal file
69
src/text_editor/raylib_utils.cpp
Normal 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;
|
||||||
|
}
|
||||||
@@ -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
50
src/text_editor/view.cpp
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/text_editor/view_draw.cpp
Normal file
70
src/text_editor/view_draw.cpp
Normal 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});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user