From 0dd62895092ca88c3f70ed9333b5ff643e827438 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Fri, 5 Jul 2024 15:24:58 +0200 Subject: [PATCH] Rewrite to IMGUI --- build_file.cpp | 125 ++- src/basic/win32.cpp | 1 - src/text_editor/buffer.cpp | 629 ------------- src/text_editor/clipboard.cpp | 62 -- src/text_editor/event_recording.cpp | 133 --- src/text_editor/layout.cpp | 512 ---------- src/text_editor/main.cpp | 809 ---------------- .../multi_cursor_text_editor_behaviors.txt | 10 - src/text_editor/profiler.cpp | 50 - src/text_editor/read_pdf.py | 37 - src/text_editor/rect2.cpp | 130 --- src/text_editor/spall.h | 443 --------- src/text_editor/ui_ideas.txt | 23 - src/text_editor/utils.cpp | 12 - src/transcript_browser/_main.cpp | 384 ++++++++ src/transcript_browser/main.cpp | 886 ++++++++++-------- 16 files changed, 969 insertions(+), 3277 deletions(-) delete mode 100644 src/text_editor/buffer.cpp delete mode 100644 src/text_editor/clipboard.cpp delete mode 100644 src/text_editor/event_recording.cpp delete mode 100644 src/text_editor/layout.cpp delete mode 100644 src/text_editor/main.cpp delete mode 100644 src/text_editor/multi_cursor_text_editor_behaviors.txt delete mode 100644 src/text_editor/profiler.cpp delete mode 100644 src/text_editor/read_pdf.py delete mode 100644 src/text_editor/rect2.cpp delete mode 100644 src/text_editor/spall.h delete mode 100644 src/text_editor/ui_ideas.txt delete mode 100644 src/text_editor/utils.cpp create mode 100644 src/transcript_browser/_main.cpp diff --git a/build_file.cpp b/build_file.cpp index bb16fd4..87bb53c 100644 --- a/build_file.cpp +++ b/build_file.cpp @@ -3,10 +3,11 @@ struct Library { Array sources; Array objects; - S8_String include_path; + Array include_paths; + Array defines; }; -Library BuildZLib() { +Library PrepareZLib() { Library l = {}; l.sources.add("../src/external/zlib-1.3.1/adler32.c"); @@ -25,7 +26,7 @@ Library BuildZLib() { l.sources.add("../src/external/zlib-1.3.1/uncompr.c"); l.sources.add("../src/external/zlib-1.3.1/zutil.c"); - l.include_path = "../src/external/zlib-1.3.1/"; + l.include_paths.add("../src/external/zlib-1.3.1/"); l.objects.add("adler32.obj"); l.objects.add("compress.obj"); l.objects.add("crc32.obj"); @@ -45,7 +46,7 @@ Library BuildZLib() { if (!OS_FileExists(l.objects[0])) { Array cmd = {}; cmd.add("cl.exe -c -nologo -Zi -MP -FC "); - cmd.add(S8_Format(Perm, "-I %.*s ", S8_Expand(l.include_path))); + For(l.include_paths) cmd.add(S8_Format(Perm, "-I %.*s ", S8_Expand(it))); cmd += l.sources; Run(cmd); } @@ -53,6 +54,63 @@ Library BuildZLib() { return l; } +Library PrepareIMGUI(S8_String sdl_inc_path) { + Library l = {}; + l.include_paths.add("../src/external/imgui/"); + l.include_paths.add("../src/external/imgui/backends"); + l.sources.add("../src/external/imgui/backends/imgui_impl_sdl2.cpp"); + l.sources.add("../src/external/imgui/backends/imgui_impl_opengl3.cpp"); + l.sources.add("../src/external/imgui/imgui.cpp"); + l.sources.add("../src/external/imgui/imgui_demo.cpp"); + l.sources.add("../src/external/imgui/imgui_draw.cpp"); + l.sources.add("../src/external/imgui/imgui_tables.cpp"); + l.sources.add("../src/external/imgui/imgui_widgets.cpp"); + + l.objects.add("imgui_impl_sdl2.obj"); + l.objects.add("imgui_impl_opengl3.obj"); + l.objects.add("imgui.obj"); + l.objects.add("imgui_demo.obj"); + l.objects.add("imgui_draw.obj"); + l.objects.add("imgui_tables.obj"); + l.objects.add("imgui_widgets.obj"); + + if (!OS_FileExists(l.objects[0])) { + Array cmd = {}; + cmd.add("cl.exe -c -nologo -Zi -MP -FC "); + cmd.add(Fmt("-I %.*s", S8_Expand(sdl_inc_path))); + For(l.include_paths) cmd.add(S8_Format(Perm, "-I %.*s ", S8_Expand(it))); + cmd += l.sources; + Run(cmd); + } + + return l; +} + +Library PrepareSDL() { + Library l = {}; + l.include_paths.add("../src/external/SDL2/include"); + l.objects.add("../src/external/SDL2/lib/x64/SDL2main.lib"); + l.objects.add("../src/external/SDL2/lib/x64/SDL2.lib"); + OS_Result os_result = OS_CopyFile("../src/external/SDL2/lib/x64/SDL2.dll", "SDL2.dll", true); + if (os_result != OS_SUCCESS) IO_Printf("failed to copy SDL2.dll\n"); + return l; +} + +Library PrepareGlad() { + Library l = {}; + l.sources.add("../src/external/glad/glad.c"); + l.include_paths.add("../src/external/glad"); + l.objects.add("glad.obj"); + if (!OS_FileExists(l.objects[0])) { + Array cmd = {}; + cmd.add("cl.exe -c -nologo -Zi -MP -FC "); + For(l.include_paths) cmd.add(S8_Format(Perm, "-I %.*s ", S8_Expand(it))); + cmd += l.sources; + Run(cmd); + } + return l; +} + void AddCommonFlags(Array *cmd, bool debug = true) { cmd->add("/MP /Zi /FC /nologo"); cmd->add("/WX /W3 /wd4200 /diagnostics:column"); @@ -75,49 +133,34 @@ int main() { int result = 0; - if (0) { - Array cmd = {}; - cmd.add("cl.exe"); - cmd.add("-Fe:transcript_browser.exe"); - cmd.add("-Fd:transcript_browser.pdb"); - cmd.add("-std:c++20"); // semaphore - AddCommonFlags(&cmd); + Array libs = {}; + libs.add(PrepareSDL()); + libs.add(PrepareGlad()); + libs.add(PrepareIMGUI(libs[0].include_paths[0])); - cmd.add("-I ../src/external/raylib/include"); - cmd.add("../src/transcript_browser/main.cpp"); - cmd.add("../src/basic/win32.cpp"); + Array cmd = {}; + cmd.add("cl.exe"); + cmd.add("-Fe:transcript_browser.exe"); + cmd.add("-Fd:transcript_browser.pdb"); + cmd.add("-std:c++20"); // semaphore + AddCommonFlags(&cmd); + For2(lib, libs) For(lib.defines) cmd.add(it); - cmd.add("/link"); - cmd.add("../src/external/raylib/raylib.lib"); - cmd.add("opengl32.lib kernel32.lib user32.lib gdi32.lib winmm.lib msvcrt.lib shell32.lib"); - cmd.add("/NODEFAULTLIB:LIBCMT"); - cmd.add("/incremental:no"); + cmd.add("../src/transcript_browser/main.cpp"); + cmd.add("../src/basic/win32.cpp"); - OS_DeleteFile("transcript_browser.pdb"); - result += Run(cmd); - } + For2(lib, libs) For(lib.include_paths) cmd.add(Fmt("-I %.*s", S8_Expand(it))); - if (1) { - Array cmd = {}; - cmd.add("cl.exe"); - cmd.add("-Fe:text_editor.exe"); - cmd.add("-Fd:text_editor.pdb"); - cmd.add("-std:c++20"); // semaphore - AddCommonFlags(&cmd); + cmd.add("/link"); + cmd.add("/incremental:no"); + cmd.add("/SUBSYSTEM:WINDOWS"); + cmd.add("opengl32.lib"); + For(libs) For2(o, it.objects) cmd.add(o); - cmd.add("-I ../src/external/raylib/include"); - cmd.add("../src/text_editor/main.cpp"); - cmd.add("../src/basic/win32.cpp"); + OS_DeleteFile("transcript_browser.pdb"); + // For(cmd) IO_Printf("%.*s\n", S8_Expand(it)); - cmd.add("/link"); - cmd.add("../src/external/raylib/raylib.lib"); - cmd.add("opengl32.lib kernel32.lib user32.lib gdi32.lib winmm.lib shell32.lib"); - cmd.add("/NODEFAULTLIB:LIBCMT"); - cmd.add("/incremental:no"); - - OS_DeleteFile("text_editor.pdb"); - result += Run(cmd); - } + result += Run(cmd); if (result != 0) { OS_DeleteFile("pdf_browser.cache"); diff --git a/src/basic/win32.cpp b/src/basic/win32.cpp index 1ae75c4..40fdee2 100644 --- a/src/basic/win32.cpp +++ b/src/basic/win32.cpp @@ -200,7 +200,6 @@ FileIter IterateFiles(Allocator alo, String path) { return it; } - Assert(it.w32->data.cFileName[0] == '.' && it.w32->data.cFileName[1] == 0); Advance(&it); return it; } diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp deleted file mode 100644 index ba7cbcd..0000000 --- a/src/text_editor/buffer.cpp +++ /dev/null @@ -1,629 +0,0 @@ - -/* -Ranges are from 0 to n+1 (one past last index). - <0,4> = 0,1,2,3 - -These seem the best, I tried other formations but these are -much better then the rest. - -First of all you can represent the cursor at the end of the buffer -by doing: . This action in itself doesn't -select anything, the formation doesn't force you to index the -buffer and so on, it's reduced to a pure position. In the end -you can nicely represent cursors in select mode and non-select mode. - -This property of being able to represent pure positions makes -it possible to clamp the values to the same range <0, buffer_len>, -and if the things are past the range we end up with a pure value. -Very nice behaviour, The program won't delete anything. - -*/ -struct Range { - int64_t min; - int64_t max; // one past last index - // <0,4> = 0,1,2,3 -}; - -// @end_of_buffer - need to make sure that we can actually lookup the end of buffer line. -// This end of buffer is also incorporated into the layout. -struct Line { - int64_t number; - Range range; - int64_t max_without_new_line; -}; - -struct LineAndColumn { - Line line; - int64_t column; -}; - -struct Edit { - Range range; - String string; -}; - -struct Cursor { - union { - Range range; - int64_t pos[2]; - }; - int64_t ifront; -}; - -// - Buffer should be initialized before use! -struct Buffer { - Allocator allocator; - char *data[2]; - int64_t cap; - int64_t len; - int bi; // current buffer index - Array lines; -}; - -int64_t GetRangeSize(Range range) { - int64_t result = range.max - range.min; - return result; -} - -Range GetRange(const Buffer &buffer) { - Range result = {0, buffer.len}; - return result; -} - -int64_t Clamp(const Buffer &buffer, int64_t pos) { - int64_t result = Clamp(pos, (int64_t)0, buffer.len); - return result; -} - -Range Clamp(const Buffer &buffer, Range range) { - Range result = {}; - result.min = Clamp(buffer, range.min); - result.max = Clamp(buffer, range.max); - return result; -} - -Range GetEnd(const Buffer &buffer) { - Range range = {buffer.len, buffer.len}; - return range; -} - -Range MakeRange(int64_t a, int64_t b) { - Range result = {}; - result.min = Min(a, b); - result.max = Max(a, b); - return result; -} - -Range MakeRange(int64_t a) { - Range result = {a, a}; - return result; -} - -int64_t GetFront(Cursor cursor) { - int64_t result = cursor.pos[cursor.ifront]; - return result; -} - -int64_t GetBack(Cursor cursor) { - int64_t index = (cursor.ifront + 1) % 2; - int64_t result = cursor.pos[index]; - return result; -} - -Cursor MakeCursor(int64_t front, int64_t back) { - Cursor result = {}; - if (front >= back) { - result.range.min = back; - result.range.max = front; - result.ifront = 1; - } else { - result.range.min = front; - result.range.max = back; - result.ifront = 0; - } - return result; -} - -Cursor ChangeBack(Cursor cursor, int64_t back) { - int64_t front = GetFront(cursor); - Cursor result = MakeCursor(front, back); - return result; -} - -Cursor ChangeFront(Cursor cursor, int64_t front) { - int64_t back = GetBack(cursor); - Cursor result = MakeCursor(front, back); - return result; -} - -void AddEdit(Array *edits, Range range, String string) { - edits->add({range, string}); -} - -bool InBounds(const Buffer &buffer, int64_t pos) { - bool result = pos >= 0 && pos < buffer.len; - return result; -} - -char GetChar(const Buffer &buffer, int64_t pos) { - if (!InBounds(buffer, pos)) return 0; - return buffer.data[buffer.bi][pos]; -} - -char *GetCharP(const Buffer &buffer, int64_t pos) { - if (!InBounds(buffer, pos)) return 0; - return buffer.data[buffer.bi] + pos; -} - -String GetString(const Buffer &buffer, Range range = {0, INT64_MAX}) { - range = Clamp(buffer, range); - String result = {GetCharP(buffer, range.min), GetRangeSize(range)}; - return result; -} - -bool AreEqual(Range a, Range b) { - bool result = a.min == b.min && a.max == b.max; - return result; -} - -bool AreEqual(Cursor a, Cursor b) { - bool result = AreEqual(a.range, b.range) && a.ifront == b.ifront; - return result; -} - -bool InRange(int64_t a, Range b) { - bool result = a >= b.min && a < b.max; - return result; -} - -void MergeSort(int64_t Count, Edit *First, Edit *Temp) { - // SortKey = range.min - if (Count == 1) { - // NOTE(casey): No work to do. - } else if (Count == 2) { - Edit *EntryA = First; - Edit *EntryB = First + 1; - if (EntryA->range.min > EntryB->range.min) { - Swap(EntryA, EntryB); - } - } else { - int64_t Half0 = Count / 2; - int64_t Half1 = Count - Half0; - - Assert(Half0 >= 1); - Assert(Half1 >= 1); - - Edit *InHalf0 = First; - Edit *InHalf1 = First + Half0; - Edit *End = First + Count; - - MergeSort(Half0, InHalf0, Temp); - MergeSort(Half1, InHalf1, Temp); - - Edit *ReadHalf0 = InHalf0; - Edit *ReadHalf1 = InHalf1; - - Edit *Out = Temp; - for (int64_t Index = 0; - Index < Count; - ++Index) { - if (ReadHalf0 == InHalf1) { - *Out++ = *ReadHalf1++; - } else if (ReadHalf1 == End) { - *Out++ = *ReadHalf0++; - } else if (ReadHalf0->range.min < ReadHalf1->range.min) { - *Out++ = *ReadHalf0++; - } else { - *Out++ = *ReadHalf1++; - } - } - Assert(Out == (Temp + Count)); - Assert(ReadHalf0 == InHalf1); - Assert(ReadHalf1 == End); - - // TODO(casey): Not really necessary if we ping-pong - for (int64_t Index = 0; - Index < Count; - ++Index) { - First[Index] = Temp[Index]; - } - } -} - -void _ApplyEdits(Buffer *buffer, Array edits) { - Scratch scratch((Arena *)buffer->allocator.object); - Assert(buffer->data[0]); - Assert(buffer->allocator.proc); - Assert(buffer->lines.len); - - // Figure out how much we insert and how much we delete so - // we can resize buffers properly if necessary - Assert(edits.len); - int64_t size_to_delete = 0; - int64_t size_to_insert = 0; - For(edits) { - Assert(it.range.min >= 0); - Assert(it.range.max >= it.range.min); - Assert(it.range.max <= buffer->len); - size_to_delete += GetRangeSize(it.range); - size_to_insert += it.string.len; - } - -#if DEBUG_BUILD - // Make sure edit ranges don't overlap - ForItem(it1, edits) { - ForItem(it2, edits) { - if (&it1 == &it2) continue; - - bool a2_inside = it2.range.min >= it1.range.min && it2.range.min < it1.range.max; - Assert(!a2_inside); - - bool b2_inside = it2.range.max > it1.range.min && it2.range.max <= it1.range.max; - Assert(!b2_inside); - } - } -#endif - - // We need to sort from lowest to higest based on range.min - { - Array edits_copy = edits.copy(scratch); - MergeSort(edits.len, edits_copy.data, edits.data); - edits = edits_copy; - } - - // Try resizing the buffers - int64_t len_offset = size_to_insert - size_to_delete; - int64_t allocated_size_required = Max((int64_t)0, len_offset); - if (buffer->len + allocated_size_required > buffer->cap) { - int64_t new_cap = AlignUp(buffer->cap + allocated_size_required, 4096); - if (buffer->allocator.proc == NULL) buffer->allocator = GetSystemAllocator(); - - for (int i = 0; i < 2; i += 1) { - char *data = AllocArray(buffer->allocator, char, new_cap); - Assert(data); - memcpy(data, buffer->data[i], buffer->len); - Dealloc(buffer->allocator, &buffer->data[i]); - buffer->data[i] = data; - } - buffer->cap = new_cap; - } - - // Figure out what we need to write to the second buffer - int srci = buffer->bi; - int dsti = (buffer->bi + 1) % 2; - - Array writes = {scratch}; - int64_t prev_source = 0; - int64_t prev_dest = 0; - - For(edits) { - TraceLog(LOG_DEBUG, "edit dsti: %d, srci: %d, range: %lld to %lld, string: '%.*s'", dsti, srci, (long long)it.range.min, (long long)it.range.max, Min(5, (int)it.string.len), it.string.data); - Range source_range = {prev_source, it.range.min}; - if (GetRangeSize(source_range) != 0) { - String source_string = {}; - source_string.data = buffer->data[srci] + source_range.min; - source_string.len = GetRangeSize(source_range); - Range dest_range = {prev_dest, prev_dest + source_string.len}; - writes.add({dest_range, source_string}); - - prev_dest = dest_range.max; - } - - Range dest_range = {prev_dest, prev_dest + it.string.len}; - writes.add({dest_range, it.string}); - prev_dest = dest_range.max; - prev_source = it.range.max; - } - - // Add remaining range - Range source_range = {prev_source, buffer->len}; - if (GetRangeSize(source_range)) { - String source_string = {}; - source_string.data = buffer->data[srci] + source_range.min; - source_string.len = GetRangeSize(source_range); - Range dest_range = {prev_dest, prev_dest + source_string.len}; - writes.add({dest_range, source_string}); - } - - // Make sure there are no gaps between ranges -#if DEBUG_BUILD - for (int64_t i = 0; i < writes.len - 1; i += 1) { - Assert(writes[i].range.max == writes[i + 1].range.min); - } -#endif - - // Write to the second buffer - int64_t new_buffer_len = 0; - For(writes) { - Assert(it.range.min >= 0); - Assert(it.range.max >= 0); - TraceLog(LOG_DEBUG, "write dsti: %d, srci: %d, range: %lld to %lld, string: '%.*s'", dsti, srci, (long long)it.range.min, (long long)it.range.max, Min(5, (int)it.string.len), it.string.data); - memcpy(buffer->data[dsti] + new_buffer_len, it.string.data, it.string.len); - new_buffer_len += it.string.len; - } - buffer->bi = dsti; - Assert(new_buffer_len == buffer->len + len_offset); - buffer->len = new_buffer_len; - - // Update lines - // Make sure we always have one line, even if the buffer is empty, - // this way we can nicely clamp things without worrying of getting - // a negative when doing (len - 1). - { - String delimiter = "\n"; - String string = {buffer->data[dsti], buffer->len}; - buffer->lines.allocator = buffer->allocator; - buffer->lines.clear(); - - int64_t index = 0; - int64_t base_index = 0; - while (Seek(string, delimiter, &index)) { - buffer->lines.add({base_index, base_index + index + delimiter.len}); - base_index += index + delimiter.len; - string = string.skip(index + delimiter.len); - } - buffer->lines.add({base_index, base_index + string.len}); - } - - Assert(buffer->data[0]); - Assert(buffer->allocator.proc); - Assert(buffer->lines.len); -} - -void InitBuffer(Allocator allocator, Buffer *buffer, int64_t _size = 4096) { - int64_t size = AlignUp(_size, 4096); - buffer->allocator = allocator; - for (int i = 0; i < 2; i += 1) { - buffer->data[i] = AllocArray(allocator, char, size); - Assert(buffer->data[i]); - } - buffer->cap = size; - buffer->lines.allocator = allocator; - buffer->lines.add({}); -} - -String CopyNullTerminated(Allocator allocator, Buffer &buffer, Range range) { - String buffer_string = GetString(buffer, range); - String result = Copy(allocator, buffer_string); - return result; -} - -int64_t AdjustUTF8Pos(String string, int64_t pos, int64_t direction) { - for (; pos >= 0 && pos < string.len;) { - if (IsUTF8ContinuationByte(string.data[pos])) { - pos += direction; - } else { - break; - } - } - return pos; -} - -int64_t AdjustUTF8Pos(const Buffer &buffer, int64_t pos, int64_t direction = 1, bool clamp = true) { - String string = GetString(buffer); - pos = AdjustUTF8Pos(string, pos, direction); - if (clamp) pos = Clamp(buffer, pos); - return pos; -} - -uint32_t GetUTF32(Buffer &buffer, int64_t pos, int64_t *codepoint_size) { - if (!InBounds(buffer, pos)) { - return 0; - } - - char *p = GetCharP(buffer, pos); - int64_t max = buffer.len - pos; - UTF32Result utf32 = UTF8ToUTF32(p, (int)max); - Assert(utf32.error == 0); - - if (utf32.error != 0) return 0; - if (codepoint_size) codepoint_size[0] = utf32.advance; - return utf32.out_str; -} - -constexpr int16_t ITERATE_FORWARD = 1; -constexpr int16_t ITERATE_BACKWARD = -1; -struct BufferIter { - Buffer *buffer; - int64_t pos; - int64_t end; - int64_t direction; - - int64_t utf8_codepoint_size; - int64_t codepoint_index; - uint32_t item; -}; - -bool IsValid(const BufferIter &iter) { - Assert(iter.direction == ITERATE_FORWARD || iter.direction == ITERATE_BACKWARD); - - bool result = false; - if (iter.direction == ITERATE_BACKWARD) { - result = iter.pos >= iter.end; - } else { - result = iter.pos < iter.end; - } - - if (result) { - Assert(!IsUTF8ContinuationByte(GetChar(*iter.buffer, iter.pos))); - Assert(InBounds(*iter.buffer, iter.pos)); - } - return result; -} - -void Advance(BufferIter *iter) { - Assert(iter->direction == ITERATE_FORWARD || iter->direction == ITERATE_BACKWARD); - - iter->codepoint_index += 1; - if (iter->direction == ITERATE_FORWARD) { - iter->pos += iter->utf8_codepoint_size; - } else { - iter->pos = AdjustUTF8Pos(*iter->buffer, iter->pos - 1, ITERATE_BACKWARD, false); - } - - if (!IsValid(*iter)) return; - iter->item = GetUTF32(*iter->buffer, iter->pos, &iter->utf8_codepoint_size); -} - -BufferIter Iterate(Buffer &buffer, Range range, int64_t direction = ITERATE_FORWARD) { - Assert(direction == ITERATE_FORWARD || direction == ITERATE_BACKWARD); - Assert(!IsUTF8ContinuationByte(GetChar(buffer, range.min))); - Assert(range.max >= range.min); - range.min = Clamp(buffer, range.min); - range.max = Clamp(buffer, range.max); - - BufferIter result = {&buffer, range.min, range.max, direction}; - result.codepoint_index = -1; - if (direction == ITERATE_BACKWARD) { - result.end = range.min; - result.pos = range.max; - } - - Advance(&result); - return result; -} - -Line GetLineByIndex(Buffer &buffer, int64_t line) { - Assert(buffer.lines.len); - line = Clamp(line, (int64_t)0, buffer.lines.len - 1); - Range range = buffer.lines[line]; - Line result = {line, range, range.max}; - if (range.max > range.min && GetChar(buffer, range.max - 1) == '\n') result.max_without_new_line -= 1; - return result; -} - -Line FindLine(Buffer &buffer, int64_t pos) { - Line result = {}; - - For(buffer.lines) { - if (pos >= it.min && pos < it.max) { - result = {buffer.lines.get_index(it), it, it.max}; - if (it.max > it.min && GetChar(buffer, it.max - 1) == '\n') result.max_without_new_line -= 1; - return result; - } - } - - if (pos == buffer.len) { // @end of buffer - auto &it = buffer.lines[buffer.lines.len - 1]; - Assert(it.max == buffer.len); - result = {buffer.lines.get_index(it), it, it.max}; - if (it.max > it.min && GetChar(buffer, it.max - 1) == '\n') result.max_without_new_line -= 1; - return result; - } - - return result; -} - -LineAndColumn FindLineAndColumn(Buffer &buffer, int64_t pos) { - LineAndColumn result = {}; - result.column = 0; - result.line = FindLine(buffer, pos); - - // @end_of_buffer - if (pos == result.line.range.min && pos == result.line.range.max) { - return result; - } - - for (BufferIter iter = Iterate(buffer, result.line.range); IsValid(iter); Advance(&iter)) { - // @todo: make sure we handle when there is invalid unicode in the stream - if (iter.pos == pos) { - result.column = iter.codepoint_index; - break; - } - } - return result; -} - -int64_t FindPos(Buffer &buffer, int64_t line_number, int64_t column) { - Line line = GetLineByIndex(buffer, line_number); - int64_t result = line.max_without_new_line; - for (BufferIter iter = Iterate(buffer, line.range); IsValid(iter); Advance(&iter)) { - if (iter.codepoint_index == column) { - result = iter.pos; - break; - } - } - return result; -} - -int64_t Seek(Buffer &buffer, int64_t pos, int64_t direction = ITERATE_FORWARD) { - Assert(direction == ITERATE_FORWARD || direction == ITERATE_BACKWARD); - // ( - inclusive - // < - non-inclusive - - int64_t min = 0; - int64_t max = 0; - char c = 0; - if (direction == ITERATE_FORWARD) { - // (pos + 1, end> - min = AdjustUTF8Pos(buffer, pos + 1, ITERATE_FORWARD); - max = buffer.len; - c = GetChar(buffer, min); - } else { - // (0, pos> - max = pos; - min = 0; - - int64_t next = AdjustUTF8Pos(buffer, max - 1, ITERATE_BACKWARD); - c = GetChar(buffer, next); - } - - bool standing_on_whitespace = IsWhitespace(c); - bool seek_whitespace = standing_on_whitespace == false; - bool seek_word = standing_on_whitespace; - - int64_t result = direction == ITERATE_BACKWARD ? 0 : buffer.len; - BufferIter iter = Iterate(buffer, {min, max}, direction); - int64_t prev_pos = iter.pos; - for (; IsValid(iter); Advance(&iter)) { - bool char_is_whitespace = iter.item < 255 && IsWhitespace(iter.item); - - if (seek_word && char_is_whitespace == false) { - result = prev_pos; - break; - } - if (seek_whitespace && char_is_whitespace) { - if (direction == ITERATE_FORWARD) { - result = iter.pos; - } else { - result = prev_pos; - } - break; - } - prev_pos = iter.pos; - } - return result; -} - -int64_t MoveRight(Buffer &buffer, int64_t pos) { - pos = pos + 1; - pos = AdjustUTF8Pos(buffer, pos); - Assert(pos >= 0 && pos <= buffer.len); - return pos; -} - -int64_t MoveLeft(Buffer &buffer, int64_t pos) { - pos = pos - 1; - pos = AdjustUTF8Pos(buffer, pos, -1); - Assert(pos >= 0 && pos <= buffer.len); - return pos; -} - -int64_t MoveDown(Buffer &buffer, int64_t pos, int64_t count = 1) { - LineAndColumn info = FindLineAndColumn(buffer, pos); - int64_t new_pos = FindPos(buffer, info.line.number + count, info.column); - return new_pos; -} - -int64_t MoveUp(Buffer &buffer, int64_t pos, int64_t count = 1) { - LineAndColumn info = FindLineAndColumn(buffer, pos); - int64_t new_pos = FindPos(buffer, info.line.number - count, info.column); - return new_pos; -} - -Range EncloseWord(Buffer &buffer, int64_t pos) { - Range result = {}; - result.min = Seek(buffer, pos, ITERATE_BACKWARD); - result.max = Seek(buffer, pos, ITERATE_FORWARD); - return result; -} \ No newline at end of file diff --git a/src/text_editor/clipboard.cpp b/src/text_editor/clipboard.cpp deleted file mode 100644 index 0e4547c..0000000 --- a/src/text_editor/clipboard.cpp +++ /dev/null @@ -1,62 +0,0 @@ -String SavedClipboardString; -Array SavedClipboardCursors; - -void CopyToClipboard(Window *window) { - Allocator sys_allocator = GetSystemAllocator(); - - SavedClipboardCursors.allocator = sys_allocator; - if (SavedClipboardCursors.data) { - For(SavedClipboardCursors) Dealloc(sys_allocator, &it.data); - SavedClipboardCursors.clear(); - } - - if (SavedClipboardString.data) { - Dealloc(sys_allocator, &SavedClipboardString.data); - SavedClipboardString = {}; - } - - // First, if there is no selection - select the entire line - For(window->cursors) { - if (GetRangeSize(it.range) == 0) { - Line line = FindLine(window->buffer, it.range.min); - it.range = line.range; - } - } - - For(window->cursors) { - String string = GetString(window->buffer, it.range); - String copy = Copy(sys_allocator, string); - SavedClipboardCursors.add(copy); - } - - // @todo: maybe only add new line if there is no new line at the end, experiment with it - SavedClipboardString = Merge(sys_allocator, SavedClipboardCursors, "\n"); - SetClipboardText(SavedClipboardString.data); -} - -void PasteFromClipboard(Window *window) { - Scratch scratch; - const char *text = GetClipboardText(); - String string = text; - - // Regular paste - if (string != SavedClipboardString || SavedClipboardCursors.len != window->cursors.len) { - BeforeEdit(window); - Array edits = {scratch}; - For(window->cursors) AddEdit(&edits, it.range, string); - ApplyEdits(window, edits); - AfterEdit(window, edits); - return; - } - - // Multicursor paste - BeforeEdit(window); - Array edits = {scratch}; - for (int64_t i = 0; i < window->cursors.len; i += 1) { - String string = SavedClipboardCursors[i]; - Cursor &it = window->cursors[i]; - AddEdit(&edits, it.range, string); - } - ApplyEdits(window, edits); - AfterEdit(window, edits); -} \ No newline at end of file diff --git a/src/text_editor/event_recording.cpp b/src/text_editor/event_recording.cpp deleted file mode 100644 index 878f7bd..0000000 --- a/src/text_editor/event_recording.cpp +++ /dev/null @@ -1,133 +0,0 @@ -bool EventRecording = false; -bool EventPlaying = false; -AutomationEventList EventList; -Array EventsToPlay; -unsigned int frameCounter; -unsigned int playFrameCounter; -unsigned int currentPlayFrame; - -void InitEventRecording() { - EventList = LoadAutomationEventList(0); - SetAutomationEventList(&EventList); -} - -void StartNextEvent() { - Assert(EventPlaying); - if (EventsToPlay.len > 0) { - AutomationEventList event = EventsToPlay.pop(); - EventList = event; - SetAutomationEventList(&EventList); - } else { - EventPlaying = false; - SetTargetFPS(60); - } - currentPlayFrame = 0; - playFrameCounter = -1; -} - -void UpdateEventRecording() { - if (IsKeyPressed(KEY_F5)) { - if (EventRecording) { - Scratch scratch; - EventRecording = false; - - StopAutomationEventRecording(); - int random_value = GetRandomValue(0, INT_MAX); - String path = Format(scratch, "%d.rae", random_value); - - if (!IsKeyDown(KEY_LEFT_CONTROL)) { - ExportAutomationEventList(EventList, path.data); - } - } else { - SetAutomationEventBaseFrame(0); - StartAutomationEventRecording(); - EventRecording = true; - } - } - - if (IsKeyPressed(KEY_F8)) { - if (!EventPlaying) { - FilePathList list = LoadDirectoryFiles("."); - for (unsigned int i = 0; i < list.count; i += 1) { - String path = list.paths[i]; - if (EndsWith(path, "rae")) { - AutomationEventList rae = LoadAutomationEventList(path.data); - EventsToPlay.add(rae); - } - } - UnloadDirectoryFiles(list); - - EventPlaying = true; - SetTargetFPS(3000); - StartNextEvent(); - } - } - - if (EventPlaying) { - // NOTE: Multiple events could be executed in a single frame - while (playFrameCounter == EventList.events[currentPlayFrame].frame) { - TraceLog(LOG_INFO, "PLAYING: PlayFrameCount: %i | currentPlayFrame: %i | Event Frame: %i, param: %i", - playFrameCounter, currentPlayFrame, EventList.events[currentPlayFrame].frame, EventList.events[currentPlayFrame].params[0]); - - PlayAutomationEvent(EventList.events[currentPlayFrame]); - currentPlayFrame++; - - if (currentPlayFrame == EventList.count) { - StartNextEvent(); - break; - } - } - - playFrameCounter++; - } -} - -void EventRecording_SimulateGetCharPressed(Window *focused_window) { - if (!EventPlaying) return; - Scratch scratch; - for (int i = KEY_A; i <= KEY_Z; i += 1) { - bool press = IsKeyPressed(i) || IsKeyPressedRepeat(i); - if (press) { - String string = "?"; - int offset = 32; - if (IsKeyDown(KEY_LEFT_SHIFT)) offset = 0; - UTF8Result utf8 = UTF32ToUTF8((uint32_t)i + offset); - if (utf8.error == 0) { - string = {(char *)utf8.out_str, (int64_t)utf8.len}; - } - - BeforeEdit(focused_window); - Array edits = {scratch}; - For(focused_window->cursors) { - AddEdit(&edits, it.range, string); - } - ApplyEdits(focused_window, edits); - AfterEdit(focused_window, edits); - } - } -} - -void EventRecording_Draw() { - if (EventRecording) Assert(EventPlaying == false); - if (EventPlaying) Assert(EventRecording == false); - Rect2 screen = GetScreenRect(); - Color color = {}; - - Rect2 l = CutLeft(&screen, 4); - Rect2 r = CutRight(&screen, 4); - Rect2 t = CutTop(&screen, 4); - Rect2 b = CutBottom(&screen, 4); - - if (EventPlaying) { - color = BLUE; - } - - if (EventRecording) { - color = RED; - } - - DrawRectangleRec(ToRectangle(l), color); - DrawRectangleRec(ToRectangle(r), color); - DrawRectangleRec(ToRectangle(t), color); - DrawRectangleRec(ToRectangle(b), color); -} \ No newline at end of file diff --git a/src/text_editor/layout.cpp b/src/text_editor/layout.cpp deleted file mode 100644 index b9c7100..0000000 --- a/src/text_editor/layout.cpp +++ /dev/null @@ -1,512 +0,0 @@ -struct LayoutColumn { - Rect2 rect; - int64_t pos; - int codepoint; -}; - -struct LayoutRow { - Rect2 rect; - Array columns; -}; - -struct Layout { - Array rows; - Vec2 buffer_world_pixel_size; - LayoutColumn *max_column; - - Range visible_line_range; -}; - -struct HistoryEntry { - Array cursors; - Array edits; -}; - -struct History { - Array undo_stack; - Array redo_stack; - int debug_edit_phase; -}; - -struct ColoredString { - Range range; - Color text_color; - Color highlight_background_color; - - uint8_t use_text_color; - uint8_t use_highlight_background_color; - uint8_t use_underline; -}; - -struct Window { - Font font; - float font_size; - float font_spacing; - - Rect2 start_rect; - Rect2 rect; - - Range selection_anchor_point; - bool mouse_selecting; - bool mouse_selecting_hori_bar; - bool mouse_selecting_vert_bar; - Vec2 scroll; // window_world_to_window_units - Cursor main_cursor_begin_frame; - Array cursors; - Buffer buffer; - History history; - - bool not_regen_layout; - Array colored_strings; - Arena layout_arena; - Layout layout; -}; - -template -struct Tuple { - T1 a; - T2 b; -}; - -void DrawString(Font font, String text, Vector2 position, float fontSize, float spacing, Color tint) { - ProfileFunction(); - if (font.texture.id == 0) font = GetFontDefault(); // Security check in case of not valid font - - float textOffsetX = 0.0f; // Offset X to next character to draw - - float scaleFactor = fontSize / font.baseSize; // Character quad scaling factor - - for (int i = 0; i < text.len;) { - // Get next codepoint from byte string and glyph index in font - int codepointByteCount = 0; - int codepoint = GetCodepointNext(&text.data[i], &codepointByteCount); - int index = GetGlyphIndex(font, codepoint); - - if ((codepoint != ' ') && (codepoint != '\t')) { - DrawTextCodepoint(font, codepoint, {position.x + textOffsetX, position.y}, fontSize, tint); - } - - if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width * scaleFactor + spacing); - else textOffsetX += ((float)font.glyphs[index].advanceX * scaleFactor + spacing); - - i += codepointByteCount; // Move text bytes counter to next codepoint - } -} - -Vector2 MeasureString(Font font, String text, float fontSize, float spacing) { - ProfileFunction(); - Vector2 textSize = {0}; - if ((font.texture.id == 0) || (text.data == NULL)) return textSize; // Security check - - int size = (int)text.len; // Get size in bytes of text - int tempByteCounter = 0; // Used to count longer text line num chars - int byteCounter = 0; - - float textWidth = 0.0f; - float tempTextWidth = 0.0f; // Used to count longer text line width - - float textHeight = fontSize; - float scaleFactor = fontSize / (float)font.baseSize; - - int letter = 0; // Current character - int index = 0; // Index position in sprite font - - for (int i = 0; i < size;) { - byteCounter++; - - int next = 0; - letter = GetCodepointNext(&text.data[i], &next); - index = GetGlyphIndex(font, letter); - - i += next; - - if (font.glyphs[index].advanceX != 0) textWidth += font.glyphs[index].advanceX; - else textWidth += (font.recs[index].width + font.glyphs[index].offsetX); - - if (tempByteCounter < byteCounter) tempByteCounter = byteCounter; - } - - if (tempTextWidth < textWidth) tempTextWidth = textWidth; - - textSize.x = tempTextWidth * scaleFactor + (float)((tempByteCounter - 1) * spacing); - textSize.y = textHeight; - - return textSize; -} - -void RegenLayout(Window *window) { - ProfileFunction(); - Clear(&window->layout_arena); - window->layout = {}; - - Buffer &buffer = window->buffer; - Arena *arena = &window->layout_arena; - Layout &layout = window->layout; - layout.rows.allocator = *arena; - - Rect2 visible_rect = {window->scroll, window->scroll + GetSize(window->rect)}; - Vec2 visible_px_size = GetSize(window->rect); - window->layout.visible_line_range = {-1, -1}; - - Range *last_range = buffer.lines.last(); - float scaleFactor = window->font_size / window->font.baseSize; // Character quad scaling factor - float text_offset_y = 0; - float text_offset_x = 0; - float line_spacing = window->font_size; - - static bool line_wrapping; - if (IsKeyPressed(KEY_F1)) line_wrapping = !line_wrapping; - - float space = MeasureString(window->font, "_", window->font_size, window->font_spacing).x; - - ForItem(line_range, buffer.lines) { - text_offset_x = 0.0f; - if (&line_range == last_range && GetRangeSize(line_range) == 0) break; // end of buffer line - - LayoutRow *row = layout.rows.alloc(); - row->columns.allocator = *arena; - row->rect.min = {text_offset_x, text_offset_y}; - - if (row->rect.min.y >= visible_rect.min.y && row->rect.min.y < visible_rect.max.y) { - if (window->layout.visible_line_range.min == -1) window->layout.visible_line_range.min = layout.rows.get_index(*row); - window->layout.visible_line_range.max = layout.rows.get_index(*row); - } - - for (BufferIter iter = Iterate(buffer, line_range); IsValid(iter); Advance(&iter)) { - int index = GetGlyphIndex(window->font, (int)iter.item); - GlyphInfo *glyph = window->font.glyphs + index; - Vec2 glyph_position = {text_offset_x, text_offset_y}; - - float x_to_offset_by = ((float)glyph->advanceX * scaleFactor + window->font_spacing); - if (glyph->advanceX == 0) x_to_offset_by = ((float)window->font.recs[index].width * scaleFactor + window->font_spacing); - - Vec2 cell_size = {x_to_offset_by, window->font_size}; - Rect2 cell_rect = {glyph_position, Vector2Add(glyph_position, cell_size)}; - row->columns.add({cell_rect, iter.pos, (int)iter.item}); - row->rect.max = cell_rect.max; - - if (layout.max_column == NULL || row->rect.max.x > layout.max_column->rect.max.x) { - layout.max_column = row->columns.last(); - } - - text_offset_x += x_to_offset_by; - - if (line_wrapping) { - if (text_offset_x > (visible_px_size.x - space)) { - text_offset_x = 0; - text_offset_y += line_spacing; - } - } - } - - layout.buffer_world_pixel_size.x = Max(layout.buffer_world_pixel_size.x, text_offset_x); - text_offset_y += line_spacing; - } - - // Add end of buffer as layout cell at the end - { - LayoutRow *row = NULL; - if (last_range->min == last_range->max) { - row = layout.rows.alloc(); - row->columns.allocator = *arena; - text_offset_x = 0; - } else { - text_offset_y -= line_spacing; - row = layout.rows.last(); - } - int index = GetGlyphIndex(window->font, ' '); - GlyphInfo *glyph = window->font.glyphs + index; - Vec2 glyph_position = {text_offset_x, text_offset_y}; - - float x_to_offset_by = ((float)glyph->advanceX * scaleFactor + window->font_spacing); - if (glyph->advanceX == 0) x_to_offset_by = ((float)window->font.recs[index].width * scaleFactor + window->font_spacing); - - Vec2 cell_size = {x_to_offset_by, line_spacing}; - Rect2 cell_rect = {glyph_position, Vector2Add(glyph_position, cell_size)}; - row->columns.add({cell_rect, buffer.len, '\0'}); - row->rect = {glyph_position, cell_rect.max}; - - if (layout.max_column == NULL || row->rect.max.x > layout.max_column->rect.max.x) { - layout.max_column = row->columns.last(); - } - } - - layout.buffer_world_pixel_size.y = text_offset_y; - window->not_regen_layout = !window->not_regen_layout; -} - -// @todo: this is not taking line wrapping into account -int64_t YPosWorldToLineNumber(Window &window, float ypos_window_buffer_world_units) { - ProfileFunction(); - float line_spacing = window.font_size; - int64_t line = (int64_t)floorf(ypos_window_buffer_world_units / line_spacing); - return line; -} - -// @todo: this is not taking line wrapping into account -LayoutRow *GetLayoutRow(Window &window, float ypos_window_buffer_world_units) { - ProfileFunction(); - int64_t line = YPosWorldToLineNumber(window, ypos_window_buffer_world_units); - if (line >= 0 && line < window.layout.rows.len) { - return window.layout.rows.data + line; - } - return NULL; -} - -LayoutColumn *GetLayoutColumn(LayoutRow *row, float xpos_window_buffer_world_units) { - ProfileFunction(); - if (!row) return NULL; - For(row->columns) { - if (xpos_window_buffer_world_units >= it.rect.min.x && xpos_window_buffer_world_units <= it.rect.max.x) { - return ⁢ - } - } - return NULL; -} - -Tuple GetRowCol(Window &window, Vec2 pos_buffer_world_units) { - ProfileFunction(); - Tuple result = {}; - result.a = GetLayoutRow(window, pos_buffer_world_units.y); - result.b = GetLayoutColumn(result.a, pos_buffer_world_units.x); - return result; -} - -Tuple GetRowCol(Window &window, int64_t pos) { - ProfileFunction(); - Tuple result = {}; - For(window.layout.rows) { - if (window.layout.rows.len == 0) continue; - int64_t first_pos = it.columns.first()->pos; - int64_t last_pos = it.columns.last()->pos; - if (pos >= first_pos && pos <= last_pos) { - result.a = ⁢ - break; - } - } - - if (result.a) { - For(result.a->columns) { - if (it.pos == pos) { - result.b = ⁢ - break; - } - } - } - - return result; -} - -Range CalculateVisibleLineRange(Window &window) { - ProfileFunction(); - Rect2 visible_rect = {window.scroll, window.scroll + GetSize(window.rect)}; - Range result = {-1, -1}; - For(window.layout.rows) { - if (it.rect.min.y >= visible_rect.min.y && it.rect.min.y <= visible_rect.max.y) { - result.max = window.layout.rows.get_index(it); - if (result.min == -1) result.min = result.max; - } else if (result.max != -1) { - break; - } - } - result.min = ClampBottom(result.min - 1, (int64_t)0); - result.max = ClampTop(result.max + 1, window.layout.rows.len); - return result; -} - -Array CalculateVisibleColumns(Arena *arena, Window &window) { - ProfileFunction(); - Range visible_line_range = CalculateVisibleLineRange(window); - Array r = {*arena}; - for (int64_t line = visible_line_range.min; line < visible_line_range.max && line >= 0 && line < window.layout.rows.len; line += 1) { - LayoutRow &row = window.layout.rows[line]; - For(row.columns) { - if (CheckCollisionRecs(ToRectangle(it.rect + window.rect.min - window.scroll), ToRectangle(window.rect))) { - r.add(it); - } - } - } - return r; -} - -void MergeCursors(Window *window) { - ProfileFunction(); - Scratch scratch; - // Merge cursors that overlap, this needs to be handled before any edits to - // make sure overlapping edits won't happen. - - // @optimize @refactor: this is retarded, I hit so many array removal bugs here, without allocation please - Array deleted_cursors = {scratch}; - ForItem(cursor, window->cursors) { - if (deleted_cursors.contains(&cursor)) goto end_of_cursor_loop; - - For(window->cursors) { - if (&it == &cursor) continue; - bool a = cursor.range.max >= it.range.min && cursor.range.max <= it.range.max; - bool b = cursor.range.min >= it.range.min && cursor.range.min <= it.range.max; - if ((a || b) && !deleted_cursors.contains(&it)) { - deleted_cursors.add(&it); - cursor.range.max = Max(cursor.range.max, it.range.max); - cursor.range.min = Min(cursor.range.min, it.range.min); - break; - } - } - end_of_cursor_loop:; - } - - Array new_cursors = {window->cursors.allocator}; - For(window->cursors) { - if (deleted_cursors.contains(&it) == false) { - new_cursors.add(it); - } - } - Assert(new_cursors.len <= window->cursors.len); - window->cursors.dealloc(); - window->cursors = new_cursors; -} - -void SaveHistoryBeforeMergeCursor(Window *window, Array *stack) { - ProfileFunction(); - HistoryEntry *entry = stack->alloc(); - Allocator sys_allocator = GetSystemAllocator(); - entry->cursors = window->cursors.tight_copy(sys_allocator); -} - -void SaveHistoryBeforeApplyEdits(Window *window, Array *stack, Array edits) { - ProfileFunction(); - HistoryEntry *entry = stack->last(); - Allocator sys_allocator = GetSystemAllocator(); - entry->edits = edits.tight_copy(sys_allocator); - - // Make reverse edits - For(entry->edits) { - Range new_range = {it.range.min, it.range.min + it.string.len}; - String string = GetString(window->buffer, it.range); - it.string = Copy(sys_allocator, string); - it.range = new_range; - } - - Array temp_edits = entry->edits.tight_copy(sys_allocator); - defer { temp_edits.dealloc(); }; - - // Fix reverse edits - ForItem(edit, edits) { - int64_t remove_size = GetRangeSize(edit.range); - int64_t insert_size = edit.string.len; - int64_t offset = insert_size - remove_size; - - for (int64_t i = 0; i < entry->edits.len; i += 1) { - Edit &new_edit = entry->edits.data[i]; - Edit &old_edit = temp_edits.data[i]; - if (old_edit.range.min > edit.range.min) { - new_edit.range.min += offset; - new_edit.range.max += offset; - } - } - } -} - -void RedoEdit(Window *window) { - ProfileFunction(); - if (window->history.redo_stack.len <= 0) return; - - HistoryEntry entry = window->history.redo_stack.pop(); - - SaveHistoryBeforeMergeCursor(window, &window->history.undo_stack); - SaveHistoryBeforeApplyEdits(window, &window->history.undo_stack, entry.edits); - _ApplyEdits(&window->buffer, entry.edits); - - window->cursors.dealloc(); - window->cursors = entry.cursors; - window->not_regen_layout = false; - - Allocator sys_allocator = GetSystemAllocator(); - For(entry.edits) Dealloc(sys_allocator, &it.string.data); - entry.edits.dealloc(); -} - -void UndoEdit(Window *window) { - ProfileFunction(); - if (window->history.undo_stack.len <= 0) return; - - HistoryEntry entry = window->history.undo_stack.pop(); - - SaveHistoryBeforeMergeCursor(window, &window->history.redo_stack); - SaveHistoryBeforeApplyEdits(window, &window->history.redo_stack, entry.edits); - _ApplyEdits(&window->buffer, entry.edits); - - window->cursors.dealloc(); - window->cursors = entry.cursors; - window->not_regen_layout = false; - - Allocator sys_allocator = GetSystemAllocator(); - For(entry.edits) Dealloc(sys_allocator, &it.string.data); - entry.edits.dealloc(); -} - -void BeforeEdit(Window *window) { - Assert(window->history.debug_edit_phase == 0); - window->history.debug_edit_phase += 1; - Assert(window->cursors.len); - SaveHistoryBeforeMergeCursor(window, &window->history.undo_stack); - - // Clear redo stack - { - For(window->history.redo_stack) { - it.cursors.dealloc(); - ForItem(edit, it.edits) Dealloc(it.edits.allocator, &edit.string.data); - it.edits.dealloc(); - } - window->history.redo_stack.dealloc(); - } - MergeCursors(window); -} - -void ApplyEdits(Window *window, Array edits) { - ProfileFunction(); - Assert(window->history.debug_edit_phase == 1); - window->history.debug_edit_phase += 1; - SaveHistoryBeforeApplyEdits(window, &window->history.undo_stack, edits); - _ApplyEdits(&window->buffer, edits); -} - -void AfterEdit(Window *window, Array edits) { - ProfileFunction(); - Assert(window->history.debug_edit_phase == 2); - window->history.debug_edit_phase -= 2; - - HistoryEntry *entry = window->history.undo_stack.last(); - Assert(entry->cursors.len); - Assert(entry->edits.len); - - for (int64_t i = 0; i < edits.len - 1; i += 1) { - Assert(edits[i].range.min <= edits[i + 1].range.min); - } - - // - // Offset all cursors by edits - // - Scratch scratch; - Array new_cursors = window->cursors.tight_copy(scratch); - ForItem(edit, edits) { - int64_t remove_size = GetRangeSize(edit.range); - int64_t insert_size = edit.string.len; - int64_t offset = insert_size - remove_size; - - for (int64_t i = 0; i < window->cursors.len; i += 1) { - Cursor &old_cursor = window->cursors.data[i]; - Cursor &new_cursor = new_cursors.data[i]; - if (old_cursor.range.min == edit.range.min) { - new_cursor.range.min = new_cursor.range.max = new_cursor.range.min + insert_size; - } else if (old_cursor.range.min > edit.range.min) { - new_cursor.range.min = new_cursor.range.max = new_cursor.range.min + offset; - } - } - } - for (int64_t i = 0; i < window->cursors.len; i += 1) window->cursors[i] = new_cursors[i]; - - // Make sure all cursors are in range - For(window->cursors) it.range = Clamp(window->buffer, it.range); - - window->not_regen_layout = false; -} diff --git a/src/text_editor/main.cpp b/src/text_editor/main.cpp deleted file mode 100644 index 092cf58..0000000 --- a/src/text_editor/main.cpp +++ /dev/null @@ -1,809 +0,0 @@ -#define BASIC_IMPL -#include "../basic/basic.h" -#include "raylib.h" -#include "raymath.h" -#include "profiler.cpp" - -// @todo: search for word -// @todo: context menu -// @todo: toy around with acme ideas -// @todo: undo tree -// @todo: resize font, reload font -// @todo: add clipboard history? -#include "rect2.cpp" -#include "buffer.cpp" -#include "layout.cpp" -#include "clipboard.cpp" -#include "utils.cpp" -#include "event_recording.cpp" - -Arena PermArena; -Arena FrameArena; -struct DebugLine { - bool persist; - String string; -}; -Array DebugLines = {}; - -void Dbg(const char *str, ...) { - STRING_FORMAT(FrameArena, str, result); - DebugLines.add({false, result}); -} -void DbgPersist(const char *str, ...) { - STRING_FORMAT(PermArena, str, result); - DebugLines.add({true, result}); -} - -void Dbg_Draw() { - int y = 0; - IterRemove(DebugLines) { - IterRemovePrepare(DebugLines); - if (it.persist == false) remove_item = true; - DrawText(it.string.data, 0, y, 20, BLACK); - y += 20; - } -} - -bool IsDoubleClick() { - static double last_click_time; - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - double time = GetTime(); - double diff = time - last_click_time; - last_click_time = time; - // @todo: don't do this manually, use windows - if (diff >= 0.05 && diff <= 0.25) { - last_click_time = 0; - return true; - } - } else if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { - last_click_time = 0; - } - return false; -} - -int main() { - BeginProfiler(); - InitScratch(); - InitWindow(800, 600, "Hello"); - SetExitKey(KEY_F1); - // @todo: dpi - SetWindowState(FLAG_WINDOW_RESIZABLE); - SetTargetFPS(60); - { - uint32_t data = 0xffdddddd; - Image window_icon_image = {(void *)&data, 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8}; - SetWindowIcon(window_icon_image); - } - - InitArena(&FrameArena); - InitArena(&PermArena); - float font_size = 14; - float font_spacing = 1; - Font font = LoadFontEx("C:/Windows/Fonts/times.ttf", (int)font_size, NULL, 250); - - Array windows = {}; - { - Window window = {}; - window.start_rect = GetScreenRect(); - window.font = font; - window.font_size = font_size; - window.font_spacing; - InitArena(&window.layout_arena); - InitBuffer(GetSystemAllocator(), &window.buffer); - - window.cursors.allocator = GetSystemAllocator(); - window.cursors.add({}); - - if (1) { - for (int i = 0; i < 50; i += 1) { - BeforeEdit(&window); - Array edits = {FrameArena}; - AddEdit(&edits, GetEnd(window.buffer), Format(FrameArena, "line number line number line number line number line number line number: %d\n", i)); - ApplyEdits(&window, edits); - AfterEdit(&window, edits); - } - } - - *window.cursors.last() = {}; - windows.add(window); - } - - int64_t frame = -1; - InitEventRecording(); - while (!WindowShouldClose()) { - Clear(&FrameArena); - For(windows) { - Assert(it.cursors.len); - it.main_cursor_begin_frame = it.cursors[0]; - it.colored_strings = {FrameArena}; - } - - frame += 1; - // Dbg("%lld", (long long)frame); - - UpdateEventRecording(); - - { - ProfileScope(focused_window_input_update); - Window *focused_window = &windows[0]; - focused_window->start_rect = GetScreenRect(); - - float mouse_wheel = GetMouseWheelMove() * 48; - focused_window->scroll.y -= mouse_wheel; - - MergeCursors(focused_window); - if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_A)) { - focused_window->cursors.clear(); - focused_window->cursors.add(MakeCursor(0, focused_window->buffer.len)); - } - - if (IsKeyPressed(KEY_ESCAPE)) { - focused_window->cursors.len = 1; - } - - if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) { - For(focused_window->cursors) { - if (IsKeyDown(KEY_LEFT_CONTROL)) { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - int64_t front = GetFront(it); - front = Seek(focused_window->buffer, front, ITERATE_BACKWARD); - it = ChangeFront(it, front); - } else { - it.range.max = it.range.min = Seek(focused_window->buffer, it.range.min, ITERATE_BACKWARD); - } - } else { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - int64_t front = GetFront(it); - front = MoveLeft(focused_window->buffer, front); - it = ChangeFront(it, front); - } else { - if (GetRangeSize(it.range) != 0) { - it.range.max = it.range.min; - } else { - it.range.max = it.range.min = MoveLeft(focused_window->buffer, it.range.min); - } - } - } - } - } - - if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) { - For(focused_window->cursors) { - if (IsKeyDown(KEY_LEFT_CONTROL)) { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - int64_t front = GetFront(it); - front = Seek(focused_window->buffer, front, ITERATE_FORWARD); - it = ChangeFront(it, front); - } else { - it.range.max = it.range.min = Seek(focused_window->buffer, it.range.min, ITERATE_FORWARD); - } - } else { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - int64_t front = GetFront(it); - front = MoveRight(focused_window->buffer, front); - it = ChangeFront(it, front); - } else { - if (GetRangeSize(it.range) != 0) { - it.range.min = it.range.max; - } else { - it.range.max = it.range.min = MoveRight(focused_window->buffer, it.range.min); - } - } - } - } - } - - if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) { - if (IsKeyDown(KEY_LEFT_SHIFT) && IsKeyDown(KEY_LEFT_ALT)) { // Default in VSCode seems to be Ctrl + Alt + down - Array cursors_to_add = {FrameArena}; - For(focused_window->cursors) { - int64_t front = GetFront(it); - front = MoveDown(focused_window->buffer, front); - cursors_to_add.add({front, front}); - } - For(cursors_to_add) focused_window->cursors.add(it); - MergeCursors(focused_window); - } else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_LEFT_ALT)) { // Default in VSCode seems to be Shift + Alt + down - BeforeEdit(focused_window); - Array edits = {FrameArena}; - For(focused_window->cursors) { - int64_t front = GetFront(it); - Line line = FindLine(focused_window->buffer, front); - String string = GetString(focused_window->buffer, line.range); - AddEdit(&edits, {line.range.max, line.range.max}, string); - } - ApplyEdits(focused_window, edits); - AfterEdit(focused_window, edits); - For(focused_window->cursors) { - it.range.max = it.range.min = MoveDown(focused_window->buffer, it.range.min); - } - } else if (IsKeyDown(KEY_LEFT_SHIFT)) { - For(focused_window->cursors) { - int64_t front = GetFront(it); - front = MoveDown(focused_window->buffer, front); - it = ChangeFront(it, front); - } - } else { - For(focused_window->cursors) { - it.range.max = it.range.min = MoveDown(focused_window->buffer, it.range.max); - } - } - } - - if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) { - if (IsKeyDown(KEY_LEFT_SHIFT) && IsKeyDown(KEY_LEFT_ALT)) { // Default in VSCode seems to be Ctrl + Alt + up - Array cursors_to_add = {FrameArena}; - For(focused_window->cursors) { - int64_t front = GetFront(it); - front = MoveUp(focused_window->buffer, front); - cursors_to_add.add({front, front}); - } - For(cursors_to_add) focused_window->cursors.add(it); - MergeCursors(focused_window); - } else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_LEFT_ALT)) { // Default in VSCode seems to be Shift + Alt + up - BeforeEdit(focused_window); - Array edits = {FrameArena}; - For(focused_window->cursors) { - int64_t front = GetFront(it); - Line line = FindLine(focused_window->buffer, front); - String string = GetString(focused_window->buffer, line.range); - AddEdit(&edits, {line.range.min, line.range.min}, string); - } - ApplyEdits(focused_window, edits); - AfterEdit(focused_window, edits); - For(focused_window->cursors) { - it.range.max = it.range.min = MoveUp(focused_window->buffer, it.range.min); - } - } else if (IsKeyDown(KEY_LEFT_SHIFT)) { - For(focused_window->cursors) { - int64_t front = GetFront(it); - front = MoveUp(focused_window->buffer, front); - it = ChangeFront(it, front); - } - } else { - For(focused_window->cursors) { - it.range.max = it.range.min = MoveUp(focused_window->buffer, it.range.min); - } - } - } - - if (IsKeyDown(KEY_LEFT_CONTROL) && (IsKeyPressed(KEY_D) || IsKeyPressedRepeat(KEY_D))) { - Cursor cursor = *focused_window->cursors.last(); - String needle = GetString(focused_window->buffer, cursor.range); - String to_seek = GetString(focused_window->buffer, {cursor.range.max, INT64_MAX}); - int64_t found_index = 0; - if (Seek(to_seek, needle, &found_index, SeekFlag_IgnoreCase)) { - found_index += cursor.range.max; - focused_window->cursors.add(MakeCursor(found_index + needle.len, found_index)); - MergeCursors(focused_window); - } - } - - For(focused_window->cursors) { - if (IsKeyPressed(KEY_HOME) || IsKeyPressedRepeat(KEY_HOME)) { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - int64_t front = GetFront(it); - Line line = FindLine(focused_window->buffer, front); - it = ChangeFront(it, line.range.min); - } else { - int64_t front = GetFront(it); - Line line = FindLine(focused_window->buffer, front); - it.range.min = it.range.max = line.range.min; - } - } - - if (IsKeyPressed(KEY_END) || IsKeyPressedRepeat(KEY_END)) { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - int64_t front = GetFront(it); - Line line = FindLine(focused_window->buffer, front); - it = ChangeFront(it, line.max_without_new_line); - } else { - int64_t front = GetFront(it); - Line line = FindLine(focused_window->buffer, front); - it.range.min = it.range.max = line.max_without_new_line; - } - } - } - - if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) { - CopyToClipboard(focused_window); - } - if (IsKeyDown(KEY_LEFT_CONTROL) && (IsKeyPressed(KEY_V) || IsKeyPressedRepeat(KEY_V))) { - PasteFromClipboard(focused_window); - } - if (IsKeyDown(KEY_LEFT_CONTROL) && (IsKeyPressed(KEY_X) || IsKeyPressedRepeat(KEY_X))) { - CopyToClipboard(focused_window); - - BeforeEdit(focused_window); - Array edits = {FrameArena}; - For(focused_window->cursors) AddEdit(&edits, it.range, ""); - ApplyEdits(focused_window, edits); - AfterEdit(focused_window, edits); - } - - if ((IsKeyPressed(KEY_Z) || IsKeyPressedRepeat(KEY_Z))) { - if (IsKeyDown(KEY_LEFT_CONTROL)) { - if (IsKeyDown(KEY_LEFT_SHIFT)) { - RedoEdit(focused_window); - } else { - UndoEdit(focused_window); - } - } - } - - if (IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) { - // Select things to delete - For(focused_window->cursors) { - if (GetRangeSize(it.range)) continue; - if (IsKeyDown(KEY_LEFT_CONTROL)) { - int64_t pos = Seek(focused_window->buffer, it.range.min, ITERATE_FORWARD); - it = MakeCursor(it.range.min, pos); - } else { - int64_t pos = MoveRight(focused_window->buffer, it.range.min); - it = MakeCursor(it.range.min, pos); - } - } - - // Merge - BeforeEdit(focused_window); - - // Modify - Array edits = {FrameArena}; - String string = {}; - For(focused_window->cursors) AddEdit(&edits, it.range, string); - ApplyEdits(focused_window, edits); - AfterEdit(focused_window, edits); - } - - if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) { - // Select things to delete - For(focused_window->cursors) { - if (GetRangeSize(it.range)) continue; - if (IsKeyDown(KEY_LEFT_CONTROL)) { - int64_t pos = Seek(focused_window->buffer, it.range.min, ITERATE_BACKWARD); - it = MakeCursor(pos, it.range.min); - } else { - int64_t pos = MoveLeft(focused_window->buffer, it.range.min); - it = MakeCursor(pos, it.range.min); - } - } - - // Merge - BeforeEdit(focused_window); - - // Modify - Array edits = {FrameArena}; - String string = {}; - For(focused_window->cursors) AddEdit(&edits, it.range, string); - ApplyEdits(focused_window, edits); - AfterEdit(focused_window, edits); - } - - if (IsKeyPressed(KEY_TAB) || IsKeyPressedRepeat(KEY_TAB)) { - BeforeEdit(focused_window); - Array edits = {FrameArena}; - For(focused_window->cursors) AddEdit(&edits, it.range, " "); - ApplyEdits(focused_window, edits); - AfterEdit(focused_window, edits); - } - - if (IsKeyPressed(KEY_ENTER) || IsKeyPressedRepeat(KEY_ENTER)) { - if (IsKeyDown(KEY_LEFT_CONTROL)) { - For(focused_window->cursors) { - int64_t front = GetFront(it); - Line line = FindLine(focused_window->buffer, front); - it.range = MakeRange(line.max_without_new_line); - } - } - - BeforeEdit(focused_window); - Array edits = {FrameArena}; - For(focused_window->cursors) AddEdit(&edits, it.range, "\n"); - ApplyEdits(focused_window, edits); - AfterEdit(focused_window, edits); - } - - // Handle user input - for (;;) { - int c = GetCharPressed(); - if (!c) break; - - String string = "?"; - UTF8Result utf8 = UTF32ToUTF8((uint32_t)c); - if (utf8.error == 0) { - string = {(char *)utf8.out_str, (int64_t)utf8.len}; - } - - BeforeEdit(focused_window); - Array edits = {FrameArena}; - For(focused_window->cursors) AddEdit(&edits, it.range, string); - ApplyEdits(focused_window, edits); - AfterEdit(focused_window, edits); - } - - if (0) { - Cursor cursor = focused_window->cursors[0]; - if (GetRangeSize(cursor.range)) { - String seek = GetString(focused_window->buffer, cursor.range); - int64_t index = 0; - int64_t base_index = 0; - String s = GetString(focused_window->buffer); - while (Seek(s, seek, &index)) { - Range range = {base_index + index, base_index + index + seek.len}; - - // ColoredString colored = {}; - // colored.range = range; - // colored.use_text_color = true; - // colored.use_underline = true; - // colored.text_color = RED; - // focused_window->colored_strings.add(colored); - - if (!AreEqual(range, cursor.range)) { - ColoredString colored = {}; - colored.range = range; - colored.use_highlight_background_color = true; - colored.highlight_background_color = {0, 0, 0, 25}; - focused_window->colored_strings.add(colored); - } - - base_index += index + seek.len; - s = s.skip(index + seek.len); - } - } - } - - EventRecording_SimulateGetCharPressed(focused_window); - } - - BeginDrawing(); - ClearBackground(RAYWHITE); - - ForItem(window, windows) { - ProfileScope(window_render_loop); - window.rect = window.start_rect; - Rect2 horizontal_bar_rect = CutBottom(&window.rect, 10); - Rect2 vertical_bar_rect = CutRight(&window.rect, 10); - Rect2 line_number_rect = CutLeft(&window.rect, MeasureString(window.font, "1234", window.font_size, window.font_spacing).x); - if (!window.not_regen_layout) RegenLayout(&window); - - bool is_focused = true; - if (is_focused) { - int64_t num = GetRangeSize(window.layout.visible_line_range); - - if (IsKeyPressed(KEY_PAGE_UP) || IsKeyPressedRepeat(KEY_PAGE_UP)) { - For(window.cursors) { - int64_t front = GetFront(it); - int64_t new_pos = MoveUp(window.buffer, front, num); - if (IsKeyDown(KEY_LEFT_SHIFT)) { - it = ChangeFront(it, new_pos); - } else { - it.range.min = it.range.max = new_pos; - } - } - } - - if (IsKeyPressed(KEY_PAGE_DOWN) || IsKeyPressedRepeat(KEY_PAGE_DOWN)) { - For(window.cursors) { - int64_t front = GetFront(it); - int64_t new_pos = MoveDown(window.buffer, front, num); - if (IsKeyDown(KEY_LEFT_SHIFT)) { - it = ChangeFront(it, new_pos); - } else { - it.range.min = it.range.max = new_pos; - } - } - } - } - - // Draw and layout window overlay - Vec2 mouse = GetMousePosition(); - { - ProfileScope(draw_rectangles); - DrawRectangleRec(ToRectangle(window.rect), WHITE); - DrawRectangleRec(ToRectangle(line_number_rect), WHITE); - - Vec2 size = GetSize(window.rect); - Vec2 min = window.scroll / (window.layout.buffer_world_pixel_size + size); - Vec2 max = (window.scroll + size) / (window.layout.buffer_world_pixel_size + size); - - { - DrawRectangleRec(ToRectangle(vertical_bar_rect), GRAY); - float vert_size = GetSize(vertical_bar_rect).y; - float vert_begin = min.y * vert_size; - float vert_end = max.y * vert_size; - Rect2 rect = vertical_bar_rect; - rect.min.y = vert_begin; - rect.max.y = vert_end; - rect = Shrink(rect, 2); - - bool colliding = CheckCollisionPointRec(mouse, ToRectangle(vertical_bar_rect)); - Color color = LIGHTGRAY; - if (colliding || window.mouse_selecting_vert_bar) color = RAYWHITE; - DrawRectangleRec(ToRectangle(rect), color); - - if (colliding) { - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - window.mouse_selecting_vert_bar = true; - } - } - - if (window.mouse_selecting_vert_bar) { - if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) == false) { - window.mouse_selecting_vert_bar = false; - } - float value = GetMouseDelta().y / vert_size; - window.scroll.y += value * window.layout.buffer_world_pixel_size.y; - } - } - - { - DrawRectangleRec(ToRectangle(horizontal_bar_rect), GRAY); - float hori_size = GetSize(horizontal_bar_rect).x; - float hori_begin = min.x * hori_size; - float hori_end = max.x * hori_size; - Rect2 rect = horizontal_bar_rect; - rect.min.x = hori_begin; - rect.max.x = hori_end; - rect = Shrink(rect, 2); - - bool colliding = CheckCollisionPointRec(mouse, ToRectangle(horizontal_bar_rect)); - Color color = LIGHTGRAY; - if (colliding || window.mouse_selecting_hori_bar) color = RAYWHITE; - DrawRectangleRec(ToRectangle(rect), color); - - if (colliding) { - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - window.mouse_selecting_hori_bar = true; - } - } - - if (window.mouse_selecting_hori_bar) { - if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) == false) { - window.mouse_selecting_hori_bar = false; - } - float value = GetMouseDelta().x / hori_size; - window.scroll.x += value * window.layout.buffer_world_pixel_size.x; - } - } - } - - { - window.scroll.y = Clamp(window.scroll.y, 0.f, window.layout.rows.last()->rect.min.y); - window.scroll.x = Clamp(window.scroll.x, 0.f, window.layout.max_column->rect.min.x); - } - - // Mouse selection - if (!window.mouse_selecting_hori_bar && !window.mouse_selecting_vert_bar) { - bool mouse_in_window = CheckCollisionPointRec(mouse, ToRectangle(window.rect)) || - CheckCollisionPointRec(mouse, ToRectangle(line_number_rect)); - if (!window.mouse_selecting && mouse_in_window) { - SetMouseCursor(MOUSE_CURSOR_IBEAM); - } - - if (!window.mouse_selecting && !mouse_in_window) { - SetMouseCursor(MOUSE_CURSOR_DEFAULT); - } - - if ((mouse_in_window && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) || window.mouse_selecting) { - Vec2 mouse_lookup = mouse - window.rect.min + window.scroll; - Vec2 mouse_in_text = mouse - window.rect.min; - LayoutRow *row = GetLayoutRow(window, mouse_lookup.y); - if (row == NULL) { - if (mouse_in_text.y < 0) { - row = window.layout.rows.first(); - } else { - row = window.layout.rows.last(); - } - } - Assert(row); - - LayoutColumn *col = GetLayoutColumn(row, mouse_lookup.x); - if (col == NULL) { - if (mouse_in_text.x < 0) { - col = row->columns.first(); - } else { - col = row->columns.last(); - } - } - Assert(col); - - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - if (!IsKeyDown(KEY_LEFT_CONTROL)) { - window.cursors.clear(); - } - window.cursors.add(MakeCursor(col->pos, col->pos)); - window.selection_anchor_point = window.cursors.last()->range; - if (IsDoubleClick()) { - Cursor *c = window.cursors.last(); - c->range = EncloseWord(window.buffer, col->pos); - window.selection_anchor_point = c->range; - } - - MergeCursors(&window); - } else if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { - window.mouse_selecting = true; - } - - if (window.mouse_selecting) { - SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL); - if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { - window.mouse_selecting = false; - } - Cursor *cursor = window.cursors.last(); - if (window.selection_anchor_point.min > col->pos) { - cursor[0] = MakeCursor(col->pos, window.selection_anchor_point.max); - } else if (window.selection_anchor_point.max < col->pos) { - cursor[0] = MakeCursor(col->pos, window.selection_anchor_point.min); - } else { - cursor[0] = MakeCursor(window.selection_anchor_point.max, window.selection_anchor_point.min); - } - } - } - } - - // Update the scroll based on first cursor - if (!AreEqual(window.main_cursor_begin_frame, window.cursors[0])) { - Range visible_line_range = CalculateVisibleLineRange(window); - int64_t visible_lines = GetRangeSize(visible_line_range); - float visible_size_y = font_size * (float)visible_lines; - - Vec2 rect_size = GetSize(window.rect); - float cut_off_y = Max((float)0, visible_size_y - rect_size.y); - - int64_t front = GetFront(window.cursors[0]); - Line line = FindLine(window.buffer, front); - - // Bottom - if (line.number > visible_line_range.max - 2) { - int64_t set_view_at_line = line.number - (visible_lines - 1); - window.scroll.y = (set_view_at_line * font_size) + cut_off_y; - } - - // Top - if (line.number < visible_line_range.min + 1) { - int64_t set_view_at_line = line.number; - window.scroll.y = (set_view_at_line * font_size); - } - - // Right - Tuple rowcol = GetRowCol(window, front); - - float x = rowcol.b->rect.max.x; - float right_edge = rect_size.x + window.scroll.x; - if (x >= right_edge) { - window.scroll.x = x - rect_size.x; - } - - // Left - x = rowcol.b->rect.min.x; - float left_edge = 0 + window.scroll.x; - if (x <= left_edge) { - window.scroll.x = x; - } - } - - // Draw the glyphs - Vec2 window_rect_size = GetSize(window.rect); - BeginScissorMode((int)window.rect.min.x, (int)window.rect.min.y, (int)window_rect_size.x, (int)window_rect_size.y); - - Array visible_columns = CalculateVisibleColumns(&FrameArena, window); - Range visible_line_range = CalculateVisibleLineRange(window); - - BeginProfileScope("draw_glyphs"); - ForItem(col, visible_columns) { - Rect2 rect = {col.rect.min + window.rect.min - window.scroll, col.rect.max + window.rect.min - window.scroll}; - - if (col.codepoint == '\n') { - // DrawTextEx(font, "\\n", rect.min, font_size, font_spacing, GRAY); - DrawTextCodepoint(font, 'n', rect.min, font_size, GRAY); - } else if (col.codepoint == '\r') { - // DrawTextEx(font, "\\r", rect.min, font_size, font_spacing, GRAY); - DrawTextCodepoint(font, 'r', rect.min, font_size, GRAY); - } else if (col.codepoint == '\0') { - DrawTextEx(font, "\\0", rect.min, font_size, font_spacing, {255, 0, 0, 150}); - } else if ((col.codepoint != ' ') && (col.codepoint != '\t')) { - - bool underline = false; - bool highlight = false; - Color highlight_color = MAGENTA; - Color text_color = BLACK; - - For(window.colored_strings) { - if (InRange(col.pos, it.range)) { - if (it.use_underline) underline = true; - if (it.use_text_color) text_color = it.text_color; - if (it.use_highlight_background_color) { - highlight = true; - highlight_color = it.highlight_background_color; - } - } - } - - if (highlight) DrawRectangleRec(ToRectangle(rect), highlight_color); - DrawTextCodepoint(font, col.codepoint, rect.min, font_size, text_color); - if (underline) { - Rect2 rect_copy = rect; - Rect2 r = CutBottom(&rect_copy, 1) - Vec2{0, window.font_size * 0.1f}; - DrawRectangleRec(ToRectangle(r), text_color); - } - } - } - EndProfileScope(); - - // Draw cursor stuff - BeginProfileScope("draw_cursor"); - Array lines_to_highlight = {FrameArena}; - ForItem(cursor, window.cursors) { - Tuple front = GetRowCol(window, GetFront(cursor)); - Tuple back = GetRowCol(window, GetBack(cursor)); - - // Line highlight - lines_to_highlight.add(front.a); - if (GetRangeSize(cursor.range) == 0) { - Rect2 rect = front.a->rect + window.rect.min - window.scroll; - DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 30}); - } - - // Selection - For(visible_columns) { - if (it.pos >= cursor.range.min && it.pos < cursor.range.max) { - Rect2 rect = it.rect + window.rect.min - window.scroll; - DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 75}); - } - } - - // Cursor blocks - if (front.b) { - Rect2 rect = front.b->rect + window.rect.min - window.scroll; - rect = CutLeft(&rect, 2); - DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 255}); - } - if (back.b) { - Rect2 rect = back.b->rect + window.rect.min - window.scroll; - rect = CutLeft(&rect, 1); - DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 255}); - } - } - EndProfileScope(); - - EndScissorMode(); - - // Draw line numbers - { - ProfileScope(draw_line_numbers); - BeginScissorMode((int)line_number_rect.min.x, (int)line_number_rect.min.y, (int)(line_number_rect.max.x - line_number_rect.min.x), (int)(line_number_rect.max.y - line_number_rect.min.y)); - for (int64_t line = visible_line_range.min; line < visible_line_range.max; line += 1) { - LayoutRow &row = window.layout.rows[line]; - Vec2 pos = {line_number_rect.min.x, row.rect.min.y + line_number_rect.min.y - window.scroll.y}; - For(lines_to_highlight) { - if (it == &row) { - Rect2 r = {pos, pos}; - r.max.x = line_number_rect.max.x; - r.max.y += GetSize(row.rect).y; - DrawRectangleRec(ToRectangle(r), {0, 0, 0, 30}); - break; - } - } - - String line_num_string = Format(FrameArena, "%lld", (long long)line); - DrawTextEx(font, line_num_string.data, pos, font_size, font_spacing, LIGHTGRAY); - } - EndScissorMode(); - } - } - - Dbg_Draw(); - EventRecording_Draw(); - - Window *focused_window = &windows[0]; - if (focused_window->mouse_selecting || EventPlaying) { - DisableEventWaiting(); - } else { - EnableEventWaiting(); - } - - EndDrawing(); - } - CloseWindow(); - EndProfiler(); -} \ No newline at end of file diff --git a/src/text_editor/multi_cursor_text_editor_behaviors.txt b/src/text_editor/multi_cursor_text_editor_behaviors.txt deleted file mode 100644 index bd91046..0000000 --- a/src/text_editor/multi_cursor_text_editor_behaviors.txt +++ /dev/null @@ -1,10 +0,0 @@ -- select word on double click - use selected word as the unselectable anchor point -- ctrl + mouse click to add and control a new cursor -- ctrl + alt + mouse click to do a rectangle select with multiple cursors @todo -- mouse wheel to scroll -- right click to open context menu with cut copy paste @todo -- shift + alt + up arrow to create a new cursor on line above -- ctrl + alt + up arrow to duplicate line, put it above and put cursor on it -- alt + up arrow to move line above @todo -- arrow to move cursor vertically or horizontally -- shift + arrow to move and select diff --git a/src/text_editor/profiler.cpp b/src/text_editor/profiler.cpp deleted file mode 100644 index 6ac7518..0000000 --- a/src/text_editor/profiler.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#if 1 - #include "spall.h" - -static SpallProfile spall_ctx; -static SpallBuffer spall_buffer; -double get_time_in_micros(void); - -void BeginProfiler() { - spall_ctx = spall_init_file("hello_world.spall", 1); - - int buffer_size = 1 * 1024 * 1024; - unsigned char *buffer = (unsigned char *)malloc(buffer_size); - spall_buffer = {buffer, (size_t)buffer_size}; - - spall_buffer_init(&spall_ctx, &spall_buffer); -} - -void EndProfiler() { - spall_buffer_quit(&spall_ctx, &spall_buffer); - free(spall_buffer.data); - spall_quit(&spall_ctx); -} - -void BeginProfileScope(const char *name) { - spall_buffer_begin(&spall_ctx, &spall_buffer, - name, // name of your name - sizeof(name) - 1, // name len minus the null terminator - get_time_in_micros() // timestamp in microseconds -- start of your timing block - ); -} - -void EndProfileScope() { - spall_buffer_end(&spall_ctx, &spall_buffer, - get_time_in_micros() // timestamp in microseconds -- end of your timing block - ); -} - #define ProfileScope(name) ProfileScope_ PROFILE_SCOPE_VAR_##name(#name) - #define ProfileFunction() ProfileScope_ PROFILE_SCOPE_FUNCTION(__FUNCTION__) -struct ProfileScope_ { - ProfileScope_(const char *name) { BeginProfileScope(name); } - ~ProfileScope_() { EndProfileScope(); } -}; -#else - #define ProfileScope(name) - #define ProfileFunction() - #define BeginProfiler() - #define EndProfiler() - #define BeginProfileScope(name) - #define EndProfileScope() -#endif \ No newline at end of file diff --git a/src/text_editor/read_pdf.py b/src/text_editor/read_pdf.py deleted file mode 100644 index 288e871..0000000 --- a/src/text_editor/read_pdf.py +++ /dev/null @@ -1,37 +0,0 @@ -import os, sys -import pypdf - -if len(sys.argv) != 2: - print("expected single argument with filename or folder to extract text from") - exit(0) - - -def write_pdf_for(filename): - reader = pypdf.PdfReader(filename) - extract_filename = filename + ".txt" - if os.path.exists(extract_filename): - print(f"skipping {extract_filename}, file exists") - return - - f = open(extract_filename, "w", encoding="utf-8") - for i in range(len(reader.pages)): - page = reader.pages[i] - text = page.extract_text() - text = text.replace("\n", "") - text = text.replace("-", "") - - f.write(f">>>>>>>>{i + 1}<<<<<<<<<\n") - f.write(text) - f.write("\n") - - f.close() - print(f"generated: {extract_filename}") - -if os.path.isdir(sys.argv[1]): - for file in os.listdir(sys.argv[1]): - if file.endswith(".pdf"): - write_pdf_for(file) -elif os.path.isfile(sys.argv[1]): - write_pdf_for(sys.argv[1]) -else: - print(f"argument you passed in: {sys.argv[1]} is not a filename or folder") \ No newline at end of file diff --git a/src/text_editor/rect2.cpp b/src/text_editor/rect2.cpp deleted file mode 100644 index b21f828..0000000 --- a/src/text_editor/rect2.cpp +++ /dev/null @@ -1,130 +0,0 @@ -using Vec2 = Vector2; -struct Rect2 { - Vec2 min; - Vec2 max; -}; - -Vec2 GetSize(Rect2 r) { - Vec2 result = {r.max.x - r.min.x, r.max.y - r.min.y}; - return result; -} - -Rectangle ToRectangle(Rect2 r) { - Rectangle result = {r.min.x, r.min.y, r.max.x - r.min.x, r.max.y - r.min.y}; - return result; -} - -// clang-format off -Rect2 operator-(Rect2 r, Rect2 value) { return { r.min.x - value.min.x, r.min.y - value.min.y, r.max.x - value.max.x, r.max.y - value.max.y }; } -Rect2 operator+(Rect2 r, Rect2 value) { return { r.min.x + value.min.x, r.min.y + value.min.y, r.max.x + value.max.x, r.max.y + value.max.y }; } -Rect2 operator*(Rect2 r, Rect2 value) { return { r.min.x * value.min.x, r.min.y * value.min.y, r.max.x * value.max.x, r.max.y * value.max.y }; } -Rect2 operator/(Rect2 r, Rect2 value) { return { r.min.x / value.min.x, r.min.y / value.min.y, r.max.x / value.max.x, r.max.y / value.max.y }; } - -Rect2 operator-(Rect2 r, Vec2 value) { return { r.min.x - value.x, r.min.y - value.y, r.max.x - value.x, r.max.y - value.y }; } -Rect2 operator+(Rect2 r, Vec2 value) { return { r.min.x + value.x, r.min.y + value.y, r.max.x + value.x, r.max.y + value.y }; } -Rect2 operator*(Rect2 r, Vec2 value) { return { r.min.x * value.x, r.min.y * value.y, r.max.x * value.x, r.max.y * value.y }; } -Rect2 operator/(Rect2 r, Vec2 value) { return { r.min.x / value.x, r.min.y / value.y, r.max.x / value.x, r.max.y / value.y }; } - -Rect2 operator-(Rect2 r, float value) { return { r.min.x - value, r.min.y - value, r.max.x - value, r.max.y - value }; } -Rect2 operator+(Rect2 r, float value) { return { r.min.x + value, r.min.y + value, r.max.x + value, r.max.y + value }; } -Rect2 operator*(Rect2 r, float value) { return { r.min.x * value, r.min.y * value, r.max.x * value, r.max.y * value }; } -Rect2 operator/(Rect2 r, float value) { return { r.min.x / value, r.min.y / value, r.max.x / value, r.max.y / value }; } - -Rect2 operator-=(Rect2 &r, Rect2 value) { r = r - value; return r; } -Rect2 operator+=(Rect2 &r, Rect2 value) { r = r + value; return r; } -Rect2 operator*=(Rect2 &r, Rect2 value) { r = r * value; return r; } -Rect2 operator/=(Rect2 &r, Rect2 value) { r = r / value; return r; } -Rect2 operator-=(Rect2 &r, Vec2 value) { r = r - value; return r; } -Rect2 operator+=(Rect2 &r, Vec2 value) { r = r + value; return r; } -Rect2 operator*=(Rect2 &r, Vec2 value) { r = r * value; return r; } -Rect2 operator/=(Rect2 &r, Vec2 value) { r = r / value; return r; } -Rect2 operator-=(Rect2 &r, float value) { r = r - value; return r; } -Rect2 operator+=(Rect2 &r, float value) { r = r + value; return r; } -Rect2 operator*=(Rect2 &r, float value) { r = r * value; return r; } -Rect2 operator/=(Rect2 &r, float value) { r = r / value; return r; } - -Vec2 operator-(Vec2 a, Vec2 b) { return {a.x - b.x, a.y - b.y}; } -Vec2 operator+(Vec2 a, Vec2 b) { return {a.x + b.x, a.y + b.y}; } -Vec2 operator*(Vec2 a, Vec2 b) { return {a.x * b.x, a.y * b.y}; } -Vec2 operator/(Vec2 a, Vec2 b) { return {a.x / b.x, a.y / b.y}; } - -Vec2 operator-(Vec2 a, float b) { return {a.x - b, a.y - b}; } -Vec2 operator+(Vec2 a, float b) { return {a.x + b, a.y + b}; } -Vec2 operator*(Vec2 a, float b) { return {a.x * b, a.y * b}; } -Vec2 operator/(Vec2 a, float b) { return {a.x / b, a.y / b}; } - -Vec2 operator-=(Vec2 &a, Vec2 b) { a = a - b; return a; } -Vec2 operator+=(Vec2 &a, Vec2 b) { a = a + b; return a; } -Vec2 operator*=(Vec2 &a, Vec2 b) { a = a * b; return a; } -Vec2 operator/=(Vec2 &a, Vec2 b) { a = a / b; return a; } -Vec2 operator-=(Vec2 &a, float b) { a = a - b; return a; } -Vec2 operator+=(Vec2 &a, float b) { a = a + b; return a; } -Vec2 operator*=(Vec2 &a, float b) { a = a * b; return a; } -Vec2 operator/=(Vec2 &a, float b) { a = a / b; return a; } - -// clang-format on - -Rect2 Rect2FromSize(Vec2 pos, Vec2 size) { - Rect2 result = {}; - result.min = pos; - result.max = pos + size; - return result; -} - -Rect2 Shrink(Rect2 result, float v) { - result.min.x += v; - result.max.x -= v; - result.min.y += v; - result.max.y -= v; - return result; -} - -Vec2 GetMid(Rect2 r) { - Vec2 size = GetSize(r); - size.x /= 2.f; - size.y /= 2.f; - Vec2 result = Vector2Add(r.min, size); - return result; -} - -Rect2 CutLeft(Rect2 *r, float value) { - float minx = r->min.x; - r->min.x = Min(r->min.x + value, r->max.x); - Rect2 result = { - { minx, r->min.y}, - {r->min.x, r->max.y} - }; - return result; -} - -Rect2 CutRight(Rect2 *r, float value) { - float maxx = r->max.x; - r->max.x = Max(r->min.x, r->max.x - value); - Rect2 result = { - {r->max.x, r->min.y}, - { maxx, r->max.y}, - }; - return result; -} - -// Y is up -Rect2 CutBottom(Rect2 *r, float value) { - float maxy = r->max.y; - r->max.y = Max(r->min.y, r->max.y - value); - Rect2 result = { - {r->min.x, r->max.y}, - {r->max.x, maxy}, - }; - return result; -} - -// Y is up -Rect2 CutTop(Rect2 *r, float value) { - float miny = r->min.y; - r->min.y = Min(r->min.y + value, r->max.y); - Rect2 result = { - {r->min.x, miny}, - {r->max.x, r->min.y}, - }; - return result; -} diff --git a/src/text_editor/spall.h b/src/text_editor/spall.h deleted file mode 100644 index 02b32f3..0000000 --- a/src/text_editor/spall.h +++ /dev/null @@ -1,443 +0,0 @@ -// SPDX-FileCopyrightText: © 2023 Phillip Trudeau-Tavara -// SPDX-License-Identifier: MIT - -/* - -TODO: Optional Helper APIs: - - - Compression API: would require a mutexed lockable context (yuck...) - - Either using a ZIP library, a name cache + TIDPID cache, or both (but ZIP is likely more than enough!!!) - - begin()/end() writes compressed chunks to a caller-determined destination - - The destination can be the buffered-writing API or a custom user destination - - Ultimately need to take a lock with some granularity... can that be the caller's responsibility? - - - Counter Event: should allow tracking arbitrary named values with a single event, for memory and frame profiling - - - Ring-buffer API - spall_ring_init - spall_ring_emit_begin - spall_ring_emit_end - spall_ring_flush -*/ - -#ifndef SPALL_H -#define SPALL_H - -#if !defined(_MSC_VER) || defined(__clang__) - #define SPALL_NOINSTRUMENT __attribute__((no_instrument_function)) - #define SPALL_FORCEINLINE __attribute__((always_inline)) -#else - #define SPALL_NOINSTRUMENT // Can't noinstrument on MSVC! - #define SPALL_FORCEINLINE __forceinline -#endif - -#include -#include -#include -#include - -#define SPALL_FN static inline SPALL_NOINSTRUMENT - -#define SPALL_MIN(a, b) (((a) < (b)) ? (a) : (b)) - -#pragma pack(push, 1) - -typedef struct SpallHeader { - uint64_t magic_header; // = 0x0BADF00D - uint64_t version; // = 1 - double timestamp_unit; - uint64_t must_be_0; -} SpallHeader; - -enum { - SpallEventType_Invalid = 0, - SpallEventType_Custom_Data = 1, // Basic readers can skip this. - SpallEventType_StreamOver = 2, - - SpallEventType_Begin = 3, - SpallEventType_End = 4, - SpallEventType_Instant = 5, - - SpallEventType_Overwrite_Timestamp = 6, // Retroactively change timestamp units - useful for incrementally improving RDTSC frequency. - SpallEventType_Pad_Skip = 7, -}; - -typedef struct SpallBeginEvent { - uint8_t type; // = SpallEventType_Begin - uint8_t category; - - uint32_t pid; - uint32_t tid; - double when; - - uint8_t name_length; - uint8_t args_length; -} SpallBeginEvent; - -typedef struct SpallBeginEventMax { - SpallBeginEvent event; - char name_bytes[255]; - char args_bytes[255]; -} SpallBeginEventMax; - -typedef struct SpallEndEvent { - uint8_t type; // = SpallEventType_End - uint32_t pid; - uint32_t tid; - double when; -} SpallEndEvent; - -typedef struct SpallPadSkipEvent { - uint8_t type; // = SpallEventType_Pad_Skip - uint32_t size; -} SpallPadSkipEvent; - -#pragma pack(pop) - -typedef struct SpallProfile SpallProfile; - -// Important!: If you define your own callbacks, mark them SPALL_NOINSTRUMENT! -typedef bool (*SpallWriteCallback)(SpallProfile *self, const void *data, size_t length); -typedef bool (*SpallFlushCallback)(SpallProfile *self); -typedef void (*SpallCloseCallback)(SpallProfile *self); - -struct SpallProfile { - double timestamp_unit; - bool is_json; - SpallWriteCallback write; - SpallFlushCallback flush; - SpallCloseCallback close; - void *data; -}; - -// Important!: If you are writing Begin/End events, then do NOT write -// events for the same PID + TID pair on different buffers!!! -typedef struct SpallBuffer { - void *data; - size_t length; - - // Internal data - don't assign this - size_t head; - SpallProfile *ctx; -} SpallBuffer; - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(SPALL_BUFFER_PROFILING) && !defined(SPALL_BUFFER_PROFILING_GET_TIME) - #error "You must #define SPALL_BUFFER_PROFILING_GET_TIME() to profile buffer flushes." -#endif - -SPALL_FN SPALL_FORCEINLINE void spall__buffer_profile(SpallProfile *ctx, SpallBuffer *wb, double spall_time_begin, double spall_time_end, const char *name, int name_len); -#ifdef SPALL_BUFFER_PROFILING - #define SPALL_BUFFER_PROFILE_BEGIN() double spall_time_begin = (SPALL_BUFFER_PROFILING_GET_TIME()) - // Don't call this with anything other than a string literal - #define SPALL_BUFFER_PROFILE_END(name) spall__buffer_profile(ctx, wb, spall_time_begin, (SPALL_BUFFER_PROFILING_GET_TIME()), "" name "", sizeof("" name "") - 1) -#else - #define SPALL_BUFFER_PROFILE_BEGIN() - #define SPALL_BUFFER_PROFILE_END(name) -#endif - -SPALL_FN SPALL_FORCEINLINE bool spall__file_write(SpallProfile *ctx, const void *p, size_t n) { - if (!ctx->data) return false; -#ifdef SPALL_DEBUG - if (feof((FILE *)ctx->data)) return false; - if (ferror((FILE *)ctx->data)) return false; -#endif - - if (fwrite(p, n, 1, (FILE *)ctx->data) != 1) return false; - return true; -} -SPALL_FN bool spall__file_flush(SpallProfile *ctx) { - if (!ctx->data) return false; - if (fflush((FILE *)ctx->data)) return false; - return true; -} -SPALL_FN void spall__file_close(SpallProfile *ctx) { - if (!ctx->data) return; - - if (ctx->is_json) { -#ifdef SPALL_DEBUG - if (!feof((FILE *)ctx->data) && !ferror((FILE *)ctx->data)) -#endif - { - fseek((FILE *)ctx->data, -2, SEEK_CUR); // seek back to overwrite trailing comma - fwrite("\n]}\n", sizeof("\n]}\n") - 1, 1, (FILE *)ctx->data); - } - } - fflush((FILE *)ctx->data); - fclose((FILE *)ctx->data); - ctx->data = NULL; -} - -SPALL_FN SPALL_FORCEINLINE bool spall__buffer_flush(SpallProfile *ctx, SpallBuffer *wb) { - // precon: wb - // precon: wb->data - // precon: wb->head <= wb->length - // precon: !ctx || ctx->write -#ifdef SPALL_DEBUG - if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL) -#endif - - if (wb->head && ctx) { - SPALL_BUFFER_PROFILE_BEGIN(); - if (!ctx->write) return false; - if (ctx->write == spall__file_write) { - if (!spall__file_write(ctx, wb->data, wb->head)) return false; - } else { - if (!ctx->write(ctx, wb->data, wb->head)) return false; - } - SPALL_BUFFER_PROFILE_END("Buffer Flush"); - } - wb->head = 0; - return true; -} - -SPALL_FN SPALL_FORCEINLINE bool spall__buffer_write(SpallProfile *ctx, SpallBuffer *wb, void *p, size_t n) { - // precon: !wb || wb->head < wb->length - // precon: !ctx || ctx->write - if (!wb) return ctx->write && ctx->write(ctx, p, n); -#ifdef SPALL_DEBUG - if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL) -#endif - if (wb->head + n > wb->length && !spall__buffer_flush(ctx, wb)) return false; - if (n > wb->length) { - SPALL_BUFFER_PROFILE_BEGIN(); - if (!ctx->write || !ctx->write(ctx, p, n)) return false; - SPALL_BUFFER_PROFILE_END("Unbuffered Write"); - return true; - } - memcpy((char *)wb->data + wb->head, p, n); - wb->head += n; - return true; -} - -SPALL_FN bool spall_buffer_flush(SpallProfile *ctx, SpallBuffer *wb) { -#ifdef SPALL_DEBUG - if (!wb) return false; - if (!wb->data) return false; -#endif - - if (!spall__buffer_flush(ctx, wb)) return false; - return true; -} - -SPALL_FN bool spall_buffer_init(SpallProfile *ctx, SpallBuffer *wb) { - if (!spall_buffer_flush(NULL, wb)) return false; - wb->ctx = ctx; - return true; -} -SPALL_FN bool spall_buffer_quit(SpallProfile *ctx, SpallBuffer *wb) { - if (!spall_buffer_flush(ctx, wb)) return false; - wb->ctx = NULL; - return true; -} - -SPALL_FN bool spall_buffer_abort(SpallBuffer *wb) { - if (!wb) return false; - wb->ctx = NULL; - if (!spall__buffer_flush(NULL, wb)) return false; - return true; -} - -SPALL_FN size_t spall_build_header(void *buffer, size_t rem_size, double timestamp_unit) { - size_t header_size = sizeof(SpallHeader); - if (header_size > rem_size) { - return 0; - } - - SpallHeader *header = (SpallHeader *)buffer; - header->magic_header = 0x0BADF00D; - header->version = 1; - header->timestamp_unit = timestamp_unit; - header->must_be_0 = 0; - return header_size; -} -SPALL_FN SPALL_FORCEINLINE size_t spall_build_begin(void *buffer, size_t rem_size, const char *name, signed long name_len, const char *args, signed long args_len, double when, uint32_t tid, uint32_t pid) { - SpallBeginEventMax *ev = (SpallBeginEventMax *)buffer; - uint8_t trunc_name_len = (uint8_t)SPALL_MIN(name_len, 255); // will be interpreted as truncated in the app (?) - uint8_t trunc_args_len = (uint8_t)SPALL_MIN(args_len, 255); // will be interpreted as truncated in the app (?) - - size_t ev_size = sizeof(SpallBeginEvent) + trunc_name_len + trunc_args_len; - if (ev_size > rem_size) { - return 0; - } - - ev->event.type = SpallEventType_Begin; - ev->event.category = 0; - ev->event.pid = pid; - ev->event.tid = tid; - ev->event.when = when; - ev->event.name_length = trunc_name_len; - ev->event.args_length = trunc_args_len; - memcpy(ev->name_bytes, name, trunc_name_len); - memcpy(ev->name_bytes + trunc_name_len, args, trunc_args_len); - - return ev_size; -} -SPALL_FN SPALL_FORCEINLINE size_t spall_build_end(void *buffer, size_t rem_size, double when, uint32_t tid, uint32_t pid) { - size_t ev_size = sizeof(SpallEndEvent); - if (ev_size > rem_size) { - return 0; - } - - SpallEndEvent *ev = (SpallEndEvent *)buffer; - ev->type = SpallEventType_End; - ev->pid = pid; - ev->tid = tid; - ev->when = when; - - return ev_size; -} - -SPALL_FN void spall_quit(SpallProfile *ctx) { - if (!ctx) return; - if (ctx->close) ctx->close(ctx); - - memset(ctx, 0, sizeof(*ctx)); -} - -SPALL_FN SpallProfile spall_init_callbacks(double timestamp_unit, - SpallWriteCallback write, - SpallFlushCallback flush, - SpallCloseCallback close, - void *userdata, - bool is_json) { - SpallProfile ctx; - memset(&ctx, 0, sizeof(ctx)); - if (timestamp_unit < 0) return ctx; - ctx.timestamp_unit = timestamp_unit; - ctx.is_json = is_json; - ctx.data = userdata; - ctx.write = write; - ctx.flush = flush; - ctx.close = close; - - if (ctx.is_json) { - if (!ctx.write(&ctx, "{\"traceEvents\":[\n", sizeof("{\"traceEvents\":[\n") - 1)) { - spall_quit(&ctx); - return ctx; - } - } else { - SpallHeader header; - size_t len = spall_build_header(&header, sizeof(header), timestamp_unit); - if (!ctx.write(&ctx, &header, len)) { - spall_quit(&ctx); - return ctx; - } - } - - return ctx; -} - -SPALL_FN SpallProfile spall_init_file_ex(const char *filename, double timestamp_unit, bool is_json) { - SpallProfile ctx; - memset(&ctx, 0, sizeof(ctx)); - if (!filename) return ctx; - ctx.data = fopen(filename, "wb"); // TODO: handle utf8 and long paths on windows - if (ctx.data) { // basically freopen() but we don't want to force users to lug along another macro define - fclose((FILE *)ctx.data); - ctx.data = fopen(filename, "ab"); - } - if (!ctx.data) { - spall_quit(&ctx); - return ctx; - } - ctx = spall_init_callbacks(timestamp_unit, spall__file_write, spall__file_flush, spall__file_close, ctx.data, is_json); - return ctx; -} - -SPALL_FN SpallProfile spall_init_file(const char *filename, double timestamp_unit) { return spall_init_file_ex(filename, timestamp_unit, false); } -SPALL_FN SpallProfile spall_init_file_json(const char *filename, double timestamp_unit) { return spall_init_file_ex(filename, timestamp_unit, true); } - -SPALL_FN bool spall_flush(SpallProfile *ctx) { -#ifdef SPALL_DEBUG - if (!ctx) return false; -#endif - - if (!ctx->flush || !ctx->flush(ctx)) return false; - return true; -} - -SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_args(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, const char *args, signed long args_len, double when, uint32_t tid, uint32_t pid) { -#ifdef SPALL_DEBUG - if (!ctx) return false; - if (!name) return false; - if (name_len <= 0) return false; - if (!wb) return false; -#endif - - if (ctx->is_json) { - char buf[1024]; - int buf_len = snprintf(buf, sizeof(buf), - "{\"ph\":\"B\",\"ts\":%f,\"pid\":%u,\"tid\":%u,\"name\":\"%.*s\",\"args\":\"%.*s\"},\n", - when * ctx->timestamp_unit, pid, tid, (int)(uint8_t)name_len, name, (int)(uint8_t)args_len, args); - if (buf_len <= 0) return false; - if (buf_len >= sizeof(buf)) return false; - if (!spall__buffer_write(ctx, wb, buf, buf_len)) return false; - } else { - if ((wb->head + sizeof(SpallBeginEventMax)) > wb->length) { - if (!spall__buffer_flush(ctx, wb)) { - return false; - } - } - - wb->head += spall_build_begin((char *)wb->data + wb->head, wb->length - wb->head, name, name_len, args, args_len, when, tid, pid); - } - - return true; -} - -SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_ex(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when, uint32_t tid, uint32_t pid) { - return spall_buffer_begin_args(ctx, wb, name, name_len, "", 0, when, tid, pid); -} - -SPALL_FN bool spall_buffer_begin(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when) { - return spall_buffer_begin_args(ctx, wb, name, name_len, "", 0, when, 0, 0); -} - -SPALL_FN SPALL_FORCEINLINE bool spall_buffer_end_ex(SpallProfile *ctx, SpallBuffer *wb, double when, uint32_t tid, uint32_t pid) { -#ifdef SPALL_DEBUG - if (!ctx) return false; - if (!wb) return false; -#endif - - if (ctx->is_json) { - char buf[512]; - int buf_len = snprintf(buf, sizeof(buf), - "{\"ph\":\"E\",\"ts\":%f,\"pid\":%u,\"tid\":%u},\n", - when * ctx->timestamp_unit, pid, tid); - if (buf_len <= 0) return false; - if (buf_len >= sizeof(buf)) return false; - if (!spall__buffer_write(ctx, wb, buf, buf_len)) return false; - } else { - if ((wb->head + sizeof(SpallEndEvent)) > wb->length) { - if (!spall__buffer_flush(ctx, wb)) { - return false; - } - } - - wb->head += spall_build_end((char *)wb->data + wb->head, wb->length - wb->head, when, tid, pid); - } - - return true; -} - -SPALL_FN bool spall_buffer_end(SpallProfile *ctx, SpallBuffer *wb, double when) { return spall_buffer_end_ex(ctx, wb, when, 0, 0); } - -SPALL_FN SPALL_FORCEINLINE void spall__buffer_profile(SpallProfile *ctx, SpallBuffer *wb, double spall_time_begin, double spall_time_end, const char *name, int name_len) { - // precon: ctx - // precon: ctx->write - char temp_buffer_data[2048]; - SpallBuffer temp_buffer = {temp_buffer_data, sizeof(temp_buffer_data)}; - if (!spall_buffer_begin_ex(ctx, &temp_buffer, name, name_len, spall_time_begin, (uint32_t)(uintptr_t)wb->data, 4222222222)) return; - if (!spall_buffer_end_ex(ctx, &temp_buffer, spall_time_end, (uint32_t)(uintptr_t)wb->data, 4222222222)) return; - if (ctx->write) ctx->write(ctx, temp_buffer_data, temp_buffer.head); -} - -#ifdef __cplusplus -} -#endif - -#endif // SPALL_H \ No newline at end of file diff --git a/src/text_editor/ui_ideas.txt b/src/text_editor/ui_ideas.txt deleted file mode 100644 index e6174a1..0000000 --- a/src/text_editor/ui_ideas.txt +++ /dev/null @@ -1,23 +0,0 @@ - -// @todo: multiple ui elements, add focus -// @todo: immediate mode interface for all this - -Buffer buffer; -Rect window_rect; -bool window_open = true; - -if (window_open) { - Node *window_node = MakeBox(rect, GenerateID()); - SetParent(window_node); - { - Node *close_button = Button("x"); - if (close_button->left_clicked) { - window_open = false; - } - - Node *node = TextField(&buffer); - if (node->text_changed) { - - } - } -} diff --git a/src/text_editor/utils.cpp b/src/text_editor/utils.cpp deleted file mode 100644 index d91e1c1..0000000 --- a/src/text_editor/utils.cpp +++ /dev/null @@ -1,12 +0,0 @@ -Rect2 GetScreenRect() { - Rect2 result = { - { 0, 0}, - {(float)GetRenderWidth(), (float)GetRenderHeight()} - }; - return result; -} - -bool AreEqual(float a, float b, float epsilon = 0.001f) { - bool result = (a - epsilon < b && a + epsilon > b); - return result; -} diff --git a/src/transcript_browser/_main.cpp b/src/transcript_browser/_main.cpp new file mode 100644 index 0000000..4622f86 --- /dev/null +++ b/src/transcript_browser/_main.cpp @@ -0,0 +1,384 @@ +#define BASIC_IMPL +#include "../basic/basic.h" +#include "../basic/filesystem.h" +#include "tests.h" + +#include +#include +#include + +Arena Perm; + +/* + +TODO: +- New threading model idea: I could just spin up a bunch of threads and then don't kill them. Just use them for searching! Each one would have it's own xarena and so on. +- Improve scrolling +- Highlight selected line +- Improve the looks of the application +*/ + +struct TimeString { + uint16_t hour; + uint16_t minute; + uint16_t second; + String string; +}; + +Array ParseSrtFile(Arena *arena, String filename) { + String content = ReadFile(*arena, filename); + Array lines = Split(*arena, content, "\n"); + + IterRemove(lines) { + IterRemovePrepare(lines); + it = Trim(it); + if (it.len == 0) remove_item = true; + } + + long section_number = 1; + Array time_strings = {*arena}; + for (int i = 0; i < lines.len;) { + String it0 = lines[i++]; + long num = strtol(it0.data, NULL, 10); + Assert(section_number == num); + section_number += 1; + + TimeString item = {}; + String it1 = lines[i++]; + item.hour = (uint16_t)strtol(it1.data, NULL, 10); + item.minute = (uint16_t)strtol(it1.data + 3, NULL, 10); + item.second = (uint16_t)strtol(it1.data + 6, NULL, 10); + + String next_section_number = Format(*arena, "%d", section_number); + while (i < lines.len && lines[i] != next_section_number) { + String it = lines[i]; + item.string = lines[i]; + time_strings.add(item); + i += 1; + } + } + + IterRemove(time_strings) { + IterRemovePrepare(time_strings); + if (i > 0 && AreEqual(time_strings[i - 1].string, time_strings[i].string, true)) { + remove_item = true; + } + } + + return time_strings; +} + +struct TimeFile { + Array time_strings; + String file; +}; + +struct ParseThreadIO { + Array input_files; + + // output + Arena *arena; + Array time_files; +}; + +void ParseThreadEntry(ParseThreadIO *io) { + io->arena = AllocArena(); + io->time_files.allocator = *io->arena; + For(io->input_files) { + Array time_strings = ParseSrtFile(io->arena, it); + io->time_files.add({time_strings, it}); + } +} + +struct XToTimeString { + String string; // String inside transcript arena + uint16_t hour; + uint16_t minute; + uint16_t second; + String filepath; +}; +Arena XArena; + +void AddFolder(String folder, Array *filenames, Array *x_to_time_string) { + Scratch scratch; + + Array srt_files = {scratch}; + for (FileIter iter = IterateFiles(scratch, folder); IsValid(iter); Advance(&iter)) { + String file = Copy(Perm, iter.absolute_path); + filenames->add(file); + if (EndsWith(iter.filename, ".srt")) { + srt_files.add(file); + } + } + + int64_t thread_count = 16; + Array threads = {scratch}; + int64_t files_per_thread = srt_files.len / thread_count; + int64_t remainder = srt_files.len % thread_count; + int64_t fi = 0; + + Array io = {scratch}; + io.reserve(thread_count); + for (int ti = 0; ti < thread_count; ti += 1) { + Array files = {scratch}; + for (int i = 0; fi < srt_files.len && i < files_per_thread + remainder; fi += 1, i += 1) { + files.add(srt_files[fi]); + } + if (remainder) remainder = 0; + + ParseThreadIO *i = io.alloc(); + i->input_files = files; + threads.add(new std::thread(ParseThreadEntry, i)); + } + + For(threads) { + it->join(); + delete it; + } + + ForItem(it_io, io) { + ForItem(it_time_file, it_io.time_files) { + For(it_time_file.time_strings) { + String s = Copy(XArena, it.string); + s.data[s.len] = ' '; + x_to_time_string->add({s, it.hour, it.minute, it.second, it_time_file.file}); + } + } + Release(it_io.arena); + } +} + +// +// Searching thread +// + +Arena MatchesArena; +Array Matches = {MatchesArena}; +XToTimeString *ItemFound; +Array Prompt; // system allocated + +std::mutex SearchThreadArrayMutex; +std::binary_semaphore SearchThreadSemaphore{0}; +bool SearchThreadStopSearching = false; +bool SearchThreadRunning = true; +void SearchThreadEntry() { + InitArena(&MatchesArena); + for (;;) { + SearchThreadSemaphore.acquire(); + if (!SearchThreadRunning) break; + SearchThreadStopSearching = false; + + if (Prompt.len) { + SearchThreadArrayMutex.lock(); + { + Matches.clear(); + } + SearchThreadArrayMutex.unlock(); + + String buffer = {(char *)XArena.data, (int64_t)XArena.len}; + String find = {Prompt.data, Prompt.len}; + int64_t index = 0; + while (Seek(buffer, find, &index, SeekFlag_IgnoreCase)) { + String found = {buffer.data + index, find.len}; + SearchThreadArrayMutex.lock(); + { + Matches.add(found); + } + SearchThreadArrayMutex.unlock(); + + if (SearchThreadStopSearching) break; + buffer = buffer.skip(index + find.len); + } + } + } +} + +void SearchThreadClose(std::thread &thread) { + SearchThreadRunning = false; + SearchThreadSemaphore.release(); + thread.join(); +} + +int main() { + InitOS(); + InitScratch(); + InitArena(&Perm); + InitArena(&XArena); + Arena *frame_arena = AllocArena(); + XArena.align = 0; + + String start_string = "read=D:/zizek"; + For(start_string) Prompt.add(it); + + std::thread search_thread(SearchThreadEntry); + int64_t chosen_text = 0; + int64_t match_search_offset = 0; + Array filenames = {}; + Array x_to_time_string = {}; + + InitWindow(1920, 1080, "Transcript Browser"); + SetWindowState(FLAG_WINDOW_RESIZABLE); + SetTargetFPS(60); + Font font = LoadFontEx("C:/Windows/Fonts/consola.ttf", 20, 0, 250); + while (!WindowShouldClose()) { + Clear(frame_arena); + + for (int key = GetCharPressed(); key; key = GetCharPressed()) { + UTF8Result utf8 = UTF32ToUTF8(key); + if (utf8.error) { + Prompt.add('?'); + continue; + } + + for (int i = 0; i < utf8.len; i += 1) { + Prompt.add(utf8.out_str[i]); + match_search_offset = 0; + SearchThreadStopSearching = true; + SearchThreadSemaphore.release(); + } + } + + if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) { + if (ItemFound) { + ItemFound = NULL; + } else if (Prompt.len > 0) { + Prompt.pop(); + match_search_offset = 0; + SearchThreadStopSearching = true; + SearchThreadSemaphore.release(); + } + } + + int64_t offset_size = 1; + if (IsKeyDown(KEY_LEFT_CONTROL)) { + offset_size = 10; + } + if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) { + chosen_text += offset_size; + if (chosen_text > 10) match_search_offset += offset_size; + } + if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) { + chosen_text -= offset_size; + match_search_offset -= offset_size; + } + chosen_text = Clamp(chosen_text, (int64_t)0, Max(Matches.len - 1, (int64_t)0)); + match_search_offset = Clamp(match_search_offset, (int64_t)0, Max(Matches.len - 1 - 10, (int64_t)0)); + + if (IsKeyPressed(KEY_ENTER)) { + String prompt = {Prompt.data, Prompt.len}; + if (StartsWith(prompt, "read=")) { + Prompt.add('\0'); + AddFolder(prompt.skip(5), &filenames, &x_to_time_string); + Prompt.clear(); + } else if (ItemFound) { + String base = ChopLastPeriod(ItemFound->filepath); // .srt + base = ChopLastPeriod(base); // .en + + For(filenames) { + if (StartsWith(it, base)) { + if (EndsWith(it, ".mkv") || EndsWith(it, ".webm") || EndsWith(it, ".mp4")) { + int seconds = ItemFound->hour * 60 * 60 + ItemFound->minute * 60 + ItemFound->second; + String copy = Copy(*frame_arena, it); + for (int i = 0; i < copy.len; i += 1) + if (copy.data[i] == '/') copy.data[i] = '\\'; + String args = Format(*frame_arena, "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe --start-time %d \"%.*s\"", seconds, FmtString(copy)); + printf("%.*s\n", FmtString(args)); + RunEx(args); + } + } + } + } else if (Matches.len) { + String string = Matches[chosen_text]; + For(x_to_time_string) { + uintptr_t begin = (uintptr_t)(it.string.data); + uintptr_t end = (uintptr_t)(it.string.data + it.string.len); + uintptr_t needle = (uintptr_t)string.data; + + if (needle >= begin && needle < end) { + ItemFound = ⁢ + break; + } + } + } + } + BeginDrawing(); + ClearBackground(RAYWHITE); + + float font_size = 20; + float y = 0; + int xwidth = (int)MeasureTextEx(font, "_", font_size, 1).x; + + if (ItemFound) { + uintptr_t begin_region = (uintptr_t)XArena.data; + uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; + + uintptr_t begin = (uintptr_t)(ItemFound->string.data - 1000); + uintptr_t end = (uintptr_t)(ItemFound->string.data + 1000); + + begin = Clamp(begin, begin_region, end_region); + end = Clamp(end, begin_region, end_region); + String string = {(char *)begin, (int64_t)(end - begin)}; + + String filename = SkipToLastSlash(ItemFound->filepath); + DrawTextEx(font, filename.data, {0, y}, font_size, 1, BLACK); + y += font_size; + + int per_line = (GetRenderWidth() / xwidth) - 20; + + for (String it = string; it.len;) { + String line = it.get_prefix(per_line); + + if (ItemFound->string.data >= line.data && ItemFound->string.data < line.data + line.len) { + DrawRectangleLines(0, (int)(y + font_size), GetRenderWidth(), 2, SKYBLUE); + } + + String line_terminated = Copy(*frame_arena, line); + DrawTextEx(font, line_terminated.data, {0, y}, font_size, 1, DARKGRAY); + + y += font_size; + it = it.skip(per_line); + } + } else { + Prompt.add('\0'); + DrawTextEx(font, "> ", {0, y}, font_size, 1, BLACK); + DrawTextEx(font, Prompt.data, {(float)xwidth * 3, y}, font_size, 1, BLACK); + Prompt.pop(); + y += font_size; + + int64_t chars_per_line = GetRenderWidth() / xwidth - Prompt.len; + + SearchThreadArrayMutex.lock(); + for (int64_t i = match_search_offset; i < Matches.len; i += 1) { + String it = Matches[i]; + uintptr_t begin_region = (uintptr_t)XArena.data; + uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; + + uintptr_t begin = (uintptr_t)(it.data - chars_per_line / 2); + uintptr_t end = (uintptr_t)(it.data + chars_per_line / 2); + + begin = Clamp(begin, begin_region, end_region); + end = Clamp(end, begin_region, end_region); + String string = Copy(*frame_arena, {(char *)begin, (int64_t)(end - begin)}); + + String string_first = Copy(*frame_arena, {(char *)begin, (int64_t)(it.data - begin)}); + String string_middle = Copy(*frame_arena, it); + int width = (int)MeasureTextEx(font, string_first.data, font_size, 1).x; + if (chosen_text == i) DrawRectangleLines(0, (int)(y + font_size), GetRenderWidth(), 2, SKYBLUE); + + String num = Format(*frame_arena, "%d", i); + DrawTextEx(font, num.data, {0, y}, font_size, 1, DARKGRAY); + DrawTextEx(font, string.data, {(float)xwidth * 4, y}, font_size, 1, DARKGRAY); + DrawTextEx(font, string_middle.data, {(float)xwidth * 4 + (float)width, y}, font_size, 1, SKYBLUE); + + y += font_size; + if (y > GetRenderHeight()) break; + } + SearchThreadArrayMutex.unlock(); + } + + if (IsKeyDown(KEY_F1)) DrawFPS(0, 0); + EndDrawing(); + } + CloseWindow(); + SearchThreadClose(search_thread); +} diff --git a/src/transcript_browser/main.cpp b/src/transcript_browser/main.cpp index 1afb615..b820111 100644 --- a/src/transcript_browser/main.cpp +++ b/src/transcript_browser/main.cpp @@ -1,385 +1,501 @@ -#define BASIC_IMPL -#include "../pdf_browser/basic.h" -#include "filesystem.h" -#include "tests.h" -#include "raylib.h" - -#include -#include -#include - -Arena Perm; - -/* - -TODO: -- New threading model idea: I could just spin up a bunch of threads and then don't kill them. Just use them for searching! Each one would have it's own xarena and so on. -- Improve scrolling -- Highlight selected line -- Improve the looks of the application -*/ - -struct TimeString { - uint16_t hour; - uint16_t minute; - uint16_t second; - String string; -}; - -Array ParseSrtFile(Arena *arena, String filename) { - String content = ReadFile(*arena, filename); - Array lines = Split(*arena, content, "\n"); - - IterRemove(lines) { - IterRemovePrepare(lines); - it = Trim(it); - if (it.len == 0) remove_item = true; - } - - long section_number = 1; - Array time_strings = {*arena}; - for (int i = 0; i < lines.len;) { - String it0 = lines[i++]; - long num = strtol(it0.data, NULL, 10); - Assert(section_number == num); - section_number += 1; - - TimeString item = {}; - String it1 = lines[i++]; - item.hour = (uint16_t)strtol(it1.data, NULL, 10); - item.minute = (uint16_t)strtol(it1.data + 3, NULL, 10); - item.second = (uint16_t)strtol(it1.data + 6, NULL, 10); - - String next_section_number = Format(*arena, "%d", section_number); - while (i < lines.len && lines[i] != next_section_number) { - String it = lines[i]; - item.string = lines[i]; - time_strings.add(item); - i += 1; - } - } - - IterRemove(time_strings) { - IterRemovePrepare(time_strings); - if (i > 0 && AreEqual(time_strings[i - 1].string, time_strings[i].string, true)) { - remove_item = true; - } - } - - return time_strings; -} - -struct TimeFile { - Array time_strings; - String file; -}; - -struct ParseThreadIO { - Array input_files; - - // output - Arena *arena; - Array time_files; -}; - -void ParseThreadEntry(ParseThreadIO *io) { - io->arena = AllocArena(); - io->time_files.allocator = *io->arena; - For(io->input_files) { - Array time_strings = ParseSrtFile(io->arena, it); - io->time_files.add({time_strings, it}); - } -} - -struct XToTimeString { - String string; // String inside transcript arena - uint16_t hour; - uint16_t minute; - uint16_t second; - String filepath; -}; -Arena XArena; - -void AddFolder(String folder, Array *filenames, Array *x_to_time_string) { - Scratch scratch; - - Array srt_files = {scratch}; - for (FileIter iter = IterateFiles(scratch, folder); IsValid(iter); Advance(&iter)) { - String file = Copy(Perm, iter.absolute_path); - filenames->add(file); - if (EndsWith(iter.filename, ".srt")) { - srt_files.add(file); - } - } - - int64_t thread_count = 16; - Array threads = {scratch}; - int64_t files_per_thread = srt_files.len / thread_count; - int64_t remainder = srt_files.len % thread_count; - int64_t fi = 0; - - Array io = {scratch}; - io.reserve(thread_count); - for (int ti = 0; ti < thread_count; ti += 1) { - Array files = {scratch}; - for (int i = 0; fi < srt_files.len && i < files_per_thread + remainder; fi += 1, i += 1) { - files.add(srt_files[fi]); - } - if (remainder) remainder = 0; - - ParseThreadIO *i = io.alloc(); - i->input_files = files; - threads.add(new std::thread(ParseThreadEntry, i)); - } - - For(threads) { - it->join(); - delete it; - } - - ForItem(it_io, io) { - ForItem(it_time_file, it_io.time_files) { - For(it_time_file.time_strings) { - String s = Copy(XArena, it.string); - s.data[s.len] = ' '; - x_to_time_string->add({s, it.hour, it.minute, it.second, it_time_file.file}); - } - } - Release(it_io.arena); - } -} - -// -// Searching thread -// - -Arena MatchesArena; -Array Matches = {MatchesArena}; -XToTimeString *ItemFound; -Array Prompt; // system allocated - -std::mutex SearchThreadArrayMutex; -std::binary_semaphore SearchThreadSemaphore{0}; -bool SearchThreadStopSearching = false; -bool SearchThreadRunning = true; -void SearchThreadEntry() { - InitArena(&MatchesArena); - for (;;) { - SearchThreadSemaphore.acquire(); - if (!SearchThreadRunning) break; - SearchThreadStopSearching = false; - - if (Prompt.len) { - SearchThreadArrayMutex.lock(); - { - Matches.clear(); - } - SearchThreadArrayMutex.unlock(); - - String buffer = {(char *)XArena.data, (int64_t)XArena.len}; - String find = {Prompt.data, Prompt.len}; - int64_t index = 0; - while (Seek(buffer, find, &index, SeekFlag_IgnoreCase)) { - String found = {buffer.data + index, find.len}; - SearchThreadArrayMutex.lock(); - { - Matches.add(found); - } - SearchThreadArrayMutex.unlock(); - - if (SearchThreadStopSearching) break; - buffer = buffer.skip(index + find.len); - } - } - } -} - -void SearchThreadClose(std::thread &thread) { - SearchThreadRunning = false; - SearchThreadSemaphore.release(); - thread.join(); -} - -int main() { - InitOS(); - InitScratch(); - InitArena(&Perm); - InitArena(&XArena); - Arena *frame_arena = AllocArena(); - XArena.align = 0; - - String start_string = "read=D:/zizek"; - For(start_string) Prompt.add(it); - - std::thread search_thread(SearchThreadEntry); - int64_t chosen_text = 0; - int64_t match_search_offset = 0; - Array filenames = {}; - Array x_to_time_string = {}; - - InitWindow(1920, 1080, "Transcript Browser"); - SetWindowState(FLAG_WINDOW_RESIZABLE); - SetTargetFPS(60); - Font font = LoadFontEx("C:/Windows/Fonts/consola.ttf", 20, 0, 250); - while (!WindowShouldClose()) { - Clear(frame_arena); - - for (int key = GetCharPressed(); key; key = GetCharPressed()) { - UTF8Result utf8 = UTF32ToUTF8(key); - if (utf8.error) { - Prompt.add('?'); - continue; - } - - for (int i = 0; i < utf8.len; i += 1) { - Prompt.add(utf8.out_str[i]); - match_search_offset = 0; - SearchThreadStopSearching = true; - SearchThreadSemaphore.release(); - } - } - - if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) { - if (ItemFound) { - ItemFound = NULL; - } else if (Prompt.len > 0) { - Prompt.pop(); - match_search_offset = 0; - SearchThreadStopSearching = true; - SearchThreadSemaphore.release(); - } - } - - int64_t offset_size = 1; - if (IsKeyDown(KEY_LEFT_CONTROL)) { - offset_size = 10; - } - if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) { - chosen_text += offset_size; - if (chosen_text > 10) match_search_offset += offset_size; - } - if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) { - chosen_text -= offset_size; - match_search_offset -= offset_size; - } - chosen_text = Clamp(chosen_text, (int64_t)0, Max(Matches.len - 1, (int64_t)0)); - match_search_offset = Clamp(match_search_offset, (int64_t)0, Max(Matches.len - 1 - 10, (int64_t)0)); - - if (IsKeyPressed(KEY_ENTER)) { - String prompt = {Prompt.data, Prompt.len}; - if (StartsWith(prompt, "read=")) { - Prompt.add('\0'); - AddFolder(prompt.skip(5), &filenames, &x_to_time_string); - Prompt.clear(); - } else if (ItemFound) { - String base = ChopLastPeriod(ItemFound->filepath); // .srt - base = ChopLastPeriod(base); // .en - - For(filenames) { - if (StartsWith(it, base)) { - if (EndsWith(it, ".mkv") || EndsWith(it, ".webm") || EndsWith(it, ".mp4")) { - int seconds = ItemFound->hour * 60 * 60 + ItemFound->minute * 60 + ItemFound->second; - String copy = Copy(*frame_arena, it); - for (int i = 0; i < copy.len; i += 1) - if (copy.data[i] == '/') copy.data[i] = '\\'; - String args = Format(*frame_arena, "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe --start-time %d \"%.*s\"", seconds, FmtString(copy)); - printf("%.*s\n", FmtString(args)); - RunEx(args); - } - } - } - } else if (Matches.len) { - String string = Matches[chosen_text]; - For(x_to_time_string) { - uintptr_t begin = (uintptr_t)(it.string.data); - uintptr_t end = (uintptr_t)(it.string.data + it.string.len); - uintptr_t needle = (uintptr_t)string.data; - - if (needle >= begin && needle < end) { - ItemFound = ⁢ - break; - } - } - } - } - BeginDrawing(); - ClearBackground(RAYWHITE); - - float font_size = 20; - float y = 0; - int xwidth = (int)MeasureTextEx(font, "_", font_size, 1).x; - - if (ItemFound) { - uintptr_t begin_region = (uintptr_t)XArena.data; - uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; - - uintptr_t begin = (uintptr_t)(ItemFound->string.data - 1000); - uintptr_t end = (uintptr_t)(ItemFound->string.data + 1000); - - begin = Clamp(begin, begin_region, end_region); - end = Clamp(end, begin_region, end_region); - String string = {(char *)begin, (int64_t)(end - begin)}; - - String filename = SkipToLastSlash(ItemFound->filepath); - DrawTextEx(font, filename.data, {0, y}, font_size, 1, BLACK); - y += font_size; - - int per_line = (GetRenderWidth() / xwidth) - 20; - - for (String it = string; it.len;) { - String line = it.get_prefix(per_line); - - if (ItemFound->string.data >= line.data && ItemFound->string.data < line.data + line.len) { - DrawRectangleLines(0, (int)(y + font_size), GetRenderWidth(), 2, SKYBLUE); - } - - String line_terminated = Copy(*frame_arena, line); - DrawTextEx(font, line_terminated.data, {0, y}, font_size, 1, DARKGRAY); - - y += font_size; - it = it.skip(per_line); - } - } else { - Prompt.add('\0'); - DrawTextEx(font, "> ", {0, y}, font_size, 1, BLACK); - DrawTextEx(font, Prompt.data, {(float)xwidth * 3, y}, font_size, 1, BLACK); - Prompt.pop(); - y += font_size; - - int64_t chars_per_line = GetRenderWidth() / xwidth - Prompt.len; - - SearchThreadArrayMutex.lock(); - for (int64_t i = match_search_offset; i < Matches.len; i += 1) { - String it = Matches[i]; - uintptr_t begin_region = (uintptr_t)XArena.data; - uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; - - uintptr_t begin = (uintptr_t)(it.data - chars_per_line / 2); - uintptr_t end = (uintptr_t)(it.data + chars_per_line / 2); - - begin = Clamp(begin, begin_region, end_region); - end = Clamp(end, begin_region, end_region); - String string = Copy(*frame_arena, {(char *)begin, (int64_t)(end - begin)}); - - String string_first = Copy(*frame_arena, {(char *)begin, (int64_t)(it.data - begin)}); - String string_middle = Copy(*frame_arena, it); - int width = (int)MeasureTextEx(font, string_first.data, font_size, 1).x; - if (chosen_text == i) DrawRectangleLines(0, (int)(y + font_size), GetRenderWidth(), 2, SKYBLUE); - - String num = Format(*frame_arena, "%d", i); - DrawTextEx(font, num.data, {0, y}, font_size, 1, DARKGRAY); - DrawTextEx(font, string.data, {(float)xwidth * 4, y}, font_size, 1, DARKGRAY); - DrawTextEx(font, string_middle.data, {(float)xwidth * 4 + (float)width, y}, font_size, 1, SKYBLUE); - - y += font_size; - if (y > GetRenderHeight()) break; - } - SearchThreadArrayMutex.unlock(); - } - - if (IsKeyDown(KEY_F1)) DrawFPS(0, 0); - EndDrawing(); - } - CloseWindow(); - SearchThreadClose(search_thread); -} +#define BASIC_IMPL +#include "../basic/basic.h" +#include "../basic/filesystem.h" + +#include +#include +#include +#include + +#include "imgui.h" +#include "imgui_impl_sdl2.h" +#include "imgui_impl_opengl3.h" +#include +#include + +Arena Perm; + +/* + +TODO: +- New threading model idea: I could just spin up a bunch of threads and then don't kill them. Just use them for searching! Each one would have it's own xarena and so on. +- Improve scrolling +- Highlight selected line +- Improve the looks of the application +*/ + +struct TimeString { + uint16_t hour; + uint16_t minute; + uint16_t second; + String string; +}; + +Array ParseSrtFile(Arena *arena, String filename) { + String content = ReadFile(*arena, filename); + Array lines = Split(*arena, content, "\n"); + + IterRemove(lines) { + IterRemovePrepare(lines); + it = Trim(it); + if (it.len == 0) remove_item = true; + } + + long section_number = 1; + Array time_strings = {*arena}; + for (int i = 0; i < lines.len;) { + String it0 = lines[i++]; + long num = strtol(it0.data, NULL, 10); + Assert(section_number == num); + section_number += 1; + + TimeString item = {}; + String it1 = lines[i++]; + item.hour = (uint16_t)strtol(it1.data, NULL, 10); + item.minute = (uint16_t)strtol(it1.data + 3, NULL, 10); + item.second = (uint16_t)strtol(it1.data + 6, NULL, 10); + + String next_section_number = Format(*arena, "%d", section_number); + while (i < lines.len && lines[i] != next_section_number) { + String it = lines[i]; + item.string = lines[i]; + time_strings.add(item); + i += 1; + } + } + + IterRemove(time_strings) { + IterRemovePrepare(time_strings); + if (i > 0 && AreEqual(time_strings[i - 1].string, time_strings[i].string, true)) { + remove_item = true; + } + } + + return time_strings; +} + +struct TimeFile { + Array time_strings; + String file; +}; + +struct ParseThreadIO { + Array input_files; + + // output + Arena *arena; + Array time_files; +}; + +void ParseThreadEntry(ParseThreadIO *io) { + io->arena = AllocArena(); + io->time_files.allocator = *io->arena; + For(io->input_files) { + Array time_strings = ParseSrtFile(io->arena, it); + io->time_files.add({time_strings, it}); + } +} + +struct XToTimeString { + String string; // String inside transcript arena + uint16_t hour; + uint16_t minute; + uint16_t second; + String filepath; +}; +Arena XArena; + +void AddFolder(String folder, Array *filenames, Array *x_to_time_string) { + Scratch scratch; + + Array srt_files = {scratch}; + for (FileIter iter = IterateFiles(scratch, folder); IsValid(iter); Advance(&iter)) { + String file = Copy(Perm, iter.absolute_path); + filenames->add(file); + if (EndsWith(iter.filename, ".srt")) { + srt_files.add(file); + } + } + + int64_t thread_count = 16; + Array threads = {scratch}; + int64_t files_per_thread = srt_files.len / thread_count; + int64_t remainder = srt_files.len % thread_count; + int64_t fi = 0; + + Array io = {scratch}; + io.reserve(thread_count); + for (int ti = 0; ti < thread_count; ti += 1) { + Array files = {scratch}; + for (int i = 0; fi < srt_files.len && i < files_per_thread + remainder; fi += 1, i += 1) { + files.add(srt_files[fi]); + } + if (remainder) remainder = 0; + + ParseThreadIO *i = io.alloc(); + i->input_files = files; + threads.add(new std::thread(ParseThreadEntry, i)); + } + + For(threads) { + it->join(); + delete it; + } + + ForItem(it_io, io) { + ForItem(it_time_file, it_io.time_files) { + For(it_time_file.time_strings) { + String s = Copy(XArena, it.string); + s.data[s.len] = ' '; + x_to_time_string->add({s, it.hour, it.minute, it.second, it_time_file.file}); + } + } + Release(it_io.arena); + } +} + +// +// Searching thread +// + +Arena MatchesArena; +Array Matches = {MatchesArena}; +XToTimeString *ItemFound; +char Prompt[256]; + +std::mutex SearchThreadArrayMutex; +std::binary_semaphore SearchThreadSemaphore{0}; +bool SearchThreadStopSearching = false; +bool SearchThreadRunning = true; +void SearchThreadEntry() { + InitArena(&MatchesArena); + for (;;) { + SearchThreadSemaphore.acquire(); + if (!SearchThreadRunning) break; + SearchThreadStopSearching = false; + + if (Prompt[0]) { + SearchThreadArrayMutex.lock(); + { + Matches.clear(); + } + SearchThreadArrayMutex.unlock(); + + String buffer = {(char *)XArena.data, (int64_t)XArena.len}; + String find = Prompt; + int64_t index = 0; + while (Seek(buffer, find, &index, SeekFlag_IgnoreCase)) { + String found = {buffer.data + index, find.len}; + SearchThreadArrayMutex.lock(); + { + Matches.add(found); + } + SearchThreadArrayMutex.unlock(); + + if (SearchThreadStopSearching) break; + buffer = buffer.skip(index + find.len); + } + } + } +} + +void SearchThreadClose(std::thread &thread) { + SearchThreadRunning = false; + SearchThreadSemaphore.release(); + thread.join(); +} + +XToTimeString *FindItem(Array &x_to_time_string, String string) { + For(x_to_time_string) { + uintptr_t begin = (uintptr_t)(it.string.data); + uintptr_t end = (uintptr_t)(it.string.data + it.string.len); + uintptr_t needle = (uintptr_t)string.data; + if (needle >= begin && needle < end) { + return ⁢ + } + } + return NULL; +} + +int main(int, char **) { + InitOS(); + InitScratch(); + InitArena(&Perm); + InitArena(&XArena); + XArena.align = 0; + + memcpy(Prompt, "read=D:/zizek", sizeof("read=D:/zizek")); + + std::thread search_thread(SearchThreadEntry); + int64_t chosen_text = 0; + int64_t match_search_offset = 0; + Array filenames = {}; + Array x_to_time_string = {}; + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { + printf("Error: %s\n", SDL_GetError()); + return -1; + } + + const char *glsl_version = "#version 150"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_Window *window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + if (window == nullptr) { + printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); + return -1; + } + + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO &io = ImGui::GetIO(); + (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + // ImGui::StyleColorsDark(); + ImGui::StyleColorsLight(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForOpenGL(window, gl_context); + ImGui_ImplOpenGL3_Init(glsl_version); + io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + bool enter_down = false; + int64_t frame = -1; + + // Main loop + bool done = false; + while (!done) { + Scratch frame_arena; + frame += 1; + + bool enter_press = false; + SDL_Event event; + while (SDL_PollEvent(&event)) { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_RETURN) { + if (enter_down == false) enter_press = true; + enter_down = true; + } + } else if (event.type == SDL_KEYUP) { + if (event.key.keysym.sym == SDLK_RETURN) { + enter_down = false; + } + } + if (event.type == SDL_QUIT) + done = true; + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + done = true; + } + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + +#if 1 + + { + + ImGuiWindowFlags window_flags = 0; + window_flags |= ImGuiWindowFlags_NoTitleBar; + window_flags |= ImGuiWindowFlags_NoScrollbar; + window_flags |= ImGuiWindowFlags_NoMove; + window_flags |= ImGuiWindowFlags_NoResize; + window_flags |= ImGuiWindowFlags_NoCollapse; + + const ImGuiViewport *main_viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(main_viewport->Size.x, 20), ImGuiCond_Always); + + ImGui::Begin("input", NULL, window_flags); + if (frame < 4) ImGui::SetKeyboardFocusHere(0); + if (ImGui::InputText("Input your query", Prompt, sizeof(Prompt))) { + match_search_offset = 0; + SearchThreadStopSearching = true; + SearchThreadSemaphore.release(); + } + ImGui::End(); + + if (enter_press) { + String prompt = Prompt; + if (StartsWith(prompt, "read=")) { + AddFolder(prompt.skip(5), &filenames, &x_to_time_string); + memset(Prompt, 0, sizeof(Prompt)); + } + } + } + + { + ImGuiWindowFlags window_flags = 0; + window_flags |= ImGuiWindowFlags_NoTitleBar; + window_flags |= ImGuiWindowFlags_NoMove; + window_flags |= ImGuiWindowFlags_NoResize; + window_flags |= ImGuiWindowFlags_NoCollapse; + + const ImGuiViewport *main_viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(ImVec2(0, 30), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(main_viewport->Size.x, main_viewport->Size.y - 30), ImGuiCond_Always); + + ImGui::Begin("result", NULL, window_flags); + + SearchThreadArrayMutex.lock(); + + float font_size = ImGui::GetFontSize(); + ImFont *font = ImGui::GetFont(); + int64_t chars_per_line = (int64_t)main_viewport->WorkSize.x / (int64_t)font->GetCharAdvance('_') - strlen(Prompt) * 2; + // int64_t chars_per_line = 200; + + ImGuiListClipper clipper; + clipper.Begin((int)Matches.len); + while (clipper.Step()) { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { + // for (int64_t i = 0; i < Matches.len; i += 1) { + auto &it = Matches[i]; + uintptr_t begin_region = (uintptr_t)XArena.data; + uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; + + uint64_t begin = (uintptr_t)(it.data - chars_per_line / 2); + uint64_t end = (uintptr_t)(it.data + it.len + chars_per_line / 2); + + uint64_t a = Clamp(begin, begin_region, end_region); + uint64_t b = Clamp((uintptr_t)it.data, begin_region, end_region); + String left = {(char *)a, (int64_t)(b - a)}; + + uint64_t c = Clamp((uintptr_t)(it.data + it.len), begin_region, end_region); + uint64_t d = Clamp(end, begin_region, end_region); + String right = {(char *)c, (int64_t)(d - c)}; + + String middle = it; + String string = Format(frame_arena, "%.*s**%.*s**%.*s##%d", + FmtString(left), FmtString(middle), FmtString(right), (int)i); + + if (ImGui::CollapsingHeader(string.data)) { + uintptr_t begin_region = (uintptr_t)XArena.data; + uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; + + uintptr_t begin = (uintptr_t)(it.data - 1000); + uintptr_t end = (uintptr_t)(it.data + 1000); + + uint64_t a = Clamp(begin, begin_region, end_region); + uint64_t b = Clamp((uintptr_t)it.data, begin_region, end_region); + String left = {(char *)a, (int64_t)(b - a)}; + + uint64_t c = Clamp((uintptr_t)(it.data + it.len), begin_region, end_region); + uint64_t d = Clamp(end, begin_region, end_region); + String right = {(char *)c, (int64_t)(d - c)}; + + String middle = it; + String string = Format(frame_arena, "%.*s***************%.*s***************%.*s", + FmtString(left), FmtString(middle), FmtString(right)); + + XToTimeString *item = FindItem(x_to_time_string, middle); + if (ImGui::Button("Open")) { + String base = ChopLastPeriod(item->filepath); // .srt + base = ChopLastPeriod(base); // .en + + For(filenames) { + if (StartsWith(it, base)) { + if (EndsWith(it, ".mkv") || EndsWith(it, ".webm") || EndsWith(it, ".mp4")) { + int seconds = item->hour * 60 * 60 + item->minute * 60 + item->second; + String copy = Copy(*frame_arena, it); + for (int i = 0; i < copy.len; i += 1) + if (copy.data[i] == '/') copy.data[i] = '\\'; + String args = Format(*frame_arena, "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe --start-time %d \"%.*s\"", seconds, FmtString(copy)); + RunEx(args); + } + } + } + } + ImGui::SameLine(); + ImGui::Text(item->filepath.data); + ImGui::TextWrapped(string.data); + } + } + } + SearchThreadArrayMutex.unlock(); + + ImGui::End(); + } + +#else + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float *)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } +#endif + + // Rendering + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); + } + + SearchThreadClose(search_thread); + + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + SDL_GL_DeleteContext(gl_context); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} \ No newline at end of file