diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..b1e43622 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "dependencies/minirent"] + path = dependencies/minirent + url = https://github.com/alex-s168/minirent.git +[submodule "dependencies/freetype"] + path = dependencies/freetype + url = https://github.com/ubawurinna/freetype-windows-binaries.git +[submodule "dependencies/minilibs"] + path = dependencies/minilibs + url = https://github.com/alex-s168/minilibs.git diff --git a/README.md b/README.md index e0f73d66..76704cb5 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ # Quick Start ## Dependencies +(Automatically downloaded on Windows) - [SDL2 2.0.9+](https://www.libsdl.org/) - [FreeType 2.13.0+](https://freetype.org/) @@ -18,10 +19,20 @@ $ ./ded src/main.c ``` ## Windows MSVC - +In any PowerShell or CMD console ```console > .\setup_dependencies.bat +``` +In a bash console +```console +$ ./setup_other.sh +``` +In a MSVC console (VS developer x64 prompt) +```console > .\build_msvc.bat +``` +Then +```console > .\ded.exe src\main.c ``` diff --git a/build.sh b/build.sh index 2c961d43..4453e5b7 100755 --- a/build.sh +++ b/build.sh @@ -4,7 +4,7 @@ set -xe CC="${CXX:-cc}" PKGS="sdl2 glew freetype2" -CFLAGS="-Wall -Wextra -std=c11 -pedantic -ggdb" +CFLAGS="-std=c11 -pedantic -ggdb -Idependencies/minilibs -Idependencies/minirent" LIBS=-lm SRC="src/main.c src/la.c src/editor.c src/file_browser.c src/free_glyph.c src/simple_renderer.c src/common.c src/lexer.c" @@ -12,4 +12,4 @@ if [ `uname` = "Darwin" ]; then CFLAGS+=" -framework OpenGL" fi -$CC $CFLAGS `pkg-config --cflags $PKGS` -o ded $SRC $LIBS `pkg-config --libs $PKGS` +$CC $CFLAGS `pkg-config --cflags $PKGS` -g -o ded $SRC $LIBS `pkg-config --libs $PKGS` diff --git a/build_msvc.bat b/build_msvc.bat index d3a87749..ce58a4d6 100644 --- a/build_msvc.bat +++ b/build_msvc.bat @@ -1,12 +1,14 @@ @echo off rem launch this from msvc-enabled console -set CFLAGS=/W4 /WX /std:c11 /wd4996 /wd5105 /FC /TC /Zi /nologo -set INCLUDES=/I dependencies\SDL2\include /I dependencies\GLFW\include /I dependencies\GLEW\include +set CFLAGS=/W4 /std:c11 /wd4996 /wd5105 /FC /TC /Zi /nologo +set INCLUDES=/I dependencies\SDL2\include /I dependencies\freetype\include /I dependencies\minirent /I dependencies\GLFW\include /I dependencies\GLEW\include /I dependencies\minilibs + set LIBS=dependencies\SDL2\lib\x64\SDL2.lib ^ dependencies\SDL2\lib\x64\SDL2main.lib ^ dependencies\GLFW\lib\glfw3.lib ^ dependencies\GLEW\lib\glew32s.lib ^ + "dependencies\freetype\release static\vs2015-2022\win64\freetype.lib" ^ opengl32.lib User32.lib Gdi32.lib Shell32.lib -cl.exe %CFLAGS% %INCLUDES% /Feded src\main.c src\la.c src\editor.c src\file_browser.c src\free_glyph.c src\simple_renderer.c src\common.c /link %LIBS% -SUBSYSTEM:windows +cl.exe %CFLAGS% %INCLUDES% /Feded src\main.c src\la.c src\editor.c src\file_browser.c src\free_glyph.c src\simple_renderer.c src\common.c src\lexer.c /link %LIBS% -SUBSYSTEM:windows diff --git a/build_msys2_mingw64.sh b/build_msys2_mingw64.sh index aa156fc9..61e7e4c4 100644 --- a/build_msys2_mingw64.sh +++ b/build_msys2_mingw64.sh @@ -3,9 +3,9 @@ set -xe PKGS="--static sdl2 glew freetype2" -CFLAGS="-Wall -Wextra -pedantic -ggdb -DGLEW_STATIC `pkg-config --cflags $PKGS` -Isrc -Dassert(expression)=((void)0) " +CFLAGS="-Wall -Wextra -pedantic -ggdb -DGLEW_STATIC `pkg-config --cflags $PKGS` -Isrc -Idependencies/minilibs -Idependencies/minirent -Dassert(expression)=((void)0) " LIBS="-lm -lopengl32 `pkg-config --libs $PKGS`" -SRC="src/main.c src/la.c src/editor.c src/file_browser.c src/free_glyph.c src/simple_renderer.c src/common.c" +SRC="src/main.c src/la.c src/editor.c src/file_browser.c src/free_glyph.c src/simple_renderer.c src/common.c src/lexer.c" OBJ=$(echo "$SRC" | sed "s/\.c/\.o/g") OBJ=$(echo "$OBJ" | sed "s/src\// /g") diff --git a/ded.miniconf b/ded.miniconf new file mode 100644 index 00000000..32334880 --- /dev/null +++ b/ded.miniconf @@ -0,0 +1,72 @@ +window: + font: "./fonts/iosevka-regular.ttf + title: "ded + x: 100 + y: 100 + w: 800 + h: 600 + +pop up: + scale: 0.6 + fade in: 300 + +pop ups: + save ok: + str: "Saved file! + color: x009966FF + last: 1000 + invalid goto: + str: "Invalid line(:col) + color: xFF2400FF + last: 2000 + can not cd: + str: "Can't change directory to "{path}"! + color: xFF2400FF + last: 2000 + can not open: + str: "Can't open file "{path}"! + color: xFF2400FF + last: 2000 + can not save: + str: "Can't save file: {err}! + color: xFF2400FF + last: 2000 + +input hints: + find: "find + save as: "path + save: "path + goto: "line + +editor: + background: x181818FF + color: xFFFFFFFF + bottom bar: + scale: 0.6 + background: xE7E7E7FF + input: + hint: + color: x5B5B5BFF + fmt: "({hint}) + spacing: 20.0 + content: + color: x181818FF + stats: + enabled: true + fmt: "{lang} {row}:{col} / {max-row} + color: x181818FF + tokens: + preprocessor directive: x95A99FFF + keyword: xFFDD33FF + comment: xCC8C3CFF + string: x73C936FF + cursor: + blink: + treshold: 500 + period: 1000 + color: xFFFFFFFF + width: 5.0 + selection: + color: x434343FF + search selection: + color: x191943FF \ No newline at end of file diff --git a/default.miniconf b/default.miniconf new file mode 100644 index 00000000..32334880 --- /dev/null +++ b/default.miniconf @@ -0,0 +1,72 @@ +window: + font: "./fonts/iosevka-regular.ttf + title: "ded + x: 100 + y: 100 + w: 800 + h: 600 + +pop up: + scale: 0.6 + fade in: 300 + +pop ups: + save ok: + str: "Saved file! + color: x009966FF + last: 1000 + invalid goto: + str: "Invalid line(:col) + color: xFF2400FF + last: 2000 + can not cd: + str: "Can't change directory to "{path}"! + color: xFF2400FF + last: 2000 + can not open: + str: "Can't open file "{path}"! + color: xFF2400FF + last: 2000 + can not save: + str: "Can't save file: {err}! + color: xFF2400FF + last: 2000 + +input hints: + find: "find + save as: "path + save: "path + goto: "line + +editor: + background: x181818FF + color: xFFFFFFFF + bottom bar: + scale: 0.6 + background: xE7E7E7FF + input: + hint: + color: x5B5B5BFF + fmt: "({hint}) + spacing: 20.0 + content: + color: x181818FF + stats: + enabled: true + fmt: "{lang} {row}:{col} / {max-row} + color: x181818FF + tokens: + preprocessor directive: x95A99FFF + keyword: xFFDD33FF + comment: xCC8C3CFF + string: x73C936FF + cursor: + blink: + treshold: 500 + period: 1000 + color: xFFFFFFFF + width: 5.0 + selection: + color: x434343FF + search selection: + color: x191943FF \ No newline at end of file diff --git a/dependencies/freetype b/dependencies/freetype new file mode 160000 index 00000000..d6fb49d1 --- /dev/null +++ b/dependencies/freetype @@ -0,0 +1 @@ +Subproject commit d6fb49d11a9d0011bf4ecfe7e570beaaa189838a diff --git a/dependencies/minilibs b/dependencies/minilibs new file mode 160000 index 00000000..f3a1f46e --- /dev/null +++ b/dependencies/minilibs @@ -0,0 +1 @@ +Subproject commit f3a1f46ea2da653e2d761537d1761f56fc556f92 diff --git a/dependencies/minirent b/dependencies/minirent new file mode 160000 index 00000000..dbbc305b --- /dev/null +++ b/dependencies/minirent @@ -0,0 +1 @@ +Subproject commit dbbc305ba37afe60003cc64c49691c2d32d0e8a7 diff --git a/setup_other.sh b/setup_other.sh new file mode 100644 index 00000000..150cc5db --- /dev/null +++ b/setup_other.sh @@ -0,0 +1,2 @@ +cp dependencies/SDL2/lib/x64/SDL2.dll . +dos2unix shaders/* diff --git a/src/common.c b/src/common.c index 46bcfc50..d8682886 100644 --- a/src/common.c +++ b/src/common.c @@ -18,6 +18,12 @@ #include "./arena.h" #define SV_IMPLEMENTATION #include "sv.h" +#define FILELIB_IMPL +#include +#define MINICONF_IMPL +#include +#define MINIFMT_IMPL +#include static Arena temporary_arena = {0}; @@ -64,12 +70,21 @@ Errno read_entire_dir(const char *dir_path, Files *files) Errno write_entire_file(const char *file_path, const char *buf, size_t buf_size) { Errno result = 0; - FILE *f = NULL; + if (buf == NULL) + return result; - f = fopen(file_path, "wb"); + FILE *f = fopen(file_path, "wb"); if (f == NULL) return_defer(errno); - fwrite(buf, 1, buf_size, f); + // TODO: why are there extra nulls at the end of the buf (buf_size)?? + // that's why we strlen it here to get the size without these nulls + size_t real_buf_size = strlen(buf); + if (buf_size < real_buf_size) + real_buf_size = buf_size; + fwrite(buf, 1, real_buf_size, f); + if (get_last(buf, real_buf_size) != '\n') { + fputc('\n', f); + } if (ferror(f)) return_defer(errno); defer: @@ -77,43 +92,28 @@ Errno write_entire_file(const char *file_path, const char *buf, size_t buf_size) return result; } -static Errno file_size(FILE *file, size_t *size) -{ - long saved = ftell(file); - if (saved < 0) return errno; - if (fseek(file, 0, SEEK_END) < 0) return errno; - long result = ftell(file); - if (result < 0) return errno; - if (fseek(file, saved, SEEK_SET) < 0) return errno; - *size = (size_t) result; - return 0; -} - Errno read_entire_file(const char *file_path, String_Builder *sb) { - Errno result = 0; - FILE *f = NULL; - - f = fopen(file_path, "r"); - if (f == NULL) return_defer(errno); - - size_t size; - Errno err = file_size(f, &size); - if (err != 0) return_defer(err); - - if (sb->capacity < size) { - sb->capacity = size; - sb->items = realloc(sb->items, sb->capacity*sizeof(*sb->items)); - assert(sb->items != NULL && "Buy more RAM lol"); + FILE *f = fopen(file_path, "r+"); + if (f == NULL) + return 1; + + int c; + while ((c = fgetc(f)) != EOF && c != '\0') { + if (c == '\t') { + da_append(sb, ' '); + da_append(sb, ' '); + da_append(sb, ' '); + da_append(sb, ' '); + } else if (c == '\r') { + continue; + } else { + da_append(sb, (char) c); + } } - fread(sb->items, size, 1, f); - if (ferror(f)) return_defer(errno); - sb->count = size; - -defer: - if (f) fclose(f); - return result; + fclose(f); + return 0; } Vec4f hex_to_vec4f(uint32_t color) @@ -133,7 +133,17 @@ Vec4f hex_to_vec4f(uint32_t color) Errno type_of_file(const char *file_path, File_Type *ft) { #ifdef _WIN32 -#error "TODO: type_of_file() is not implemented for Windows" + DWORD file_obj_type = GetFileAttributesA(file_path); + if (file_obj_type & FILE_ATTRIBUTE_DIRECTORY) { + *ft = FT_DIRECTORY; + } + // I have no idea why, but a 'normal' file is considered an archive file? + else if (file_obj_type & FILE_ATTRIBUTE_ARCHIVE) { + *ft = FT_REGULAR; + } + else { + *ft = FT_OTHER; + } #else struct stat sb = {0}; if (stat(file_path, &sb) < 0) return errno; diff --git a/src/common.h b/src/common.h index 761b093f..1ac08a5a 100644 --- a/src/common.h +++ b/src/common.h @@ -6,8 +6,6 @@ #include #include "./la.h" -#define SCREEN_WIDTH 800 -#define SCREEN_HEIGHT 600 #define FPS 60 #define DELTA_TIME (1.0f / FPS) #define CURSOR_OFFSET 0.13f @@ -88,7 +86,9 @@ typedef struct { size_t n = strlen(s); \ da_append_many(sb, s, n); \ } while (0) -#define sb_append_null(sb) da_append_many(sb, "", 1) +#define get_last(items, count) ((count) > 0 ? (items)[(count) - 1] : '\0') +#define sb_get_last(sb) get_last((sb).items, (sb).count) +#define sb_append_null(sb) da_append_many((sb), "", 1) #define sb_to_sv(sb) sv_from_parts((sb).items, (sb).count) diff --git a/src/editor.c b/src/editor.c index 1aaeee76..9e12204b 100644 --- a/src/editor.c +++ b/src/editor.c @@ -8,9 +8,9 @@ void editor_backspace(Editor *e) { - if (e->searching) { - if (e->search.count > 0) { - e->search.count -= 1; + if (e->input.active) { + if (e->input.text.count > 0) { + e->input.text.count -= 1; } } else { if (e->cursor > e->data.count) { @@ -43,8 +43,39 @@ void editor_delete(Editor *e) editor_retokenize(e); } -// TODO: make sure that you always have new line at the end of the file while saving -// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206 +void editor_delete_selection(Editor *e) +{ + assert(e->selection); + + if (e->cursor > e->select_begin) { + if (e->cursor > e->data.count) { + e->cursor = e->data.count; + } + if (e->cursor == 0) return; + + size_t nchars = e->cursor - e->select_begin; + memmove( + &e->data.items[e->cursor - nchars], + &e->data.items[e->cursor], + e->data.count - e->cursor + ); + + e->cursor -= nchars; + e->data.count -= nchars; + } else { + if (e->cursor >= e->data.count) return; + + size_t nchars = e->select_begin - e->cursor; + memmove( + &e->data.items[e->cursor], + &e->data.items[e->cursor + nchars], + e->data.count - e->cursor - nchars + ); + + e->data.count -= nchars; + } + editor_retokenize(e); +} Errno editor_save_as(Editor *e, const char *file_path) { @@ -54,14 +85,31 @@ Errno editor_save_as(Editor *e, const char *file_path) e->file_path.count = 0; sb_append_cstr(&e->file_path, file_path); sb_append_null(&e->file_path); + + editor_configured_popup(e, "save ok", (PlaceholderList) { + .elems = (Placeholder[]) { + PLACEHOLDER_STR("path", file_path), + }, + .elems_size = 1 + }); + return 0; } -Errno editor_save(const Editor *e) +Errno editor_save(Editor *e) { assert(e->file_path.count > 0); printf("Saving as %s...\n", e->file_path.items); - return write_entire_file(e->file_path.items, e->data.items, e->data.count); + Errno err = write_entire_file(e->file_path.items, e->data.items, e->data.count); + if (err == 0) { + editor_configured_popup(e, "save ok", (PlaceholderList) { + .elems = (Placeholder[]) { + PLACEHOLDER_STR("path", e->file_path.items), + }, + .elems_size = 1 + }); + } + return err; } Errno editor_load_from_file(Editor *e, const char *file_path) @@ -95,12 +143,28 @@ size_t editor_cursor_row(const Editor *e) return e->lines.count - 1; } +void editor_goto(Editor *e, size_t line, size_t col) +{ + if (e->lines.count == 0) + line = 0; + else if (line >= e->lines.count) + line = e->lines.count - 1; + + Line target_line = e->lines.items[line]; + size_t target_line_size = target_line.end - target_line.begin; + if (target_line_size == 0) + col = 0; + else if (col >= target_line_size) + col = target_line_size - 1; + e->cursor = target_line.begin + col; +} + void editor_move_line_up(Editor *e) { editor_stop_search(e); size_t cursor_row = editor_cursor_row(e); - size_t cursor_col = e->cursor - e->lines.items[cursor_row].begin; + size_t cursor_col = EDITOR_CURSOR_COL(cursor_row,e); if (cursor_row > 0) { Line next_line = e->lines.items[cursor_row - 1]; size_t next_line_size = next_line.end - next_line.begin; @@ -114,7 +178,7 @@ void editor_move_line_down(Editor *e) editor_stop_search(e); size_t cursor_row = editor_cursor_row(e); - size_t cursor_col = e->cursor - e->lines.items[cursor_row].begin; + size_t cursor_col = EDITOR_CURSOR_COL(cursor_row,e); if (cursor_row < e->lines.count - 1) { Line next_line = e->lines.items[cursor_row + 1]; size_t next_line_size = next_line.end - next_line.begin; @@ -164,34 +228,21 @@ void editor_insert_char(Editor *e, char x) void editor_insert_buf(Editor *e, char *buf, size_t buf_len) { - if (e->searching) { - sb_append_buf(&e->search, buf, buf_len); - bool matched = false; - for (size_t pos = e->cursor; pos < e->data.count; ++pos) { - if (editor_search_matches_at(e, pos)) { - e->cursor = pos; - matched = true; - break; - } - } - if (!matched) e->search.count -= buf_len; - } else { - if (e->cursor > e->data.count) { - e->cursor = e->data.count; - } + if (e->cursor > e->data.count) { + e->cursor = e->data.count; + } - for (size_t i = 0; i < buf_len; ++i) { - da_append(&e->data, '\0'); - } - memmove( - &e->data.items[e->cursor + buf_len], - &e->data.items[e->cursor], - e->data.count - e->cursor - buf_len - ); - memcpy(&e->data.items[e->cursor], buf, buf_len); - e->cursor += buf_len; - editor_retokenize(e); + for (size_t i = 0; i < buf_len; ++i) { + da_append(&e->data, '\0'); } + memmove( + &e->data.items[e->cursor + buf_len], + &e->data.items[e->cursor], + e->data.count - e->cursor - buf_len + ); + memcpy(&e->data.items[e->cursor], buf, buf_len); + e->cursor += buf_len; + editor_retokenize(e); } void editor_retokenize(Editor *e) @@ -218,7 +269,8 @@ void editor_retokenize(Editor *e) // Syntax Highlighting { e->tokens.count = 0; - Lexer l = lexer_new(e->atlas, e->data.items, e->data.count); + Lexer l = lexer_new(e->atlas, e->data.items, e->data.count, e->file_path); + e->file_ext = l.file_ext; Token t = lexer_next(&l); while (t.kind != TOKEN_END) { da_append(&e->tokens, t); @@ -255,6 +307,10 @@ const char *editor_line_starts_with_one_of(Editor *e, size_t row, size_t col, co return NULL; } +static Vec4f token_color(Editor *editor, Token_Kind token) { + return editor->configs.editor.tokens[token]; +} + void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor) { int w, h; @@ -297,7 +353,7 @@ void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer atlas, editor->data.items + select_begin_chr, select_end_chr - select_begin_chr, &select_end_scr); - Vec4f selection_color = vec4f(.25, .25, .25, 1); + Vec4f selection_color = editor->configs.editor.selection; simple_renderer_solid_rect(sr, select_begin_scr, vec2f(select_end_scr.x - select_begin_scr.x, FREE_GLYPH_FONT_SIZE), selection_color); } } @@ -321,12 +377,12 @@ void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer // Render search { - if (editor->searching) { + if (editor->searching && editor_search_matches_at(editor, editor->cursor)) { simple_renderer_set_shader(sr, SHADER_FOR_COLOR); - Vec4f selection_color = vec4f(.10, .10, .25, 1); + Vec4f selection_color = editor->configs.editor.search.selection; Vec2f p1 = cursor_pos; Vec2f p2 = p1; - free_glyph_atlas_measure_line_sized(editor->atlas, editor->search.items, editor->search.count, &p2); + free_glyph_atlas_measure_line_sized(editor->atlas, editor->input.text.items, editor->input.text.count, &p2); simple_renderer_solid_rect(sr, p1, vec2f(p2.x - p1.x, FREE_GLYPH_FONT_SIZE), selection_color); simple_renderer_flush(sr); } @@ -338,23 +394,7 @@ void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer for (size_t i = 0; i < editor->tokens.count; ++i) { Token token = editor->tokens.items[i]; Vec2f pos = token.position; - Vec4f color = vec4fs(1); - switch (token.kind) { - case TOKEN_PREPROC: - color = hex_to_vec4f(0x95A99FFF); - break; - case TOKEN_KEYWORD: - color = hex_to_vec4f(0xFFDD33FF); - break; - case TOKEN_COMMENT: - color = hex_to_vec4f(0xCC8C3CFF); - break; - case TOKEN_STRING: - color = hex_to_vec4f(0x73c936ff); - break; - default: - {} - } + Vec4f color = token_color(editor, token.kind); free_glyph_atlas_render_line_sized(atlas, sr, token.text, token.text_len, &pos, color); // TODO: the max_line_len should be calculated based on what's visible on the screen right now if (max_line_len < pos.x) max_line_len = pos.x; @@ -365,9 +405,9 @@ void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer // Render cursor simple_renderer_set_shader(sr, SHADER_FOR_COLOR); { - float CURSOR_WIDTH = 5.0f; - Uint32 CURSOR_BLINK_THRESHOLD = 500; - Uint32 CURSOR_BLINK_PERIOD = 1000; + float CURSOR_WIDTH = editor->configs.editor.cursor.width; + Uint32 CURSOR_BLINK_THRESHOLD = editor->configs.editor.cursor.blink_threshold; + Uint32 CURSOR_BLINK_PERIOD = editor->configs.editor.cursor.blink_period; Uint32 t = SDL_GetTicks() - editor->last_stroke; sr->verticies_count = 0; @@ -375,12 +415,160 @@ void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer simple_renderer_solid_rect( sr, cursor_pos, vec2f(CURSOR_WIDTH, FREE_GLYPH_FONT_SIZE), - vec4fs(1)); + editor->configs.editor.cursor.color); } simple_renderer_flush(sr); } + // Render pop-ups + { + float scale = (float) editor->configs.popup.scale; + float oscale = 1.0f / scale; + + Simple_Camera oldCam = sr->cam; + sr->cam = (Simple_Camera) { + .pos = vec2f((float) w / 2 * oscale - 20.0f * scale, -((float) h / 2 * oscale) + 80.0f * scale), + .scale = scale, + .scale_vel = 0.0f, + .vel = vec2f(0, 0) + }; + simple_renderer_set_shader(sr, SHADER_FOR_TEXT); + + for (size_t i = 0; i < editor->popUps.count; i ++) { + PopUp *p = &editor->popUps.items[i]; + if (p->lasts == 0) + continue; + + if (p->when + p->lasts < SDL_GetTicks()) { + editor_remove_popup(editor, p->uid); + i --; + } + } + + for (size_t i = 0; i < editor->popUps.count; i ++) { + PopUp *p = &editor->popUps.items[i]; + + float t = (SDL_GetTicks() - p->when) / (float) editor->configs.popup.fade_in; + if (t > 1) + t = 1; + else if (t < 0) + t = 0; + + Vec2f pos = vec2f( + lerpf(-180 * oscale, 0, t), + -(i * 80.0f * scale) + ); + free_glyph_atlas_render_line_sized(atlas, sr, p->msg, p->msg_size, &pos, p->color); + } + + simple_renderer_flush(sr); + sr->cam = oldCam; + } + + // Render bottom bar + { + float scale = (float) editor->configs.editor.bottom.scale; + float oscale = 1.0f / scale; + + Simple_Camera oldCam = sr->cam; + sr->cam = (Simple_Camera) { + .pos = vec2f((float) w / 2 * oscale, ((float) h / 2 * oscale) - 60.0f * scale), + .scale = scale, + .scale_vel = 0.0f, + .vel = vec2f(0, 0) + }; + + if (editor->configs.editor.bottom.stats.enabled || editor->input.active) { + simple_renderer_set_shader(sr, SHADER_FOR_COLOR); + Vec4f bg = editor->configs.editor.bottom.background; + Vec2f p1 = vec2f(0, -60.0f * scale); + Vec2f s = vec2f(w * oscale, 60.0f + 60.0f * scale); + simple_renderer_solid_rect(sr, p1, s, bg); + simple_renderer_flush(sr); + } + + float x = 20.0f * scale; + + // Render input + if (editor->input.active) { + do { + char *hint = fmt_fmt_fmt(editor->configs.editor.bottom.input.fmt_hint, (PlaceholderList) { + .elems = (Placeholder[]) { + PLACEHOLDER_STR("hint", editor->input.hint), + }, + .elems_size = 1 + }); + + if (hint == NULL) { + fprintf(stderr, "\"editor/bottom bar/input/hint/fmt\" format error!\n"); + break; + } + + simple_renderer_set_shader(sr, SHADER_FOR_TEXT); + Vec4f color = editor->configs.editor.bottom.input.color_hint; + Vec2f pos = vec2f(x, -20 * scale); + free_glyph_atlas_render_line_sized(atlas, sr, + hint, + strlen(hint), + &pos, color); + x = pos.x + editor->configs.editor.bottom.input.spacing; + simple_renderer_flush(sr); + + free(hint); + } while (0); + + { + simple_renderer_set_shader(sr, SHADER_FOR_TEXT); + Vec4f color = editor->configs.editor.bottom.input.color; + Vec2f pos = vec2f(x, -20 * scale); + free_glyph_atlas_render_line_sized(atlas, sr, + editor->input.text.items, + editor->input.text.count, + &pos, color); + simple_renderer_flush(sr); + } + } + // Render additional info + else if (editor->configs.editor.bottom.stats.enabled) do { + const size_t cursor_row = editor_cursor_row(editor); + const size_t cursor_col = EDITOR_CURSOR_COL(cursor_row,editor); + + char *str = fmt_fmt_fmt(editor->configs.editor.bottom.stats.fmt, (PlaceholderList) { + .elems = (Placeholder[]) { + PLACEHOLDER_STR("lang", file_ext_str(editor->file_ext)), + + PLACEHOLDER_LONG("row", cursor_row + 1), + PLACEHOLDER_LONG("max-row", editor->lines.count), + + PLACEHOLDER_LONG("col", cursor_col + 1) + }, + .elems_size = 4 + }); + + if (str == NULL) { + fprintf(stderr, "\"editor/bottom bar/stats/fmt\" format error!\n"); + break; + } + + { + simple_renderer_set_shader(sr, SHADER_FOR_TEXT); + Vec4f color = editor->configs.editor.bottom.stats.color; + Vec2f pos = vec2f(x, -20 * scale); + free_glyph_atlas_render_line_sized(atlas, sr, + str, + strlen(str), + &pos, color); + x = pos.x; + simple_renderer_flush(sr); + } + + free(str); + } while (0); + + sr->cam = oldCam; + } + // Update camera { if (max_line_len > 1000.0f) { @@ -395,18 +583,18 @@ void editor_render(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer if (target_scale > 3.0f) { target_scale = 3.0f; } else { - offset = cursor_pos.x - w/3/sr->camera_scale; + offset = cursor_pos.x - w/3/sr->cam.scale; if (offset < 0.0f) offset = 0.0f; - target = vec2f(w/3/sr->camera_scale + offset, cursor_pos.y); + target = vec2f(w/3/sr->cam.scale + offset, cursor_pos.y); } - sr->camera_vel = vec2f_mul( - vec2f_sub(target, sr->camera_pos), + sr->cam.vel = vec2f_mul( + vec2f_sub(target, sr->cam.pos), vec2fs(2.0f)); - sr->camera_scale_vel = (target_scale - sr->camera_scale) * 2.0f; + sr->cam.scale_vel = (target_scale - sr->cam.scale) * 2.0f; - sr->camera_pos = vec2f_add(sr->camera_pos, vec2f_mul(sr->camera_vel, vec2fs(DELTA_TIME))); - sr->camera_scale = sr->camera_scale + sr->camera_scale_vel * DELTA_TIME; + sr->cam.pos = vec2f_add(sr->cam.pos, vec2f_mul(sr->cam.vel, vec2fs(DELTA_TIME))); + sr->cam.scale = sr->cam.scale + sr->cam.scale_vel * DELTA_TIME; } } @@ -466,11 +654,17 @@ void editor_start_search(Editor *e) } } else { e->searching = true; + editor_start_input(e); + e->input.hint = editor_configured_inline_hint(e, "find"); + e->input.hint_len = strlen(e->input.hint); if (e->selection) { + size_t begin = e->select_begin; + size_t end = e->cursor; + if (begin > end) SWAP(size_t, begin, end); + + sb_append_buf(&e->input.text, &e->data.items[begin], end - begin + 1); + e->cursor = begin; e->selection = false; - // TODO: put the selection into the search automatically - } else { - e->search.count = 0; } } } @@ -478,13 +672,15 @@ void editor_start_search(Editor *e) void editor_stop_search(Editor *e) { e->searching = false; + if (!e->input.required) + e->input.active = false; } bool editor_search_matches_at(Editor *e, size_t pos) { - if (e->data.count - pos < e->search.count) return false; - for (size_t i = 0; i < e->search.count; ++i) { - if (e->search.items[i] != e->data.items[pos + i]) { + if (e->data.count - pos < e->input.text.count) return false; + for (size_t i = 0; i < e->input.text.count; ++i) { + if (e->input.text.items[i] != e->data.items[pos + i]) { return false; } } @@ -542,3 +738,363 @@ void editor_move_paragraph_down(Editor *e) } e->cursor = e->lines.items[row].begin; } + +Uint32 nextPopUpUid = 0; +Uint32 editor_add_popup(Editor *editor, PopUp *popUp) +{ + editor->popUps.count ++; + editor->popUps.items = realloc(editor->popUps.items, sizeof(PopUp) * editor->popUps.count); + memcpy(&editor->popUps.items[editor->popUps.count - 1], popUp, sizeof(PopUp)); + editor->popUps.items[editor->popUps.count - 1].uid = nextPopUpUid; + return nextPopUpUid ++; +} + +void editor_remove_popup(Editor *editor, Uint32 uid) +{ + for (size_t i = 0; i < editor->popUps.count; i ++) { + PopUp *p = &editor->popUps.items[i]; + if (p->uid != uid) + continue; + + if (p->free_msg) + free((char *) p->msg); + + memcpy(p, p + 1, (editor->popUps.count - i - 1) * sizeof(PopUp)); + editor->popUps.count --; + editor->popUps.items = realloc(editor->popUps.items, sizeof(PopUp) * editor->popUps.count); + break; + } +} + +void flash_error_str(Editor *editor, const char *str) +{ + fputs(str, stderr); + fputc('\n', stderr); + + PopUp p; + p.msg = str; + p.free_msg = false; + p.msg_size = strlen(str); + p.color = hex_to_vec4f(0xff2400ff); + p.when = SDL_GetTicks(); + p.lasts = 2000; + + (void) editor_add_popup(editor, &p); +} + +void editor_start_input(Editor *editor) +{ + editor->input.active = true; + editor->input.onDone = NULL; + editor->input.required = false; + editor->input.hint_len = 0; + if (editor->input.text.items) { + free(editor->input.text.items); + editor->input.text = (String_Builder){0}; + } +} + +void editor_configured_popup(Editor *editor, const char *type, PlaceholderList placeholders) { + Config cfg; + config_init(&cfg); + bool ok; + config_child(&cfg, editor->configs.popup_messages, type, &ok); + if (!ok) { + fprintf(stderr, "\"pop ups/%s\" not configured!\n", type); + goto err; + } + + PopUp popup; + + const char *fmt_str = config_get_str_at(cfg, "str", &ok); + if (!ok) { + fprintf(stderr, "\"pop ups/%s/str\" not configured!\n", type); + goto err; + } + + popup.color = hex_to_vec4f(config_get_long_at(cfg, "color", &ok)); + if (!ok) { + fprintf(stderr, "\"pop ups/%s/color\" not configured!\n", type); + goto err; + } + + popup.lasts = config_get_long_at(cfg, "last", &ok); + if (!ok) { + fprintf(stderr, "\"pop ups/%s/last\" not configured!\n", type); + goto err; + } + + { + size_t fmt_str_len = strlen(fmt_str); + char *fmt_str_copy = malloc(fmt_str_len + 1); + memcpy(fmt_str_copy, fmt_str, fmt_str_len + 1); + + Fmt fmt = fmt_compile(fmt_str_copy); + popup.msg = fmt_fmt_fmt(fmt, placeholders); + fmt_destroy(fmt); + + free(fmt_str_copy); + + if (popup.msg == NULL) { + fprintf(stderr, "\"pop ups/%s/str\" format error!\n", type); + goto err; + } + + popup.msg_size = strlen(popup.msg); + + popup.free_msg = true; + } + + popup.when = SDL_GetTicks(); + (void) editor_add_popup(editor, &popup); + + config_destroy(&cfg); + return; + +err: + flash_error(editor, "Config error! See program output for more info"); + config_destroy(&cfg); + return; +} + +const char *editor_configured_inline_hint(Editor *editor, const char *type) { + bool ok; + const char *str = config_get_str_at(editor->configs.input_hints, type, &ok); + if (!ok) { + fprintf(stderr, "\"input hints/%s\" not configured!\n", type); + flash_error(editor, "Config error! See program output for more info"); + return "???"; + } + return str; +} + +bool editor_load_config(Editor *editor, const char *config_path) { + config_destroy(&editor->configs.cfg); + config_init(&editor->configs.cfg); + allocgroup_free(editor->configs.alloc); + + FILE *file = fopen(config_path, "r"); + if (file == NULL) + return false; + editor->configs.alloc = config_add_file(&editor->configs.cfg, file); + fclose(file); + + { + Config window; + config_init(&window); + bool ok; + config_child(&window, editor->configs.cfg, "window", &ok); + if (!ok) { + fprintf(stderr, "\"window\" not found in config!"); + config_destroy(&window); + return false; + } + +#define CHECK_NF(what) if (!ok) { \ + fprintf(stderr, "\"window/" what "\" not found in config!"); \ + config_destroy(&window); \ + return false; \ +} + + editor->configs.window.font = config_get_str_at(window, "font", &ok); + CHECK_NF("font") + + editor->configs.window.title = config_get_str_at(window, "title", &ok); + CHECK_NF("title"); + + editor->configs.window.x = config_get_long_at(window, "x", &ok); + CHECK_NF("x"); + + editor->configs.window.y = config_get_long_at(window, "y", &ok); + CHECK_NF("y"); + + editor->configs.window.w = config_get_long_at(window, "w", &ok); + CHECK_NF("w"); + + editor->configs.window.h = config_get_long_at(window, "h", &ok); + CHECK_NF("h"); + +#undef CHECK_NF + + config_destroy(&window); + } + + { + Config popup; + config_init(&popup); + bool ok; + config_child(&popup, editor->configs.cfg, "pop up", &ok); + if (!ok) { + fprintf(stderr, "\"pop up\" not found in config!\n"); + config_destroy(&popup); + return false; + } + + editor->configs.popup.scale = config_get_double_at(popup, "scale", &ok); + if (!ok) { + fprintf(stderr, "\"pop up/scale\" not found in config!\n"); + config_destroy(&popup); + return false; + } + + editor->configs.popup.fade_in = config_get_long_at(popup, "fade in", &ok); + if (!ok) { + fprintf(stderr, "\"pop up/fade in\" not found in config!\n"); + config_destroy(&popup); + return false; + } + + config_destroy(&popup); + } + + { + config_destroy(&editor->configs.popup_messages); + config_init(&editor->configs.popup_messages); + bool ok; + config_child(&editor->configs.popup_messages, editor->configs.cfg, "pop ups", &ok); + if (!ok) { + fprintf(stderr, "\"pop ups\" not found in config!\n"); + return false; + } + } + + { + config_destroy(&editor->configs.input_hints); + config_init(&editor->configs.input_hints); + bool ok; + config_child(&editor->configs.input_hints, editor->configs.cfg, "input hints", &ok); + if (!ok) { + fprintf(stderr, "\"input hints\" not found in config!\n"); + return false; + } + } + + Config edit; + config_init(&edit); + bool ok; + config_child(&edit, editor->configs.cfg, "editor", &ok); + if (!ok) { + fprintf(stderr, "\"editor\" not found in config!\n"); + config_destroy(&edit); + return false; + } + +#define CHECK_NF(what) if (!ok) { \ + fprintf(stderr, "\"editor/" what "\" not found in config!\n"); \ + config_destroy(&edit); \ + return false; \ +} + + editor->configs.editor.background = hex_to_vec4f(config_get_long_at(edit, "background", &ok)); + CHECK_NF("background") + + editor->configs.editor.color = hex_to_vec4f(config_get_long_at(edit, "color", &ok)); + CHECK_NF("color") + + // TODO + // we should get each sub config manually but I'm lazy + + editor->configs.editor.bottom.scale = config_get_double_at(edit, "bottom bar/scale", &ok); + CHECK_NF("bottom bar/scale") + + editor->configs.editor.bottom.background = hex_to_vec4f(config_get_long_at(edit, "bottom bar/background", &ok)); + CHECK_NF("bottom bar/background") + + editor->configs.editor.bottom.input.color_hint = hex_to_vec4f(config_get_long_at(edit, "bottom bar/input/hint/color", &ok)); + CHECK_NF("bottom bar/input/hint/color") + + editor->configs.editor.bottom.input.spacing = config_get_double_at(edit, "bottom bar/input/hint/spacing", &ok); + CHECK_NF("bottom bar/input/hint/spacing") + + { + const char *fmt_str = config_get_str_at(edit, "bottom bar/input/hint/fmt", &ok); + CHECK_NF("bottom bar/input/hint/fmt") + + free(editor->configs.editor.bottom.input.fmt_str); + fmt_destroy(editor->configs.editor.bottom.input.fmt_hint); + + size_t fmt_str_len = strlen(fmt_str); + char *fmt_str_copy = malloc(fmt_str_len + 1); + memcpy(fmt_str_copy, fmt_str, fmt_str_len + 1); + + editor->configs.editor.bottom.input.fmt_hint = fmt_compile(fmt_str_copy); + editor->configs.editor.bottom.input.fmt_str = fmt_str_copy; + } + + editor->configs.editor.bottom.input.color = hex_to_vec4f(config_get_long_at(edit, "bottom bar/input/content/color", &ok)); + CHECK_NF("bottom bar/input/content/color") + + bool status_bar_enabled = config_get_bool_at(edit, "bottom bar/stats/enabled", &ok); + CHECK_NF("bottom bar/stats/enabled") + editor->configs.editor.bottom.stats.enabled = status_bar_enabled; + + if (status_bar_enabled) { + { + const char *fmt_str = config_get_str_at(edit, "bottom bar/stats/fmt", &ok); + CHECK_NF("bottom bar/stats/fmt") + + free(editor->configs.editor.bottom.stats.fmt_str); + fmt_destroy(editor->configs.editor.bottom.stats.fmt); + + size_t fmt_str_len = strlen(fmt_str); + char *fmt_str_copy = malloc(fmt_str_len + 1); + memcpy(fmt_str_copy, fmt_str, fmt_str_len + 1); + + editor->configs.editor.bottom.stats.fmt = fmt_compile(fmt_str_copy); + editor->configs.editor.bottom.stats.fmt_str = fmt_str_copy; + } + + editor->configs.editor.bottom.stats.color = hex_to_vec4f(config_get_long_at(edit, "bottom bar/stats/color", &ok)); + CHECK_NF("bottom bar/stats/color") + } + + { + Config tokens; + config_init(&tokens); + bool ok; + config_child(&tokens, edit, "tokens", &ok); + if (!ok) { + fprintf(stderr, "\"editor/tokens\" not found in config!"); + config_destroy(&tokens); + return false; + } + + for (size_t i = 0; i < TOKEN_KIND_SIZE; i ++) { + const char *name = token_kind_name[i]; + + long colorLong = config_get_long_at(tokens, name, &ok); + Vec4f color; + if (ok) + color = hex_to_vec4f(colorLong); + else + color = editor->configs.editor.color; + + editor->configs.editor.tokens[i] = color; + } + + config_destroy(&tokens); + } + + editor->configs.editor.cursor.blink_threshold = config_get_long_at(edit, "cursor/blink/treshold", &ok); + CHECK_NF("cursor/blink/treshold") + + editor->configs.editor.cursor.blink_period = config_get_long_at(edit, "cursor/blink/period", &ok); + CHECK_NF("cursor/blink/period") + + editor->configs.editor.cursor.color = hex_to_vec4f(config_get_long_at(edit, "cursor/color", &ok)); + CHECK_NF("cursor/color") + + editor->configs.editor.cursor.width = config_get_double_at(edit, "cursor/width", &ok); + CHECK_NF("cursor/width") + + editor->configs.editor.selection = hex_to_vec4f(config_get_long_at(edit, "selection/color", &ok)); + CHECK_NF("selection/color") + + editor->configs.editor.search.selection = hex_to_vec4f(config_get_long_at(edit, "search selection/color", &ok)); + CHECK_NF("search selection") + +#undef CHECK_NF + + config_destroy(&edit); + return true; +} \ No newline at end of file diff --git a/src/editor.h b/src/editor.h index 4b5ddd58..56f5b409 100644 --- a/src/editor.h +++ b/src/editor.h @@ -1,6 +1,7 @@ #ifndef EDITOR_H_ #define EDITOR_H_ +#include #include #include "common.h" #include "free_glyph.h" @@ -8,6 +9,8 @@ #include "lexer.h" #include +#include +#include typedef struct { size_t begin; @@ -27,15 +30,106 @@ typedef struct { } Tokens; typedef struct { + Uint32 uid; + const char *msg; + size_t msg_size; + bool free_msg; + Vec4f color; + Uint32 when; + Uint32 lasts; +} PopUp; + +typedef struct { + PopUp *items; + size_t count; +} PopUps; + +struct Editor_s; + +typedef struct { + String_Builder text; + bool active; + bool required; + void (*onDone)(struct Editor_s *); + const char *hint; + size_t hint_len; +} Input; + +typedef struct { + Config cfg; + AllocGroup alloc; + + struct { + const char *font; + const char *title; + size_t x; + size_t y; + size_t w; + size_t h; + } window; + + struct { + double scale; + long fade_in; + } popup; + + Config popup_messages; + + Config input_hints; + + struct { + Vec4f background; + Vec4f color; + + struct { + double scale; + Vec4f background; + + struct { + Vec4f color_hint; + Vec4f color; + char *fmt_str; + Fmt fmt_hint; + double spacing; + } input; + + struct { + bool enabled; + Fmt fmt; + char *fmt_str; + Vec4f color; + } stats; + } bottom; + + Vec4f tokens[TOKEN_KIND_SIZE]; + + struct { + long blink_threshold; + long blink_period; + Vec4f color; + double width; + } cursor; + + Vec4f selection; + + struct { + Vec4f selection; + } search; + } editor; +} EditorConfig; + +typedef struct Editor_s { Free_Glyph_Atlas *atlas; String_Builder data; Lines lines; Tokens tokens; String_Builder file_path; + PopUps popUps; + Input input; + File_Extension file_ext; bool searching; - String_Builder search; bool selection; size_t select_begin; @@ -44,16 +138,31 @@ typedef struct { Uint32 last_stroke; String_Builder clipboard; + + EditorConfig configs; } Editor; +#define EDITOR_CURSOR_COL(cursor_row,editor) (editor->cursor - editor->lines.items[cursor_row].begin) + +bool editor_load_config(Editor *editor, const char *config_path); + Errno editor_save_as(Editor *editor, const char *file_path); -Errno editor_save(const Editor *editor); +Errno editor_save(Editor *editor); Errno editor_load_from_file(Editor *editor, const char *file_path); +Uint32 editor_add_popup(Editor *editor, PopUp *popUp); +void editor_remove_popup(Editor *editor, Uint32 uid); +void editor_configured_popup(Editor *editor, const char *type, PlaceholderList placeholders); +const char *editor_configured_inline_hint(Editor *editor, const char *type); + +void editor_start_input(Editor *editor); + void editor_backspace(Editor *editor); void editor_delete(Editor *editor); +void editor_delete_selection(Editor *editor); size_t editor_cursor_row(const Editor *e); +void editor_goto(Editor *e, size_t line, size_t col); void editor_move_line_up(Editor *e); void editor_move_line_down(Editor *e); void editor_move_char_left(Editor *e); @@ -80,4 +189,9 @@ void editor_start_search(Editor *e); void editor_stop_search(Editor *e); bool editor_search_matches_at(Editor *e, size_t pos); +void flash_error_str(Editor *editor, const char *str); + +// TODO: display errors reported via flash_error right in the text editor window somehow +#define flash_error(editor, ...) do { static char buf[200]; sprintf(buf, __VA_ARGS__); flash_error_str(editor, buf); } while(0) + #endif // EDITOR_H_ diff --git a/src/file_browser.c b/src/file_browser.c index d4afb45e..11670d8b 100644 --- a/src/file_browser.c +++ b/src/file_browser.c @@ -105,26 +105,32 @@ Errno fb_change_dir(File_Browser *fb) const char *dir_name = fb->files.items[fb->cursor]; - fb->dir_path.count -= 1; + String_Builder new_path = { 0 }; + da_append_many(&new_path, fb->dir_path.items, fb->dir_path.count); - // TODO: fb->dir_path grows indefinitely if we hit the root - sb_append_cstr(&fb->dir_path, "/"); - sb_append_cstr(&fb->dir_path, dir_name); - - String_Builder result = {0}; - normpath(sb_to_sv(fb->dir_path), &result); - da_move(&fb->dir_path, result); - sb_append_null(&fb->dir_path); + new_path.count -= 1; - printf("Changed dir to %s\n", fb->dir_path.items); + // TODO: fb->dir_path grows indefinitely if we hit the root + sb_append_cstr(&new_path, "/"); + sb_append_cstr(&new_path, dir_name); - fb->files.count = 0; - fb->cursor = 0; - Errno err = read_entire_dir(fb->dir_path.items, &fb->files); + String_Builder result = { 0 }; + normpath(sb_to_sv(new_path), &result); + da_move(&new_path, result); + sb_append_null(&new_path); + Files new_files = { 0 }; + Errno err = read_entire_dir(new_path.items, &new_files); if (err != 0) { return err; } + + da_move(&fb->files, new_files); + da_move(&fb->dir_path, new_path); + fb->cursor = 0; + + printf("Changed dir to %s\n", fb->dir_path.items); + qsort(fb->files.items, fb->files.count, sizeof(*fb->files.items), file_cmp); return 0; @@ -184,18 +190,18 @@ void fb_render(const File_Browser *fb, SDL_Window *window, Free_Glyph_Atlas *atl if (target_scale > 3.0f) { target_scale = 3.0f; } else { - offset = cursor_pos.x - w/3/sr->camera_scale; + offset = cursor_pos.x - w/3/sr->cam.scale; if (offset < 0.0f) offset = 0.0f; - target = vec2f(w/3/sr->camera_scale + offset, cursor_pos.y); + target = vec2f(w/3/sr->cam.scale + offset, cursor_pos.y); } - sr->camera_vel = vec2f_mul( - vec2f_sub(target, sr->camera_pos), + sr->cam.vel = vec2f_mul( + vec2f_sub(target, sr->cam.pos), vec2fs(2.0f)); - sr->camera_scale_vel = (target_scale - sr->camera_scale) * 2.0f; + sr->cam.scale_vel = (target_scale - sr->cam.scale) * 2.0f; - sr->camera_pos = vec2f_add(sr->camera_pos, vec2f_mul(sr->camera_vel, vec2fs(DELTA_TIME))); - sr->camera_scale = sr->camera_scale + sr->camera_scale_vel * DELTA_TIME; + sr->cam.pos = vec2f_add(sr->cam.pos, vec2f_mul(sr->cam.vel, vec2fs(DELTA_TIME))); + sr->cam.scale = sr->cam.scale + sr->cam.scale_vel * DELTA_TIME; } } diff --git a/src/lexer.c b/src/lexer.c index cfd9022c..32245450 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "common.h" #include "lexer.h" @@ -19,7 +20,27 @@ Literal_Token literal_tokens[] = { }; #define literal_tokens_count (sizeof(literal_tokens)/sizeof(literal_tokens[0])) -const char *keywords[] = { +const char *jKeywords[] = { + "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", + "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", + "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", + "interface", "long", "native", "new", "package", "private", "protected", "public", + "return", "short", "static", "super", "switch", "synchronized", "this", "throw", + "throws", "transient", "try", "void", "volatile", "while", "non-sealed", "open", + "opens", "permits", "provides", "record", "sealed", "to", "transitive", "uses", "var", + "with", "yield", "true", "false", "null", "const", "goto", "strictfp", +}; +#define jKeywords_count (sizeof(jKeywords)/sizeof(jKeywords[0])) + +const char *ktKeywords[] = { + "abstract", "break", "catch", "class", "const", "continue", "else", "enum", "is", "as", + "when", "val", "var", "for", "if", "import", "interface", "data", "external", "inner", + "package", "private", "protected", "return", "super", "when", "this", "throw", + "try", "while", "sealed", "open", "true", "false", "null", "fun", "typealias", "in", +}; +#define ktKeywords_count (sizeof(ktKeywords)/sizeof(ktKeywords[0])) + +const char *cKeywords[] = { "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "int", "long", "register", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", @@ -34,46 +55,76 @@ const char *keywords[] = { "template", "this", "thread_local", "throw", "true", "try", "typeid", "typename", "using", "virtual", "wchar_t", "xor", "xor_eq", }; -#define keywords_count (sizeof(keywords)/sizeof(keywords[0])) +#define cKeywords_count (sizeof(cKeywords)/sizeof(cKeywords[0])) -const char *token_kind_name(Token_Kind kind) -{ - switch (kind) { - case TOKEN_END: - return "end of content"; - case TOKEN_INVALID: - return "invalid token"; - case TOKEN_PREPROC: - return "preprocessor directive"; - case TOKEN_SYMBOL: - return "symbol"; - case TOKEN_OPEN_PAREN: - return "open paren"; - case TOKEN_CLOSE_PAREN: - return "close paren"; - case TOKEN_OPEN_CURLY: - return "open curly"; - case TOKEN_CLOSE_CURLY: - return "close curly"; - case TOKEN_SEMICOLON: - return "semicolon"; - case TOKEN_KEYWORD: - return "keyword"; - default: - UNREACHABLE("token_kind_name"); - } - return NULL; -} +const char *pyKeywords[] = { + "False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", + "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", + "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", + "return", "try", "while", "with", "yield", +}; +#define pyKeywords_count (sizeof(pyKeywords)/sizeof(pyKeywords[0])) + +const char *token_kind_name[TOKEN_KIND_SIZE] = { + [TOKEN_END] = "end of content", + [TOKEN_INVALID] = "invalid token", + [TOKEN_PREPROC] = "preprocessor directive", + [TOKEN_SYMBOL] = "symbol", + [TOKEN_OPEN_PAREN] = "open paren", + [TOKEN_CLOSE_PAREN] = "close paren", + [TOKEN_OPEN_CURLY] = "open curly", + [TOKEN_CLOSE_CURLY] = "close curly", + [TOKEN_SEMICOLON] = "semicolon", + [TOKEN_KEYWORD] = "keyword", + [TOKEN_COMMENT] = "comment", + [TOKEN_STRING] = "string", +}; -Lexer lexer_new(Free_Glyph_Atlas *atlas, const char *content, size_t content_len) +Lexer lexer_new(Free_Glyph_Atlas *atlas, const char *content, size_t content_len, String_Builder file_path) { Lexer l = {0}; l.atlas = atlas; l.content = content; l.content_len = content_len; + l.file_ext = FEXT_CPP; + if (file_path.items != NULL) { + l.file_path.items = (char*) malloc(sizeof(char*) * (strlen(file_path.items) + 1)); + strcpy(l.file_path.items, file_path.items); + + const char *filename = l.file_path.items; + const char *dot = strrchr(filename, '.'); + if (dot && dot != filename) { + const char *file_ext_str = dot + 1; + + if (strcmp(file_ext_str, "kt") == 0 || strcmp(file_ext_str, "kts") == 0) { + l.file_ext = FEXT_KOTLIN; + } else if (strcmp(file_ext_str, "py") == 0) { + l.file_ext = FEXT_PYTHON; + } else if (strcmp(file_ext_str, "java") == 0) { + l.file_ext = FEXT_JAVA; + } + } + } + return l; } +const char *file_ext_str(File_Extension ext) +{ + switch (ext) { + case FEXT_KOTLIN: + return "Kotlin"; + case FEXT_JAVA: + return "Java"; + case FEXT_CPP: + return "C++"; + case FEXT_PYTHON: + return "Python"; + default: + return "?"; + } +} + bool lexer_starts_with(Lexer *l, const char *prefix) { size_t prefix_len = strlen(prefix); @@ -202,6 +253,30 @@ Token lexer_next(Lexer *l) lexer_chop_char(l, 1); token.text_len += 1; } + + const char **keywords; + size_t keywords_count; + switch (l->file_ext) { + case FEXT_JAVA: + keywords = jKeywords; + keywords_count = jKeywords_count; + break; + + case FEXT_KOTLIN: + keywords = ktKeywords; + keywords_count = ktKeywords_count; + break; + + case FEXT_PYTHON: + keywords = pyKeywords; + keywords_count = pyKeywords_count; + break; + + default: + keywords = cKeywords; + keywords_count = cKeywords_count; + } + for (size_t i = 0; i < keywords_count; ++i) { size_t keyword_len = strlen(keywords[i]); @@ -210,7 +285,7 @@ Token lexer_next(Lexer *l) break; } } - + return token; } diff --git a/src/lexer.h b/src/lexer.h index 6ee721ed..16789ca9 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -4,6 +4,14 @@ #include #include "./la.h" #include "./free_glyph.h" +#include "./common.h" + +typedef enum { + FEXT_KOTLIN, + FEXT_JAVA, + FEXT_CPP, + FEXT_PYTHON, +} File_Extension; typedef enum { TOKEN_END = 0, @@ -20,7 +28,9 @@ typedef enum { TOKEN_STRING, } Token_Kind; -const char *token_kind_name(Token_Kind kind); +#define TOKEN_KIND_SIZE TOKEN_STRING + 1 + +extern const char *token_kind_name[TOKEN_KIND_SIZE]; typedef struct { Token_Kind kind; @@ -37,9 +47,13 @@ typedef struct { size_t line; size_t bol; float x; + String_Builder file_path; + File_Extension file_ext; } Lexer; -Lexer lexer_new(Free_Glyph_Atlas *atlas, const char *content, size_t content_len); +const char *file_ext_str(File_Extension ext); + +Lexer lexer_new(Free_Glyph_Atlas *atlas, const char *content, size_t content_len, String_Builder file_path); Token lexer_next(Lexer *l); #endif // LEXER_H_ diff --git a/src/main.c b/src/main.c index eac9ac5f..71ed4ef1 100644 --- a/src/main.c +++ b/src/main.c @@ -22,12 +22,7 @@ #include "./lexer.h" #include "./sv.h" -// TODO: Save file dialog -// Needed when ded is ran without any file so it does not know where to save. - // TODO: An ability to create a new file -// TODO: Delete a word -// TODO: Delete selection // TODO: Undo/redo system void MessageCallback(GLenum source, @@ -52,9 +47,36 @@ static Simple_Renderer sr = {0}; static Editor editor = {0}; static File_Browser fb = {0}; -// TODO: display errors reported via flash_error right in the text editor window somehow -#define flash_error(...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while(0) +static void onSaveInputPath(Editor *e) +{ + sb_append_null(&e->input.text); + editor_save_as(e, e->input.text.items); +} +static void onGoToLine(Editor *e) +{ + sb_append_null(&e->input.text); + const char *linestr = e->input.text.items; + char *end; + size_t linenum = strtol(linestr, &end, 10); + size_t colnum = 0; + if (*end == ':') { + char *end2; + colnum = strtol(end + 1, &end2, 10); + if (*end2 != '\0') + goto invalid_input; + } else if (*end != '\0') + goto invalid_input; + if (linenum == 0) + linenum = 1; + if (colnum == 0) + colnum = 1; + editor_goto(e, linenum - 1, colnum - 1); + return; + +invalid_input: + editor_configured_popup(e, "invalid goto", (PlaceholderList) { .elems_size = 0 }); +} int main(int argc, char **argv) { @@ -68,17 +90,23 @@ int main(int argc, char **argv) return 1; } + const char *const conf_file_path = "ded.miniconf"; + if (!editor_load_config(&editor, conf_file_path)) { + fprintf(stderr, "ERROR: Could not read config file \"%s\"\n", conf_file_path); + return 1; + } + // TODO: users should be able to customize the font // const char *const font_file_path = "./fonts/VictorMono-Regular.ttf"; - const char *const font_file_path = "./fonts/iosevka-regular.ttf"; + const char *const font_file_path = editor.configs.window.font; FT_Face face; error = FT_New_Face(library, font_file_path, 0, &face); if (error == FT_Err_Unknown_File_Format) { - fprintf(stderr, "ERROR: `%s` has an unknown format\n", font_file_path); + fprintf(stderr, "ERROR: font file `%s` has an unknown format\n", font_file_path); return 1; } else if (error) { - fprintf(stderr, "ERROR: Could not load file `%s`\n", font_file_path); + fprintf(stderr, "ERROR: Could not load font file `%s`\n", font_file_path); return 1; } @@ -111,9 +139,9 @@ int main(int argc, char **argv) } SDL_Window *window = - SDL_CreateWindow("ded", - 0, 0, - SCREEN_WIDTH, SCREEN_HEIGHT, + SDL_CreateWindow(editor.configs.window.title, + editor.configs.window.x, editor.configs.window.y, + editor.configs.window.w, editor.configs.window.h, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL); if (window == NULL) { fprintf(stderr, "ERROR: Could not create SDL window: %s\n", SDL_GetError()); @@ -148,7 +176,7 @@ int main(int argc, char **argv) if (GLEW_ARB_debug_output) { glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(MessageCallback, 0); + glDebugMessageCallback((GLDEBUGPROC) MessageCallback, 0); } else { fprintf(stderr, "WARNING: GLEW_ARB_debug_output is not available"); } @@ -159,6 +187,7 @@ int main(int argc, char **argv) editor.atlas = &atlas; editor_retokenize(&editor); + bool is_fullscreen = false; bool quit = false; bool file_browser = false; while (!quit) { @@ -172,13 +201,16 @@ int main(int argc, char **argv) break; case SDL_KEYDOWN: { - if (file_browser) { - switch (event.key.keysym.sym) { - case SDLK_F3: { - file_browser = false; + switch (event.key.keysym.sym) { + case SDLK_F11: { + is_fullscreen = !is_fullscreen; + SDL_SetWindowFullscreen(window, is_fullscreen * SDL_WINDOW_FULLSCREEN_DESKTOP); } break; + } + if (file_browser) { + switch (event.key.keysym.sym) { case SDLK_UP: { if (fb.cursor > 0) fb.cursor -= 1; } @@ -195,23 +227,39 @@ int main(int argc, char **argv) File_Type ft; err = type_of_file(file_path, &ft); if (err != 0) { - flash_error("Could not determine type of file %s: %s", file_path, strerror(err)); + editor_configured_popup(&editor, "can not open", (PlaceholderList) { + .elems = (Placeholder[]) { + PLACEHOLDER_STR("path", file_path), + PLACEHOLDER_STR("err", strerror(err)), + }, + .elems_size = 2 + }); } else { switch (ft) { case FT_DIRECTORY: { err = fb_change_dir(&fb); if (err != 0) { - flash_error("Could not change directory to %s: %s", file_path, strerror(err)); + editor_configured_popup(&editor, "can not cd", (PlaceholderList) { + .elems = (Placeholder[]) { + PLACEHOLDER_STR("path", file_path), + PLACEHOLDER_STR("err", strerror(err)), + }, + .elems_size = 2 + }); } } break; case FT_REGULAR: { - // TODO: before opening a new file make sure you don't have unsaved changes - // And if you do, annoy the user about it. (just like all the other editors do) err = editor_load_from_file(&editor, file_path); if (err != 0) { - flash_error("Could not open file %s: %s", file_path, strerror(err)); + editor_configured_popup(&editor, "can not open", (PlaceholderList) { + .elems = (Placeholder[]) { + PLACEHOLDER_STR("path", file_path), + PLACEHOLDER_STR("err", strerror(err)), + }, + .elems_size = 2 + }); } else { file_browser = false; } @@ -219,7 +267,13 @@ int main(int argc, char **argv) break; case FT_OTHER: { - flash_error("%s is neither a regular file nor a directory. We can't open it.", file_path); + editor_configured_popup(&editor, "can not open", (PlaceholderList) { + .elems = (Placeholder[]) { + PLACEHOLDER_STR("path", file_path), + PLACEHOLDER_STR("err", strerror(err)), + }, + .elems_size = 2 + }); } break; @@ -240,7 +294,6 @@ int main(int argc, char **argv) } else { editor_move_to_line_begin(&editor); } - editor.last_stroke = SDL_GetTicks(); } break; case SDLK_END: { @@ -250,30 +303,51 @@ int main(int argc, char **argv) } else { editor_move_to_line_end(&editor); } - editor.last_stroke = SDL_GetTicks(); } break; case SDLK_BACKSPACE: { - editor_backspace(&editor); - editor.last_stroke = SDL_GetTicks(); + if (editor.selection) { + editor_delete_selection(&editor); + editor.selection = false; + } else { + editor_backspace(&editor); + } } break; - case SDLK_F2: { - if (editor.file_path.count > 0) { - err = editor_save(&editor); - if (err != 0) { - flash_error("Could not save currently edited file: %s", strerror(err)); + case SDLK_s: { + if (event.key.keysym.mod & KMOD_CTRL) { + if (event.key.keysym.mod & KMOD_SHIFT) { + editor_start_input(&editor); + editor.input.onDone = onSaveInputPath; + editor.input.hint = editor_configured_inline_hint(&editor, "save as"); + editor.input.hint_len = strlen(editor.input.hint); + } else if (editor.file_path.count > 0) { + err = editor_save(&editor); + if (err != 0) { + editor_configured_popup(&editor, "can not save", (PlaceholderList) { + .elems = (Placeholder[]) { + PLACEHOLDER_STR("err", strerror(err)), + }, + .elems_size = 1 + }); + } + } else { + editor_start_input(&editor); + editor.input.onDone = onSaveInputPath; + editor.input.required = true; + editor.input.hint = editor_configured_inline_hint(&editor, "save"); + editor.input.hint_len = strlen(editor.input.hint); } - } else { - // TODO: ask the user for the path to save to in this situation - flash_error("Nowhere to save the text"); } } break; - case SDLK_F3: { - file_browser = true; + case SDLK_o: { + // TODO: annoy user if unsaved changes + if (event.key.keysym.mod & KMOD_CTRL) { + file_browser = true; + } } break; @@ -285,16 +359,27 @@ int main(int argc, char **argv) case SDLK_RETURN: { if (editor.searching) { editor_stop_search(&editor); + } else if (editor.input.active) { + editor.input.active = false; + if (editor.input.onDone) + editor.input.onDone(&editor); } else { + if (editor.selection) { + editor_delete_selection(&editor); + editor.selection = false; + } editor_insert_char(&editor, '\n'); - editor.last_stroke = SDL_GetTicks(); } } break; case SDLK_DELETE: { - editor_delete(&editor); - editor.last_stroke = SDL_GetTicks(); + if (editor.selection) { + editor_delete_selection(&editor); + editor.selection = false; + } else { + editor_delete(&editor); + } } break; @@ -321,15 +406,19 @@ int main(int argc, char **argv) break; case SDLK_TAB: { - // TODO: indent on Tab instead of just inserting 4 spaces at the cursor - // That is insert the spaces at the beginning of the line. Shift+TAB should - // do unindent, that is remove 4 spaces from the beginning of the line. - // TODO: customizable indentation style - // - tabs/spaces - // - tab width - // - etc. - for (size_t i = 0; i < 4; ++i) { - editor_insert_char(&editor, ' '); + if (editor.searching) { + editor_start_search(&editor); + } else { + // TODO: indent on Tab instead of just inserting 4 spaces at the cursor + // That is insert the spaces at the beginning of the line. Shift+TAB should + // do unindent, that is remove 4 spaces from the beginning of the line. + // TODO: customizable indentation style + // - tabs/spaces + // - tab width + // - etc. + for (size_t i = 0; i < 4; ++i) { + editor_insert_char(&editor, ' '); + } } } break; @@ -341,6 +430,17 @@ int main(int argc, char **argv) } break; + case SDLK_x: { + if (event.key.keysym.mod & KMOD_CTRL) { + editor_clipboard_copy(&editor); + if (editor.selection) { + editor_delete_selection(&editor); + editor.selection = false; + } + } + } + break; + case SDLK_v: { if (event.key.keysym.mod & KMOD_CTRL) { editor_clipboard_paste(&editor); @@ -348,6 +448,16 @@ int main(int argc, char **argv) } break; + case SDLK_g: { + if (event.key.keysym.mod & KMOD_CTRL) { + editor_start_input(&editor); + editor.input.onDone = onGoToLine; + editor.input.hint = editor_configured_inline_hint(&editor, "goto");; + editor.input.hint_len = strlen(editor.input.hint); + } + } + break; + case SDLK_UP: { editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); if (event.key.keysym.mod & KMOD_CTRL) { @@ -355,7 +465,6 @@ int main(int argc, char **argv) } else { editor_move_line_up(&editor); } - editor.last_stroke = SDL_GetTicks(); } break; @@ -366,7 +475,6 @@ int main(int argc, char **argv) } else { editor_move_line_down(&editor); } - editor.last_stroke = SDL_GetTicks(); } break; @@ -377,7 +485,6 @@ int main(int argc, char **argv) } else { editor_move_char_left(&editor); } - editor.last_stroke = SDL_GetTicks(); } break; @@ -388,16 +495,31 @@ int main(int argc, char **argv) } else { editor_move_char_right(&editor); } - editor.last_stroke = SDL_GetTicks(); } break; } + + editor.last_stroke = SDL_GetTicks(); + } + } + break; + + case SDL_WINDOWEVENT: { + switch(event.window.event) { + case SDL_WINDOWEVENT_RESIZED: { + int w, h; + SDL_GetWindowSize(window, &w, &h); + glViewport(0, 0, w, h); + } + break; } } break; case SDL_TEXTINPUT: { - if (file_browser) { + if (editor.input.active) { + sb_append_cstr(&editor.input.text, event.text.text); + } else if (file_browser) { // Nothing for now // Once we have incremental search in the file browser this may become useful } else { @@ -408,19 +530,15 @@ int main(int argc, char **argv) } editor.last_stroke = SDL_GetTicks(); } + if (editor.searching && !editor_search_matches_at(&editor, editor.cursor)) { + editor_start_search(&editor); + } } break; } } - { - int w, h; - SDL_GetWindowSize(window, &w, &h); - // TODO(#19): update the viewport and the resolution only on actual window change - glViewport(0, 0, w, h); - } - - Vec4f bg = hex_to_vec4f(0x181818FF); + Vec4f bg = editor.configs.editor.background; glClearColor(bg.x, bg.y, bg.z, bg.w); glClear(GL_COLOR_BUFFER_BIT); diff --git a/src/minirent.h b/src/minirent.h deleted file mode 100644 index 40d3ca5a..00000000 --- a/src/minirent.h +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2021 Alexey Kutepov -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// -// ============================================================ -// -// minirent — 0.0.1 — A subset of dirent interface for Windows. -// -// https://github.com/tsoding/minirent -// -// ============================================================ -// -// ChangeLog (https://semver.org/ is implied) -// -// 0.0.1 First Official Release - -#ifndef MINIRENT_H_ -#define MINIRENT_H_ - -#define WIN32_LEAN_AND_MEAN -#include "windows.h" - -struct dirent -{ - char d_name[MAX_PATH+1]; -}; - -typedef struct DIR DIR; - -DIR *opendir(const char *dirpath); -struct dirent *readdir(DIR *dirp); -int closedir(DIR *dirp); - -#endif // MINIRENT_H_ - -#ifdef MINIRENT_IMPLEMENTATION - -struct DIR -{ - HANDLE hFind; - WIN32_FIND_DATA data; - struct dirent *dirent; -}; - -DIR *opendir(const char *dirpath) -{ - assert(dirpath); - - char buffer[MAX_PATH]; - snprintf(buffer, MAX_PATH, "%s\\*", dirpath); - - DIR *dir = (DIR*)calloc(1, sizeof(DIR)); - - dir->hFind = FindFirstFile(buffer, &dir->data); - if (dir->hFind == INVALID_HANDLE_VALUE) { - // TODO: opendir should set errno accordingly on FindFirstFile fail - // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror - errno = ENOSYS; - goto fail; - } - - return dir; - -fail: - if (dir) { - free(dir); - } - - return NULL; -} - -struct dirent *readdir(DIR *dirp) -{ - assert(dirp); - - if (dirp->dirent == NULL) { - dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent)); - } else { - if(!FindNextFile(dirp->hFind, &dirp->data)) { - if (GetLastError() != ERROR_NO_MORE_FILES) { - // TODO: readdir should set errno accordingly on FindNextFile fail - // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror - errno = ENOSYS; - } - - return NULL; - } - } - - memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name)); - - strncpy( - dirp->dirent->d_name, - dirp->data.cFileName, - sizeof(dirp->dirent->d_name) - 1); - - return dirp->dirent; -} - -int closedir(DIR *dirp) -{ - assert(dirp); - - if(!FindClose(dirp->hFind)) { - // TODO: closedir should set errno accordingly on FindClose fail - // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror - errno = ENOSYS; - return -1; - } - - if (dirp->dirent) { - free(dirp->dirent); - } - free(dirp); - - return 0; -} - -#endif // MINIRENT_IMPLEMENTATION diff --git a/src/simple_renderer.c b/src/simple_renderer.c index 546678b7..cb601376 100644 --- a/src/simple_renderer.c +++ b/src/simple_renderer.c @@ -29,10 +29,11 @@ static const char *shader_type_as_cstr(GLuint shader) } } -static bool compile_shader_source(const GLchar *source, GLenum shader_type, GLuint *shader) +static bool compile_shader_source(String_Builder source, GLenum shader_type, GLuint *shader) { *shader = glCreateShader(shader_type); - glShaderSource(*shader, 1, &source, NULL); + GLint source_count=(GLint)source.count; + glShaderSource(*shader, 1, &source.items, &source_count); glCompileShader(*shader); GLint compiled = 0; @@ -62,7 +63,7 @@ static bool compile_shader_file(const char *file_path, GLenum shader_type, GLuin } sb_append_null(&source); - if (!compile_shader_source(source.items, shader_type, shader)) { + if (!compile_shader_source(source, shader_type, shader)) { fprintf(stderr, "ERROR: failed to compile `%s` shader file\n", file_path); return_defer(false); } @@ -130,7 +131,7 @@ static void get_uniform_location(GLuint program, GLint locations[COUNT_UNIFORM_S void simple_renderer_init(Simple_Renderer *sr) { - sr->camera_scale = 3.0f; + sr->cam.scale = 3.0f; { glGenVertexArrays(1, &sr->vao); @@ -321,8 +322,8 @@ void simple_renderer_set_shader(Simple_Renderer *sr, Simple_Shader shader) get_uniform_location(sr->programs[sr->current_shader], sr->uniforms); glUniform2f(sr->uniforms[UNIFORM_SLOT_RESOLUTION], sr->resolution.x, sr->resolution.y); glUniform1f(sr->uniforms[UNIFORM_SLOT_TIME], sr->time); - glUniform2f(sr->uniforms[UNIFORM_SLOT_CAMERA_POS], sr->camera_pos.x, sr->camera_pos.y); - glUniform1f(sr->uniforms[UNIFORM_SLOT_CAMERA_SCALE], sr->camera_scale); + glUniform2f(sr->uniforms[UNIFORM_SLOT_CAMERA_POS], sr->cam.pos.x, sr->cam.pos.y); + glUniform1f(sr->uniforms[UNIFORM_SLOT_CAMERA_SCALE], sr->cam.scale); } void simple_renderer_flush(Simple_Renderer *sr) diff --git a/src/simple_renderer.h b/src/simple_renderer.h index b67bdb03..d2b398e1 100644 --- a/src/simple_renderer.h +++ b/src/simple_renderer.h @@ -43,6 +43,13 @@ typedef enum { COUNT_SIMPLE_SHADERS, } Simple_Shader; +typedef struct { + Vec2f pos; + float scale; + float scale_vel; + Vec2f vel; +} Simple_Camera; + typedef struct { GLuint vao; GLuint vbo; @@ -56,10 +63,7 @@ typedef struct { Vec2f resolution; float time; - Vec2f camera_pos; - float camera_scale; - float camera_scale_vel; - Vec2f camera_vel; + Simple_Camera cam; } Simple_Renderer; void simple_renderer_init(Simple_Renderer *sr);