Rewrite to IMGUI
This commit is contained in:
125
build_file.cpp
125
build_file.cpp
@@ -3,10 +3,11 @@
|
||||
struct Library {
|
||||
Array<S8_String> sources;
|
||||
Array<S8_String> objects;
|
||||
S8_String include_path;
|
||||
Array<S8_String> include_paths;
|
||||
Array<S8_String> 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<S8_String> 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<S8_String> 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<S8_String> 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<S8_String> *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<S8_String> 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<Library> 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<S8_String> 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<S8_String> 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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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: <buffer_len, buffer_len>. 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<Range> 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<Edit> *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<Edit> 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<Edit> 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<Edit> 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;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
String SavedClipboardString;
|
||||
Array<String> 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<Edit> edits = {scratch};
|
||||
For(window->cursors) AddEdit(&edits, it.range, string);
|
||||
ApplyEdits(window, edits);
|
||||
AfterEdit(window, edits);
|
||||
return;
|
||||
}
|
||||
|
||||
// Multicursor paste
|
||||
BeforeEdit(window);
|
||||
Array<Edit> 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);
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
bool EventRecording = false;
|
||||
bool EventPlaying = false;
|
||||
AutomationEventList EventList;
|
||||
Array<AutomationEventList> 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<Edit> 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);
|
||||
}
|
||||
@@ -1,512 +0,0 @@
|
||||
struct LayoutColumn {
|
||||
Rect2 rect;
|
||||
int64_t pos;
|
||||
int codepoint;
|
||||
};
|
||||
|
||||
struct LayoutRow {
|
||||
Rect2 rect;
|
||||
Array<LayoutColumn> columns;
|
||||
};
|
||||
|
||||
struct Layout {
|
||||
Array<LayoutRow> rows;
|
||||
Vec2 buffer_world_pixel_size;
|
||||
LayoutColumn *max_column;
|
||||
|
||||
Range visible_line_range;
|
||||
};
|
||||
|
||||
struct HistoryEntry {
|
||||
Array<Cursor> cursors;
|
||||
Array<Edit> edits;
|
||||
};
|
||||
|
||||
struct History {
|
||||
Array<HistoryEntry> undo_stack;
|
||||
Array<HistoryEntry> 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<Cursor> cursors;
|
||||
Buffer buffer;
|
||||
History history;
|
||||
|
||||
bool not_regen_layout;
|
||||
Array<ColoredString> colored_strings;
|
||||
Arena layout_arena;
|
||||
Layout layout;
|
||||
};
|
||||
|
||||
template <class T1, class T2>
|
||||
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<LayoutRow *, LayoutColumn *> GetRowCol(Window &window, Vec2 pos_buffer_world_units) {
|
||||
ProfileFunction();
|
||||
Tuple<LayoutRow *, LayoutColumn *> result = {};
|
||||
result.a = GetLayoutRow(window, pos_buffer_world_units.y);
|
||||
result.b = GetLayoutColumn(result.a, pos_buffer_world_units.x);
|
||||
return result;
|
||||
}
|
||||
|
||||
Tuple<LayoutRow *, LayoutColumn *> GetRowCol(Window &window, int64_t pos) {
|
||||
ProfileFunction();
|
||||
Tuple<LayoutRow *, LayoutColumn *> 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<LayoutColumn> CalculateVisibleColumns(Arena *arena, Window &window) {
|
||||
ProfileFunction();
|
||||
Range visible_line_range = CalculateVisibleLineRange(window);
|
||||
Array<LayoutColumn> 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<Cursor *> 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<Cursor> 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<HistoryEntry> *stack) {
|
||||
ProfileFunction();
|
||||
HistoryEntry *entry = stack->alloc();
|
||||
Allocator sys_allocator = GetSystemAllocator();
|
||||
entry->cursors = window->cursors.tight_copy(sys_allocator);
|
||||
}
|
||||
|
||||
void SaveHistoryBeforeApplyEdits(Window *window, Array<HistoryEntry> *stack, Array<Edit> 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<Edit> 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<Edit> 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<Edit> 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<Cursor> 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;
|
||||
}
|
||||
@@ -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<DebugLine> 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<Window> 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<Edit> 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<Cursor> 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<Edit> 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<Cursor> 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<Edit> 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<Edit> 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<Edit> 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<Edit> 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<Edit> 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<Edit> 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<Edit> 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<LayoutRow *, LayoutColumn *> 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<LayoutColumn> 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<LayoutRow *> lines_to_highlight = {FrameArena};
|
||||
ForItem(cursor, window.cursors) {
|
||||
Tuple<LayoutRow *, LayoutColumn *> front = GetRowCol(window, GetFront(cursor));
|
||||
Tuple<LayoutRow *, LayoutColumn *> 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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,443 +0,0 @@
|
||||
// SPDX-FileCopyrightText: © 2023 Phillip Trudeau-Tavara <pmttavara@protonmail.com>
|
||||
// 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
384
src/transcript_browser/_main.cpp
Normal file
384
src/transcript_browser/_main.cpp
Normal file
@@ -0,0 +1,384 @@
|
||||
#define BASIC_IMPL
|
||||
#include "../basic/basic.h"
|
||||
#include "../basic/filesystem.h"
|
||||
#include "tests.h"
|
||||
|
||||
#include <thread>
|
||||
#include <semaphore>
|
||||
#include <mutex>
|
||||
|
||||
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<TimeString> ParseSrtFile(Arena *arena, String filename) {
|
||||
String content = ReadFile(*arena, filename);
|
||||
Array<String> lines = Split(*arena, content, "\n");
|
||||
|
||||
IterRemove(lines) {
|
||||
IterRemovePrepare(lines);
|
||||
it = Trim(it);
|
||||
if (it.len == 0) remove_item = true;
|
||||
}
|
||||
|
||||
long section_number = 1;
|
||||
Array<TimeString> 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<TimeString> time_strings;
|
||||
String file;
|
||||
};
|
||||
|
||||
struct ParseThreadIO {
|
||||
Array<String> input_files;
|
||||
|
||||
// output
|
||||
Arena *arena;
|
||||
Array<TimeFile> time_files;
|
||||
};
|
||||
|
||||
void ParseThreadEntry(ParseThreadIO *io) {
|
||||
io->arena = AllocArena();
|
||||
io->time_files.allocator = *io->arena;
|
||||
For(io->input_files) {
|
||||
Array<TimeString> 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<String> *filenames, Array<XToTimeString> *x_to_time_string) {
|
||||
Scratch scratch;
|
||||
|
||||
Array<String> 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<std::thread *> 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<ParseThreadIO> io = {scratch};
|
||||
io.reserve(thread_count);
|
||||
for (int ti = 0; ti < thread_count; ti += 1) {
|
||||
Array<String> 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<String> Matches = {MatchesArena};
|
||||
XToTimeString *ItemFound;
|
||||
Array<char> 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<String> filenames = {};
|
||||
Array<XToTimeString> 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);
|
||||
}
|
||||
@@ -1,385 +1,501 @@
|
||||
#define BASIC_IMPL
|
||||
#include "../pdf_browser/basic.h"
|
||||
#include "filesystem.h"
|
||||
#include "tests.h"
|
||||
#include "raylib.h"
|
||||
|
||||
#include <thread>
|
||||
#include <semaphore>
|
||||
#include <mutex>
|
||||
|
||||
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<TimeString> ParseSrtFile(Arena *arena, String filename) {
|
||||
String content = ReadFile(*arena, filename);
|
||||
Array<String> lines = Split(*arena, content, "\n");
|
||||
|
||||
IterRemove(lines) {
|
||||
IterRemovePrepare(lines);
|
||||
it = Trim(it);
|
||||
if (it.len == 0) remove_item = true;
|
||||
}
|
||||
|
||||
long section_number = 1;
|
||||
Array<TimeString> 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<TimeString> time_strings;
|
||||
String file;
|
||||
};
|
||||
|
||||
struct ParseThreadIO {
|
||||
Array<String> input_files;
|
||||
|
||||
// output
|
||||
Arena *arena;
|
||||
Array<TimeFile> time_files;
|
||||
};
|
||||
|
||||
void ParseThreadEntry(ParseThreadIO *io) {
|
||||
io->arena = AllocArena();
|
||||
io->time_files.allocator = *io->arena;
|
||||
For(io->input_files) {
|
||||
Array<TimeString> 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<String> *filenames, Array<XToTimeString> *x_to_time_string) {
|
||||
Scratch scratch;
|
||||
|
||||
Array<String> 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<std::thread *> 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<ParseThreadIO> io = {scratch};
|
||||
io.reserve(thread_count);
|
||||
for (int ti = 0; ti < thread_count; ti += 1) {
|
||||
Array<String> 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<String> Matches = {MatchesArena};
|
||||
XToTimeString *ItemFound;
|
||||
Array<char> 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<String> filenames = {};
|
||||
Array<XToTimeString> 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 <thread>
|
||||
#include <semaphore>
|
||||
#include <mutex>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_sdl2.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <SDL.h>
|
||||
#include <SDL_opengl.h>
|
||||
|
||||
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<TimeString> ParseSrtFile(Arena *arena, String filename) {
|
||||
String content = ReadFile(*arena, filename);
|
||||
Array<String> lines = Split(*arena, content, "\n");
|
||||
|
||||
IterRemove(lines) {
|
||||
IterRemovePrepare(lines);
|
||||
it = Trim(it);
|
||||
if (it.len == 0) remove_item = true;
|
||||
}
|
||||
|
||||
long section_number = 1;
|
||||
Array<TimeString> 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<TimeString> time_strings;
|
||||
String file;
|
||||
};
|
||||
|
||||
struct ParseThreadIO {
|
||||
Array<String> input_files;
|
||||
|
||||
// output
|
||||
Arena *arena;
|
||||
Array<TimeFile> time_files;
|
||||
};
|
||||
|
||||
void ParseThreadEntry(ParseThreadIO *io) {
|
||||
io->arena = AllocArena();
|
||||
io->time_files.allocator = *io->arena;
|
||||
For(io->input_files) {
|
||||
Array<TimeString> 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<String> *filenames, Array<XToTimeString> *x_to_time_string) {
|
||||
Scratch scratch;
|
||||
|
||||
Array<String> 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<std::thread *> 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<ParseThreadIO> io = {scratch};
|
||||
io.reserve(thread_count);
|
||||
for (int ti = 0; ti < thread_count; ti += 1) {
|
||||
Array<String> 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<String> 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<XToTimeString> &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<String> filenames = {};
|
||||
Array<XToTimeString> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user