From 7803bc87a6055d94e98b3c28ee93da1dd0117bf1 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Fri, 26 Jul 2024 17:08:14 +0200 Subject: [PATCH] Transitioning text editor to SDL --- src/platform/font.cpp | 149 ++++++++++++++ src/platform/platform.cpp | 155 ++++++++++++++ src/platform/render_opengl.cpp | 301 +++++++++++++++++++++++++++ src/text_editor/new_text_editor.cpp | 309 ++++++++++++++++++++++++++++ src/text_editor/todo.txt | 14 ++ 5 files changed, 928 insertions(+) create mode 100644 src/platform/font.cpp create mode 100644 src/platform/platform.cpp create mode 100644 src/platform/render_opengl.cpp create mode 100644 src/text_editor/new_text_editor.cpp create mode 100644 src/text_editor/todo.txt diff --git a/src/platform/font.cpp b/src/platform/font.cpp new file mode 100644 index 0000000..4000c74 --- /dev/null +++ b/src/platform/font.cpp @@ -0,0 +1,149 @@ +struct Glyph { + Vec2 size; + Vec2 offset; + float xadvance; + float left_side_bearing; + Rect2 atlas_bounding_box; +}; + +struct Font { + Array glyphs; + uint32_t first_char, last_char; + uint32_t texture_id; + + float size; + float descent; + float ascent; + float line_gap; + + Rect2 white_texture_bounding_box; +}; + +struct Atlas { + uint8_t *bitmap; + Vec2I size; + Vec2 inverse_size; + int xcursor; + int ycursor; + + int32_t biggest_height; + Rect2 white_texture_bounding_box; + uint32_t texture_id; +}; + +Atlas CreateAtlas(Allocator allocator, Vec2I size) { + Atlas result = {}; + result.size = size; + result.inverse_size.x = 1.f / (float)result.size.x; + result.inverse_size.y = 1.f / (float)result.size.y; + result.bitmap = AllocArray(allocator, uint8_t, size.x * size.y); + + // Add a whitebox first for rectangle rendering + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + uint8_t *dst = result.bitmap + x + y * result.size.x; + *dst = 0xff; + } + } + + result.white_texture_bounding_box = {2.f * result.inverse_size.x, 2.f / (float)result.size.y, 14.f * result.inverse_size.x, 14.f / (float)result.size.y}; + result.xcursor += 16; + result.biggest_height += 16; + return result; +} + +Rect2 PackBitmap(Atlas *atlas, uint8_t *bitmap, int width, int height) { + // Packing into a texture atlas + // @Inefficient The algorithm is a simplest thing I had in mind, first we advance + // through the atlas in X packing consecutive glyphs. After we get to the end of the row + // we advance to the next row by the Y size of the biggest packed glyph. If we get to the + // end of atlas and fail to pack everything the app panics. + + int spacing = 4; + if (atlas->xcursor + width > atlas->size.x) { + if (atlas->ycursor + height < atlas->size.y) { + atlas->xcursor = 0; + atlas->ycursor += atlas->biggest_height + spacing; + } else { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Font loading error!", "Error while packing a font into atlas. Atlas size for this font scale is a bit too small", NULL); + } + } + + uint8_t *src = bitmap; + for (int y = atlas->ycursor; y < atlas->ycursor + height; y += 1) { + for (int x = atlas->xcursor; x < atlas->xcursor + width; x += 1) { + uint8_t *dst = atlas->bitmap + x + y * atlas->size.x; + *dst = *src++; + } + } + + Vec2 size = {(float)width * atlas->inverse_size.x, (float)height * atlas->inverse_size.y}; + Vec2 pos = {(float)atlas->xcursor * atlas->inverse_size.x, (float)atlas->ycursor * atlas->inverse_size.y}; + Rect2 result = {pos.x, pos.y, pos.x + size.x, pos.y + size.y}; + + atlas->xcursor += width + spacing; + if (height > atlas->biggest_height) { + atlas->biggest_height = height; + } + + return result; +} + +Font CreateFont(Atlas *atlas, int32_t size, String path) { + Scratch scratch; + Font result = {}; + + String file = ReadFile(scratch, path); + if (file.len == 0) { + return result; + } + + stbtt_fontinfo stb_font; + int success = stbtt_InitFont(&stb_font, (const unsigned char *)file.data, 0); + if (!success) { + return result; + } + + float scale = stbtt_ScaleForPixelHeight(&stb_font, (float)size); + float em_scale = stbtt_ScaleForMappingEmToPixels(&stb_font, (float)size); + + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&stb_font, &ascent, &descent, &line_gap); + result.ascent = (float)ascent * scale; + result.descent = (float)descent * scale; + result.line_gap = (float)line_gap * scale; + result.size = (float)size; + result.first_char = ' '; + result.last_char = '~'; + result.white_texture_bounding_box = atlas->white_texture_bounding_box; + + for (uint32_t ascii_symbol = result.first_char; ascii_symbol <= result.last_char; ascii_symbol++) { + int width, height, xoff, yoff; + uint8_t *bitmap = (uint8_t *)stbtt_GetCodepointBitmap(&stb_font, 0, scale, ascii_symbol, &width, &height, &xoff, &yoff); + defer { stbtt_FreeBitmap(bitmap, 0); }; + + int xadvance, left_side_bearing; + stbtt_GetCodepointHMetrics(&stb_font, ascii_symbol, &xadvance, &left_side_bearing); + + Glyph glyph = {}; + glyph.atlas_bounding_box = PackBitmap(atlas, bitmap, width, height); + glyph.size = {(float)width, (float)height}; + glyph.offset = {(float)xoff, (float)yoff}; + glyph.xadvance = (float)xadvance * scale; + glyph.left_side_bearing = (float)left_side_bearing * scale; + Add(&result.glyphs, glyph); + } + + return result; +} + +Glyph *GetGlyph(Font *font, uint32_t codepoint) { + bool is_in_range = codepoint >= font->first_char && codepoint <= font->last_char; + if (is_in_range) { + uint32_t index = codepoint - font->first_char; + return &font->glyphs[index]; + } else { + uint32_t index = '?' - font->first_char; + return &font->glyphs[index]; + } +} \ No newline at end of file diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp new file mode 100644 index 0000000..054f029 --- /dev/null +++ b/src/platform/platform.cpp @@ -0,0 +1,155 @@ +#define BASIC_IMPL +#include "basic/basic.h" +#include "basic/filesystem.h" + +#include "SDL3/SDL.h" +#include "external/glad/glad.h" +#include "external/stb_truetype.h" +#include "external/stb_truetype.c" + +#include "basic/math_int.cpp" +#include "basic/math.cpp" +#include "font.cpp" +#include "render_opengl.cpp" + +bool AppIsRunning = true; +bool WaitForEvents = false; + +int64_t FrameID; + +void ProcessSDLEvent(SDL_Event *event) { + switch (event->type) { + case SDL_EVENT_QUIT: AppIsRunning = false; return; + case SDL_EVENT_KEY_DOWN: { + SDL_KeyboardEvent &key = event->key; + bool shift = key.mod & SDL_KMOD_SHIFT; + bool ctrl = key.mod & SDL_KMOD_CTRL; + bool alt = key.mod & SDL_KMOD_ALT; + bool super = key.mod & SDL_KMOD_GUI; + + if (key.key == SDLK_F5) { + AppIsRunning = false; + return; + } + + } break; + case SDL_EVENT_KEY_UP: { + SDL_KeyboardEvent &key = event->key; + bool shift = key.mod & SDL_KMOD_SHIFT; + bool ctrl = key.mod & SDL_KMOD_CTRL; + bool alt = key.mod & SDL_KMOD_ALT; + bool super = key.mod & SDL_KMOD_GUI; + + } break; + case SDL_EVENT_TEXT_INPUT: { + } break; + case SDL_EVENT_MOUSE_MOTION: { + } break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: { + } break; + case SDL_EVENT_MOUSE_BUTTON_UP: { + } break; + case SDL_EVENT_MOUSE_WHEEL: { + } break; + } +} + +void GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user) { + printf("%s", message); + if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "OpenGL error", message, NULL); + } +} + +#if _WIN32 +int WinMain(void *hInstance, void *hPrevInstance, const char *lpCmdLine, int nShowCmd) +#else +int main() +#endif +{ + InitScratch(); + if (SDL_Init(SDL_INIT_VIDEO) == -1) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't initialize SDL!", SDL_GetError(), NULL); + return 1; + } + + const char *glsl_version = "#version 450"; + 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, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5); + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + Uint32 window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; + SDL_Window *window = SDL_CreateWindow("Text editor", 1280, 720, window_flags); + if (window == NULL) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window!", SDL_GetError(), NULL); + return 1; + } + + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + SDL_ShowWindow(window); + + if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't load opengl!", SDL_GetError(), NULL); + return 1; + } + + SDL_GL_SetSwapInterval(1); // vsync + glDebugMessageCallback(&GLDebugCallback, NULL); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + + { + Scratch scratch; + Atlas atlas = CreateAtlas(scratch, {1024, 1024}); + MainFont = CreateFont(&atlas, 16, "C:\\Windows\\Fonts\\consola.ttf"); + { + glCreateTextures(GL_TEXTURE_2D, 1, &atlas.texture_id); + glTextureParameteri(atlas.texture_id, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(atlas.texture_id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureStorage2D(atlas.texture_id, 1, GL_R8, (GLsizei)atlas.size.x, (GLsizei)atlas.size.y); + glTextureSubImage2D(atlas.texture_id, 0, 0, 0, (GLsizei)atlas.size.x, (GLsizei)atlas.size.y, GL_RED, GL_UNSIGNED_BYTE, atlas.bitmap); + } + MainFont.texture_id = atlas.texture_id; + } + + InitRender(); + + while (AppIsRunning) { + FrameID += 1; + int window_x, window_y; + SDL_GetWindowSize(window, &window_x, &window_y); + Vec2 window_size = {(float)window_x, (float)window_y}; + BeginFrameRender(window_size); + + SDL_Event event; + if (WaitForEvents) { + SDL_WaitEvent(&event); + ProcessSDLEvent(&event); + } + while (SDL_PollEvent(&event)) { + ProcessSDLEvent(&event); + } + + { + Scratch scratch; + uint64_t ms = SDL_GetTicks(); + double time = (double)ms / 1000.0; + String string = Format(scratch, "%d %f %f", (int)FrameID, time, (time / (double)FrameID)); + DrawString(&MainFont, ToString16(scratch, string), {0, 0}, {255, 0, 0, 255}); + } + EndFrameRender({0, 0, 0, 1}); + SDL_GL_SwapWindow(window); + } + + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} \ No newline at end of file diff --git a/src/platform/render_opengl.cpp b/src/platform/render_opengl.cpp new file mode 100644 index 0000000..aec2ad1 --- /dev/null +++ b/src/platform/render_opengl.cpp @@ -0,0 +1,301 @@ +struct Shader { + uint32_t pipeline; + uint32_t fshader; + uint32_t vshader; +}; +Shader CreateShader(char *glsl_vshader, char *glsl_fshader); + +struct Vertex2D { + Vec2 pos; + Vec2 tex; + Color color; +}; + +struct VertexNode2D { + VertexNode2D *next; + int count; + Vertex2D vertices[1024 * 64]; +}; + +struct VertexList2D { + VertexNode2D *first; + VertexNode2D *last; +}; + +VertexList2D Vertices; +int64_t TotalVertexCount; + +unsigned VBO, VAO; +Shader Shader2D; +Arena RenderArena; +Vec2 WindowSize; + +Font MainFont; +Int FontLineSpacing; +Int FontCharSpacing; + +void BeginFrameRender(Vec2 window_size) { + Clear(&RenderArena); + TotalVertexCount = 0; + Vertices.first = NULL; + Vertices.last = NULL; + WindowSize = window_size; +} + +void EndFrameRender(Color color) { + glEnable(GL_BLEND); + glEnable(GL_SCISSOR_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + glViewport(0, 0, (GLsizei)WindowSize.x, (GLsizei)WindowSize.y); + glClearColor(color.r, color.g, color.b, color.a); + glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + // Default draw using the font texture + glBindProgramPipeline(Shader2D.pipeline); + float xinverse = 1.f / (WindowSize.x / 2.f); + float yinverse = 1.f / (WindowSize.y / 2.f); + glProgramUniform2f(Shader2D.vshader, 0, xinverse, yinverse); + for (VertexNode2D *it = Vertices.first; it; it = it->next) { + glNamedBufferSubData(VBO, 0, it->count * sizeof(Vertex2D), it->vertices); + glBindVertexArray(VAO); + GLint s_texture = 0; // texture unit that sampler2D will use in GLSL code + glBindTextureUnit(s_texture, MainFont.texture_id); + glDrawArrays(GL_TRIANGLES, 0, it->count); + } +} + +void GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user) { + printf("%s", message); + if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "OpenGL error", message, NULL); + } +} + +void InitRender() { + InitArena(&RenderArena); + + glDebugMessageCallback(&GLDebugCallback, NULL); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + + glCreateBuffers(1, &VBO); + glNamedBufferStorage(VBO, Lengthof(VertexNode2D::vertices) * sizeof(Vertex2D), 0, GL_DYNAMIC_STORAGE_BIT); + + glCreateVertexArrays(1, &VAO); + + GLint vbuf_index = 0; + glVertexArrayVertexBuffer(VAO, vbuf_index, VBO, 0, sizeof(struct Vertex2D)); + + GLint a_pos = 0; + glVertexArrayAttribFormat(VAO, a_pos, 2, GL_FLOAT, GL_FALSE, offsetof(struct Vertex2D, pos)); + glVertexArrayAttribBinding(VAO, a_pos, vbuf_index); + glEnableVertexArrayAttrib(VAO, a_pos); + + GLint a_tex = 1; + glVertexArrayAttribFormat(VAO, a_tex, 2, GL_FLOAT, GL_FALSE, offsetof(struct Vertex2D, tex)); + glVertexArrayAttribBinding(VAO, a_tex, vbuf_index); + glEnableVertexArrayAttrib(VAO, a_tex); + + GLint a_color = 2; + glVertexArrayAttribFormat(VAO, a_color, 4, GL_UNSIGNED_BYTE, GL_FALSE, offsetof(struct Vertex2D, color)); + glVertexArrayAttribBinding(VAO, a_color, vbuf_index); + glEnableVertexArrayAttrib(VAO, a_color); + + char *glsl_vshader = R"==( + #version 450 core + + layout(location=0) uniform vec2 U_InvHalfScreenSize; + layout(location=0) in vec2 VPos; + layout(location=1) in vec2 VTex; + layout(location=2) in vec4 VColor; + + out gl_PerVertex { vec4 gl_Position; }; // required because of ARB_separate_shader_objects + out vec2 FUV; + out vec4 FColor; + + void main() { + vec2 pos = VPos * U_InvHalfScreenSize; + pos.y = 2 - pos.y; // invert y axis + pos -= vec2(1, 1); // convert to 0,1 range + + gl_Position = vec4(pos, 0, 1); + FUV = VTex; + + FColor = VColor / 255.0; + } + )=="; + + char *glsl_fshader = R"==( + #version 450 core + + in vec2 FUV; + in vec4 FColor; + + layout (binding=0) uniform sampler2D S_Texture; + layout (location=0) out vec4 OutColor; + + void main() { + vec4 c = FColor; + c.a *= texture(S_Texture, FUV).r; + OutColor = c; + } + )=="; + + Shader2D = CreateShader(glsl_vshader, glsl_fshader); +} + +Shader CreateShader(char *glsl_vshader, char *glsl_fshader) { + Shader result = {}; + result.vshader = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl_vshader); + result.fshader = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &glsl_fshader); + + GLint linked; + glGetProgramiv(result.vshader, GL_LINK_STATUS, &linked); + if (!linked) { + char message[1024]; + glGetProgramInfoLog(result.vshader, sizeof(message), NULL, message); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Failed to create vertex shader!", message, NULL); + } + + glGetProgramiv(result.fshader, GL_LINK_STATUS, &linked); + if (!linked) { + char message[1024]; + glGetProgramInfoLog(result.fshader, sizeof(message), NULL, message); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Failed to create fragment shader!", message, NULL); + } + + glGenProgramPipelines(1, &result.pipeline); + glUseProgramStages(result.pipeline, GL_VERTEX_SHADER_BIT, result.vshader); + glUseProgramStages(result.pipeline, GL_FRAGMENT_SHADER_BIT, result.fshader); + return result; +} + +#define SLL_QUEUE_ADD_MOD(f, l, n, next) \ + do { \ + (n)->next = 0; \ + if ((f) == 0) { \ + (f) = (l) = (n); \ + } else { \ + (l) = (l)->next = (n); \ + } \ + } while (0) +#define SLL_QUEUE_ADD(f, l, n) SLL_QUEUE_ADD_MOD(f, l, n, next) + +Vertex2D *AllocVertex2D(Allocator allocator, VertexList2D *list, int count) { + VertexNode2D *node = list->last; + if (node == 0 || node->count + count > Lengthof(node->vertices)) { + node = AllocType(allocator, VertexNode2D); + SLL_QUEUE_ADD(list->first, list->last, node); + } + + TotalVertexCount += count; + Vertex2D *result = node->vertices + node->count; + node->count += count; + return result; +} + +void PushVertex2D(Allocator allocator, VertexList2D *list, Vertex2D *vertices, int count) { + Vertex2D *result = AllocVertex2D(allocator, list, count); + for (int i = 0; i < count; i += 1) result[i] = vertices[i]; +} + +void PushQuad2D(Allocator arena, VertexList2D *list, Rect2 rect, Rect2 tex, Color color, float rotation = 0.f, Vec2 rotation_point = {}) { + Vertex2D *v = AllocVertex2D(arena, list, 6); + v[0] = { + {rect.min.x, rect.max.y}, + { tex.min.x, tex.max.y}, + color + }; + v[1] = { + {rect.max.x, rect.max.y}, + { tex.max.x, tex.max.y}, + color + }; + v[2] = { + {rect.min.x, rect.min.y}, + { tex.min.x, tex.min.y}, + color + }; + v[3] = { + {rect.min.x, rect.min.y}, + { tex.min.x, tex.min.y}, + color + }; + v[4] = { + {rect.max.x, rect.max.y}, + { tex.max.x, tex.max.y}, + color + }; + v[5] = { + {rect.max.x, rect.min.y}, + { tex.max.x, tex.min.y}, + color + }; + if (rotation != 0.f) { + float s = sinf(rotation); + float c = cosf(rotation); + for (int i = 0; i < 6; i += 1) { + v[i].pos -= rotation_point; + v[i].pos = {v[i].pos.x * c + v[i].pos.y * (-s), v[i].pos.x * s + v[i].pos.y * c}; + v[i].pos += rotation_point; + } + } +} + +void DrawRect(Rect2 rect, Color color) { + PushQuad2D(RenderArena, &Vertices, rect, MainFont.white_texture_bounding_box, color); +} + +Vec2 DrawString(Font *font, String16 string, Vec2 pos, Color color, bool draw = true) { + pos.y += font->ascent; + Vec2 original_pos = pos; + For(string) { + Glyph *g = GetGlyph(font, it); + Rect2 rect = Rect2FromSize(pos + g->offset, g->size); + if (draw && it != '\n' && it != ' ' && it != '\t') { + PushQuad2D(RenderArena, &Vertices, rect, g->atlas_bounding_box, color); + } + pos.x += g->xadvance; + } + Vec2 result = {pos.x - original_pos.x, font->size}; + return result; +} + +Vec2 GetStringSize(Font *font, String16 string) { + return DrawString(font, string, {}, {}, false); +} + +Int GetCharSpacing(Font *font) { + Glyph *g = GetGlyph(font, '_'); + if (g->xadvance) return (Int)g->xadvance; + return (Int)g->size.x; +} + +Int GetLineSpacing(Font *font) { + Int result = (Int)(font->ascent - font->descent + font->line_gap); + return result; +} + +void BeginScissor(Rect2 rect) { + glScissor((GLint)rect.min.x, (GLint)rect.min.y, (GLsizei)(rect.max.x - rect.min.x), (GLsizei)(rect.max.y - rect.min.y)); +} + +void BeginScissor(Rect2I rect) { + glScissor((GLint)rect.min.x, (GLint)rect.min.y, (GLsizei)(rect.max.x - rect.min.x), (GLsizei)(rect.max.y - rect.min.y)); +} + +void EndScissor() { + glScissor(0, 0, (GLsizei)WindowSize.x, (GLsizei)WindowSize.y); +} + +Rect2 GetScreenRectF() { + Rect2 result = {0, 0, WindowSize.x, WindowSize.y}; + return result; +} + +Rect2I GetScreenRectI() { + Rect2I result = {0, 0, (Int)WindowSize.x, (Int)WindowSize.y}; + return result; +} \ No newline at end of file diff --git a/src/text_editor/new_text_editor.cpp b/src/text_editor/new_text_editor.cpp new file mode 100644 index 0000000..110fc17 --- /dev/null +++ b/src/text_editor/new_text_editor.cpp @@ -0,0 +1,309 @@ +#include + +#define BASIC_IMPL +#include "basic/basic.h" +#include "basic/filesystem.h" +#include "basic/string16.cpp" +#include "basic/math_int.cpp" +#include "basic/math.cpp" +#include "profiler/profiler.cpp" + +#include "SDL3/SDL.h" +#include "external/glad/glad.h" +#include "external/stb_truetype.h" +#include "external/stb_truetype.c" + +#include "platform/font.cpp" +#include "platform/render_opengl.cpp" + +#include "text_editor.h" +#include "buffer_helpers.cpp" +#include "buffer.cpp" +#include "buffer_multi_cursor.cpp" +#include "buffer_history.cpp" +#include "buffer_fuzzy_search.cpp" +#include "buffer_test_load.cpp" + +#include "management.cpp" +#include "window.cpp" + +#include "colors.cpp" +#include "window_draw.cpp" + +#include "lua.hpp" +#include "lua_api.cpp" + +bool AppIsRunning = true; +bool WaitForEvents = false; + +void ProcessSDLEvent(SDL_Event *event) { + switch (event->type) { + case SDL_EVENT_QUIT: AppIsRunning = false; return; + case SDL_EVENT_KEY_DOWN: { + SDL_KeyboardEvent &key = event->key; + bool shift = key.mod & SDL_KMOD_SHIFT; + bool ctrl = key.mod & SDL_KMOD_CTRL; + bool alt = key.mod & SDL_KMOD_ALT; + bool super = key.mod & SDL_KMOD_GUI; + + if (key.key == SDLK_F5) { + AppIsRunning = false; + return; + } + + } break; + case SDL_EVENT_KEY_UP: { + SDL_KeyboardEvent &key = event->key; + bool shift = key.mod & SDL_KMOD_SHIFT; + bool ctrl = key.mod & SDL_KMOD_CTRL; + bool alt = key.mod & SDL_KMOD_ALT; + bool super = key.mod & SDL_KMOD_GUI; + + } break; + case SDL_EVENT_TEXT_INPUT: { + } break; + case SDL_EVENT_MOUSE_MOTION: { + } break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: { + } break; + case SDL_EVENT_MOUSE_BUTTON_UP: { + } break; + case SDL_EVENT_MOUSE_WHEEL: { + } break; + } +} + +#if _WIN32 +int WinMain(void *hInstance, void *hPrevInstance, const char *lpCmdLine, int nShowCmd) +#else +int main() +#endif +{ + BeginProfiler(); + BeginProfileScope("main"); + + InitScratch(); + InitArena(&Perm); + WorkingDir = GetWorkingDir(Perm); + + if (SDL_Init(SDL_INIT_VIDEO) == -1) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't initialize SDL!", SDL_GetError(), NULL); + return 1; + } + + const char *glsl_version = "#version 450"; + 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, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5); + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + Uint32 window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; + SDL_Window *window = SDL_CreateWindow("Text editor", 1280, 720, window_flags); + if (window == NULL) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window!", SDL_GetError(), NULL); + return 1; + } + + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + SDL_ShowWindow(window); + + if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't load opengl!", SDL_GetError(), NULL); + return 1; + } + + SDL_GL_SetSwapInterval(1); // vsync + InitRender(); + InitLua(); + + { + Scratch scratch; + Atlas atlas = CreateAtlas(scratch, {1024, 1024}); + MainFont = CreateFont(&atlas, 16, "C:\\Windows\\Fonts\\consola.ttf"); + { + glCreateTextures(GL_TEXTURE_2D, 1, &atlas.texture_id); + glTextureParameteri(atlas.texture_id, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(atlas.texture_id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureStorage2D(atlas.texture_id, 1, GL_R8, (GLsizei)atlas.size.x, (GLsizei)atlas.size.y); + glTextureSubImage2D(atlas.texture_id, 0, 0, 0, (GLsizei)atlas.size.x, (GLsizei)atlas.size.y, GL_RED, GL_UNSIGNED_BYTE, atlas.bitmap); + } + MainFont.texture_id = atlas.texture_id; + FontCharSpacing = GetCharSpacing(&MainFont); + FontLineSpacing = GetLineSpacing(&MainFont); + } + + Allocator sys_allocator = GetSystemAllocator(); + { + Buffer *buffer = CreateBuffer(sys_allocator, "*scratch*"); + } + + { + Window *w = CreateWindow(); + Buffer *b = CreateBuffer(sys_allocator, "*load_text_a*"); + View *v = CreateView(b->id); + LoadTextA(b); + AddView(w, v->id); + } + SetActiveWindow({1}); + + { + Window *w = CreateWindow(); + Buffer *b = CreateBuffer(sys_allocator, "*load_unicode*"); + View *v = CreateView(b->id); + LoadUnicode(b); + AddView(w, v->id); + } + { + Window *w = CreateWindow(); + Buffer *b = GetBuffer({0}); + View *v = CreateView(b->id); + AddView(w, v->id); + } + { + Window *w = CreateWindow(); + w->draw_scrollbar = false; + w->draw_line_numbers = false; + w->dont_save_in_active_window_history = true; + w->deactivate_on_escape = true; + Buffer *b = CreateBuffer(sys_allocator, "*infobar*"); + b->no_history = true; + View *v = CreateView(b->id); + AddView(w, v->id); + InfoBarWindowID = w->id; + } + + { + Window *w = CreateWindow(); + w->draw_scrollbar = false; + w->draw_line_numbers = false; + w->visible = false; + w->fuzzy_search = true; + w->execute_line = true; + w->invisible_when_inactive = true; + w->dont_save_in_active_window_history = true; + w->deactivate_on_escape = true; + Buffer *b = CreateBuffer(sys_allocator, "*commands*"); + View *v = CreateView(b->id); + AddView(w, v->id); + CommandWindowID = w->id; + + // Command_EvalLua(v, L"open \"./\""); + } + + { + Window *w = CreateWindow(); + w->draw_scrollbar = false; + w->draw_line_numbers = false; + w->visible = false; + w->dont_save_in_active_window_history = true; + w->invisible_when_inactive = true; + w->deactivate_on_escape = true; + Buffer *b = CreateBuffer(sys_allocator, "*search*"); + View *v = CreateView(b->id); + AddView(w, v->id); + SearchWindowID = w->id; + } + + while (AppIsRunning) { + FrameID += 1; + int window_x, window_y; + SDL_GetWindowSize(window, &window_x, &window_y); + Vec2 window_size = {(float)window_x, (float)window_y}; + BeginFrameRender(window_size); + + Rect2I screen_rect = GetScreenRectI(); + Rect2I infobar_rect = CutBottom(&screen_rect, (Int)FontLineSpacing); + float line_numbers_size = GetStringSize(&MainFont, L"1234567891").x; + { + int i = 5; + if (Windows[i].visible) { + Rect2I rect = CutBottom(&screen_rect, FontLineSpacing); + Windows[i].total_rect = rect; + Windows[i].document_rect = Windows[i].total_rect; + } + } + { + int i = 0; + Windows[i].total_rect = CutLeft(&screen_rect, (Int)((double)GetSize(screen_rect).x * 0.33)); + Windows[i].document_rect = Windows[i].total_rect; + if (Windows[i].draw_scrollbar) Windows[i].scrollbar_rect = CutRight(&Windows[i].document_rect, 10); + if (Windows[i].draw_line_numbers) Windows[i].line_numbers_rect = CutLeft(&Windows[i].document_rect, (Int)line_numbers_size); + } + { + int i = 1; + Windows[i].total_rect = CutLeft(&screen_rect, (Int)((double)GetSize(screen_rect).x * 0.5)); + Windows[i].document_rect = Windows[i].total_rect; + if (Windows[i].draw_scrollbar) Windows[i].scrollbar_rect = CutRight(&Windows[i].document_rect, 10); + if (Windows[i].draw_line_numbers) Windows[i].line_numbers_rect = CutLeft(&Windows[i].document_rect, (Int)line_numbers_size); + } + { + int i = 2; + Windows[i].total_rect = CutLeft(&screen_rect, (Int)((double)GetSize(screen_rect).x * 1.0)); + Windows[i].document_rect = Windows[i].total_rect; + if (Windows[i].draw_scrollbar) Windows[i].scrollbar_rect = CutRight(&Windows[i].document_rect, 10); + if (Windows[i].draw_line_numbers) Windows[i].line_numbers_rect = CutLeft(&Windows[i].document_rect, (Int)line_numbers_size); + } + { + int i = 3; + Windows[i].total_rect = infobar_rect; + Windows[i].document_rect = Windows[i].total_rect; + } + { + int i = 4; + Rect2 screen_rect = GetScreenRectF(); + Vec2 size = GetSize(screen_rect); + CutTop(&screen_rect, size.y * 0.05f); + CutLeft(&screen_rect, size.x * 0.2f); + CutRight(&screen_rect, size.x * 0.2f); + Rect2 r = CutTop(&screen_rect, FontLineSpacing * 30.f); + + Windows[i].z = 1; + Windows[i].total_rect = ToRect2I(r); + Windows[i].document_rect = Windows[i].total_rect; + } + + SDL_Event event; + if (WaitForEvents) { + SDL_WaitEvent(&event); + ProcessSDLEvent(&event); + } + while (SDL_PollEvent(&event)) { + ProcessSDLEvent(&event); + } + + Scratch scratch; + Array order = GetWindowZOrder(scratch); + For(IterateInReverse(&order)) { + Window &window = Windows[it]; + if (window.visible) { + // HandleWindowBindings(&window); + DrawWindow(window); + } + } + + // { + // Scratch scratch; + // uint64_t ms = SDL_GetTicks(); + // double time = (double)ms / 1000.0; + // String string = Format(scratch, "%d %f %f", (int)FrameID, time, (time / (double)FrameID)); + // DrawString(&MainFont, ToString16(scratch, string), {0, 0}, {255, 0, 0, 255}); + // } + EndFrameRender(ColorBackground); + SDL_GL_SwapWindow(window); + } + + SDL_DestroyWindow(window); + SDL_Quit(); + + EndProfileScope(); + EndProfiler(); + + return 0; +} \ No newline at end of file diff --git a/src/text_editor/todo.txt b/src/text_editor/todo.txt new file mode 100644 index 0000000..510ef2c --- /dev/null +++ b/src/text_editor/todo.txt @@ -0,0 +1,14 @@ +- Save file (utf16->utf8) +- resize windows +- file dock on left side + - We can actually combine this with command window and lua, it's just going to be a buffer of + - open "asd/asd/asd/asd" +- Ctrl + F + +- word completion +- Colored strings + +- move off raylib + - Adjust text position a little bit down? + - proper double click that works on laptop + - font cache and on demand unicode loads \ No newline at end of file