#define BASIC_IMPL #include "../basic/basic.h" #include "raylib.h" #include "raymath.h" #include "buffer.cpp" Arena FrameArena; using Vec2 = Vector2; struct Rect2 { Vec2 min; Vec2 max; }; Vec2 GetSize(Rect2 r) { Vec2 result = {r.max.x - r.min.x, r.max.y - r.min.y}; return result; } Rectangle ToRectangle(Rect2 r) { Rectangle result = {r.min.x, r.min.y, r.max.x - r.min.x, r.max.y - r.min.y}; return result; } // 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) // WindowWorld units - positions offset by a position inside the buffer struct Window { Rect2 rect_in_world_units; Vec2 window_world_to_window_units; Array cursors; Buffer buffer; }; Vec2 WindowWorldToWindowUnits(Vec2 value, const Window &window) { Vec2 result = Vector2Subtract(value, window.window_world_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 result = { { 0, 0}, {(float)GetRenderWidth(), (float)GetRenderHeight()} }; return result; } // Draw text using Font // NOTE: chars spacing is NOT proportional to fontSize 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 } } int main() { InitScratch(); RunBufferTests(); InitWindow(800, 600, "Hello"); SetTargetFPS(60); InitArena(&FrameArena); float font_size = 25; float font_spacing = 1; Font font = LoadFontEx("C:/Windows/Fonts/times.ttf", (int)font_size, NULL, 250); Array windows = {}; windows.add({GetScreenRectRenderUnits()}); { Buffer *buffer = &windows[0].buffer; InitBuffer(buffer); for (int i = 0; i < 100; i += 1) { Array edits = {FrameArena}; AddEdit(&edits, GetEnd(*buffer), Format(FrameArena, "line number: %d\n", i)); ApplyEdits(buffer, edits); } } Vec2 camera_offset_world_to_render_units = {}; while (!WindowShouldClose()) { { Window *focused_window = &windows[0]; if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) || IsKeyDown(KEY_SPACE)) { camera_offset_world_to_render_units = Vector2Subtract(camera_offset_world_to_render_units, GetMouseDelta()); } float mouse_wheel = GetMouseWheelMove() * 48; focused_window->window_world_to_window_units.y -= mouse_wheel; focused_window->window_world_to_window_units.y = ClampBottom(focused_window->window_world_to_window_units.y, 0.f); for (int c = GetCharPressed(); c; c = GetCharPressed()) { String string = "?"; UTF8Result utf8 = UTF32ToUTF8((uint32_t)c); if (utf8.error == 0) { string = {(char *)utf8.out_str, (int64_t)utf8.len}; } Array edits = {FrameArena}; AddEdit(&edits, {}, string); ApplyEdits(&focused_window->buffer, edits); } } BeginDrawing(); 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); DrawRectangleRec(rectangle_in_render_units, WHITE); // // Line rendering // // @todo: how to constrain x? // @optimize: I could clip to visible on screen window_rectangle but I couldn't get it right Vec2 s = GetSize(window_rect_in_render_units); float line_offset = font_size; float _line_min_y = (window.window_world_to_window_units.y) / line_offset; float _line_max_y = (s.y + window.window_world_to_window_units.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); Vec2 window_rect_in_render_units_size = GetSize(window_rect_in_render_units); BeginScissorMode((int)window_rect_in_render_units.min.x, (int)window_rect_in_render_units.min.y, (int)window_rect_in_render_units_size.x, (int)window_rect_in_render_units_size.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_world_window_units = {0, line_offset * (float)line}; Vec2 text_position_in_window_units = WindowWorldToWindowUnits(text_position_in_world_window_units, window); 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); // // Glyph inside the line rendering // 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 (int i = 0; i < text.len;) { int codepointByteCount = 0; int codepoint = GetCodepointNext(&text.data[i], &codepointByteCount); 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)}; DrawRectangleLinesEx(ToRectangle(cell_rect), 1, {255, 0, 0, 120}); if ((codepoint != ' ') && (codepoint != '\t')) { DrawTextCodepoint(font, codepoint, glyph_position, font_size, BLACK); } textOffsetX += x_to_offset_by; i += codepointByteCount; // Move text bytes counter to next codepoint } } EndScissorMode(); } EndDrawing(); } }