From 95cafa3200ffbc4bfc5cf6304a81978be8569f80 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Fri, 28 Jun 2024 09:30:22 +0200 Subject: [PATCH] Major refactor of text layout --- .gitignore | 3 +- src/text_editor/layout.cpp | 134 ++++++++++++++ src/text_editor/main.cpp | 347 ++++++++++--------------------------- src/text_editor/rect2.cpp | 21 +++ 4 files changed, 248 insertions(+), 257 deletions(-) create mode 100644 src/text_editor/layout.cpp diff --git a/.gitignore b/.gitignore index 90e54fd..785fdc0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ x64/Release .vs/ src/external -build/ \ No newline at end of file +build/ +*.rdbg \ No newline at end of file diff --git a/src/text_editor/layout.cpp b/src/text_editor/layout.cpp new file mode 100644 index 0000000..ac5f12c --- /dev/null +++ b/src/text_editor/layout.cpp @@ -0,0 +1,134 @@ +struct LayoutColumn { + Rect2 rect; + int64_t pos; + int codepoint; +}; + +struct LayoutRow { + Rect2 rect; + Array columns; +}; + +struct Layout { + Array rows; + Vec2 buffer_world_pixel_size; +}; + +struct Window { + Font font; + float font_size; + float font_spacing; + Rect2 rect; + + Vec2 scroll; // window_world_to_window_units + Cursor main_cursor_begin_frame; + Array cursors; + Buffer buffer; + + Arena layout_arena; + Layout layout; +}; + +template +struct Tuple { + T1 a; + T2 b; +}; + +Layout CalculateLayout(Arena *arena, Buffer &buffer, Font font, float font_size, float font_spacing) { + Layout layout = {}; + layout.rows.allocator = *arena; + + float scaleFactor = font_size / font.baseSize; // Character quad scaling factor + float text_offset_y = 0; + ForItem(line_range, buffer.lines) { + float textOffsetX = 0.0f; + + LayoutRow *row = layout.rows.alloc(); + row->columns.allocator = *arena; + row->rect.min = {textOffsetX, 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); + GlyphInfo *glyph = font.glyphs + index; + Vec2 glyph_position = {textOffsetX, 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->rect.max = cell_rect.max; + + textOffsetX += x_to_offset_by; + if (end_of_buffer || new_line) break; + } + + layout.buffer_world_pixel_size.x = Max(layout.buffer_world_pixel_size.x, textOffsetX); + text_offset_y += font_size; + } + layout.buffer_world_pixel_size.y = text_offset_y; + + return layout; +} + +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); + if (line >= 0 && line < window.layout.rows.len) { + return window.layout.rows.data + line; + } + return NULL; +} + +LayoutColumn *GetLayoutColumn(LayoutRow *row, float xpos_window_buffer_world_units) { + if (!row) return NULL; + For(row->columns) { + if (xpos_window_buffer_world_units >= it.rect.min.x && xpos_window_buffer_world_units <= it.rect.max.x) { + return ⁢ + } + } + return NULL; +} + +Tuple GetRowCol(Window &window, Vec2 pos_buffer_world_units) { + Tuple result = {}; + result.a = GetLayoutRow(window, pos_buffer_world_units.y); + result.b = GetLayoutColumn(result.a, pos_buffer_world_units.x); + return result; +} + +Tuple GetRowCol(Window &window, int64_t pos) { + Tuple result = {}; + For(window.layout.rows) { + if (window.layout.rows.len == 0) continue; + int64_t first_pos = it.columns.first()->pos; + int64_t last_pos = it.columns.last()->pos; + if (pos >= first_pos && pos <= last_pos) { + result.a = ⁢ + break; + } + } + + if (result.a) { + For(result.a->columns) { + if (it.pos == pos) { + result.b = ⁢ + break; + } + } + } + + return result; +} diff --git a/src/text_editor/main.cpp b/src/text_editor/main.cpp index 1642969..e6047da 100644 --- a/src/text_editor/main.cpp +++ b/src/text_editor/main.cpp @@ -5,50 +5,11 @@ // @todo: add history (undo, redo) // @todo: add clipboard history? -#include "buffer.cpp" #include "rect2.cpp" +#include "buffer.cpp" +#include "layout.cpp" -// Render units - positions ready to draw, y -// World units - positions offset by screen movement -// Window units - positions inside the window (starts in left top of window) -// WindowBuffer units -// WindowBufferWorld units - -struct Window { - uint64_t flags; - Rect2 rect_in_world_units; - Vec2 scroll; // window_world_to_window_units - float title_bar_pixel_size; - float line_number_bar_pixel_size; - float right_scroll_bar_pixel_size; - float bottom_scroll_bar_pixel_size; - - Cursor main_cursor_begin_frame; - Array cursors; - Buffer buffer; -}; - -Vec2 WindowBufferWorldToWindowBufferUnits(Vec2 value, const Window &window) { - Vec2 result = Vector2Subtract(value, window.scroll); - return result; -} - -Vec2 WindowBufferToWindowUnits(Vec2 value, Vec2 window_buffer_to_window_units) { - Vec2 result = Vector2Add(value, window_buffer_to_window_units); - return result; -} - -Vec2 WorldToRenderUnits(Vec2 value, Vec2 camera_offset_world_to_render_units) { - Vec2 result = Vector2Subtract(value, camera_offset_world_to_render_units); - return result; -} - -Vec2 WindowToWorldUnits(Vec2 value, const Window &window) { - Vector2 result = Vector2Add(value, window.rect_in_world_units.min); - return result; -} - -Rect2 GetScreenRectRenderUnits() { +Rect2 GetScreenRect() { Rect2 result = { { 0, 0}, {(float)GetRenderWidth(), (float)GetRenderHeight()} @@ -165,7 +126,7 @@ void BeforeEdit(Window *window) { } void AfterEdit(Window *window, Array edits) { - // First offset all cursors by edits + // Offset all cursors by edits ForItem(edit, edits) { int64_t remove_size = GetRangeSize(edit.range); int64_t insert_size = edit.string.len; @@ -188,7 +149,12 @@ void AfterEdit(Window *window, Array edits) { } } } + + // Make sure all cursors are in range For(window->cursors) it.range = Clamp(window->buffer, it.range); + + Clear(&window->layout_arena); + window->layout = CalculateLayout(&window->layout_arena, window->buffer, window->font, window->font_size, window->font_spacing); } Arena FrameArena; @@ -210,26 +176,25 @@ int main() { Array windows = {}; { - Window window = {}; - window.rect_in_world_units = GetScreenRectRenderUnits(); - window.rect_in_world_units.max.x *= 2; - window.rect_in_world_units.max.y *= 2; - - window.title_bar_pixel_size = 20; - window.line_number_bar_pixel_size = 40; - window.right_scroll_bar_pixel_size = 30; - window.bottom_scroll_bar_pixel_size = 30; + Window window = {}; + window.rect = GetScreenRect(); + window.font = font; + window.font_size = font_size; + window.font_spacing; + InitArena(&window.layout_arena); 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 sddddddddddddssssssssss faasda s: %d\n", i)); + AddEdit(&edits, GetEnd(window.buffer), Format(FrameArena, "line number: %d\n", i)); ApplyEdits(&window.buffer, edits); } } window.cursors.add({}); + AfterEdit(&window, {}); + windows.add(window); } @@ -487,139 +452,44 @@ int main() { ClearBackground(RAYWHITE); ForItem(window, windows) { - 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), - }; - Rectangle rectangle_in_render_units = ToRectangle(window_rect_in_render_units); + Rectangle rectangle_in_render_units = ToRectangle(window.rect); DrawRectangleRec(rectangle_in_render_units, WHITE); - Rect2 window_text_rect_in_render_units = window_rect_in_render_units; - Rect2 window_text_rect_in_render_units_clamped_to_screen = window_text_rect_in_render_units; - Rect2 screen_rect_in_render_units = GetScreenRectRenderUnits(); - window_text_rect_in_render_units_clamped_to_screen.min.x = Clamp(window_text_rect_in_render_units_clamped_to_screen.min.x, screen_rect_in_render_units.min.x, screen_rect_in_render_units.max.x); - window_text_rect_in_render_units_clamped_to_screen.max.x = Clamp(window_text_rect_in_render_units_clamped_to_screen.max.x, screen_rect_in_render_units.min.x, screen_rect_in_render_units.max.x); - window_text_rect_in_render_units_clamped_to_screen.min.y = Clamp(window_text_rect_in_render_units_clamped_to_screen.min.y, screen_rect_in_render_units.min.y, screen_rect_in_render_units.max.y); - window_text_rect_in_render_units_clamped_to_screen.max.y = Clamp(window_text_rect_in_render_units_clamped_to_screen.max.y, screen_rect_in_render_units.min.y, screen_rect_in_render_units.max.y); + // Figure out which lines to draw + Vec2 s = GetSize(window.rect); + float line_offset = 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); + int64_t visible_lines = line_max_y - line_min_y; - Vec2 window_buffer_to_window_units = {window.line_number_bar_pixel_size, window.title_bar_pixel_size}; - Rect2 title_bar_in_render_units = CutTop(&window_text_rect_in_render_units_clamped_to_screen, window.title_bar_pixel_size); - Rect2 line_number_bar_in_render_units = CutLeft(&window_text_rect_in_render_units_clamped_to_screen, window.line_number_bar_pixel_size); - Rect2 bottom_scroll_bar_in_render_units = CutBottom(&window_text_rect_in_render_units_clamped_to_screen, window.bottom_scroll_bar_pixel_size); - Rect2 right_scroll_bar_in_render_units = CutRight(&window_text_rect_in_render_units_clamped_to_screen, window.right_scroll_bar_pixel_size); - - DrawRectangleRec(ToRectangle(title_bar_in_render_units), GRAY); - DrawRectangleRec(ToRectangle(line_number_bar_in_render_units), GRAY); - DrawRectangleRec(ToRectangle(bottom_scroll_bar_in_render_units), GRAY); - DrawRectangleRec(ToRectangle(right_scroll_bar_in_render_units), GRAY); - DrawString(title_bar_font, "title bar :)", title_bar_in_render_units.min, title_bar_font_size, font_spacing, BLACK); - - if (0) { - window_text_rect_in_render_units_clamped_to_screen = Shrink(window_text_rect_in_render_units_clamped_to_screen, 10); - DrawRectangleRec(ToRectangle(window_text_rect_in_render_units_clamped_to_screen), {255, 0, 0, 50}); - } - - struct Cell { - Rect2 rect; - int codepoint; - int64_t column; - int64_t pos; - }; - - struct CellRow { - Array cells; - Rect2 rect; - int64_t line; - }; - - // Compute visible rows and cells - Array rows = {FrameArena}; - { - // Figure out which lines to draw - Vec2 s = GetSize(window_text_rect_in_render_units); - float line_offset = 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); - - for (int64_t line = line_min_y; line < line_max_y; line += 1) { - if (line < 0) break; - if (line >= window.buffer.lines.len) break; - Range line_range = window.buffer.lines[line]; - - Vec2 text_position_in_window_buffer_world_units = {0, line_offset * (float)line}; - Vec2 text_position_in_window_buffer_units = WindowBufferWorldToWindowBufferUnits(text_position_in_window_buffer_world_units, window); - Vec2 text_position_in_window_units = WindowBufferToWindowUnits(text_position_in_window_buffer_units, window_buffer_to_window_units); - 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); - - 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 (BufferIter iter = Iterate(window.buffer, line_range);; Advance(&iter)) { - bool end_of_buffer = iter.pos == window.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'; - if (in_range) codepoint = iter.item; - - 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); - - 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_text_rect_in_render_units_clamped_to_screen))) { - row.cells.add({cell_rect, codepoint, iter.codepoint_index, iter.pos}); - row.rect.max = cell_rect.max; - } - - textOffsetX += x_to_offset_by; - if (end_of_buffer || new_line) break; + Array visible_columns = {FrameArena}; + for (int64_t line = line_min_y; line < line_max_y && line >= 0 && line < window.layout.rows.len; line += 1) { + LayoutRow &row = window.layout.rows[line]; + For(row.columns) { + if (CheckCollisionRecs(ToRectangle(it.rect), ToRectangle(window.rect))) { + visible_columns.add(it); } - - if (row.cells.len) rows.add(row); } } // Update the scroll based on first cursor if (!AreEqual(window.main_cursor_begin_frame, window.cursors[0])) { - Vec2 rect_in_render_units = GetSize(window_text_rect_in_render_units_clamped_to_screen); - float visible_cells_in_render_units = font_size * (float)rows.len; - float cut_off_in_render_units = visible_cells_in_render_units - rect_in_render_units.y; + Vec2 rect_size = GetSize(window.rect); + float visible_cells_in_render_units = font_size * (float)visible_lines; + float cut_off_in_render_units = visible_cells_in_render_units - rect_size.y; Cursor cursor = window.cursors[0]; int64_t front = GetFront(cursor); Line line = FindLine(window.buffer, front); // Scroll Y - int64_t min_line = rows[0].line; - int64_t max_line = rows[rows.len - 1].line; - if (line.number < min_line) { + if (line.number < line_min_y) { window.scroll.y = line.number * font_size; - } else if (line.number >= max_line) { - int64_t diff = line.number - max_line; - window.scroll.y = (min_line + diff) * font_size + cut_off_in_render_units; + } else if (line.number >= line_max_y) { + int64_t diff = line.number - line_max_y; + window.scroll.y = (line_min_y + diff) * font_size + cut_off_in_render_units; } // Scroll X @@ -631,7 +501,7 @@ int main() { GlyphInfo info = GetGlyphInfo(font, ' '); float right_scroll_zone = (float)(info.image.width + info.advanceX) * 3; - float window_buffer_world_right_edge = (rect_in_render_units.x + window.scroll.x) - right_scroll_zone; + float window_buffer_world_right_edge = (rect_size.x + window.scroll.x) - right_scroll_zone; float window_buffer_world_left_edge = window.scroll.x; if (x_cursor_position_in_window_buffer_world_units >= window_buffer_world_right_edge) { float diff = x_cursor_position_in_window_buffer_world_units - window_buffer_world_right_edge; @@ -642,108 +512,73 @@ int main() { } } - // Draw debug markers - if (0) { - ForItem(row, rows) { - For(row.cells) { - DrawRectangleLinesEx(ToRectangle(it.rect), 1, {255, 0, 0, 50}); - } - DrawRectangleLinesEx(ToRectangle(row.rect), 1, {0, 190, 50, 255}); - } - } - - // Mouse in text area + // Mouse selection @todo { - // @todo: click twice to select word - // @tood: scrolling when selecting (Y and X) - // @todo: change cursors - Vec2 mouse_in_render_units = GetMousePosition(); - if (CheckCollisionPointRec(mouse_in_render_units, ToRectangle(window_text_rect_in_render_units_clamped_to_screen))) { - ForItem(row, rows) { - ForItem(cell, row.cells) { - if (CheckCollisionPointRec(mouse_in_render_units, ToRectangle(cell.rect))) { - DrawRectangleRec(ToRectangle(cell.rect), {0, 0, 255, 40}); - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - if (IsKeyDown(KEY_LEFT_CONTROL)) { - window.cursors.add({cell.pos, cell.pos}); - } else { - window.cursors.clear(); - window.cursors.add({cell.pos, cell.pos}); - } - } else if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { - Cursor *c = window.cursors.last(); - *c = ChangeBack(*c, cell.pos); - } - } - } + Vec2 mouse = GetMousePosition(); + Vec2 mouse_lookup = Vector2Add(mouse, window.scroll); + + Tuple rowcol = GetRowCol(window, mouse_lookup); + if (rowcol.b) { + Rect2 col_rect = rowcol.b->rect - window.scroll; + Rectangle col_rectangle = ToRectangle(col_rect); + if (CheckCollisionPointRec(mouse, col_rectangle)) { + DrawRectangleRec(col_rectangle, {0, 0, 255, 40}); } } } // Draw the glyphs - Vec2 window_text_rect_in_render_units_clamped_to_screen_size = GetSize(window_text_rect_in_render_units_clamped_to_screen); - BeginScissorMode((int)window_text_rect_in_render_units_clamped_to_screen.min.x, (int)window_text_rect_in_render_units_clamped_to_screen.min.y, (int)window_text_rect_in_render_units_clamped_to_screen_size.x, (int)window_text_rect_in_render_units_clamped_to_screen_size.y); - ForItem(row, rows) { - For(row.cells) { - if (it.codepoint == '\n') { - Vec2 mid = GetMid(it.rect); + Vec2 window_rect_size = GetSize(window.rect); + BeginScissorMode((int)window.rect.min.x, (int)window.rect.min.y, (int)window_rect_size.x, (int)window_rect_size.y); + + for (int64_t line = line_min_y; line < line_max_y; line += 1) { + if (line < 0 || line >= window.layout.rows.len) continue; + LayoutRow &row = window.layout.rows[line]; + + ForItem(col, row.columns) { + Vec2 p0 = Vector2Subtract(col.rect.min, window.scroll); + Vec2 p1 = Vector2Subtract(col.rect.max, window.scroll); + Rect2 rect = {p0, p1}; + + if (!CheckCollisionRecs(ToRectangle(rect), ToRectangle(window.rect))) { + continue; // Clip everything that is outside the window and screen + } + + if (col.codepoint == '\n') { + Vec2 mid = GetMid(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); + } else if ((col.codepoint != ' ') && (col.codepoint != '\t')) { + DrawTextCodepoint(font, col.codepoint, rect.min, font_size, BLACK); } } } - // Draw cursor stuff + // Draw cursor stuff @todo: draw selection ForItem(cursor, window.cursors) { - Line min_line = FindLine(window.buffer, cursor.range.min); - Line max_line = FindLine(window.buffer, cursor.range.max); - bool selecting = cursor.range.min != cursor.range.max; - ForItem(row, rows) { - // Draw line highlight - if (row.line == min_line.number) { - DrawRectangleRec(ToRectangle(row.rect), {255, 0, 0, 30}); - } + auto front = GetRowCol(window, GetFront(cursor)); + auto back = GetRowCol(window, GetBack(cursor)); - ForItem(cell, row.cells) { - // Draw selection - if (selecting) { - if (row.line >= min_line.number && row.line <= max_line.number) { - if (cell.pos >= cursor.range.min && cell.pos < cursor.range.max) { - DrawRectangleRec(ToRectangle(cell.rect), BLUE); - } - } - } - - // Draw cursors - if (cell.pos == GetFront(cursor)) { - Rect2 rect = cell.rect; - rect = CutLeft(&rect, 4); - DrawRectangleRec(ToRectangle(rect), RED); - } - if (cell.pos == GetBack(cursor)) { - Rect2 rect = cell.rect; - rect = CutLeft(&rect, 2); - DrawRectangleRec(ToRectangle(rect), GREEN); - } + For(visible_columns) { + if (it.pos >= cursor.range.min && it.pos < cursor.range.max) { + DrawRectangleRec(ToRectangle(it.rect), {0, 50, 150, 50}); } } + + if (front.b) { + Rect2 rect = front.b->rect; + rect -= window.scroll; + rect = CutLeft(&rect, 4); + DrawRectangleRec(ToRectangle(rect), RED); + } + if (back.b) { + Rect2 rect = back.b->rect; + rect -= window.scroll; + rect = CutLeft(&rect, 2); + DrawRectangleRec(ToRectangle(rect), GREEN); + } } + EndScissorMode(); - - // Draw the line numbers - { - Rectangle bar_rectangle = ToRectangle(line_number_bar_in_render_units); - BeginScissorMode((int)bar_rectangle.x, (int)bar_rectangle.y, (int)bar_rectangle.width, (int)bar_rectangle.height); - Vec2 text_position_in_render_units = line_number_bar_in_render_units.min; - For(rows) { - Assert(it.cells.len); - text_position_in_render_units.y = it.cells[0].rect.min.y; - String string_num = Format(FrameArena, "%lld", (long long)it.line); - DrawString(font, string_num, text_position_in_render_units, font_size, font_spacing, BLACK); - } - EndScissorMode(); - } } EndDrawing(); diff --git a/src/text_editor/rect2.cpp b/src/text_editor/rect2.cpp index 2002014..ad3650e 100644 --- a/src/text_editor/rect2.cpp +++ b/src/text_editor/rect2.cpp @@ -71,3 +71,24 @@ Rect2 CutTop(Rect2 *r, float value) { }; return result; } + +Rect2 operator-(Rect2 r, float value) { + r.min.x -= value; + r.min.y -= value; + r.max.x -= value; + r.max.y -= value; + return r; +} + +Rect2 operator-(Rect2 r, Vec2 value) { + r.min.x -= value.x; + r.min.y -= value.y; + r.max.x -= value.x; + r.max.y -= value.y; + return r; +} + +Rect2 operator-=(Rect2 &r, Vec2 value) { + r = r - value; + return r; +} \ No newline at end of file