diff --git a/src/text_editor/layout.cpp b/src/text_editor/layout.cpp index 9761433..aebf940 100644 --- a/src/text_editor/layout.cpp +++ b/src/text_editor/layout.cpp @@ -13,6 +13,8 @@ struct Layout { Array rows; Vec2 buffer_world_pixel_size; LayoutColumn *max_column; + + Range visible_line_range; }; struct HistoryEntry { @@ -30,6 +32,7 @@ struct Window { Font font; float font_size; float font_spacing; + Rect2 start_rect; Rect2 rect; @@ -53,80 +56,6 @@ struct Tuple { T2 b; }; -Layout CalculateLayout(Arena *arena, Buffer &buffer, Font font, float font_size, float font_spacing) { - Layout layout = {}; - layout.rows.allocator = *arena; - - 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) { - 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 = {text_offset_x, text_offset_y}; - - 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 = {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, (int)iter.item}); - row->rect.max = cell_rect.max; - - if (layout.max_column == NULL || row->rect.max.x > layout.max_column->rect.max.x) { - layout.max_column = row->columns.last(); - } - - text_offset_x += x_to_offset_by; - } - - 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 = NULL; - if (last_range->min == last_range->max) { - row = layout.rows.alloc(); - row->columns.allocator = *arena; - text_offset_x = 0; - } else { - text_offset_y -= line_spacing; - row = layout.rows.last(); - } - 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}; - - if (layout.max_column == NULL || row->rect.max.x > layout.max_column->rect.max.x) { - layout.max_column = row->columns.last(); - } - } - - 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 @@ -191,6 +120,105 @@ Vector2 MeasureString(Font font, String text, float fontSize, float spacing) { return textSize; } +void RegenLayout(Window *window) { + Clear(&window->layout_arena); + window->layout = {}; + + Buffer &buffer = window->buffer; + Arena *arena = &window->layout_arena; + Layout &layout = window->layout; + layout.rows.allocator = *arena; + + Rect2 visible_rect = {window->scroll, window->scroll + GetSize(window->rect)}; + Vec2 visible_px_size = GetSize(window->rect); + window->layout.visible_line_range = {-1, -1}; + + Range *last_range = buffer.lines.last(); + float scaleFactor = window->font_size / window->font.baseSize; // Character quad scaling factor + float text_offset_y = 0; + float text_offset_x = 0; + float line_spacing = window->font_size; + + static bool line_wrapping; + if (IsKeyPressed(KEY_F1)) line_wrapping = !line_wrapping; + + float space = MeasureString(window->font, "_", window->font_size, window->font_spacing).x; + + ForItem(line_range, buffer.lines) { + 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 = {text_offset_x, text_offset_y}; + + if (row->rect.min.y >= visible_rect.min.y && row->rect.min.y < visible_rect.max.y) { + if (window->layout.visible_line_range.min == -1) window->layout.visible_line_range.min = layout.rows.get_index(*row); + window->layout.visible_line_range.max = layout.rows.get_index(*row); + } + + for (BufferIter iter = Iterate(buffer, line_range); IsValid(iter); Advance(&iter)) { + int index = GetGlyphIndex(window->font, (int)iter.item); + GlyphInfo *glyph = window->font.glyphs + index; + Vec2 glyph_position = {text_offset_x, text_offset_y}; + + float x_to_offset_by = ((float)glyph->advanceX * scaleFactor + window->font_spacing); + if (glyph->advanceX == 0) x_to_offset_by = ((float)window->font.recs[index].width * scaleFactor + window->font_spacing); + + Vec2 cell_size = {x_to_offset_by, window->font_size}; + Rect2 cell_rect = {glyph_position, Vector2Add(glyph_position, cell_size)}; + row->columns.add({cell_rect, iter.pos, (int)iter.item}); + row->rect.max = cell_rect.max; + + if (layout.max_column == NULL || row->rect.max.x > layout.max_column->rect.max.x) { + layout.max_column = row->columns.last(); + } + + text_offset_x += x_to_offset_by; + + if (line_wrapping) { + if (text_offset_x > (visible_px_size.x - space)) { + text_offset_x = 0; + text_offset_y += line_spacing; + } + } + } + + 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 = NULL; + if (last_range->min == last_range->max) { + row = layout.rows.alloc(); + row->columns.allocator = *arena; + text_offset_x = 0; + } else { + text_offset_y -= line_spacing; + row = layout.rows.last(); + } + int index = GetGlyphIndex(window->font, ' '); + GlyphInfo *glyph = window->font.glyphs + index; + Vec2 glyph_position = {text_offset_x, text_offset_y}; + + float x_to_offset_by = ((float)glyph->advanceX * scaleFactor + window->font_spacing); + if (glyph->advanceX == 0) x_to_offset_by = ((float)window->font.recs[index].width * scaleFactor + window->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}; + + if (layout.max_column == NULL || row->rect.max.x > layout.max_column->rect.max.x) { + layout.max_column = row->columns.last(); + } + } + + layout.buffer_world_pixel_size.y = text_offset_y; +} + int64_t YPosWorldToLineNumber(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); @@ -247,13 +275,18 @@ Tuple GetRowCol(Window &window, int64_t pos) { } Range CalculateVisibleLineRange(Window &window) { - Vec2 s = GetSize(window.rect); - float line_offset = window.font_size; - float _line_min_y = (window.scroll.y) / line_offset; - float _line_max_y = (s.y + window.scroll.y) / line_offset; - int64_t line_min_y = (int64_t)floorf(_line_min_y); - int64_t line_max_y = (int64_t)ceilf(_line_max_y); - Range result = {line_min_y, line_max_y}; + Rect2 visible_rect = {window.scroll, window.scroll + GetSize(window.rect)}; + Range result = {-1, -1}; + For(window.layout.rows) { + if (it.rect.min.y >= visible_rect.min.y && it.rect.min.y <= visible_rect.max.y) { + result.max = window.layout.rows.get_index(it); + if (result.min == -1) result.min = result.max; + } else if (result.max != -1) { + break; + } + } + result.min = ClampBottom(result.min - 1, (int64_t)0); + result.max = ClampTop(result.max + 1, window.layout.rows.len); return result; } @@ -360,10 +393,6 @@ void RedoEdit(Window *window) { Allocator sys_allocator = GetSystemAllocator(); For(entry.edits) Dealloc(sys_allocator, &it.string.data); entry.edits.dealloc(); - - // Generate layout - Clear(&window->layout_arena); - window->layout = CalculateLayout(&window->layout_arena, window->buffer, window->font, window->font_size, window->font_spacing); } void UndoEdit(Window *window) { @@ -381,10 +410,6 @@ void UndoEdit(Window *window) { Allocator sys_allocator = GetSystemAllocator(); For(entry.edits) Dealloc(sys_allocator, &it.string.data); entry.edits.dealloc(); - - // Generate layout - Clear(&window->layout_arena); - window->layout = CalculateLayout(&window->layout_arena, window->buffer, window->font, window->font_size, window->font_spacing); } void BeforeEdit(Window *window) { @@ -448,8 +473,4 @@ void AfterEdit(Window *window, Array edits) { // Make sure all cursors are in range For(window->cursors) it.range = Clamp(window->buffer, it.range); - - // Generate layout - Clear(&window->layout_arena); - window->layout = CalculateLayout(&window->layout_arena, window->buffer, window->font, window->font_size, window->font_spacing); } diff --git a/src/text_editor/main.cpp b/src/text_editor/main.cpp index 3e3b6af..c8e7b94 100644 --- a/src/text_editor/main.cpp +++ b/src/text_editor/main.cpp @@ -3,13 +3,12 @@ #include "raylib.h" #include "raymath.h" -// @todo: add clipboard history? -// @todo: mouse double click -// @todo: context menu -// @todo: scrollbars -// @todo: line numbers +// @todo: highlight all occurences of selected word +// @todo: systematic way of coloring words // @todo: line wrapping +// @todo: context menu // @todo: toy around with acme ideas +// @todo: add clipboard history? #include "rect2.cpp" #include "buffer.cpp" #include "layout.cpp" @@ -51,7 +50,7 @@ bool IsDoubleClick() { double diff = time - last_click_time; last_click_time = time; // @todo: don't do this manually, use windows - if (diff >= 0.15 && diff <= 0.5) { + if (diff >= 0.05 && diff <= 0.25) { last_click_time = 0; return true; } @@ -64,6 +63,8 @@ bool IsDoubleClick() { int main() { InitScratch(); InitWindow(800, 600, "Hello"); + // @todo: dpi + SetWindowState(FLAG_WINDOW_RESIZABLE); SetTargetFPS(60); { uint32_t data = 0xffdddddd; @@ -75,7 +76,7 @@ int main() { InitArena(&PermArena); float font_size = 64; float font_spacing = 1; - Font font = LoadFontEx("C:/Windows/Fonts/consola.ttf", (int)font_size, NULL, 250); + Font font = LoadFontEx("C:/Windows/Fonts/times.ttf", (int)font_size, NULL, 250); Array windows = {}; { @@ -119,7 +120,8 @@ int main() { UpdateEventRecording(); { - Window *focused_window = &windows[0]; + Window *focused_window = &windows[0]; + focused_window->start_rect = GetScreenRect(); float mouse_wheel = GetMouseWheelMove() * 48; focused_window->scroll.y -= mouse_wheel; @@ -416,23 +418,23 @@ int main() { ClearBackground(RAYWHITE); ForItem(window, windows) { - // Draw and layout window overlay - Vec2 mouse = GetMousePosition(); - Rect2 line_number_rect = {}; - { - window.rect = window.start_rect; - Rect2 horizontal_bar_rect = CutBottom(&window.rect, 10); - Rect2 vertical_bar_rect = CutRight(&window.rect, 10); - line_number_rect = CutLeft(&window.rect, MeasureString(window.font, "1234", window.font_size, window.font_spacing).x); + window.rect = window.start_rect; + Rect2 horizontal_bar_rect = CutBottom(&window.rect, 10); + Rect2 vertical_bar_rect = CutRight(&window.rect, 10); + Rect2 line_number_rect = CutLeft(&window.rect, MeasureString(window.font, "1234", window.font_size, window.font_spacing).x); + RegenLayout(&window); + // Draw and layout window overlay + Vec2 mouse = GetMousePosition(); + { DrawRectangleRec(ToRectangle(window.rect), WHITE); Vec2 size = GetSize(window.rect); Vec2 min = window.scroll / (window.layout.buffer_world_pixel_size + size); Vec2 max = (window.scroll + size) / (window.layout.buffer_world_pixel_size + size); - DrawRectangleRec(ToRectangle(vertical_bar_rect), GRAY); { + DrawRectangleRec(ToRectangle(vertical_bar_rect), GRAY); float vert_size = GetSize(vertical_bar_rect).y; float vert_begin = min.y * vert_size; float vert_end = max.y * vert_size; @@ -440,9 +442,13 @@ int main() { rect.min.y = vert_begin; rect.max.y = vert_end; rect = Shrink(rect, 2); - DrawRectangleRec(ToRectangle(rect), LIGHTGRAY); - if (CheckCollisionPointRec(mouse, ToRectangle(vertical_bar_rect))) { + bool colliding = CheckCollisionPointRec(mouse, ToRectangle(vertical_bar_rect)); + Color color = LIGHTGRAY; + if (colliding || window.mouse_selecting_vert_bar) color = RAYWHITE; + DrawRectangleRec(ToRectangle(rect), color); + + if (colliding) { if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { window.mouse_selecting_vert_bar = true; } @@ -457,8 +463,8 @@ int main() { } } - DrawRectangleRec(ToRectangle(horizontal_bar_rect), GRAY); { + DrawRectangleRec(ToRectangle(horizontal_bar_rect), GRAY); float hori_size = GetSize(horizontal_bar_rect).x; float hori_begin = min.x * hori_size; float hori_end = max.x * hori_size; @@ -466,9 +472,13 @@ int main() { rect.min.x = hori_begin; rect.max.x = hori_end; rect = Shrink(rect, 2); - DrawRectangleRec(ToRectangle(rect), LIGHTGRAY); - if (CheckCollisionPointRec(mouse, ToRectangle(horizontal_bar_rect))) { + bool colliding = CheckCollisionPointRec(mouse, ToRectangle(horizontal_bar_rect)); + Color color = LIGHTGRAY; + if (colliding || window.mouse_selecting_hori_bar) color = RAYWHITE; + DrawRectangleRec(ToRectangle(rect), color); + + if (colliding) { if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { window.mouse_selecting_hori_bar = true; } @@ -605,7 +615,6 @@ int main() { Range visible_line_range = CalculateVisibleLineRange(window); for (int64_t line = visible_line_range.min; line < visible_line_range.max; line += 1) { - if (line < 0 || line >= window.layout.rows.len) continue; LayoutRow &row = window.layout.rows[line]; ForItem(col, row.columns) { @@ -658,7 +667,6 @@ int main() { Rect2 rect = line_number_rect; BeginScissorMode((int)rect.min.x, (int)rect.min.y, (int)(rect.max.x - rect.min.x), (int)(rect.max.y - rect.min.y)); for (int64_t line = visible_line_range.min; line < visible_line_range.max; line += 1) { - if (line < 0 || line >= window.layout.rows.len) continue; LayoutRow &row = window.layout.rows[line]; Vec2 pos = {rect.min.x, row.rect.min.y + rect.min.y - window.scroll.y};