Text editor: prototype based setup
This commit is contained in:
34
examples/text_editor/basic.lc
Normal file
34
examples/text_editor/basic.lc
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -27,25 +27,6 @@ GetRangeSize :: proc(range: Range): int {
|
|||||||
return result;
|
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 {
|
AdjustUTF8PosUnsafe :: proc(buffer: *Buffer, pos: int, direction: int = 1): int {
|
||||||
for pos >= 0 && pos < buffer.len && IsUTF8ContinuationByte(buffer.data[pos]) {
|
for pos >= 0 && pos < buffer.len && IsUTF8ContinuationByte(buffer.data[pos]) {
|
||||||
pos += direction;
|
pos += direction;
|
||||||
|
|||||||
@@ -6,448 +6,16 @@
|
|||||||
- Ctrl+V
|
- Ctrl+V
|
||||||
- Ctrl+X
|
- Ctrl+X
|
||||||
- Open a document dialog, save document to disk
|
- Open a document dialog, save document to disk
|
||||||
|
- Buffer bound undo,redo
|
||||||
*/
|
*/
|
||||||
import "raylib";
|
import "raylib";
|
||||||
import "std_types";
|
import "std_types";
|
||||||
import "libc";
|
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() {
|
InvalidCodepath :: proc() {
|
||||||
assert(:*char("invalid codepath") == :*char(""));
|
assert(:*char("invalid codepath") == :*char(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
main :: proc(): int {
|
main :: proc(): int {
|
||||||
InitWindow(800, 600, "TextEditor");
|
InitWindow(800, 600, "TextEditor");
|
||||||
SetWindowState(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_MAXIMIZED);
|
SetWindowState(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_MAXIMIZED);
|
||||||
@@ -457,28 +25,80 @@ main :: proc(): int {
|
|||||||
font_spacing: float = 1;
|
font_spacing: float = 1;
|
||||||
font: Font = LoadFontEx("C:/Windows/Fonts/consola.ttf", :int(font_size), nil, 0);
|
font: Font = LoadFontEx("C:/Windows/Fonts/consola.ttf", :int(font_size), nil, 0);
|
||||||
|
|
||||||
glyph_info: GlyphInfo = GetGlyphInfo(font, 'A');
|
SANDBOX_TEXT_EDITOR :: 1;
|
||||||
size := MeasureTextEx(font, "A", font_size, font_spacing);
|
SANDBOX_PROTOTYPE :: 2;
|
||||||
Monosize = {:float(glyph_info.image.width), size.y};
|
sandbox_chosen := SANDBOX_TEXT_EDITOR;
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
for !WindowShouldClose() {
|
for !WindowShouldClose() {
|
||||||
screen_size: Vector2 = {:f32(GetScreenWidth()), :f32(GetScreenHeight())};
|
screen_size: Vector2 = {:f32(GetScreenWidth()), :f32(GetScreenHeight())};
|
||||||
screen_rect := Rect2PSize(0, 0, screen_size.x, screen_size.y);
|
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();
|
BeginDrawing();
|
||||||
ClearBackground(RAYWHITE);
|
ClearBackground(RAYWHITE);
|
||||||
|
|
||||||
|
if sandbox_chosen == SANDBOX_TEXT_EDITOR {
|
||||||
|
if !TeInited InitTextEditor(font, font_size, font_spacing);
|
||||||
|
ComputeWindowRects(screen_rect);
|
||||||
UpdateAndDrawWindows(font, font_size);
|
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();
|
EndDrawing();
|
||||||
}
|
}
|
||||||
CloseWindow();
|
CloseWindow();
|
||||||
|
|||||||
@@ -3,22 +3,6 @@ Rect2P :: struct {
|
|||||||
max: Vector2;
|
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 {
|
GetRectSize :: proc(rect: Rect2P): Vector2 {
|
||||||
result: Vector2 = {rect.max.x - rect.min.x, rect.max.y - rect.min.y};
|
result: Vector2 = {rect.max.x - rect.min.x, rect.max.y - rect.min.y};
|
||||||
return result;
|
return result;
|
||||||
@@ -36,7 +20,7 @@ GetRectX :: proc(rect: Rect2P): f32 {
|
|||||||
|
|
||||||
CutLeft :: proc(r: *Rect2P, value: f32): Rect2P {
|
CutLeft :: proc(r: *Rect2P, value: f32): Rect2P {
|
||||||
minx := r.min.x;
|
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{
|
return :Rect2P{
|
||||||
{ minx, r.min.y},
|
{ minx, r.min.y},
|
||||||
{r.min.x, r.max.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 {
|
CutRight :: proc(r: *Rect2P, value: f32): Rect2P {
|
||||||
maxx := r.max.x;
|
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{
|
return :Rect2P{
|
||||||
{r.max.x, r.min.y},
|
{r.max.x, r.min.y},
|
||||||
{ maxx, r.max.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
|
CutBottom :: proc(r: *Rect2P, value: f32): Rect2P { // Y is down
|
||||||
maxy := r.max.y;
|
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{
|
return :Rect2P{
|
||||||
{r.min.x, r.max.y},
|
{r.min.x, r.max.y},
|
||||||
{r.max.x, maxy},
|
{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
|
CutTop :: proc(r: *Rect2P, value: f32): Rect2P { // Y is down
|
||||||
miny := r.min.y;
|
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{
|
return :Rect2P{
|
||||||
{r.min.x, miny},
|
{r.min.x, miny},
|
||||||
{r.max.x, r.min.y},
|
{r.max.x, r.min.y},
|
||||||
|
|||||||
470
examples/text_editor/text_editor.lc
Normal file
470
examples/text_editor/text_editor.lc
Normal file
@@ -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});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user