TeInited := false; TeFontSize: float = 14; TeFontSpacing: float = 1; TeFont: Font; TeBuffer: Buffer; FocusedWindow: *Window; WindowStack: [8]Window; WindowStackCount: int; UpdateTextEditor :: proc(rect: Rect2P, font: Font, font_size: float, font_spacing: float) { if !TeInited { TeInited = true; TeFont = font; TeFontSpacing = font_spacing; TeFontSize = font_size; glyph_info: GlyphInfo = GetGlyphInfo(TeFont, 'A'); size := MeasureTextEx(TeFont, "A", TeFontSize, TeFontSpacing); Monosize = {:float(glyph_info.image.width), size.y}; file_content := LoadFileText("C:/Work/language/examples/text_editor/main.lc"); AddText(&TeBuffer, {file_content, :int(strlen(file_content))}); UnloadFileText(file_content); AddWindow({buffer = &TeBuffer}); AddWindow({buffer = &TeBuffer}); FocusedWindow = &WindowStack[0]; } ComputeWindowRects(rect); UpdateAndDrawWindows(font, font_size); } Window :: struct { buffer: *Buffer; cursor: Selection; scroll: Vector2; mouse_scrolling: bool; mouse_selecting: bool; rect: Rect2P; } Selection :: struct { a: int; b: int; } GetRange :: proc(s: Selection): Range { result: Range = {MinInt(s.a, s.b), MaxInt(s.a, s.b)}; return result; } AddWindow :: proc(window: Window) { if (WindowStackCount + 1 >= 8) return; WindowStack[WindowStackCount] = window; WindowStackCount += 1; } ComputeWindowRects :: proc(screen_rect: Rect2P) { WindowStack[0].rect = CutLeft(&screen_rect, 0.5 * GetRectX(screen_rect)); WindowStack[1].rect = CutLeft(&screen_rect, 1.0 * GetRectX(screen_rect)); } UpdateAndDrawWindows :: proc(font: Font, font_size: float) { SetMouseCursor(MOUSE_CURSOR_DEFAULT); for i := 0; i < WindowStackCount; i += 1 { window: *Window = &WindowStack[i]; UpdateAndDrawWindow(window, font, font_size); } } UpdateAndDrawWindow :: proc(w: *Window, font: Font, font_size: float) { initial_cursor: Selection = w.cursor; text_window_rect := w.rect; bar_rect := CutRight(&text_window_rect, 10); horizontal_bar_rect := CutBottom(&text_window_rect, 10); horizontal_bar_rect; @unused // line_numbers_rect := CutLeft(&text_window_rect, Monosize.x * 4); buffer_end_vpos := CalculateVisualPos(w.buffer, w.buffer.len - 1); buffer_end_wpos := CalculateWorldPosUnscrolled(buffer_end_vpos); if CheckCollisionPointRec(GetMousePosition(), Rect2PToRectangle(w.rect)) { if w != FocusedWindow { SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); } if IsMouseButtonPressed(MOUSE_BUTTON_LEFT) { FocusedWindow = w; } } if w == FocusedWindow { if IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT) { if IsKeyDown(KEY_LEFT_SHIFT) { if IsKeyDown(KEY_LEFT_CONTROL) { w.cursor.b = SeekOnWordBoundary(w.buffer, w.cursor.b, GO_BACKWARD); } else { w.cursor.b = MoveLeft(w.buffer, w.cursor.b); } } else { if IsKeyDown(KEY_LEFT_CONTROL) { w.cursor.a = SeekOnWordBoundary(w.buffer, w.cursor.a, GO_BACKWARD); w.cursor.b = w.cursor.a; } else { range := GetRange(w.cursor); if GetRangeSize(range) > 0 { w.cursor.a = MinInt(w.cursor.a, w.cursor.b); w.cursor.b = MinInt(w.cursor.a, w.cursor.b); } else { w.cursor.a = MoveLeft(w.buffer, w.cursor.a); w.cursor.b = w.cursor.a; } } } } if IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT) { if IsKeyDown(KEY_LEFT_SHIFT) { if IsKeyDown(KEY_LEFT_CONTROL) { w.cursor.b = SeekOnWordBoundary(w.buffer, w.cursor.b, GO_FORWARD); } else { w.cursor.b = MoveRight(w.buffer, w.cursor.b); } } else { if IsKeyDown(KEY_LEFT_CONTROL) { w.cursor.a = SeekOnWordBoundary(w.buffer, w.cursor.a, GO_FORWARD); w.cursor.b = w.cursor.a; } else { range := GetRange(w.cursor); if GetRangeSize(range) > 0 { w.cursor.a = MaxInt(w.cursor.a, w.cursor.b); w.cursor.b = MaxInt(w.cursor.a, w.cursor.b); } else { w.cursor.a = MoveRight(w.buffer, w.cursor.a); w.cursor.b = w.cursor.a; } } } } if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) { if IsKeyDown(KEY_LEFT_SHIFT) { w.cursor.b = MoveDown(w.buffer, w.cursor.b); } else { range := GetRange(w.cursor); if GetRangeSize(range) > 0 { w.cursor.b = range.max; w.cursor.a = range.max; } w.cursor.a = MoveDown(w.buffer, w.cursor.a); w.cursor.b = w.cursor.a; } } if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) { if IsKeyDown(KEY_LEFT_SHIFT) { w.cursor.b = MoveUp(w.buffer, w.cursor.b); } else { range := GetRange(w.cursor); if GetRangeSize(range) > 0 { w.cursor.b = range.min; w.cursor.a = range.min; } w.cursor.a = MoveUp(w.buffer, w.cursor.a); w.cursor.b = w.cursor.a; } } if IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE) { range := GetRange(w.cursor); range_size := GetRangeSize(range); if range_size == 0 { if IsKeyDown(KEY_LEFT_CONTROL) { left := SeekOnWordBoundary(w.buffer, w.cursor.a, GO_BACKWARD); ReplaceText(w.buffer, {left, w.cursor.a}, ""); w.cursor.a = left; w.cursor.b = w.cursor.a; } else { left := MoveLeft(w.buffer, w.cursor.a); ReplaceText(w.buffer, {left, w.cursor.a}, ""); w.cursor.a = left; w.cursor.b = w.cursor.a; } } else { ReplaceText(w.buffer, range, ""); w.cursor.b = range.min; w.cursor.a = w.cursor.b; } } if IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE) { range := GetRange(w.cursor); range_size := GetRangeSize(range); if range_size == 0 { if IsKeyDown(KEY_LEFT_CONTROL) { right := SeekOnWordBoundary(w.buffer, w.cursor.a); ReplaceText(w.buffer, {w.cursor.a, right}, ""); } else { right := MoveRight(w.buffer, w.cursor.a); ReplaceText(w.buffer, {w.cursor.a, right}, ""); } w.cursor.b = w.cursor.a; } else { ReplaceText(w.buffer, range, ""); w.cursor.b = range.min; w.cursor.a = w.cursor.b; } } if IsKeyPressed(KEY_ENTER) || IsKeyPressedRepeat(KEY_ENTER) { ReplaceText(w.buffer, GetRange(w.cursor), "\n"); w.cursor.a = MoveRight(w.buffer, w.cursor.a); w.cursor.b = w.cursor.a; } if IsKeyPressed(KEY_TAB) || IsKeyPressedRepeat(KEY_TAB) { selection_range := GetRange(w.cursor); ReplaceText(w.buffer, selection_range, " "); range_size := GetRangeSize(selection_range); if range_size != 0 { w.cursor.a = selection_range.min; w.cursor.b = selection_range.min; } w.cursor.a = MoveRight(w.buffer, w.cursor.a + 3); w.cursor.b = w.cursor.a; } if IsKeyPressed(KEY_HOME) { line := FindLineOfPos(w.buffer, w.cursor.b); if IsKeyDown(KEY_LEFT_SHIFT) { w.cursor.b = line.range.min; } else { w.cursor.a = line.range.min; w.cursor.b = line.range.min; } } if IsKeyPressed(KEY_END) { line := FindLineOfPos(w.buffer, w.cursor.b); if IsKeyDown(KEY_LEFT_SHIFT) { w.cursor.b = line.range.max; } else { w.cursor.a = line.range.max; w.cursor.b = line.range.max; } } // @todo: buffer.len abstract if IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_A) { w.cursor.a = 0; w.cursor.b = w.buffer.len - 1; } if IsKeyPressed(KEY_PAGE_DOWN) || IsKeyPressedRepeat(KEY_PAGE_DOWN) { vpos := CalculateVisualPos(w.buffer, w.cursor.b); move_by := :int(roundf(GetRectY(text_window_rect) / Monosize.y)); vpos.y += move_by; w.cursor.b = CalculatePosFromVisualPos(w.buffer, vpos); if !IsKeyDown(KEY_LEFT_SHIFT) { w.cursor.a = w.cursor.b; } } if IsKeyPressed(KEY_PAGE_UP) || IsKeyPressedRepeat(KEY_PAGE_UP) { vpos := CalculateVisualPos(w.buffer, w.cursor.b); move_by := :int(roundf(GetRectY(text_window_rect) / Monosize.y)); vpos.y -= move_by; w.cursor.b = CalculatePosFromVisualPos(w.buffer, vpos); if !IsKeyDown(KEY_LEFT_SHIFT) { w.cursor.a = w.cursor.b; } } for key := GetCharPressed(); key; key = GetCharPressed() { selection_range := GetRange(w.cursor); result: UTF8_Result = UTF32ToUTF8(:u32(key)); if result.error == 0 { ReplaceText(w.buffer, selection_range, {:*char(&result.out_str[0]), result.len}); } else { ReplaceText(w.buffer, selection_range, "?"); } range_size := GetRangeSize(selection_range); if range_size != 0 { w.cursor.a = selection_range.min; w.cursor.b = selection_range.min; } w.cursor.a = MoveRight(w.buffer, w.cursor.a); w.cursor.b = w.cursor.a; } // // Scrolling // mouse_wheel := GetMouseWheelMove() * 48; w.scroll.y -= mouse_wheel; if initial_cursor.b != w.cursor.b { cursor_vpos := CalculateVisualPos(w.buffer, w.cursor.b); world_pos := CalculateWorldPosUnscrolled(cursor_vpos); world_pos_cursor_end := Vector2Add(world_pos, Monosize); scrolled_begin := w.scroll; scrolled_end := Vector2Add(w.scroll, GetRectSize(text_window_rect)); if world_pos_cursor_end.x > scrolled_end.x { w.scroll.x += world_pos_cursor_end.x - scrolled_end.x; } if world_pos.x < scrolled_begin.x { w.scroll.x -= scrolled_begin.x - world_pos.x; } if world_pos_cursor_end.y > scrolled_end.y { w.scroll.y += world_pos_cursor_end.y - scrolled_end.y; } if world_pos.y < scrolled_begin.y { w.scroll.y -= scrolled_begin.y - world_pos.y; } } if (w.scroll.x < 0) w.scroll.x = 0; if (w.scroll.y < 0) w.scroll.y = 0; adjusted_kind_of_end := buffer_end_wpos.y - GetRectY(text_window_rect) * 0.8; if (w.scroll.y > adjusted_kind_of_end) w.scroll.y = adjusted_kind_of_end; // // Mouse // mouse_p := GetMousePosition(); if CheckCollisionPointRec(mouse_p, Rect2PToRectangle(w.rect)) { if CheckCollisionPointRec(mouse_p, Rect2PToRectangle(text_window_rect)) && !w.mouse_scrolling { if IsMouseButtonPressed(MOUSE_BUTTON_LEFT) { w.mouse_selecting = true; p := Vector2Add(mouse_p, w.scroll); p = Vector2Subtract(p, text_window_rect.min); p = Vector2Divide(p, Monosize); x := :int(floorf(p.x)); y := :int(floorf(p.y)); w.cursor.a = CalculatePosFromVisualPos(w.buffer, {x, y}); w.cursor.b = w.cursor.a; } SetMouseCursor(MOUSE_CURSOR_IBEAM); } else { SetMouseCursor(MOUSE_CURSOR_DEFAULT); if IsMouseButtonPressed(MOUSE_BUTTON_LEFT) { w.mouse_scrolling = true; } } } if w.mouse_selecting && IsMouseButtonDown(MOUSE_BUTTON_LEFT) { SetMouseCursor(MOUSE_CURSOR_DEFAULT); p := Vector2Add(mouse_p, w.scroll); p = Vector2Subtract(p, text_window_rect.min); p = Vector2Divide(p, Monosize); x := :int(floorf(p.x)); y := :int(floorf(p.y)); w.cursor.b = CalculatePosFromVisualPos(w.buffer, {x, y}); } else { w.mouse_selecting = false; } if w.mouse_scrolling { mouse_scroll_marker_normalized := (mouse_p.y - w.rect.min.y) / GetRectY(bar_rect); w.scroll.y = mouse_scroll_marker_normalized * buffer_end_wpos.y; if IsMouseButtonReleased(MOUSE_BUTTON_LEFT) w.mouse_scrolling = false; } } buffer_pixel_size := GetRectSize(text_window_rect); _miny := w.scroll.y / Monosize.y; _maxy := (w.scroll.y + buffer_pixel_size.y) / Monosize.y; _minx := w.scroll.x / Monosize.x; _maxx := (w.scroll.x + buffer_pixel_size.x) / Monosize.x; miny := :int(floorf(_miny)); minx := :int(floorf(_minx)); maxy := :int(ceilf(_maxy)); maxx := :int(ceilf(_maxx)); // Draw grid if 0 { for y := miny; y < maxy; y += 1 { for x := minx; x < maxx; x += 1 { p := CalculateWorldPos(w.scroll, {x, y}); rect := Rect2PSize(p.x, p.y, Monosize.x, Monosize.y); rect = Shrink(rect, 1); DrawRect(rect, {255, 0, 0, 40}); } } } BeginScissorMode(:int(w.rect.min.x), :int(w.rect.min.y), :int(GetRectX(w.rect)), :int(GetRectY(w.rect))); // Draw text and line numbers { FONT_SPACING :: 1; // DrawRect(line_numbers_rect, LIGHTGRAY); miny = ClampInt(miny, 0, w.buffer.lines.len - 1); maxy = ClampInt(maxy, 0, w.buffer.lines.len - 1); for y := miny; y <= maxy; y += 1 { line_range := w.buffer.lines.data[y]; string := AllocStringFromBuffer(w.buffer, line_range, null_terminate = true); defer free(string.str); pos := CalculateWorldPos(w.scroll, {0, y}); pos_adjusted_by_buffer := Vector2Add(pos, text_window_rect.min); DrawTextEx(font, string.str, pos_adjusted_by_buffer, font_size, FONT_SPACING, BLACK); // line_number_pos: Vector2 = {w.rect.min.x, pos_adjusted_by_buffer.y}; // line_number_string := TextFormat("%d", y); // DrawTextEx(font, line_number_string, line_number_pos, font_size, FONT_SPACING, GRAY); } } // Draw selection { range := GetRange(w.cursor); start_vpos := CalculateVisualPos(w.buffer, range.min); pos := start_vpos; for iter := Iterate(w.buffer, range.min, range.max); IsValid(iter); Advance(&iter) { world_pos := CalculateWorldPos(w.scroll, pos); world_pos_adjusted_by_buffer := Vector2Add(world_pos, text_window_rect.min); rect := Rect2PSize(world_pos_adjusted_by_buffer.x, world_pos_adjusted_by_buffer.y, Monosize.x, Monosize.y); DrawRect(rect, {0, 92, 222, 40}); pos.x += 1; if iter.item == '\n' { pos.x = 0; pos.y += 1; } } } // Draw cursor { c := CalculateVisualPos(w.buffer, w.cursor.b); p := CalculateWorldPos(w.scroll, c); pos_adjusted_by_buffer := Vector2Add(p, text_window_rect.min); cursor_size: f32 = Monosize.x * 0.2; rect := Rect2PSize(pos_adjusted_by_buffer.x, pos_adjusted_by_buffer.y, Monosize.x, Monosize.y); rect = CutLeft(&rect, cursor_size); DrawRect(rect, RED); } EndScissorMode(); // Draw bar { DrawRect(bar_rect, LIGHTGRAY); DrawRect(horizontal_bar_rect, LIGHTGRAY); scroll_start_normalized := :f32(w.scroll.y) / :f32(buffer_end_wpos.y); scroll_start := scroll_start_normalized * GetRectSize(bar_rect).y; CutTop(&bar_rect, scroll_start); marker := CutTop(&bar_rect, 20); DrawRect(marker, GRAY); } if w != FocusedWindow { DrawRect(w.rect, {0,0,0,20}); } }