651 lines
27 KiB
C++
651 lines
27 KiB
C++
#define BASIC_IMPL
|
|
#include "../basic/basic.h"
|
|
#include "raylib.h"
|
|
#include "raymath.h"
|
|
|
|
// @todo: add history (undo, redo)
|
|
// @todo: add clipboard history?
|
|
#include "rect2.cpp"
|
|
#include "buffer.cpp"
|
|
#include "layout.cpp"
|
|
|
|
Rect2 GetScreenRect() {
|
|
Rect2 result = {
|
|
{ 0, 0},
|
|
{(float)GetRenderWidth(), (float)GetRenderHeight()}
|
|
};
|
|
return result;
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
Vector2 MeasureString(Font font, String text, float fontSize, float spacing) {
|
|
Vector2 textSize = {0};
|
|
if ((font.texture.id == 0) || (text.data == NULL)) return textSize; // Security check
|
|
|
|
int size = (int)text.len; // Get size in bytes of text
|
|
int tempByteCounter = 0; // Used to count longer text line num chars
|
|
int byteCounter = 0;
|
|
|
|
float textWidth = 0.0f;
|
|
float tempTextWidth = 0.0f; // Used to count longer text line width
|
|
|
|
float textHeight = fontSize;
|
|
float scaleFactor = fontSize / (float)font.baseSize;
|
|
|
|
int letter = 0; // Current character
|
|
int index = 0; // Index position in sprite font
|
|
|
|
for (int i = 0; i < size;) {
|
|
byteCounter++;
|
|
|
|
int next = 0;
|
|
letter = GetCodepointNext(&text.data[i], &next);
|
|
index = GetGlyphIndex(font, letter);
|
|
|
|
i += next;
|
|
|
|
if (font.glyphs[index].advanceX != 0) textWidth += font.glyphs[index].advanceX;
|
|
else textWidth += (font.recs[index].width + font.glyphs[index].offsetX);
|
|
|
|
if (tempByteCounter < byteCounter) tempByteCounter = byteCounter;
|
|
}
|
|
|
|
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
|
|
|
|
textSize.x = tempTextWidth * scaleFactor + (float)((tempByteCounter - 1) * spacing);
|
|
textSize.y = textHeight;
|
|
|
|
return textSize;
|
|
}
|
|
|
|
int64_t MoveRight(Buffer &buffer, int64_t pos) {
|
|
pos = pos + 1;
|
|
pos = AdjustUTF8Pos(buffer, pos);
|
|
Assert(pos >= 0 && pos <= buffer.len);
|
|
return pos;
|
|
}
|
|
|
|
int64_t MoveLeft(Buffer &buffer, int64_t pos) {
|
|
pos = pos - 1;
|
|
pos = AdjustUTF8Pos(buffer, pos, -1);
|
|
Assert(pos >= 0 && pos <= buffer.len);
|
|
return pos;
|
|
}
|
|
|
|
int64_t MoveDown(Buffer &buffer, int64_t pos) {
|
|
LineAndColumn info = FindLineAndColumn(buffer, pos);
|
|
int64_t new_pos = FindPos(buffer, info.line.number + 1, info.column);
|
|
return new_pos;
|
|
}
|
|
|
|
int64_t MoveUp(Buffer &buffer, int64_t pos) {
|
|
LineAndColumn info = FindLineAndColumn(buffer, pos);
|
|
int64_t new_pos = FindPos(buffer, info.line.number - 1, info.column);
|
|
return new_pos;
|
|
}
|
|
|
|
bool AreEqual(float a, float b, float epsilon = 0.001f) {
|
|
bool result = (a - epsilon < b && a + epsilon > b);
|
|
return result;
|
|
}
|
|
|
|
void BeforeEdit(Window *window) {
|
|
// Merge cursors that overlap, this needs to be handled before any edits to
|
|
// make sure overlapping edits won't happen.
|
|
for (int64_t cursor_i = 0; cursor_i < window->cursors.len; cursor_i += 1) {
|
|
Cursor &cursor = window->cursors[cursor_i];
|
|
IterRemove(window->cursors) {
|
|
IterRemovePrepare(window->cursors);
|
|
if (&cursor == &it) continue;
|
|
|
|
if (cursor.range.max >= it.range.min && cursor.range.max <= it.range.max) {
|
|
remove_item = true;
|
|
cursor.range.max = it.range.max;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AfterEdit(Window *window, Array<Edit> edits) {
|
|
// Offset all cursors by edits
|
|
ForItem(edit, edits) {
|
|
int64_t remove_size = GetRangeSize(edit.range);
|
|
int64_t insert_size = edit.string.len;
|
|
int64_t offset = insert_size - remove_size;
|
|
|
|
ForItem(cursor, window->cursors) {
|
|
if (edit.range.min == cursor.range.min) {
|
|
if (GetRangeSize(edit.range)) {
|
|
cursor.range.min += edit.string.len;
|
|
} else {
|
|
cursor.range.min += offset;
|
|
}
|
|
cursor.range.max = cursor.range.min;
|
|
} else if (edit.range.min <= cursor.range.min) {
|
|
if (GetRangeSize(edit.range)) {
|
|
cursor.range.min += edit.string.len;
|
|
}
|
|
cursor.range.min += offset;
|
|
cursor.range.max = cursor.range.min;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 PermArena;
|
|
Arena FrameArena;
|
|
struct DebugLine {
|
|
bool persist;
|
|
String string;
|
|
};
|
|
Array<DebugLine> DebugLines = {};
|
|
|
|
void Dbg(const char *str, ...) {
|
|
STRING_FORMAT(FrameArena, str, result);
|
|
DebugLines.add({false, result});
|
|
}
|
|
void DbgPersist(const char *str, ...) {
|
|
STRING_FORMAT(PermArena, str, result);
|
|
DebugLines.add({true, result});
|
|
}
|
|
|
|
void Dbg_Draw() {
|
|
int y = 0;
|
|
IterRemove(DebugLines) {
|
|
IterRemovePrepare(DebugLines);
|
|
if (it.persist == false) remove_item = true;
|
|
DrawText(it.string.data, 0, y, 20, BLACK);
|
|
y += 20;
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
InitScratch();
|
|
RunBufferTests();
|
|
|
|
InitWindow(800, 600, "Hello");
|
|
SetTargetFPS(60);
|
|
|
|
InitArena(&FrameArena);
|
|
InitArena(&PermArena);
|
|
float font_size = 64;
|
|
float font_spacing = 1;
|
|
Font font = LoadFontEx("C:/Windows/Fonts/times.ttf", (int)font_size, NULL, 250);
|
|
|
|
float title_bar_font_size = 15;
|
|
Font title_bar_font = LoadFontEx("C:/Windows/Fonts/times.ttf", (int)title_bar_font_size, NULL, 250);
|
|
if (0) font = LoadFontEx("C:/Windows/Fonts/consola.ttf", (int)font_size, NULL, 250);
|
|
|
|
Array<Window> windows = {};
|
|
{
|
|
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<Edit> edits = {FrameArena};
|
|
AddEdit(&edits, GetEnd(window.buffer), Format(FrameArena, "line number: %d\n", i));
|
|
ApplyEdits(&window.buffer, edits);
|
|
}
|
|
}
|
|
|
|
window.cursors.add({});
|
|
AfterEdit(&window, {});
|
|
|
|
windows.add(window);
|
|
}
|
|
|
|
// @todo: multiple ui elements, add focus
|
|
// @todo: immediate mode interface for all this
|
|
Vec2 camera_offset_world_to_render_units = {};
|
|
while (!WindowShouldClose()) {
|
|
For(windows) {
|
|
Assert(it.cursors.len);
|
|
it.main_cursor_begin_frame = it.cursors[0];
|
|
}
|
|
{
|
|
Window *focused_window = &windows[0];
|
|
|
|
if (IsKeyDown(KEY_F1)) {
|
|
camera_offset_world_to_render_units = Vector2Subtract(camera_offset_world_to_render_units, GetMouseDelta());
|
|
}
|
|
if (IsKeyDown(KEY_F2)) {
|
|
SetTraceLogLevel(LOG_DEBUG);
|
|
}
|
|
|
|
float mouse_wheel = GetMouseWheelMove() * 48;
|
|
focused_window->scroll.y -= mouse_wheel;
|
|
focused_window->scroll.y = ClampBottom(focused_window->scroll.y, 0.f);
|
|
|
|
BeforeEdit(focused_window); // Merge cursors
|
|
if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_A)) {
|
|
focused_window->cursors.clear();
|
|
focused_window->cursors.add(MakeCursor(0, focused_window->buffer.len));
|
|
}
|
|
if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) {
|
|
For(focused_window->cursors) {
|
|
if (IsKeyDown(KEY_LEFT_CONTROL)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
int64_t front = GetFront(it);
|
|
front = Seek(focused_window->buffer, front, ITERATE_BACKWARD);
|
|
it = ChangeFront(it, front);
|
|
} else {
|
|
it.range.max = it.range.min = Seek(focused_window->buffer, it.range.min, ITERATE_BACKWARD);
|
|
}
|
|
} else {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
int64_t front = GetFront(it);
|
|
front = MoveLeft(focused_window->buffer, front);
|
|
it = ChangeFront(it, front);
|
|
} else {
|
|
if (GetRangeSize(it.range) != 0) {
|
|
it.range.max = it.range.min;
|
|
} else {
|
|
it.range.max = it.range.min = MoveLeft(focused_window->buffer, it.range.min);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) {
|
|
For(focused_window->cursors) {
|
|
if (IsKeyDown(KEY_LEFT_CONTROL)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
int64_t front = GetFront(it);
|
|
front = Seek(focused_window->buffer, front, ITERATE_FORWARD);
|
|
it = ChangeFront(it, front);
|
|
} else {
|
|
it.range.max = it.range.min = Seek(focused_window->buffer, it.range.min, ITERATE_FORWARD);
|
|
}
|
|
} else {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
int64_t front = GetFront(it);
|
|
front = MoveRight(focused_window->buffer, front);
|
|
it = ChangeFront(it, front);
|
|
} else {
|
|
if (GetRangeSize(it.range) != 0) {
|
|
it.range.min = it.range.max;
|
|
} else {
|
|
it.range.max = it.range.min = MoveRight(focused_window->buffer, it.range.min);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT) && IsKeyDown(KEY_LEFT_ALT)) { // Default in VSCode seems to be Ctrl + Alt + down
|
|
For(focused_window->cursors) {
|
|
int64_t front = GetFront(it);
|
|
front = MoveDown(focused_window->buffer, front);
|
|
focused_window->cursors.add({front, front});
|
|
}
|
|
} else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_LEFT_ALT)) { // Default in VSCode seems to be Shift + Alt + down
|
|
BeforeEdit(focused_window);
|
|
Array<Edit> edits = {FrameArena};
|
|
For(focused_window->cursors) {
|
|
int64_t front = GetFront(it);
|
|
Line line = FindLine(focused_window->buffer, front);
|
|
String string = GetString(focused_window->buffer, {line.range.min, line.range.max + 1});
|
|
AddEdit(&edits, {line.range.max + 1, line.range.max + 1}, string);
|
|
}
|
|
ApplyEdits(&focused_window->buffer, edits);
|
|
AfterEdit(focused_window, edits);
|
|
For(focused_window->cursors) {
|
|
it.range.max = it.range.min = MoveDown(focused_window->buffer, it.range.min);
|
|
}
|
|
} else if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
For(focused_window->cursors) {
|
|
int64_t front = GetFront(it);
|
|
front = MoveDown(focused_window->buffer, front);
|
|
it = ChangeFront(it, front);
|
|
}
|
|
} else {
|
|
For(focused_window->cursors) {
|
|
it.range.max = it.range.min = MoveDown(focused_window->buffer, it.range.min);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT) && IsKeyDown(KEY_LEFT_ALT)) { // Default in VSCode seems to be Ctrl + Alt + up
|
|
For(focused_window->cursors) {
|
|
int64_t front = GetFront(it);
|
|
front = MoveUp(focused_window->buffer, front);
|
|
focused_window->cursors.add({front, front});
|
|
}
|
|
} else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_LEFT_ALT)) { // Default in VSCode seems to be Shift + Alt + up
|
|
BeforeEdit(focused_window);
|
|
Array<Edit> edits = {FrameArena};
|
|
For(focused_window->cursors) {
|
|
int64_t front = GetFront(it);
|
|
Line line = FindLine(focused_window->buffer, front);
|
|
String string = GetString(focused_window->buffer, {line.range.min, line.range.max + 1});
|
|
AddEdit(&edits, {line.range.min, line.range.min}, string);
|
|
}
|
|
ApplyEdits(&focused_window->buffer, edits);
|
|
AfterEdit(focused_window, edits);
|
|
For(focused_window->cursors) {
|
|
it.range.max = it.range.min = MoveUp(focused_window->buffer, it.range.min);
|
|
}
|
|
} else if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
For(focused_window->cursors) {
|
|
int64_t front = GetFront(it);
|
|
front = MoveUp(focused_window->buffer, front);
|
|
it = ChangeFront(it, front);
|
|
}
|
|
} else {
|
|
For(focused_window->cursors) {
|
|
it.range.max = it.range.min = MoveUp(focused_window->buffer, it.range.min);
|
|
}
|
|
}
|
|
}
|
|
|
|
// @todo: improve behaviour of all copy pasting
|
|
if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) {
|
|
Array<String> strings = {FrameArena};
|
|
For(focused_window->cursors) {
|
|
String string = GetString(focused_window->buffer, it.range);
|
|
strings.add(string);
|
|
}
|
|
String to_save = Merge(FrameArena, strings, "\n");
|
|
SetClipboardText(to_save.data);
|
|
}
|
|
if (IsKeyDown(KEY_LEFT_CONTROL) && (IsKeyPressed(KEY_V) || IsKeyPressedRepeat(KEY_V))) {
|
|
BeforeEdit(focused_window);
|
|
Array<Edit> edits = {FrameArena};
|
|
const char *text = GetClipboardText();
|
|
String string = text;
|
|
For(focused_window->cursors) {
|
|
AddEdit(&edits, it.range, string);
|
|
}
|
|
ApplyEdits(&focused_window->buffer, edits);
|
|
AfterEdit(focused_window, edits);
|
|
}
|
|
if (IsKeyDown(KEY_LEFT_CONTROL) && (IsKeyPressed(KEY_X) || IsKeyPressedRepeat(KEY_X))) {
|
|
// First, if there is no selection - select the entire line
|
|
For(focused_window->cursors) {
|
|
if (GetRangeSize(it.range) == 0) {
|
|
Line line = FindLine(focused_window->buffer, it.range.min);
|
|
it.range = {line.range.min, line.range.max + 1};
|
|
}
|
|
}
|
|
|
|
BeforeEdit(focused_window);
|
|
Array<Edit> edits = {FrameArena};
|
|
Array<String> strings = {FrameArena};
|
|
For(focused_window->cursors) {
|
|
AddEdit(&edits, it.range, "");
|
|
String string = GetString(focused_window->buffer, it.range);
|
|
strings.add(string);
|
|
}
|
|
String to_save = Merge(FrameArena, strings, "\n");
|
|
SetClipboardText(to_save.data);
|
|
ApplyEdits(&focused_window->buffer, edits);
|
|
AfterEdit(focused_window, edits);
|
|
}
|
|
|
|
if (IsKeyPressed(KEY_ENTER)) {
|
|
if (IsKeyDown(KEY_LEFT_CONTROL)) {
|
|
} else {
|
|
}
|
|
}
|
|
|
|
if (IsKeyPressed(KEY_HOME) || IsKeyPressedRepeat(KEY_HOME)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
} else {
|
|
}
|
|
}
|
|
|
|
if (IsKeyPressed(KEY_END) || IsKeyPressedRepeat(KEY_END)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
} else {
|
|
}
|
|
}
|
|
|
|
if (IsKeyDown(KEY_LEFT_CONTROL) && (IsKeyPressed(KEY_D) || IsKeyPressedRepeat(KEY_D))) {
|
|
}
|
|
|
|
if (IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
}
|
|
}
|
|
if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
} else {
|
|
BeforeEdit(focused_window);
|
|
Array<Edit> edits = {FrameArena};
|
|
String string = {};
|
|
For(focused_window->cursors) {
|
|
int64_t pos = MoveLeft(focused_window->buffer, it.range.min);
|
|
AddEdit(&edits, {pos, it.range.min}, string);
|
|
}
|
|
ApplyEdits(&focused_window->buffer, edits);
|
|
AfterEdit(focused_window, edits);
|
|
}
|
|
}
|
|
|
|
// Handle user input
|
|
for (;;) {
|
|
int c = GetCharPressed();
|
|
if (!c) break;
|
|
|
|
String string = "?";
|
|
UTF8Result utf8 = UTF32ToUTF8((uint32_t)c);
|
|
if (utf8.error == 0) {
|
|
string = {(char *)utf8.out_str, (int64_t)utf8.len};
|
|
}
|
|
|
|
BeforeEdit(focused_window);
|
|
Array<Edit> edits = {FrameArena};
|
|
For(focused_window->cursors) {
|
|
AddEdit(&edits, it.range, string);
|
|
}
|
|
ApplyEdits(&focused_window->buffer, edits);
|
|
AfterEdit(focused_window, edits);
|
|
}
|
|
}
|
|
|
|
BeginDrawing();
|
|
ClearBackground(RAYWHITE);
|
|
|
|
ForItem(window, windows) {
|
|
Rectangle rectangle_in_render_units = ToRectangle(window.rect);
|
|
DrawRectangleRec(rectangle_in_render_units, WHITE);
|
|
|
|
Array<LayoutColumn> visible_columns = CalculateVisibleColumns(&FrameArena, window);
|
|
// Mouse selection
|
|
// @todo: multicursor
|
|
// @todo: selecting while not hovering over glyph shapes
|
|
{
|
|
SetMouseCursor(MOUSE_CURSOR_DEFAULT);
|
|
Vec2 mouse = GetMousePosition();
|
|
Vec2 mouse_lookup = Vector2Add(mouse, window.scroll);
|
|
|
|
Tuple<LayoutRow *, LayoutColumn *> 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)) {
|
|
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
|
window.cursors.clear();
|
|
window.cursors.add({rowcol.b->pos, rowcol.b->pos});
|
|
window.mouse_selecting = true;
|
|
}
|
|
|
|
if (!window.mouse_selecting) {
|
|
SetMouseCursor(MOUSE_CURSOR_IBEAM);
|
|
}
|
|
}
|
|
|
|
if (window.mouse_selecting) {
|
|
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL);
|
|
if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
|
|
window.mouse_selecting = false;
|
|
}
|
|
Cursor *cursor = window.cursors.last();
|
|
cursor[0] = ChangeFront(*cursor, rowcol.b->pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the scroll based on first cursor
|
|
// @todo: needs a rewrite, make sure we also handle scrolling for mouse
|
|
if (!AreEqual(window.main_cursor_begin_frame, window.cursors[0])) {
|
|
#if 1
|
|
Range visible_line_range = CalculateVisibleLineRange(window);
|
|
int64_t visible_lines = GetRangeSize(visible_line_range);
|
|
float visible_size_y = font_size * (float)visible_lines;
|
|
|
|
Vec2 rect_size = GetSize(window.rect);
|
|
float cut_off_y = visible_size_y - rect_size.y;
|
|
|
|
int64_t front = GetFront(window.cursors[0]);
|
|
Line line = FindLine(window.buffer, front);
|
|
// Tuple<LayoutRow, LayoutColumn> rowcol = GetRowCol(window, front);
|
|
|
|
// DbgPersist("line num = %d visible_line_range.max = %d", (int)line.number, (int)visible_line_range.max);
|
|
// Bottom
|
|
if (line.number > visible_line_range.max - 2) {
|
|
int64_t set_view_at_line = line.number - (visible_lines - 1);
|
|
window.scroll.y = (set_view_at_line * font_size) + cut_off_y;
|
|
}
|
|
|
|
if (line.number < visible_line_range.min + 1) {
|
|
int64_t set_view_at_line = line.number;
|
|
window.scroll.y = (set_view_at_line * font_size);
|
|
}
|
|
|
|
#else
|
|
Range visible_line_range = CalculateVisibleLineRange(window);
|
|
Vec2 rect_size = GetSize(window.rect);
|
|
float visible_cells_in_render_units = font_size * (float)GetRangeSize(visible_line_range);
|
|
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
|
|
if (line.number < (visible_line_range.min)) {
|
|
window.scroll.y = line.number * font_size;
|
|
} else if (line.number >= (visible_line_range.max)) {
|
|
int64_t diff = line.number - visible_line_range.max;
|
|
window.scroll.y = (visible_line_range.min + diff) * font_size + cut_off_in_render_units;
|
|
}
|
|
|
|
// Scroll X
|
|
Range x_distance = {line.range.min, front};
|
|
String x_distance_string = GetString(window.buffer, x_distance);
|
|
Vec2 size = MeasureString(font, x_distance_string, font_size, font_spacing);
|
|
if (x_distance_string.len <= 0) size.x = 0;
|
|
float x_cursor_position_in_window_buffer_world_units = size.x;
|
|
|
|
GlyphInfo info = GetGlyphInfo(font, ' ');
|
|
float right_scroll_zone = (float)(info.image.width + info.advanceX) * 3;
|
|
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;
|
|
window.scroll.x += diff;
|
|
} else if (x_cursor_position_in_window_buffer_world_units <= window_buffer_world_left_edge) {
|
|
float diff = x_cursor_position_in_window_buffer_world_units - window_buffer_world_left_edge;
|
|
window.scroll.x += diff;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Draw the glyphs
|
|
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);
|
|
|
|
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) {
|
|
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 ((col.codepoint != ' ') && (col.codepoint != '\t')) {
|
|
DrawTextCodepoint(font, col.codepoint, rect.min, font_size, BLACK);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw cursor stuff
|
|
ForItem(cursor, window.cursors) {
|
|
auto front = GetRowCol(window, GetFront(cursor));
|
|
auto back = GetRowCol(window, GetBack(cursor));
|
|
|
|
For(visible_columns) {
|
|
if (it.pos >= cursor.range.min && it.pos < cursor.range.max) {
|
|
Rect2 rect = it.rect - window.scroll;
|
|
DrawRectangleRec(ToRectangle(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();
|
|
}
|
|
|
|
Dbg_Draw();
|
|
EndDrawing();
|
|
}
|
|
} |