Basic line wrapping works

This commit is contained in:
Krzosa Karol
2024-07-03 07:14:57 +02:00
parent 0a75cde80c
commit a77a149664
2 changed files with 146 additions and 117 deletions

View File

@@ -13,6 +13,8 @@ struct Layout {
Array<LayoutRow> 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<LayoutRow *, LayoutColumn *> 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<Edit> 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);
}

View File

@@ -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<Window> 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};