Basic multiline buffer concept
This commit is contained in:
@@ -87,6 +87,7 @@ T ClampBottom(T bottom, T b) {
|
||||
|
||||
template <class T>
|
||||
T Clamp(T value, T min, T max) {
|
||||
Assert(max >= min);
|
||||
if (value > max) return max;
|
||||
if (value < min) return min;
|
||||
return value;
|
||||
@@ -151,6 +152,7 @@ inline void *AllocSize(Allocator alo, size_t size) {
|
||||
|
||||
template <class T>
|
||||
void Dealloc(Allocator alo, T **p) {
|
||||
if (*p == NULL) return;
|
||||
alo.proc(alo.object, AllocatorKind_Deallocate, *p, 0);
|
||||
*p = NULL;
|
||||
}
|
||||
|
||||
160
src/text_editor/buffer.cpp
Normal file
160
src/text_editor/buffer.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
struct Buffer {
|
||||
Allocator allocator;
|
||||
char *data[2];
|
||||
int64_t cap;
|
||||
int64_t len;
|
||||
int bi; // current buffer index
|
||||
};
|
||||
|
||||
struct Range {
|
||||
int64_t a;
|
||||
int64_t b; // one past last index
|
||||
// <0,4> = 0,1,2,3
|
||||
};
|
||||
|
||||
int64_t GetRangeSize(Range range) {
|
||||
int64_t result = range.b - range.a;
|
||||
return result;
|
||||
}
|
||||
|
||||
Range GetRange(const Buffer &buffer) {
|
||||
Range result = {0, buffer.len};
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Edit {
|
||||
Range range;
|
||||
String string;
|
||||
};
|
||||
|
||||
void ApplyEdits(Buffer *buffer, Array<Edit> edits) {
|
||||
int64_t size_to_delete = 0;
|
||||
int64_t size_to_insert = 0;
|
||||
int64_t end_of_buffer = Max((int64_t)0, buffer->len - 1);
|
||||
For(edits) {
|
||||
it.range.a = Clamp(it.range.a, (int64_t)0, end_of_buffer);
|
||||
it.range.b = Clamp(it.range.b, (int64_t)0, buffer->len);
|
||||
size_to_delete += GetRangeSize(it.range);
|
||||
size_to_insert += it.string.len;
|
||||
}
|
||||
|
||||
#if DEBUG_BUILD
|
||||
ForItem(it1, edits) {
|
||||
ForItem(it2, edits) {
|
||||
if (&it1 == &it2) continue;
|
||||
|
||||
bool a2_inside = it2.range.a >= it1.range.a && it2.range.a < it1.range.b;
|
||||
Assert(!a2_inside);
|
||||
|
||||
bool b2_inside = it2.range.b > it1.range.a && it2.range.b <= it1.range.b;
|
||||
Assert(!b2_inside);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int64_t len_offset = size_to_insert - size_to_delete;
|
||||
int64_t allocated_size_required = Max((int64_t)0, len_offset);
|
||||
if (buffer->len + allocated_size_required > buffer->cap) {
|
||||
int64_t new_cap = AlignUp(buffer->cap + allocated_size_required, 4096);
|
||||
if (buffer->allocator.proc == NULL) buffer->allocator = GetSystemAllocator();
|
||||
|
||||
{
|
||||
char *data = AllocArray(buffer->allocator, char, new_cap);
|
||||
Assert(data);
|
||||
memcpy(data, buffer->data[0], buffer->len);
|
||||
Dealloc(buffer->allocator, &buffer->data[0]);
|
||||
buffer->data[0] = data;
|
||||
}
|
||||
{
|
||||
char *data = AllocArray(buffer->allocator, char, new_cap);
|
||||
Assert(data);
|
||||
memcpy(data, buffer->data[1], buffer->len);
|
||||
Dealloc(buffer->allocator, &buffer->data[1]);
|
||||
buffer->data[1] = data;
|
||||
}
|
||||
buffer->cap = new_cap;
|
||||
}
|
||||
|
||||
Scratch scratch;
|
||||
Array<Edit> writes = {scratch};
|
||||
int64_t prev_source = 0;
|
||||
int64_t prev_dest = 0;
|
||||
|
||||
For(edits) {
|
||||
Range source_range = {prev_source, it.range.a};
|
||||
if (GetRangeSize(source_range) != 0) {
|
||||
String source_string = {};
|
||||
source_string.data = buffer->data[buffer->bi] + source_range.a;
|
||||
source_string.len = GetRangeSize(source_range);
|
||||
Range dest_range = {prev_dest, prev_dest + source_string.len};
|
||||
writes.add({dest_range, source_string});
|
||||
|
||||
prev_dest = dest_range.b;
|
||||
}
|
||||
|
||||
Range dest_range = {prev_dest, prev_dest + it.string.len};
|
||||
writes.add({dest_range, it.string});
|
||||
prev_dest = dest_range.b;
|
||||
prev_source = it.range.b;
|
||||
}
|
||||
|
||||
// Add remaining range
|
||||
Range source_range = {prev_source, buffer->len};
|
||||
if (GetRangeSize(source_range)) {
|
||||
String source_string = {};
|
||||
source_string.data = buffer->data[buffer->bi] + source_range.a;
|
||||
source_string.len = GetRangeSize(source_range);
|
||||
Range dest_range = {prev_dest, prev_dest + source_string.len};
|
||||
writes.add({dest_range, source_string});
|
||||
}
|
||||
|
||||
#if DEBUG_BUILD
|
||||
for (int64_t i = 0; i < writes.len - 1; i += 1) {
|
||||
Assert(writes[i].range.b == writes[i + 1].range.a);
|
||||
}
|
||||
#endif
|
||||
|
||||
int64_t new_buffer_len = 0;
|
||||
int dsti = (buffer->bi + 1) % 2;
|
||||
For(writes) {
|
||||
memcpy(buffer->data[dsti] + new_buffer_len, it.string.data, it.string.len);
|
||||
new_buffer_len += it.string.len;
|
||||
}
|
||||
buffer->bi = dsti;
|
||||
Assert(new_buffer_len == buffer->len + len_offset);
|
||||
buffer->len = new_buffer_len;
|
||||
}
|
||||
|
||||
void AddEdit(Array<Edit> *edits, Range range, String string) {
|
||||
edits->add({range, string});
|
||||
}
|
||||
|
||||
void RunBufferTests() {
|
||||
Scratch scratch;
|
||||
{
|
||||
Buffer buffer = {scratch};
|
||||
Array<Edit> edits = {scratch};
|
||||
AddEdit(&edits, {0, 0}, "Things and other things");
|
||||
ApplyEdits(&buffer, edits);
|
||||
String string = {buffer.data[buffer.bi], buffer.len};
|
||||
Assert(string == "Things and other things");
|
||||
}
|
||||
{
|
||||
Buffer buffer = {scratch};
|
||||
Array<Edit> edits = {scratch};
|
||||
edits.add({
|
||||
{0, 0},
|
||||
"Things and other things"
|
||||
});
|
||||
ApplyEdits(&buffer, edits);
|
||||
edits.clear();
|
||||
|
||||
AddEdit(&edits, {0, 6}, "Memes");
|
||||
AddEdit(&edits, {7, 10}, "dna");
|
||||
AddEdit(&edits, {11, 16}, "BigOther");
|
||||
ApplyEdits(&buffer, edits);
|
||||
|
||||
String string = {buffer.data[buffer.bi], buffer.len};
|
||||
Assert(string == "Memes dna BigOther things");
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,104 @@
|
||||
#define BASIC_IMPL
|
||||
#include "../pdf_browser/basic.h"
|
||||
|
||||
#include "raylib.h"
|
||||
#include "raymath.h"
|
||||
|
||||
#include "buffer.cpp"
|
||||
|
||||
using Vec2 = Vector2;
|
||||
struct Rect2 {
|
||||
Vec2 min;
|
||||
Vec2 max;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Render units - positions ready to draw, y
|
||||
// World units - positions offset by screen movement
|
||||
// Window units - positions inside the window (starts in left top of window)
|
||||
// World window units
|
||||
|
||||
struct Window {
|
||||
Rect2 rect_in_world_units;
|
||||
};
|
||||
|
||||
Vec2 WorldToRenderUnits(Vec2 value, Vec2 camera_offset_world_to_render_units) {
|
||||
Vec2 result = Vector2Subtract(value, camera_offset_world_to_render_units);
|
||||
return result;
|
||||
}
|
||||
|
||||
Vec2 WindowToWorldUnits(Vec2 value, const Window &window) {
|
||||
Vector2 result = Vector2Add(value, window.rect_in_world_units.min);
|
||||
return result;
|
||||
}
|
||||
|
||||
Rect2 GetScreenRectRenderUnits() {
|
||||
Rect2 result = {
|
||||
{ 0, 0},
|
||||
{(float)GetRenderWidth(), (float)GetRenderHeight()}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
int main() {
|
||||
InitWindow(1280, 720, "Text editor");
|
||||
SetWindowState(FLAG_WINDOW_RESIZABLE);
|
||||
InitScratch();
|
||||
RunBufferTests();
|
||||
|
||||
return 0;
|
||||
InitWindow(800, 600, "Hello");
|
||||
SetTargetFPS(60);
|
||||
|
||||
float font_size = 14;
|
||||
float font_spacing = 1;
|
||||
Font font = LoadFontEx("C:/Windows/Fonts/consola.ttf", (int)font_size, NULL, 250);
|
||||
|
||||
Array<char> buffer = {};
|
||||
Array<Window> windows = {};
|
||||
windows.add({GetScreenRectRenderUnits()});
|
||||
|
||||
Vec2 camera_offset_world_to_render_units = {};
|
||||
while (!WindowShouldClose()) {
|
||||
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) || IsKeyDown(KEY_SPACE)) {
|
||||
camera_offset_world_to_render_units = Vector2Subtract(camera_offset_world_to_render_units, GetMouseDelta());
|
||||
}
|
||||
|
||||
for (int c = GetCharPressed(); c; c = GetCharPressed()) {
|
||||
Assert(c >= ' ' && c <= '~');
|
||||
buffer.add((char)c);
|
||||
}
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(RAYWHITE);
|
||||
|
||||
For(windows) {
|
||||
Rect2 rect_in_render_units = {
|
||||
WorldToRenderUnits(it.rect_in_world_units.min, camera_offset_world_to_render_units),
|
||||
WorldToRenderUnits(it.rect_in_world_units.max, camera_offset_world_to_render_units),
|
||||
};
|
||||
Rectangle rectangle_in_render_units = ToRectangle(rect_in_render_units);
|
||||
DrawRectangleRec(rectangle_in_render_units, WHITE);
|
||||
|
||||
{
|
||||
Vec2 text_position_in_window_units = {};
|
||||
Vec2 text_position_in_world_units = WindowToWorldUnits(text_position_in_window_units, it);
|
||||
Vec2 text_position_in_render_units = WorldToRenderUnits(text_position_in_world_units, camera_offset_world_to_render_units);
|
||||
DrawTextEx(font, "window 1", text_position_in_render_units, font_size, font_spacing, BLACK);
|
||||
}
|
||||
|
||||
{
|
||||
Vec2 text_position_in_window_units = {0, font_size};
|
||||
Vec2 text_position_in_world_units = WindowToWorldUnits(text_position_in_window_units, it);
|
||||
Vec2 text_position_in_render_units = WorldToRenderUnits(text_position_in_world_units, camera_offset_world_to_render_units);
|
||||
|
||||
buffer.add('\0');
|
||||
DrawTextEx(font, buffer.data, text_position_in_render_units, font_size, font_spacing, BLACK);
|
||||
buffer.pop();
|
||||
}
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
CloseWindow();
|
||||
}
|
||||
Reference in New Issue
Block a user