diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index dbcce7a..2e42def 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -27,6 +27,7 @@ struct Range { struct Line { int64_t number; Range range; + int64_t max_without_new_line; }; struct LineAndColumn { @@ -91,6 +92,11 @@ Range MakeRange(int64_t a, int64_t b) { return result; } +Range MakeRange(int64_t a) { + Range result = {a, a}; + return result; +} + int64_t GetFront(Cursor cursor) { int64_t result = cursor.pos[cursor.ifront]; return result; @@ -342,7 +348,7 @@ void ApplyEdits(Buffer *buffer, Array edits) { int64_t index = 0; int64_t base_index = 0; while (Seek(string, delimiter, &index)) { - buffer->lines.add({base_index, base_index + index}); + buffer->lines.add({base_index, base_index + index + delimiter.len}); base_index += index + delimiter.len; string = string.skip(index + delimiter.len); } @@ -457,25 +463,28 @@ BufferIter Iterate(Buffer &buffer, Range range, int64_t direction = ITERATE_FORW return result; } -Line GetLine(Buffer &buffer, int64_t line) { +Line GetLineByIndex(Buffer &buffer, int64_t line) { Assert(buffer.lines.len); line = Clamp(line, (int64_t)0, buffer.lines.len - 1); Range range = buffer.lines[line]; - Line result = {line, range}; + Line result = {line, range, range.max}; + if (GetChar(buffer, range.max - 1) == '\n') result.max_without_new_line -= 1; return result; } Line FindLine(Buffer &buffer, int64_t pos) { + Line result = {}; For(buffer.lines) { - // The program is doing '<= it.max' so as to include the new line. - // Otherwise this function wouldn't be able to find certain positions. - if (pos >= it.min && pos <= it.max) { - Line result = {buffer.lines.get_index(it), it}; - return result; + bool found_last = (pos == it.min && pos == it.max); + bool found = pos >= it.min && pos < it.max; + if (found || found_last) { + result = {buffer.lines.get_index(it), it, it.max}; + if (GetChar(buffer, it.max - 1) == '\n') result.max_without_new_line -= 1; + break; } } - return {}; + return result; } LineAndColumn FindLineAndColumn(Buffer &buffer, int64_t pos) { @@ -493,8 +502,8 @@ LineAndColumn FindLineAndColumn(Buffer &buffer, int64_t pos) { } int64_t FindPos(Buffer &buffer, int64_t line_number, int64_t column) { - Line line = GetLine(buffer, line_number); - int64_t result = line.range.max; + Line line = GetLineByIndex(buffer, line_number); + int64_t result = line.max_without_new_line; for (BufferIter iter = Iterate(buffer, line.range); IsValid(iter); Advance(&iter)) { if (iter.codepoint_index == column) { result = iter.pos; @@ -552,146 +561,3 @@ int64_t Seek(Buffer &buffer, int64_t pos, int64_t direction = ITERATE_FORWARD) { } return result; } - -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"); - Assert(buffer.lines.len == 1); - Assert(GetString(buffer, buffer.lines[0]) == "Things and other things"); - - edits.clear(); - AddEdit(&edits, GetEnd(buffer), " memes"); - ApplyEdits(&buffer, edits); - Assert(GetString(buffer, buffer.lines[0]) == "Things and other things memes"); - } - { - Buffer buffer = {scratch}; - Array edits = {scratch}; - edits.add({}); - ApplyEdits(&buffer, edits); - Assert("" == GetString(buffer)); - Assert(buffer.lines.len == 1); - } - { - 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"); - Assert(buffer.lines.len == 1); - Assert(GetString(buffer, buffer.lines[0]) == "Memes dna BigOther things"); - } - { - Buffer buffer = {scratch}; - Array edits = {scratch}; - edits.add({ - {0, 0}, - "Things and other things\n" - "Things and other things\n" - }); - ApplyEdits(&buffer, edits); - Assert(buffer.lines.len == 3); - Assert(GetString(buffer, buffer.lines[1]) == "Things and other things"); - Assert(GetString(buffer, buffer.lines[0]) == "Things and other things"); - Assert(GetString(buffer, buffer.lines[2]) == ""); - - { - Array s = {scratch}; - for (BufferIter iter = Iterate(buffer, {0, 6}); IsValid(iter); Advance(&iter)) { - Assert(iter.item < 255); - - s.add((char)iter.item); - } - String str = {s.data, s.len}; - Assert(str == "Things"); - } - { - Array s = {scratch}; - for (BufferIter iter = Iterate(buffer, {0, 6}, ITERATE_BACKWARD); IsValid(iter); Advance(&iter)) { - Assert(iter.item < 255); - - s.add((char)iter.item); - } - String str = {s.data, s.len}; - Assert(str == "sgnihT"); - } - { - Array s = {scratch}; - for (BufferIter iter = Iterate(buffer, {0, buffer.len}); IsValid(iter); Advance(&iter)) { - Assert(iter.item < 255); - - s.add((char)iter.item); - } - String str = {s.data, s.len}; - String b = {GetCharP(buffer, 0), buffer.len}; - Assert(str == b); - } - { - Array s = {scratch}; - for (BufferIter iter = Iterate(buffer, {0, buffer.len}, ITERATE_BACKWARD); IsValid(iter); Advance(&iter)) { - Assert(iter.item < 255); - - s.add((char)iter.item); - } - String str = {s.data, s.len}; - String b = {GetCharP(buffer, 0), buffer.len}; - Assert(str.len == b.len); - } - } - - { - Arena *arena = AllocArena(); - Buffer buffer = {*arena}; - Array edits = {*arena}; - edits.add({ - {0, 0}, - "Things and other things\n" - "Things and other things\n" - }); - - int iters = 100; - for (int i = 0; i < iters; i += 1) { - ApplyEdits(&buffer, edits); - for (int64_t j = 0; j < i; j += 1) { - String string = GetString(buffer, {edits[0].string.len * j, edits[0].string.len * (j + 1)}); - Assert(string == edits[0].string); - } - } - Assert(edits[0].string.len * iters == buffer.len); - Assert(buffer.lines.len == iters * 2 + 1); - - Line l0 = FindLine(buffer, 4); - Assert(l0.number == 0); - Assert(l0.range.min == 0); - Assert(l0.range.max < 30); - - Line l1 = FindLine(buffer, 30); - Assert(l1.number == 1); - Assert(l1.range.min > 20); - Assert(l1.range.max < 50); - Assert(l1.range.max == GetLine(buffer, 1).range.max); - - // Make sure there are no gaps - for (int64_t i = 100; i < 600; i += 1) { - Line l2 = FindLine(buffer, i); - Assert(l2.number > 0); - } - } -} \ No newline at end of file diff --git a/src/text_editor/layout.cpp b/src/text_editor/layout.cpp index b2054a5..4e8d320 100644 --- a/src/text_editor/layout.cpp +++ b/src/text_editor/layout.cpp @@ -40,50 +40,130 @@ Layout CalculateLayout(Arena *arena, Buffer &buffer, Font font, float font_size, Layout layout = {}; layout.rows.allocator = *arena; - float scaleFactor = font_size / font.baseSize; // Character quad scaling factor - float text_offset_y = 0; + Range *last_range = buffer.lines.last(); + float scaleFactor = font_size / font.baseSize; // Character quad scaling factor + float text_offset_y = 0; + float text_offset_x = 0; + float line_spacing = font_size; ForItem(line_range, buffer.lines) { - float textOffsetX = 0.0f; + text_offset_x = 0.0f; + if (&line_range == last_range && GetRangeSize(line_range) == 0) break; // end of buffer line LayoutRow *row = layout.rows.alloc(); row->columns.allocator = *arena; - row->rect.min = {textOffsetX, text_offset_y}; + row->rect.min = {text_offset_x, text_offset_y}; - BufferIter iter = Iterate(buffer, line_range); - for (;; Advance(&iter)) { - bool end_of_buffer = iter.pos == buffer.len; - bool new_line = iter.pos == line_range.max; - bool in_range = IsValid(iter); - bool continue_looping = end_of_buffer || new_line || in_range; - if (!continue_looping) break; - - int codepoint = '\n'; // @todo: questionable choice - if (in_range) codepoint = iter.item; - - int index = GetGlyphIndex(font, codepoint); + for (BufferIter iter = Iterate(buffer, line_range); IsValid(iter); Advance(&iter)) { + int index = GetGlyphIndex(font, (int)iter.item); GlyphInfo *glyph = font.glyphs + index; - Vec2 glyph_position = {textOffsetX, text_offset_y}; + Vec2 glyph_position = {text_offset_x, text_offset_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); Vec2 cell_size = {x_to_offset_by, font_size}; Rect2 cell_rect = {glyph_position, Vector2Add(glyph_position, cell_size)}; - row->columns.add({cell_rect, iter.pos, codepoint}); + row->columns.add({cell_rect, iter.pos, (int)iter.item}); row->rect.max = cell_rect.max; - textOffsetX += x_to_offset_by; - if (end_of_buffer || new_line) break; + text_offset_x += x_to_offset_by; } - layout.buffer_world_pixel_size.x = Max(layout.buffer_world_pixel_size.x, textOffsetX); - text_offset_y += font_size; + layout.buffer_world_pixel_size.x = Max(layout.buffer_world_pixel_size.x, text_offset_x); + text_offset_y += line_spacing; } + + // Add end of buffer as layout cell at the end + { + LayoutRow *row = layout.rows.alloc(); + row->columns.allocator = *arena; + if (last_range->min == last_range->max) { + text_offset_x = 0; + } else { + text_offset_y -= line_spacing; + } + int index = GetGlyphIndex(font, ' '); + GlyphInfo *glyph = font.glyphs + index; + Vec2 glyph_position = {text_offset_x, text_offset_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); + + Vec2 cell_size = {x_to_offset_by, line_spacing}; + Rect2 cell_rect = {glyph_position, Vector2Add(glyph_position, cell_size)}; + row->columns.add({cell_rect, buffer.len, '\0'}); + row->rect = {glyph_position, cell_rect.max}; + } + layout.buffer_world_pixel_size.y = text_offset_y; return layout; } +void DrawString(Font font, String 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;) { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetCodepointNext(&text.data[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + if ((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); + + i += codepointByteCount; // Move text bytes counter to next codepoint + } +} + +Vector2 MeasureString(Font font, String 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; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + for (int i = 0; i < size;) { + byteCounter++; + + int next = 0; + letter = GetCodepointNext(&text.data[i], &next); + index = GetGlyphIndex(font, letter); + + i += next; + + if (font.glyphs[index].advanceX != 0) textWidth += font.glyphs[index].advanceX; + else textWidth += (font.recs[index].width + font.glyphs[index].offsetX); + + if (tempByteCounter < byteCounter) tempByteCounter = byteCounter; + } + + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + + textSize.x = tempTextWidth * scaleFactor + (float)((tempByteCounter - 1) * spacing); + textSize.y = textHeight; + + return textSize; +} + LayoutRow *GetLayoutRow(Window &window, float ypos_window_buffer_world_units) { float line_spacing = window.font_size; int64_t line = (int64_t)floorf(ypos_window_buffer_world_units / line_spacing); diff --git a/src/text_editor/main.cpp b/src/text_editor/main.cpp index c801622..665224c 100644 --- a/src/text_editor/main.cpp +++ b/src/text_editor/main.cpp @@ -17,70 +17,6 @@ Rect2 GetScreenRect() { return result; } -void DrawString(Font font, String 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;) { - // Get next codepoint from byte string and glyph index in font - int codepointByteCount = 0; - int codepoint = GetCodepointNext(&text.data[i], &codepointByteCount); - int index = GetGlyphIndex(font, codepoint); - - if ((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); - - i += codepointByteCount; // Move text bytes counter to next codepoint - } -} - -Vector2 MeasureString(Font font, String 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; - - int letter = 0; // Current character - int index = 0; // Index position in sprite font - - for (int i = 0; i < size;) { - byteCounter++; - - int next = 0; - letter = GetCodepointNext(&text.data[i], &next); - index = GetGlyphIndex(font, letter); - - i += next; - - if (font.glyphs[index].advanceX != 0) textWidth += font.glyphs[index].advanceX; - else textWidth += (font.recs[index].width + font.glyphs[index].offsetX); - - if (tempByteCounter < byteCounter) tempByteCounter = byteCounter; - } - - if (tempTextWidth < textWidth) tempTextWidth = textWidth; - - textSize.x = tempTextWidth * scaleFactor + (float)((tempByteCounter - 1) * spacing); - textSize.y = textHeight; - - return textSize; -} - int64_t MoveRight(Buffer &buffer, int64_t pos) { pos = pos + 1; pos = AdjustUTF8Pos(buffer, pos); @@ -192,8 +128,6 @@ void Dbg_Draw() { int main() { InitScratch(); - RunBufferTests(); - InitWindow(800, 600, "Hello"); SetTargetFPS(60); @@ -218,11 +152,14 @@ int main() { InitBuffer(&window.buffer); if (1) { - for (int i = 0; i < 50; i += 1) { - Array edits = {FrameArena}; - AddEdit(&edits, GetEnd(window.buffer), Format(FrameArena, "line number line number line number line number: %d\n", i)); - ApplyEdits(&window.buffer, edits); - } + Array edits = {FrameArena}; + AddEdit(&edits, GetEnd(window.buffer), Format(FrameArena, "line number: 1")); + ApplyEdits(&window.buffer, edits); + // for (int i = 0; i < 5; i += 1) { + // Array edits = {FrameArena}; + // AddEdit(&edits, GetEnd(window.buffer), Format(FrameArena, "line number: %d\n", i)); + // ApplyEdits(&window.buffer, edits); + // } } window.cursors.add({}); @@ -321,8 +258,8 @@ int main() { For(focused_window->cursors) { int64_t front = GetFront(it); Line line = FindLine(focused_window->buffer, front); - String string = GetString(focused_window->buffer, {line.range.min, line.range.max + 1}); - AddEdit(&edits, {line.range.max + 1, line.range.max + 1}, string); + String string = GetString(focused_window->buffer, line.range); + AddEdit(&edits, {line.range.max, line.range.max}, string); } ApplyEdits(&focused_window->buffer, edits); AfterEdit(focused_window, edits); @@ -355,7 +292,7 @@ int main() { For(focused_window->cursors) { int64_t front = GetFront(it); Line line = FindLine(focused_window->buffer, front); - String string = GetString(focused_window->buffer, {line.range.min, line.range.max + 1}); + String string = GetString(focused_window->buffer, line.range); AddEdit(&edits, {line.range.min, line.range.min}, string); } ApplyEdits(&focused_window->buffer, edits); @@ -376,6 +313,32 @@ int main() { } } + For(focused_window->cursors) { + if (IsKeyPressed(KEY_HOME) || IsKeyPressedRepeat(KEY_HOME)) { + if (IsKeyDown(KEY_LEFT_SHIFT)) { + int64_t front = GetFront(it); + Line line = FindLine(focused_window->buffer, front); + it = ChangeFront(it, line.range.min); + } else { + int64_t front = GetFront(it); + Line line = FindLine(focused_window->buffer, front); + it.range.min = it.range.max = line.range.min; + } + } + + if (IsKeyPressed(KEY_END) || IsKeyPressedRepeat(KEY_END)) { + if (IsKeyDown(KEY_LEFT_SHIFT)) { + int64_t front = GetFront(it); + Line line = FindLine(focused_window->buffer, front); + it = ChangeFront(it, line.max_without_new_line); + } else { + int64_t front = GetFront(it); + Line line = FindLine(focused_window->buffer, front); + it.range.min = it.range.max = line.max_without_new_line; + } + } + } + // @todo: improve behaviour of all copy pasting if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) { Array strings = {FrameArena}; @@ -402,7 +365,7 @@ int main() { For(focused_window->cursors) { if (GetRangeSize(it.range) == 0) { Line line = FindLine(focused_window->buffer, it.range.min); - it.range = {line.range.min, line.range.max + 1}; + it.range = line.range; } } @@ -420,24 +383,6 @@ int main() { AfterEdit(focused_window, edits); } - if (IsKeyPressed(KEY_ENTER)) { - if (IsKeyDown(KEY_LEFT_CONTROL)) { - } else { - } - } - - if (IsKeyPressed(KEY_HOME) || IsKeyPressedRepeat(KEY_HOME)) { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - } else { - } - } - - if (IsKeyPressed(KEY_END) || IsKeyPressedRepeat(KEY_END)) { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - } else { - } - } - if (IsKeyDown(KEY_LEFT_CONTROL) && (IsKeyPressed(KEY_D) || IsKeyPressedRepeat(KEY_D))) { } @@ -460,6 +405,22 @@ int main() { } } + if (IsKeyPressed(KEY_ENTER)) { + if (IsKeyDown(KEY_LEFT_CONTROL)) { + For(focused_window->cursors) { + int64_t front = GetFront(it); + Line line = FindLine(focused_window->buffer, front); + it.range = MakeRange(line.max_without_new_line); + } + } + + BeforeEdit(focused_window); + Array edits = {FrameArena}; + For(focused_window->cursors) AddEdit(&edits, it.range, "\n"); + ApplyEdits(&focused_window->buffer, edits); + AfterEdit(focused_window, edits); + } + // Handle user input for (;;) { int c = GetCharPressed(); @@ -584,8 +545,9 @@ int main() { } if (col.codepoint == '\n') { - Vec2 mid = GetMid(rect); - DrawCircle((int)mid.x, (int)mid.y, font_size / 8, {0, 0, 0, 120}); + DrawTextEx(font, "\\n", rect.min, font_size, font_spacing, GRAY); + } else if (col.codepoint == '\0') { + DrawTextEx(font, "\\0", rect.min, font_size, font_spacing, {255, 0, 0, 150}); } else if ((col.codepoint != ' ') && (col.codepoint != '\t')) { DrawTextCodepoint(font, col.codepoint, rect.min, font_size, BLACK); }