From 5539bbd3b94597ec89603fb94ebf628f2dd6ae95 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Thu, 20 Jun 2024 09:40:05 +0200 Subject: [PATCH] Basic multiline buffer concept --- src/pdf_browser/basic.h | 2 + src/text_editor/buffer.cpp | 160 +++++++++++++++++++++++++++++++++++++ src/text_editor/main.cpp | 95 +++++++++++++++++++++- 3 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 src/text_editor/buffer.cpp diff --git a/src/pdf_browser/basic.h b/src/pdf_browser/basic.h index 036ced2..d5afb50 100644 --- a/src/pdf_browser/basic.h +++ b/src/pdf_browser/basic.h @@ -87,6 +87,7 @@ T ClampBottom(T bottom, T b) { template 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 void Dealloc(Allocator alo, T **p) { + if (*p == NULL) return; alo.proc(alo.object, AllocatorKind_Deallocate, *p, 0); *p = NULL; } diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp new file mode 100644 index 0000000..68d7f0f --- /dev/null +++ b/src/text_editor/buffer.cpp @@ -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 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 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 *edits, Range range, String string) { + edits->add({range, string}); +} + +void RunBufferTests() { + Scratch scratch; + { + Buffer buffer = {scratch}; + Array 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 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"); + } +} \ No newline at end of file diff --git a/src/text_editor/main.cpp b/src/text_editor/main.cpp index 73fc405..5140697 100644 --- a/src/text_editor/main.cpp +++ b/src/text_editor/main.cpp @@ -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 buffer = {}; + Array 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(); } \ No newline at end of file