diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index 3ca508d..f451e84 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -252,17 +252,22 @@ Line FindLine(Buffer &buffer, int64_t pos) { return {}; } -int64_t AdjustUTF8Pos(const Buffer &buffer, int64_t pos, int64_t direction = 1) { - int64_t result = pos; - for (; result >= 0 && result < buffer.len;) { - char c = GetChar(buffer, pos); - if (IsUTF8ContinuationByte(c)) { - result += direction; +int64_t AdjustUTF8Pos(String string, int64_t pos, int64_t direction) { + for (; pos >= 0 && pos < string.len;) { + if (IsUTF8ContinuationByte(string.data[pos])) { + pos += direction; } else { break; } } - return result; + return pos; +} + +int64_t AdjustUTF8Pos(const Buffer &buffer, int64_t pos, int64_t direction = 1, bool clamp = true) { + String string = GetString(buffer); + pos = AdjustUTF8Pos(string, pos, direction); + if (clamp) pos = Clamp(buffer, pos); + return pos; } uint32_t GetUTF32(Buffer &buffer, int64_t pos, int64_t *codepoint_size) { @@ -317,7 +322,7 @@ void Advance(BufferIter *iter) { if (iter->direction == ITERATE_FORWARD) { iter->pos += iter->utf8_codepoint_size; } else { - iter->pos = AdjustUTF8Pos(*iter->buffer, iter->pos - 1, ITERATE_BACKWARD); + iter->pos = AdjustUTF8Pos(*iter->buffer, iter->pos - 1, ITERATE_BACKWARD, false); } if (!IsValid(*iter)) return; diff --git a/src/text_editor/main.cpp b/src/text_editor/main.cpp index 2039bc0..09590ef 100644 --- a/src/text_editor/main.cpp +++ b/src/text_editor/main.cpp @@ -68,6 +68,18 @@ void DrawString(Font font, String text, Vector2 position, float fontSize, float } } +int64_t MoveRight(Buffer &buffer, int64_t pos) { + int64_t result = pos + 1; + result = AdjustUTF8Pos(buffer, result); + return result; +} + +int64_t MoveLeft(Buffer &buffer, int64_t pos) { + int64_t result = pos - 1; + result = AdjustUTF8Pos(buffer, result, -1); + return result; +} + int main() { InitScratch(); RunBufferTests(); @@ -76,9 +88,10 @@ int main() { SetTargetFPS(60); InitArena(&FrameArena); - float font_size = 25; + float font_size = 64; float font_spacing = 1; Font font = LoadFontEx("C:/Windows/Fonts/times.ttf", (int)font_size, NULL, 250); + if (0) font = LoadFontEx("C:/Windows/Fonts/consola.ttf", (int)font_size, NULL, 250); Array windows = {}; windows.add({GetScreenRectRenderUnits()}); @@ -86,10 +99,12 @@ int main() { Window *window = &windows[0]; Buffer *buffer = &window->buffer; InitBuffer(buffer); - for (int i = 0; i < 100; i += 1) { - Array edits = {FrameArena}; - AddEdit(&edits, GetEnd(*buffer), Format(FrameArena, "line number: %d\n", i)); - ApplyEdits(buffer, edits); + if (0) { + for (int i = 0; i < 100; i += 1) { + Array edits = {FrameArena}; + AddEdit(&edits, GetEnd(*buffer), Format(FrameArena, "line number: %d\n", i)); + ApplyEdits(buffer, edits); + } } window->cursors.add({}); @@ -100,7 +115,7 @@ int main() { { Window *focused_window = &windows[0]; - if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) || IsKeyDown(KEY_SPACE)) { + if (IsKeyDown(KEY_F1)) { camera_offset_world_to_render_units = Vector2Subtract(camera_offset_world_to_render_units, GetMouseDelta()); } @@ -116,14 +131,21 @@ int main() { } Array edits = {FrameArena}; - For(focused_window->cursors) { - AddEdit(&edits, it, string); - } + For(focused_window->cursors) AddEdit(&edits, it, string); ApplyEdits(&focused_window->buffer, edits); + For(focused_window->cursors) + it.max = it.min = MoveRight(focused_window->buffer, it.min); } - if (IsKeyPressed(KEY_F1)) { - font = LoadFontEx("C:/Windows/Fonts/consola.ttf", (int)font_size, NULL, 250); + if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) { + For(focused_window->cursors) { + it.max = it.min = MoveLeft(focused_window->buffer, it.min); + } + } + if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) { + For(focused_window->cursors) { + it.max = it.min = MoveRight(focused_window->buffer, it.min); + } } } @@ -131,7 +153,6 @@ int main() { ClearBackground(RAYWHITE); ForItem(window, windows) { - // Draw the frame Rect2 window_rect_in_render_units = { WorldToRenderUnits(window.rect_in_world_units.min, camera_offset_world_to_render_units), WorldToRenderUnits(window.rect_in_world_units.max, camera_offset_world_to_render_units), @@ -170,33 +191,44 @@ int main() { int64_t line; }; + // Compute visible rows and cells Array rows = {FrameArena}; for (int64_t line = line_min_y; line < line_max_y; line += 1) { if (line < 0) break; if (line >= window.buffer.lines.len) break; - - CellRow row = {}; - row.line = line; - row.cells.allocator = FrameArena; - Range line_range = window.buffer.lines[line]; Vec2 text_position_in_world_window_units = {0, line_offset * (float)line}; Vec2 text_position_in_window_units = WindowWorldToWindowUnits(text_position_in_world_window_units, window); Vec2 text_position_in_world_units = WindowToWorldUnits(text_position_in_window_units, window); Vec2 text_position_in_render_units = WorldToRenderUnits(text_position_in_world_units, camera_offset_world_to_render_units); - row.rect.min = text_position_in_render_units; + CellRow row = {}; + row.line = line; + row.cells.allocator = FrameArena; + row.rect.min = text_position_in_render_units; + + // Iterate the string and compute cells for current font + // we are also incorporating - new line and end of buffer glyphs + // into the stream! String text = GetString(window.buffer, line_range); if (font.texture.id == 0) font = GetFontDefault(); float textOffsetX = 0.0f; float scaleFactor = font_size / font.baseSize; // Character quad scaling factor - for (int64_t i = 0; i < text.len;) { - int codepointByteCount = 0; - int codepoint = GetCodepointNext(&text.data[i], &codepointByteCount); - int index = GetGlyphIndex(font, codepoint); - GlyphInfo *glyph = font.glyphs + index; - Vec2 glyph_position = {text_position_in_render_units.x + textOffsetX, text_position_in_render_units.y}; + for (int64_t i = 0;;) { + bool end_of_buffer = i == window.buffer.len; + bool new_line = i == line_range.max && i != window.buffer.len; + bool in_range = i < text.len; + bool continue_looping = end_of_buffer || new_line || in_range; + if (!continue_looping) break; + + int codepointByteCount = 1; + int codepoint = '\n'; + if (in_range) codepoint = GetCodepointNext(&text.data[i], &codepointByteCount); + + int index = GetGlyphIndex(font, codepoint); + GlyphInfo *glyph = font.glyphs + index; + Vec2 glyph_position = {text_position_in_render_units.x + textOffsetX, text_position_in_render_units.y}; float x_to_offset_by = ((float)glyph->advanceX * scaleFactor + font_spacing); if (glyph->advanceX == 0) x_to_offset_by = ((float)font.recs[index].width * scaleFactor + font_spacing); @@ -204,6 +236,8 @@ int main() { Vec2 cell_size = {x_to_offset_by, font_size}; Rect2 cell_rect = {glyph_position, Vector2Add(glyph_position, cell_size)}; Rectangle cell_rectangle = ToRectangle(cell_rect); + + // Clip everything that is outside the window and screen if (CheckCollisionRecs(cell_rectangle, ToRectangle(window_rect_in_render_units_clamped_to_screen))) { row.cells.add({cell_rect, codepoint, line_range.min + i}); row.rect.max = cell_rect.max; @@ -231,16 +265,21 @@ int main() { BeginScissorMode((int)window_rect_in_render_units.min.x, (int)window_rect_in_render_units.min.y, (int)window_rect_in_render_units_size.x, (int)window_rect_in_render_units_size.y); ForItem(row, rows) { For(row.cells) { - if ((it.codepoint != ' ') && (it.codepoint != '\t')) { + if (it.codepoint == '\n') { + Vec2 mid = GetMid(it.rect); + DrawCircle((int)mid.x, (int)mid.y, font_size / 8, {0, 0, 0, 120}); + } else if ((it.codepoint != ' ') && (it.codepoint != '\t')) { DrawTextCodepoint(font, it.codepoint, it.rect.min, font_size, BLACK); } } } + // Draw the cursor ForItem(cursor, window.cursors) { ForItem(row, rows) { Range line_range = window.buffer.lines[row.line]; if (cursor.min >= line_range.min && cursor.min <= line_range.max) { + ForItem(cell, row.cells) { if (cursor.min == cell.pos) { Rect2 rect = cell.rect; diff --git a/src/text_editor/rect2.cpp b/src/text_editor/rect2.cpp index 0ab7eb6..2002014 100644 --- a/src/text_editor/rect2.cpp +++ b/src/text_editor/rect2.cpp @@ -22,6 +22,14 @@ Rect2 Shrink(Rect2 result, float v) { return result; } +Vec2 GetMid(Rect2 r) { + Vec2 size = GetSize(r); + size.x /= 2.f; + size.y /= 2.f; + Vec2 result = Vector2Add(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);