Basic line wrapping works
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user