diff --git a/examples/text_editor/basic.lc b/examples/text_editor/basic.lc new file mode 100644 index 0000000..6059bfe --- /dev/null +++ b/examples/text_editor/basic.lc @@ -0,0 +1,34 @@ +ClampInt :: proc(val: int, min: int, max: int): int { + result := val; + if (val < min) result = min; + if (val > max) result = max; + return result; +} + +MinInt :: proc(a: int, b: int): int { + result := a; + if (a > b) result = b; + return result; +} + +MaxInt :: proc(a: int, b: int): int { + result := b; + if (a > b) result = a; + return result; +} + +MaxFloat :: proc(a: float, b: float): float { + if a > b return a; + return b; +} + +MinFloat :: proc(a: float, b: float): float { + if a > b return b; + return a; +} + +ClampFloat :: proc(val: float, min: float, max: float): float { + if (val > max) return max; + if (val < min) return min; + return val; +} diff --git a/examples/text_editor/buffer.lc b/examples/text_editor/buffer.lc index f703ea6..c84dd13 100644 --- a/examples/text_editor/buffer.lc +++ b/examples/text_editor/buffer.lc @@ -27,25 +27,6 @@ GetRangeSize :: proc(range: Range): int { return result; } -ClampInt :: proc(val: int, min: int, max: int): int { - result := val; - if (val < min) result = min; - if (val > max) result = max; - return result; -} - -MinInt :: proc(a: int, b: int): int { - result := a; - if (a > b) result = b; - return result; -} - -MaxInt :: proc(a: int, b: int): int { - result := b; - if (a > b) result = a; - return result; -} - AdjustUTF8PosUnsafe :: proc(buffer: *Buffer, pos: int, direction: int = 1): int { for pos >= 0 && pos < buffer.len && IsUTF8ContinuationByte(buffer.data[pos]) { pos += direction; diff --git a/examples/text_editor/main.lc b/examples/text_editor/main.lc index ef0e908..738e528 100644 --- a/examples/text_editor/main.lc +++ b/examples/text_editor/main.lc @@ -6,448 +6,16 @@ - Ctrl+V - Ctrl+X - Open a document dialog, save document to disk +- Buffer bound undo,redo */ import "raylib"; import "std_types"; import "libc"; -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; -} - -Window :: struct { - buffer: *Buffer; - cursor: Selection; - scroll: Vector2; - mouse_scrolling: bool; - - rect: Rect2P; -} - -FocusedWindow: *Window; -WindowStack: [8]Window; -WindowStackCount: int; - -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) { - 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 := CutRight(&text_window_rect, 10); - line_numbers_rect := CutLeft(&text_window_rect, Monosize.x * 4); - line_numbers_rect; @unused - - 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; - } - } - - 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; - if (w.scroll.y > buffer_end_wpos.y) w.scroll.y = buffer_end_wpos.y; - - // - // 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) { - 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; - } else if IsMouseButtonDown(MOUSE_BUTTON_LEFT) { - 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}); - } - SetMouseCursor(MOUSE_CURSOR_IBEAM); - } else { - SetMouseCursor(MOUSE_CURSOR_DEFAULT); - - if IsMouseButtonPressed(MOUSE_BUTTON_LEFT) { - w.mouse_scrolling = true; - } - } - } - - if w.mouse_scrolling { - mouse_scroll_marker_normalized := (mouse_p.y - w.rect.min.y) / GetRectY(bar); - 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, 255, 0, 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, LIGHTGRAY); - - scroll_start_normalized := :f32(w.scroll.y) / :f32(buffer_end_wpos.y); - scroll_start := scroll_start_normalized * GetRectSize(bar).y; - CutTop(&bar, scroll_start); - marker := CutTop(&bar, 20); - DrawRect(marker, GRAY); - } - - if w != FocusedWindow { - DrawRect(w.rect, {0,0,0,20}); - } -} - InvalidCodepath :: proc() { assert(:*char("invalid codepath") == :*char("")); } - main :: proc(): int { InitWindow(800, 600, "TextEditor"); SetWindowState(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_MAXIMIZED); @@ -457,28 +25,80 @@ main :: proc(): int { font_spacing: float = 1; font: Font = LoadFontEx("C:/Windows/Fonts/consola.ttf", :int(font_size), nil, 0); - glyph_info: GlyphInfo = GetGlyphInfo(font, 'A'); - size := MeasureTextEx(font, "A", font_size, font_spacing); - Monosize = {:float(glyph_info.image.width), size.y}; - - buffer: Buffer; - file_content := LoadFileText("C:/Work/language/examples/text_editor/main.lc"); - AddText(&buffer, {file_content, :int(strlen(file_content))}); - UnloadFileText(file_content); - - AddWindow({buffer = &buffer}); - AddWindow({buffer = &buffer}); - FocusedWindow = &WindowStack[0]; + SANDBOX_TEXT_EDITOR :: 1; + SANDBOX_PROTOTYPE :: 2; + sandbox_chosen := SANDBOX_TEXT_EDITOR; for !WindowShouldClose() { screen_size: Vector2 = {:f32(GetScreenWidth()), :f32(GetScreenHeight())}; screen_rect := Rect2PSize(0, 0, screen_size.x, screen_size.y); - ComputeWindowRects(screen_rect); + top_bar := CutTop(&screen_rect, 30); + top_bar_original := top_bar; + button1_text := "Text Editor"; + button1_text_size := MeasureTextEx(font, button1_text, font_size, font_spacing); + button1 := CutLeft(&top_bar, :float(button1_text_size.x) * 1.5); + button1 = Shrink(button1, 4); + button1_text_pos: Vector2 = { + x = button1.min.x + (GetRectX(button1) - :float(button1_text_size.x)) * 0.5, + y = button1.min.y + (GetRectY(button1) - :float(button1_text_size.y)) * 0.7, + }; + + button1_hover := false; + button1_click := false; + if CheckCollisionPointRec(GetMousePosition(), Rect2PToRectangle(button1)) { + button1_hover = true; + if IsMouseButtonPressed(MOUSE_BUTTON_LEFT) { + button1_click = true; + } + } + + button2_text := "Prototype"; + button2_text_size := MeasureTextEx(font, button2_text, font_size, font_spacing); + button2 := CutLeft(&top_bar, :float(button2_text_size.x) * 1.5); + button2 = Shrink(button2, 4); + button2_text_pos: Vector2 = { + x = button2.min.x + (GetRectX(button2) - :float(button2_text_size.x)) * 0.5, + y = button2.min.y + (GetRectY(button2) - :float(button2_text_size.y)) * 0.7, + }; + + button2_hover := false; + button2_click := false; + if CheckCollisionPointRec(GetMousePosition(), Rect2PToRectangle(button2)) { + button2_hover = true; + if IsMouseButtonPressed(MOUSE_BUTTON_LEFT) { + button2_click = true; + } + } + + if button1_click { + sandbox_chosen = SANDBOX_TEXT_EDITOR; + } else if button2_click { + sandbox_chosen = SANDBOX_PROTOTYPE; + } BeginDrawing(); ClearBackground(RAYWHITE); - UpdateAndDrawWindows(font, font_size); + + if sandbox_chosen == SANDBOX_TEXT_EDITOR { + if !TeInited InitTextEditor(font, font_size, font_spacing); + ComputeWindowRects(screen_rect); + UpdateAndDrawWindows(font, font_size); + } else if sandbox_chosen == SANDBOX_PROTOTYPE { + + } + + DrawRect(top_bar_original, LIGHTGRAY); + + DrawRectangleRoundedLines(Rect2PToRectangle(button1), 0.3, 12, 2, BLACK); + if button1_hover DrawRectangleRounded(Rect2PToRectangle(button1), 0.3, 12, WHITE); + DrawTextEx(font, button1_text, button1_text_pos, font_size, font_spacing, BLACK); + + DrawRectangleRoundedLines(Rect2PToRectangle(button2), 0.3, 12, 2, BLACK); + if button2_hover DrawRectangleRounded(Rect2PToRectangle(button2), 0.3, 12, WHITE); + DrawTextEx(font, button2_text, button2_text_pos, font_size, font_spacing, BLACK); + + EndDrawing(); } CloseWindow(); diff --git a/examples/text_editor/rect2p.lc b/examples/text_editor/rect2p.lc index 52ba302..4903e3d 100644 --- a/examples/text_editor/rect2p.lc +++ b/examples/text_editor/rect2p.lc @@ -3,22 +3,6 @@ Rect2P :: struct { max: Vector2; } -F32_Max :: proc(a: f32, b: f32): f32 { - if a > b return a; - return b; -} - -F32_Min :: proc(a: f32, b: f32): f32 { - if a > b return b; - return a; -} - -F32_Clamp :: proc(val: f32, min: f32, max: f32): f32 { - if (val > max) return max; - if (val < min) return min; - return val; -} - GetRectSize :: proc(rect: Rect2P): Vector2 { result: Vector2 = {rect.max.x - rect.min.x, rect.max.y - rect.min.y}; return result; @@ -36,7 +20,7 @@ GetRectX :: proc(rect: Rect2P): f32 { CutLeft :: proc(r: *Rect2P, value: f32): Rect2P { minx := r.min.x; - r.min.x = F32_Min(r.max.x, r.min.x + value); + r.min.x = MinFloat(r.max.x, r.min.x + value); return :Rect2P{ { minx, r.min.y}, {r.min.x, r.max.y}, @@ -45,7 +29,7 @@ CutLeft :: proc(r: *Rect2P, value: f32): Rect2P { CutRight :: proc(r: *Rect2P, value: f32): Rect2P { maxx := r.max.x; - r.max.x = F32_Max(r.max.x - value, r.min.x); + r.max.x = MaxFloat(r.max.x - value, r.min.x); return :Rect2P{ {r.max.x, r.min.y}, { maxx, r.max.y}, @@ -54,7 +38,7 @@ CutRight :: proc(r: *Rect2P, value: f32): Rect2P { CutBottom :: proc(r: *Rect2P, value: f32): Rect2P { // Y is down maxy := r.max.y; - r.max.y = F32_Max(r.min.y, r.max.y - value); + r.max.y = MaxFloat(r.min.y, r.max.y - value); return :Rect2P{ {r.min.x, r.max.y}, {r.max.x, maxy}, @@ -63,7 +47,7 @@ CutBottom :: proc(r: *Rect2P, value: f32): Rect2P { // Y is down CutTop :: proc(r: *Rect2P, value: f32): Rect2P { // Y is down miny := r.min.y; - r.min.y = F32_Min(r.min.y + value, r.max.y); + r.min.y = MinFloat(r.min.y + value, r.max.y); return :Rect2P{ {r.min.x, miny}, {r.max.x, r.min.y}, diff --git a/examples/text_editor/text_editor.lc b/examples/text_editor/text_editor.lc new file mode 100644 index 0000000..a66199e --- /dev/null +++ b/examples/text_editor/text_editor.lc @@ -0,0 +1,470 @@ +TeInited := false; +TeFontSize: float = 14; +TeFontSpacing: float = 1; +TeFont: Font; +TeBuffer: Buffer; + +FocusedWindow: *Window; +WindowStack: [8]Window; +WindowStackCount: int; + +InitTextEditor :: proc(font: Font, font_size: float, font_spacing: float) { + 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]; +} + +Window :: struct { + buffer: *Buffer; + cursor: Selection; + scroll: Vector2; + mouse_scrolling: 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) { + 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 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}); + } + + 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}); + } +}