Init new repository
This commit is contained in:
249
examples/text_editor/buffer.lc
Normal file
249
examples/text_editor/buffer.lc
Normal file
@@ -0,0 +1,249 @@
|
||||
Buffer :: struct {
|
||||
data: *u8;
|
||||
len: int;
|
||||
cap: int;
|
||||
|
||||
lines: Lines;
|
||||
}
|
||||
|
||||
Lines :: struct {
|
||||
data: *Range;
|
||||
len: int;
|
||||
cap: int;
|
||||
}
|
||||
|
||||
Range :: struct {
|
||||
min: int;
|
||||
max: int;
|
||||
}
|
||||
|
||||
LineInfo :: struct {
|
||||
number: int;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
GetRangeSize :: proc(range: Range): int {
|
||||
result := range.max - range.min;
|
||||
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;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
AdjustUTF8Pos :: proc(buffer: *Buffer, pos: int, direction: int = 1): int {
|
||||
assert(direction == 1 || direction == -1);
|
||||
pos = AdjustUTF8PosUnsafe(buffer, pos, direction);
|
||||
pos = ClampInt(pos, 0, buffer.len - 1);
|
||||
if (buffer.data) assert(!IsUTF8ContinuationByte(buffer.data[pos]));
|
||||
return pos;
|
||||
}
|
||||
|
||||
AdjustRange :: proc(buffer: *Buffer, range: *Range) {
|
||||
range.min = AdjustUTF8Pos(buffer, range.min, direction = -1);
|
||||
range.max = AdjustUTF8Pos(buffer, range.max, direction = +1);
|
||||
}
|
||||
|
||||
ReplaceText :: proc(buffer: *Buffer, replace: Range, with: String) {
|
||||
AdjustRange(buffer, &replace);
|
||||
|
||||
new_buffer_len := buffer.len + :int(with.len) - GetRangeSize(replace);
|
||||
new_buffer_size := new_buffer_len + 1; // possible addition of a null terminator
|
||||
if new_buffer_size > buffer.cap {
|
||||
new_buffer_cap := MaxInt(4096, new_buffer_size * 2);
|
||||
new_buffer := malloc(:usize(new_buffer_cap));
|
||||
if (buffer.data) {
|
||||
memcpy(new_buffer, buffer.data, :usize(buffer.len));
|
||||
free(buffer.data);
|
||||
}
|
||||
buffer.data = new_buffer;
|
||||
buffer.cap = new_buffer_cap;
|
||||
}
|
||||
|
||||
old_text_range: Range = replace;
|
||||
new_text_range: Range = {old_text_range.min, old_text_range.min + :int(with.len)};
|
||||
right_range: Range = {old_text_range.max, buffer.len};
|
||||
|
||||
memmove(&buffer.data[new_text_range.max], &buffer.data[right_range.min], :usize(GetRangeSize(right_range)));
|
||||
memmove(&buffer.data[new_text_range.min], with.str, :usize(GetRangeSize(new_text_range)));
|
||||
|
||||
buffer.len = new_buffer_len;
|
||||
if buffer.len && buffer.data[buffer.len - 1] != 0 {
|
||||
buffer.data[buffer.len] = 0;
|
||||
buffer.len += 1;
|
||||
assert(buffer.len <= buffer.cap);
|
||||
}
|
||||
|
||||
UpdateBufferLines(buffer);
|
||||
}
|
||||
|
||||
AddLine :: proc(lines: *Lines, line: Range) {
|
||||
if lines.len + 1 > lines.cap {
|
||||
new_cap := MaxInt(16, lines.cap * 2);
|
||||
lines.data = realloc(lines.data, :usize(new_cap) * sizeof(:Range));
|
||||
lines.cap = new_cap;
|
||||
}
|
||||
lines.data[lines.len] = line;
|
||||
lines.len += 1;
|
||||
}
|
||||
|
||||
UpdateBufferLines :: proc(buffer: *Buffer) {
|
||||
buffer.lines.len = 0;
|
||||
|
||||
line: Range = {0, 0};
|
||||
for i := 0; i < buffer.len; i += 1 {
|
||||
if buffer.data[i] == '\n' {
|
||||
AddLine(&buffer.lines, line);
|
||||
line.min = i + 1;
|
||||
line.max = i + 1;
|
||||
} else {
|
||||
line.max += 1;
|
||||
}
|
||||
}
|
||||
|
||||
line.min = AdjustUTF8Pos(buffer, line.min);
|
||||
line.max = AdjustUTF8Pos(buffer, line.max);
|
||||
AddLine(&buffer.lines, line);
|
||||
}
|
||||
|
||||
AllocStringFromBuffer :: proc(buffer: *Buffer, range: Range, null_terminate: bool = false): String {
|
||||
AdjustRange(buffer, &range);
|
||||
size := GetRangeSize(range);
|
||||
|
||||
alloc_size := :usize(size);
|
||||
if (null_terminate) alloc_size += 1;
|
||||
data := malloc(alloc_size);
|
||||
memcpy(data, &buffer.data[range.min], :usize(size));
|
||||
|
||||
result: String = {data, size};
|
||||
if (null_terminate) result.str[result.len] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
GetStringFromBuffer :: proc(buffer: *Buffer, range: Range): String {
|
||||
AdjustRange(buffer, &range);
|
||||
size := GetRangeSize(range);
|
||||
result: String = {:*char(&buffer.data[range.min]), size};
|
||||
return result;
|
||||
}
|
||||
|
||||
AddText :: proc(buffer: *Buffer, text: String) {
|
||||
end_of_buffer: Range = {buffer.len - 1, buffer.len - 1};
|
||||
ReplaceText(buffer, end_of_buffer, text);
|
||||
}
|
||||
|
||||
FindLineOfPos :: proc(buffer: *Buffer, pos: int): LineInfo {
|
||||
for i := 0; i < buffer.lines.len; i += 1 {
|
||||
line := buffer.lines.data[i];
|
||||
if pos >= line.min && pos <= line.max {
|
||||
return {i, line};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
IsLineValid :: proc(buffer: *Buffer, line: int): bool {
|
||||
result := line >= 0 && line < buffer.lines.len;
|
||||
return result;
|
||||
}
|
||||
|
||||
GetLine :: proc(buffer: *Buffer, line: int): LineInfo {
|
||||
line = ClampInt(line, 0, buffer.lines.len - 1);
|
||||
range := buffer.lines.data[line];
|
||||
return {line, range};
|
||||
}
|
||||
|
||||
IsPosInBounds :: proc(buffer: *Buffer, pos: int): bool {
|
||||
result := buffer.len != 0 && pos >= 0 && pos < buffer.len;
|
||||
return result;
|
||||
}
|
||||
|
||||
GetChar :: proc(buffer: *Buffer, pos: int): u8 {
|
||||
result: u8;
|
||||
if IsPosInBounds(buffer, pos) {
|
||||
result = buffer.data[pos];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GetUTF32 :: proc(buffer: *Buffer, pos: int, codepoint_size: *int = nil): u32 {
|
||||
if !IsPosInBounds(buffer, pos) return 0;
|
||||
p := &buffer.data[pos];
|
||||
max := buffer.len - pos;
|
||||
utf32 := UTF8ToUTF32(:*uchar(p), max);
|
||||
assert(utf32.error == 0);
|
||||
if (utf32.error != 0) return 0;
|
||||
if (codepoint_size) codepoint_size[0] = utf32.advance;
|
||||
return utf32.out_str;
|
||||
}
|
||||
|
||||
IsWhitespace :: proc(w: u8): bool {
|
||||
result := w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r';
|
||||
return result;
|
||||
}
|
||||
|
||||
SeekOnWordBoundary :: proc(buffer: *Buffer, pos: int, direction: int = GO_FORWARD): int {
|
||||
assert(direction == GO_FORWARD || direction == GO_BACKWARD);
|
||||
check_pos: int;
|
||||
if direction == GO_FORWARD {
|
||||
check_pos = AdjustUTF8Pos(buffer, pos + 1, direction);
|
||||
pos = check_pos;
|
||||
// this difference here because the Backward for loop is not inclusive.
|
||||
// It doesn't move an inch forward after "pos"
|
||||
// - - - - pos - -
|
||||
// It goes backward on first Advance
|
||||
} else {
|
||||
check_pos = AdjustUTF8Pos(buffer, pos - 1, direction);
|
||||
}
|
||||
|
||||
cursor_char := GetChar(buffer, check_pos);
|
||||
standing_on_whitespace := IsWhitespace(cursor_char);
|
||||
seek_whitespace := standing_on_whitespace == false;
|
||||
seek_word := standing_on_whitespace;
|
||||
|
||||
end := buffer.len - 1;
|
||||
if (direction == GO_BACKWARD) end = 0;
|
||||
|
||||
result := end;
|
||||
iter := Iterate(buffer, pos, end, direction);
|
||||
prev_pos := iter.pos;
|
||||
for IsValid(iter); Advance(&iter) {
|
||||
if seek_word && !IsWhitespace(:u8(iter.item)) {
|
||||
result = prev_pos;
|
||||
break;
|
||||
}
|
||||
if seek_whitespace && IsWhitespace(:u8(iter.item)) {
|
||||
if direction == GO_FORWARD {
|
||||
result = iter.pos;
|
||||
} else {
|
||||
result = prev_pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
prev_pos = iter.pos;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
54
examples/text_editor/buffer_iter.lc
Normal file
54
examples/text_editor/buffer_iter.lc
Normal file
@@ -0,0 +1,54 @@
|
||||
GO_FORWARD :: 1;
|
||||
GO_BACKWARD :: -1;
|
||||
|
||||
BufferIter :: struct {
|
||||
buffer: *Buffer;
|
||||
pos: int;
|
||||
end: int;
|
||||
item: u32;
|
||||
|
||||
utf8_codepoint_size: int;
|
||||
direction: int;
|
||||
codepoint_index: int;
|
||||
}
|
||||
|
||||
IsValid :: proc(iter: BufferIter): bool {
|
||||
assert(iter.direction == GO_FORWARD || iter.direction == GO_BACKWARD);
|
||||
|
||||
result := false;
|
||||
if iter.direction == GO_BACKWARD {
|
||||
result = iter.pos >= iter.end;
|
||||
} else {
|
||||
result = iter.pos < iter.end;
|
||||
}
|
||||
|
||||
if result {
|
||||
assert(!IsUTF8ContinuationByte(GetChar(iter.buffer, iter.pos)));
|
||||
assert(IsPosInBounds(iter.buffer, iter.pos));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Advance :: proc(iter: *BufferIter) {
|
||||
assert(iter.direction == GO_FORWARD || iter.direction == GO_BACKWARD);
|
||||
|
||||
iter.codepoint_index += 1;
|
||||
if iter.direction == GO_FORWARD {
|
||||
iter.pos += iter.utf8_codepoint_size;
|
||||
} else {
|
||||
iter.pos = AdjustUTF8PosUnsafe(iter.buffer, iter.pos - 1, GO_BACKWARD);
|
||||
}
|
||||
|
||||
if !IsValid(*iter) return;
|
||||
iter.item = GetUTF32(iter.buffer, iter.pos, &iter.utf8_codepoint_size);
|
||||
}
|
||||
|
||||
Iterate :: proc(buffer: *Buffer, pos: int, end: int, direction: int = GO_FORWARD): BufferIter {
|
||||
assert(!IsUTF8ContinuationByte(GetChar(buffer, pos)));
|
||||
assert(!IsUTF8ContinuationByte(GetChar(buffer, end)));
|
||||
assert(direction == GO_FORWARD || direction == GO_BACKWARD);
|
||||
|
||||
result: BufferIter = {buffer = buffer, pos = pos, end = end, direction = direction, codepoint_index = -1};
|
||||
Advance(&result);
|
||||
return result;
|
||||
}
|
||||
34
examples/text_editor/build.cpp
Normal file
34
examples/text_editor/build.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
bool text_editor() {
|
||||
LC_Lang *lang = LC_LangAlloc();
|
||||
lang->use_colored_terminal_output = UseColoredIO;
|
||||
lang->breakpoint_on_error = BreakpointOnError;
|
||||
LC_LangBegin(lang);
|
||||
defer { LC_LangEnd(lang); };
|
||||
|
||||
LC_RegisterPackageDir("../pkgs");
|
||||
LC_RegisterPackageDir("../examples");
|
||||
|
||||
LC_Intern name = LC_ILit("text_editor");
|
||||
LC_ASTRefList packages = LC_ResolvePackageByName(name);
|
||||
if (L->errors) return false;
|
||||
|
||||
DebugVerifyAST(packages);
|
||||
if (L->errors) return false;
|
||||
|
||||
LC_FindUnusedLocalsAndRemoveUnusedGlobalDecls();
|
||||
|
||||
OS_MakeDir("examples");
|
||||
OS_MakeDir("examples/text_editor");
|
||||
OS_CopyFile(RaylibDLL, "examples/text_editor/raylib.dll", true);
|
||||
|
||||
S8_String code = LC_GenerateUnityBuild(packages);
|
||||
S8_String path = "examples/text_editor/text_editor.c";
|
||||
OS_WriteFile(path, code);
|
||||
|
||||
if (!UseCL) return true;
|
||||
S8_String cmd = Fmt("cl %.*s -Zi -std:c11 -nologo -FC -Fd:examples/text_editor/a.pdb -Fe:examples/text_editor/text_editor.exe %.*s", S8_Expand(path), S8_Expand(RaylibLIB));
|
||||
int errcode = Run(cmd);
|
||||
if (errcode != 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
362
examples/text_editor/main.lc
Normal file
362
examples/text_editor/main.lc
Normal file
@@ -0,0 +1,362 @@
|
||||
import "raylib";
|
||||
import "std_types";
|
||||
import "libc";
|
||||
|
||||
MainCursor: Selection;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
main :: proc(): int {
|
||||
InitWindow(800, 600, "TextEditor");
|
||||
SetTargetFPS(60);
|
||||
|
||||
font_size: float = 35;
|
||||
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;
|
||||
AddText(&buffer, "1Testing and stuff 1Testing and stuff 1Testing and stuff 1Testing and stuff\n");
|
||||
AddText(&buffer, "2Testing and stuff\n");
|
||||
AddText(&buffer, "3Testing and stuff\n");
|
||||
AddText(&buffer, "4Testing and stuff\n");
|
||||
AddText(&buffer, "5Testing and stuff\n");
|
||||
AddText(&buffer, "6Testing and stuff\n");
|
||||
AddText(&buffer, "7Testing and stuff\n");
|
||||
AddText(&buffer, "8Testing and stuff\n");
|
||||
AddText(&buffer, "9Testing and stuff\n");
|
||||
AddText(&buffer, "1Testing and stuff\n");
|
||||
AddText(&buffer, "2Testing and stuff\n");
|
||||
AddText(&buffer, "3Testing and stuff\n");
|
||||
AddText(&buffer, "4Testing and stuff\n");
|
||||
AddText(&buffer, "5Testing and stuff\n");
|
||||
AddText(&buffer, "6Testing and stuff\n");
|
||||
AddText(&buffer, "7Testing and stuff\n");
|
||||
AddText(&buffer, "8Testing and stuff\n");
|
||||
AddText(&buffer, "9Testing and stuff\n");
|
||||
AddText(&buffer, "1Testing and stuff\n");
|
||||
AddText(&buffer, "22esting and stuff\n");
|
||||
AddText(&buffer, "3Testing and stuff\n");
|
||||
AddText(&buffer, "4Testing and stuff\n");
|
||||
AddText(&buffer, "5Testing and stuff\n");
|
||||
AddText(&buffer, "6Testing and stuff\n");
|
||||
AddText(&buffer, "7Testing and stuff\n");
|
||||
AddText(&buffer, "8Testing and stuff\n");
|
||||
AddText(&buffer, "9Testing and stuff\n");
|
||||
AddText(&buffer, "1Testing and stuff\n");
|
||||
AddText(&buffer, "2Testing and stuff\n");
|
||||
AddText(&buffer, "3Testing and stuff\n");
|
||||
AddText(&buffer, "4Testing and stuff\n");
|
||||
|
||||
for !WindowShouldClose() {
|
||||
ScreenSize = {:f32(GetScreenWidth()), :f32(GetScreenHeight())};
|
||||
initial_cursor: Selection = MainCursor;
|
||||
|
||||
if IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT) {
|
||||
if IsKeyDown(KEY_LEFT_SHIFT) {
|
||||
if IsKeyDown(KEY_LEFT_CONTROL) {
|
||||
MainCursor.b = SeekOnWordBoundary(&buffer, MainCursor.b, GO_BACKWARD);
|
||||
} else {
|
||||
MainCursor.b = MoveLeft(&buffer, MainCursor.b);
|
||||
}
|
||||
} else {
|
||||
if IsKeyDown(KEY_LEFT_CONTROL) {
|
||||
MainCursor.a = SeekOnWordBoundary(&buffer, MainCursor.a, GO_BACKWARD);
|
||||
MainCursor.b = MainCursor.a;
|
||||
} else {
|
||||
MainCursor.a = MoveLeft(&buffer, MainCursor.a);
|
||||
MainCursor.b = MainCursor.a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT) {
|
||||
if IsKeyDown(KEY_LEFT_SHIFT) {
|
||||
if IsKeyDown(KEY_LEFT_CONTROL) {
|
||||
MainCursor.b = SeekOnWordBoundary(&buffer, MainCursor.b, GO_FORWARD);
|
||||
} else {
|
||||
MainCursor.b = MoveRight(&buffer, MainCursor.b);
|
||||
}
|
||||
} else {
|
||||
if IsKeyDown(KEY_LEFT_CONTROL) {
|
||||
MainCursor.a = SeekOnWordBoundary(&buffer, MainCursor.a, GO_FORWARD);
|
||||
MainCursor.b = MainCursor.a;
|
||||
} else {
|
||||
MainCursor.a = MoveRight(&buffer, MainCursor.a);
|
||||
MainCursor.b = MainCursor.a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) {
|
||||
if IsKeyDown(KEY_LEFT_SHIFT) {
|
||||
MainCursor.b = MoveDown(&buffer, MainCursor.b);
|
||||
} else {
|
||||
range := GetRange(MainCursor);
|
||||
if GetRangeSize(range) > 0 {
|
||||
MainCursor.b = range.max;
|
||||
MainCursor.a = range.max;
|
||||
}
|
||||
MainCursor.a = MoveDown(&buffer, MainCursor.a);
|
||||
MainCursor.b = MainCursor.a;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) {
|
||||
if IsKeyDown(KEY_LEFT_SHIFT) {
|
||||
MainCursor.b = MoveUp(&buffer, MainCursor.b);
|
||||
} else {
|
||||
range := GetRange(MainCursor);
|
||||
if GetRangeSize(range) > 0 {
|
||||
MainCursor.b = range.min;
|
||||
MainCursor.a = range.min;
|
||||
}
|
||||
MainCursor.a = MoveUp(&buffer, MainCursor.a);
|
||||
MainCursor.b = MainCursor.a;
|
||||
}
|
||||
}
|
||||
|
||||
if IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE) {
|
||||
range := GetRange(MainCursor);
|
||||
range_size := GetRangeSize(range);
|
||||
|
||||
if range_size == 0 {
|
||||
if IsKeyDown(KEY_LEFT_CONTROL) {
|
||||
left := SeekOnWordBoundary(&buffer, MainCursor.a, GO_BACKWARD);
|
||||
ReplaceText(&buffer, {left, MainCursor.a}, "");
|
||||
MainCursor.a = left;
|
||||
MainCursor.b = MainCursor.a;
|
||||
} else {
|
||||
left := MoveLeft(&buffer, MainCursor.a);
|
||||
ReplaceText(&buffer, {left, MainCursor.a}, "");
|
||||
MainCursor.a = left;
|
||||
MainCursor.b = MainCursor.a;
|
||||
}
|
||||
} else {
|
||||
ReplaceText(&buffer, range, "");
|
||||
MainCursor.b = range.min;
|
||||
MainCursor.a = MainCursor.b;
|
||||
}
|
||||
}
|
||||
|
||||
if IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE) {
|
||||
range := GetRange(MainCursor);
|
||||
range_size := GetRangeSize(range);
|
||||
|
||||
if range_size == 0 {
|
||||
if IsKeyDown(KEY_LEFT_CONTROL) {
|
||||
right := SeekOnWordBoundary(&buffer, MainCursor.a);
|
||||
ReplaceText(&buffer, {MainCursor.a, right}, "");
|
||||
} else {
|
||||
right := MoveRight(&buffer, MainCursor.a);
|
||||
ReplaceText(&buffer, {MainCursor.a, right}, "");
|
||||
}
|
||||
MainCursor.b = MainCursor.a;
|
||||
} else {
|
||||
ReplaceText(&buffer, range, "");
|
||||
MainCursor.b = range.min;
|
||||
MainCursor.a = MainCursor.b;
|
||||
}
|
||||
}
|
||||
|
||||
if IsKeyPressed(KEY_ENTER) || IsKeyPressedRepeat(KEY_ENTER) {
|
||||
ReplaceText(&buffer, GetRange(MainCursor), "\n");
|
||||
MainCursor.a = MoveRight(&buffer, MainCursor.a);
|
||||
MainCursor.b = MainCursor.a;
|
||||
}
|
||||
|
||||
if IsKeyPressed(KEY_HOME) {
|
||||
line := FindLineOfPos(&buffer, MainCursor.b);
|
||||
if IsKeyDown(KEY_LEFT_SHIFT) {
|
||||
MainCursor.b = line.range.min;
|
||||
} else {
|
||||
MainCursor.a = line.range.min;
|
||||
MainCursor.b = line.range.min;
|
||||
}
|
||||
}
|
||||
if IsKeyPressed(KEY_END) {
|
||||
line := FindLineOfPos(&buffer, MainCursor.b);
|
||||
if IsKeyDown(KEY_LEFT_SHIFT) {
|
||||
MainCursor.b = line.range.max;
|
||||
} else {
|
||||
MainCursor.a = line.range.max;
|
||||
MainCursor.b = line.range.max;
|
||||
}
|
||||
}
|
||||
|
||||
if IsKeyPressed(KEY_PAGE_DOWN) || IsKeyPressedRepeat(KEY_PAGE_DOWN) {
|
||||
vpos := CalculateVisualPos(&buffer, MainCursor.b);
|
||||
move_by := :int(roundf(ScreenSize.y / Monosize.y));
|
||||
vpos.y += move_by;
|
||||
MainCursor.b = CalculatePosFromVisualPos(&buffer, vpos);
|
||||
if !IsKeyDown(KEY_LEFT_SHIFT) {
|
||||
MainCursor.a = MainCursor.b;
|
||||
}
|
||||
}
|
||||
if IsKeyPressed(KEY_PAGE_UP) || IsKeyPressedRepeat(KEY_PAGE_UP) {
|
||||
vpos := CalculateVisualPos(&buffer, MainCursor.b);
|
||||
move_by := :int(roundf(ScreenSize.y / Monosize.y));
|
||||
vpos.y -= move_by;
|
||||
MainCursor.b = CalculatePosFromVisualPos(&buffer, vpos);
|
||||
if !IsKeyDown(KEY_LEFT_SHIFT) {
|
||||
MainCursor.a = MainCursor.b;
|
||||
}
|
||||
}
|
||||
|
||||
mouse_p := GetMousePosition();
|
||||
if IsMouseButtonDown(MOUSE_BUTTON_LEFT) {
|
||||
p := Vector2Add(mouse_p, Scroll);
|
||||
p = Vector2Divide(p, Monosize);
|
||||
x := :int(floorf(p.x));
|
||||
y := :int(floorf(p.y));
|
||||
MainCursor.a = CalculatePosFromVisualPos(&buffer, {x, y});
|
||||
MainCursor.b = MainCursor.a;
|
||||
}
|
||||
|
||||
if CheckCollisionPointRec(mouse_p, {0, 0, ScreenSize.x, ScreenSize.y}) {
|
||||
SetMouseCursor(MOUSE_CURSOR_IBEAM);
|
||||
} else {
|
||||
SetMouseCursor(MOUSE_CURSOR_DEFAULT);
|
||||
}
|
||||
|
||||
for key := GetCharPressed(); key; key = GetCharPressed() {
|
||||
selection_range := GetRange(MainCursor);
|
||||
|
||||
result: UTF8_Result = UTF32ToUTF8(:u32(key));
|
||||
if result.error == 0 {
|
||||
ReplaceText(&buffer, selection_range, {:*char(&result.out_str[0]), result.len});
|
||||
} else {
|
||||
ReplaceText(&buffer, selection_range, "?");
|
||||
}
|
||||
|
||||
range_size := GetRangeSize(selection_range);
|
||||
if range_size != 0 {
|
||||
MainCursor.a = selection_range.min;
|
||||
MainCursor.b = selection_range.min;
|
||||
}
|
||||
MainCursor.a = MoveRight(&buffer, MainCursor.a);
|
||||
MainCursor.b = MainCursor.a;
|
||||
}
|
||||
|
||||
//
|
||||
// Scrolling
|
||||
//
|
||||
mouse_wheel := GetMouseWheelMove() * 16;
|
||||
Scroll.y -= mouse_wheel;
|
||||
|
||||
if initial_cursor.b != MainCursor.b {
|
||||
cursor_vpos := CalculateVisualPos(&buffer, MainCursor.b);
|
||||
|
||||
world_pos := CalculateWorldPosUnscrolled(cursor_vpos);
|
||||
world_pos_cursor_end := Vector2Add(world_pos, Monosize);
|
||||
|
||||
scrolled_begin := Scroll;
|
||||
scrolled_end := Vector2Add(Scroll, ScreenSize);
|
||||
|
||||
if world_pos_cursor_end.x > scrolled_end.x {
|
||||
Scroll.x += world_pos_cursor_end.x - scrolled_end.x;
|
||||
}
|
||||
if world_pos.x < scrolled_begin.x {
|
||||
Scroll.x -= scrolled_begin.x - world_pos.x;
|
||||
}
|
||||
if world_pos_cursor_end.y > scrolled_end.y {
|
||||
Scroll.y += world_pos_cursor_end.y - scrolled_end.y;
|
||||
}
|
||||
if world_pos.y < scrolled_begin.y {
|
||||
Scroll.y -= scrolled_begin.y - world_pos.y;
|
||||
}
|
||||
}
|
||||
if (Scroll.x < 0) Scroll.x = 0;
|
||||
if (Scroll.y < 0) Scroll.y = 0;
|
||||
|
||||
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(RAYWHITE);
|
||||
|
||||
_miny := Scroll.y / Monosize.y;
|
||||
_maxy := (Scroll.y + ScreenSize.y) / Monosize.y;
|
||||
|
||||
_minx := Scroll.x / Monosize.x;
|
||||
_maxx := (Scroll.x + ScreenSize.x) / Monosize.x;
|
||||
|
||||
miny := :int(floorf(_miny));
|
||||
minx := :int(floorf(_minx));
|
||||
|
||||
maxy := :int(ceilf(_maxy));
|
||||
maxx := :int(ceilf(_maxx));
|
||||
|
||||
// Draw grid
|
||||
{
|
||||
for y := miny; y < maxy; y += 1 {
|
||||
for x := minx; x < maxx; x += 1 {
|
||||
p := CalculateWorldPos({x, y});
|
||||
rect := Rect2PSize(p.x, p.y, Monosize.x, Monosize.y);
|
||||
rect = Shrink(rect, 1);
|
||||
DrawRect(rect, {255, 0, 0, 40});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw text
|
||||
{
|
||||
miny = ClampInt(miny, 0, buffer.lines.len - 1);
|
||||
maxy = ClampInt(maxy, 0, buffer.lines.len - 1);
|
||||
for y := miny; y <= maxy; y += 1 {
|
||||
line := buffer.lines.data[y];
|
||||
|
||||
string := AllocStringFromBuffer(&buffer, line, null_terminate = true);
|
||||
defer free(string.str);
|
||||
|
||||
pos := CalculateWorldPos({0, y});
|
||||
DrawTextEx(font, string.str, pos, font_size, font_spacing, BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw selection
|
||||
{
|
||||
range := GetRange(MainCursor);
|
||||
start_vpos := CalculateVisualPos(&buffer, range.min);
|
||||
pos := start_vpos;
|
||||
for iter := Iterate(&buffer, range.min, range.max); IsValid(iter); Advance(&iter) {
|
||||
world_pos := CalculateWorldPos(pos);
|
||||
rect := Rect2PSize(world_pos.x, world_pos.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(&buffer, MainCursor.b);
|
||||
p := CalculateWorldPos(c);
|
||||
|
||||
cursor_size: f32 = Monosize.x * 0.2;
|
||||
rect := Rect2PSize(p.x, p.y, Monosize.x, Monosize.y);
|
||||
rect = CutLeft(&rect, cursor_size);
|
||||
|
||||
DrawRect(rect, RED);
|
||||
}
|
||||
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
CloseWindow();
|
||||
return 0;
|
||||
}
|
||||
79
examples/text_editor/rect2p.lc
Normal file
79
examples/text_editor/rect2p.lc
Normal file
@@ -0,0 +1,79 @@
|
||||
Rect2P :: struct {
|
||||
min: 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 {
|
||||
result: Vector2 = {rect.max.x - rect.min.x, rect.max.y - rect.min.y};
|
||||
return result;
|
||||
}
|
||||
|
||||
CutLeft :: proc(r: *Rect2P, value: f32): Rect2P {
|
||||
minx := r.min.x;
|
||||
r.min.x = F32_Min(r.max.x, r.min.x + value);
|
||||
return :Rect2P{
|
||||
{ minx, r.min.y},
|
||||
{r.min.x, r.max.y},
|
||||
};
|
||||
}
|
||||
|
||||
CutRight :: proc(r: *Rect2P, value: f32): Rect2P {
|
||||
maxx := r.max.x;
|
||||
r.max.x = F32_Max(r.max.x - value, r.min.x);
|
||||
return :Rect2P{
|
||||
{r.max.x, r.min.y},
|
||||
{ maxx, r.max.y},
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
return :Rect2P{
|
||||
{r.min.x, r.max.y},
|
||||
{r.max.x, maxy},
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
return :Rect2P{
|
||||
{r.min.x, miny},
|
||||
{r.max.x, r.min.y},
|
||||
};
|
||||
}
|
||||
|
||||
Rect2PToRectangle :: proc(r: Rect2P): Rectangle {
|
||||
result: Rectangle = {r.min.x, r.min.y, r.max.x - r.min.x, r.max.y - r.min.y};
|
||||
return result;
|
||||
}
|
||||
|
||||
Rect2PSize :: proc(x: f32, y: f32, w: f32, h: f32): Rect2P {
|
||||
result: Rect2P = {{x, y}, {x+w, y+h}};
|
||||
return result;
|
||||
}
|
||||
|
||||
Shrink :: proc(r: Rect2P, v: f32): Rect2P {
|
||||
r.min.x += v;
|
||||
r.min.y += v;
|
||||
r.max.x -= v;
|
||||
r.max.y -= v;
|
||||
return r;
|
||||
}
|
||||
94
examples/text_editor/unicode.lc
Normal file
94
examples/text_editor/unicode.lc
Normal file
@@ -0,0 +1,94 @@
|
||||
UTF32_Result :: struct {
|
||||
out_str: u32;
|
||||
advance: int;
|
||||
error: int;
|
||||
}
|
||||
|
||||
UTF8_Result :: struct {
|
||||
out_str: [4]u8;
|
||||
len: int;
|
||||
error: int;
|
||||
}
|
||||
|
||||
IsUTF8ContinuationByte :: proc(c: u8): bool {
|
||||
result := (c & 0b11000000) == 0b10000000;
|
||||
return result;
|
||||
}
|
||||
|
||||
UTF8ToUTF32 :: proc(c: *uchar, max_advance: int): UTF32_Result {
|
||||
result: UTF32_Result;
|
||||
|
||||
if (c[0] & 0x80) == 0 { // Check if leftmost zero of first byte is unset
|
||||
if max_advance >= 1 {
|
||||
result.out_str = :u32(c[0]);
|
||||
result.advance = 1;
|
||||
}
|
||||
else result.error = 1;
|
||||
}
|
||||
|
||||
else if (c[0] & 0xe0) == 0xc0 {
|
||||
if (c[1] & 0xc0) == 0x80 { // Continuation byte required
|
||||
if max_advance >= 2 {
|
||||
result.out_str = (:u32(c[0] & 0x1f) << 6) | :u32(c[1] & 0x3f);
|
||||
result.advance = 2;
|
||||
}
|
||||
else result.error = 2;
|
||||
}
|
||||
else result.error = 2;
|
||||
}
|
||||
|
||||
else if (c[0] & 0xf0) == 0xe0 {
|
||||
if ((c[1] & 0xc0) == 0x80) && ((c[2] & 0xc0) == 0x80) { // Two continuation bytes required
|
||||
if max_advance >= 3 {
|
||||
result.out_str = (:u32(c[0] & 0xf) << 12) | (:u32(c[1] & 0x3f) << 6) | :u32(c[2] & 0x3f);
|
||||
result.advance = 3;
|
||||
}
|
||||
else result.error = 3;
|
||||
}
|
||||
else result.error = 3;
|
||||
}
|
||||
|
||||
else if (c[0] & 0xf8) == 0xf0 {
|
||||
if (c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80 && (c[3] & 0xc0) == 0x80 { // Three continuation bytes required
|
||||
if max_advance >= 4 {
|
||||
result.out_str = :u32(c[0] & 0xf) << 18 | :u32(c[1] & 0x3f) << 12 | :u32(c[2] & 0x3f) << 6 | :u32(c[3] & 0x3f);
|
||||
result.advance = 4;
|
||||
}
|
||||
else result.error = 4;
|
||||
}
|
||||
else result.error = 4;
|
||||
}
|
||||
else result.error = 4;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UTF32ToUTF8 :: proc(codepoint: u32): UTF8_Result {
|
||||
result: UTF8_Result;
|
||||
|
||||
if codepoint <= 0x7F {
|
||||
result.len = 1;
|
||||
result.out_str[0] = :u8(codepoint);
|
||||
}
|
||||
else if codepoint <= 0x7FF {
|
||||
result.len = 2;
|
||||
result.out_str[0] = :u8 (0xc0 | 0x1f & (codepoint >> 6));
|
||||
result.out_str[1] = :u8 (0x80 | 0x3f & codepoint);
|
||||
}
|
||||
else if codepoint <= 0xFFFF { // 16 bit word
|
||||
result.len = 3;
|
||||
result.out_str[0] = :u8 (0xe0 | 0xf & (codepoint >> 12)); // 4 bits
|
||||
result.out_str[1] = :u8 (0x80 | 0x3f & (codepoint >> 6)); // 6 bits
|
||||
result.out_str[2] = :u8 (0x80 | 0x3f & codepoint); // 6 bits
|
||||
}
|
||||
else if codepoint <= 0x10FFFF { // 21 bit word
|
||||
result.len = 4;
|
||||
result.out_str[0] = :u8 (0xf0 | 0x7 & (codepoint >> 18)); // 3 bits
|
||||
result.out_str[1] = :u8 (0x80 | 0x3f & (codepoint >> 12)); // 6 bits
|
||||
result.out_str[2] = :u8 (0x80 | 0x3f & (codepoint >> 6)); // 6 bits
|
||||
result.out_str[3] = :u8 (0x80 | 0x3f & codepoint); // 6 bits
|
||||
}
|
||||
else result.error = 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
76
examples/text_editor/visual_pos.lc
Normal file
76
examples/text_editor/visual_pos.lc
Normal file
@@ -0,0 +1,76 @@
|
||||
Monosize: Vector2;
|
||||
ScreenSize: Vector2;
|
||||
Scroll: Vector2;
|
||||
|
||||
Vec2I :: struct {
|
||||
x: int;
|
||||
y: int;
|
||||
}
|
||||
|
||||
MoveLeft :: proc(buffer: *Buffer, pos: int): int {
|
||||
pos -= 1;
|
||||
pos = AdjustUTF8Pos(buffer, pos, direction = -1);
|
||||
return pos;
|
||||
}
|
||||
|
||||
MoveRight :: proc(buffer: *Buffer, pos: int): int {
|
||||
pos += 1;
|
||||
pos = AdjustUTF8Pos(buffer, pos, direction = +1);
|
||||
return pos;
|
||||
}
|
||||
|
||||
CalculateVisualPos :: proc(buffer: *Buffer, pos: int): Vec2I {
|
||||
line: LineInfo = FindLineOfPos(buffer, pos);
|
||||
|
||||
iter := Iterate(buffer, line.range.min, line.range.max);
|
||||
for IsValid(iter); Advance(&iter) {
|
||||
if iter.pos == pos {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result: Vec2I = {iter.codepoint_index, line.number};
|
||||
return result;
|
||||
}
|
||||
|
||||
CalculatePosFromVisualPos :: proc(buffer: *Buffer, vpos: Vec2I): int {
|
||||
line := GetLine(buffer, vpos.y);
|
||||
iter := Iterate(buffer, line.range.min, line.range.max);
|
||||
for IsValid(iter); Advance(&iter) {
|
||||
if iter.codepoint_index == vpos.x {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return iter.pos;
|
||||
}
|
||||
|
||||
CalculateWorldPosUnscrolled :: proc(vpos: Vec2I): Vector2 {
|
||||
result: Vector2 = {Monosize.x * :f32(vpos.x), Monosize.y * :f32(vpos.y)};
|
||||
return result;
|
||||
}
|
||||
|
||||
CalculateWorldPos :: proc(vpos: Vec2I): Vector2 {
|
||||
result: Vector2 = {Monosize.x * :f32(vpos.x) - Scroll.x, Monosize.y * :f32(vpos.y) - Scroll.y};
|
||||
return result;
|
||||
}
|
||||
|
||||
MoveDown :: proc(buffer: *Buffer, pos: int): int {
|
||||
vpos := CalculateVisualPos(buffer, pos);
|
||||
if !IsLineValid(buffer, vpos.y + 1) return pos;
|
||||
|
||||
result := CalculatePosFromVisualPos(buffer, {vpos.x, vpos.y + 1});
|
||||
return result;
|
||||
}
|
||||
|
||||
MoveUp :: proc(buffer: *Buffer, pos: int): int {
|
||||
vpos := CalculateVisualPos(buffer, pos);
|
||||
if !IsLineValid(buffer, vpos.y - 1) return pos;
|
||||
|
||||
result := CalculatePosFromVisualPos(buffer, {vpos.x, vpos.y - 1});
|
||||
return result;
|
||||
}
|
||||
|
||||
DrawRect :: proc(rec: Rect2P, color: Color) {
|
||||
rectangle := Rect2PToRectangle(rec);
|
||||
DrawRectangleRec(rectangle, color);
|
||||
}
|
||||
Reference in New Issue
Block a user