Browse Source

Merge branch 'file-browser'

rexim 2 years ago
parent
commit
286bac5aba
16 changed files with 768 additions and 149 deletions
  1. 1 1
      build.sh
  2. 1 1
      build_msvc.bat
  3. 0 1
      shaders/simple_image.frag
  4. 173 0
      src/arena.h
  5. 104 0
      src/common.c
  6. 73 0
      src/common.h
  7. 40 20
      src/editor.c
  8. 5 15
      src/editor.h
  9. 21 0
      src/file_browser.c
  10. 13 0
      src/file_browser.h
  11. 9 7
      src/free_glyph.c
  12. 1 1
      src/free_glyph.h
  13. 189 58
      src/main.c
  14. 136 0
      src/minirent.h
  15. 2 44
      src/simple_renderer.c
  16. 0 1
      src/simple_renderer.h

+ 1 - 1
build.sh

@@ -6,7 +6,7 @@ CC="${CXX:-cc}"
 PKGS="sdl2 glew freetype2"
 CFLAGS="-Wall -Wextra -std=c11 -pedantic -ggdb"
 LIBS=-lm
-SRC="src/main.c src/la.c src/editor.c src/free_glyph.c src/simple_renderer.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"
 
 if [ `uname` = "Darwin" ]; then
     CFLAGS+=" -framework OpenGL"

+ 1 - 1
build_msvc.bat

@@ -9,4 +9,4 @@ set LIBS=dependencies\SDL2\lib\x64\SDL2.lib ^
          dependencies\GLEW\lib\glew32s.lib ^
          opengl32.lib User32.lib Gdi32.lib Shell32.lib
 
-cl.exe %CFLAGS% %INCLUDES% /Feded src\main.c src\la.c src\editor.c src\free_glyph.c src\simple_renderer.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 /link %LIBS% -SUBSYSTEM:windows

+ 0 - 1
shaders/simple_image.frag

@@ -6,5 +6,4 @@ in vec2 out_uv;
 
 void main() {
     gl_FragColor = texture(image, out_uv);
-    // gl_FragColor = vec4(out_uv, 0, 1);
 }

+ 173 - 0
src/arena.h

@@ -0,0 +1,173 @@
+// Copyright 2022 Alexey Kutepov <[email protected]>
+
+// 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.
+
+#ifndef ARENA_H_
+#define ARENA_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifndef ARENA_ASSERT
+#include <assert.h>
+#define ARENA_ASSERT assert
+#endif
+
+#define ARENA_BACKEND_LIBC_MALLOC 0
+#define ARENA_BACKEND_LINUX_MMAP 1
+#define ARENA_BACKEND_WIN32_VIRTUALALLOC 2
+#define ARENA_BACKEND_WASM_HEAPBASE 3
+
+#ifndef ARENA_BACKEND
+#define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC
+#endif // ARENA_BACKEND
+
+typedef struct Region Region;
+
+struct Region {
+    Region *next;
+    size_t count;
+    size_t capacity;
+    uintptr_t data[];
+};
+
+typedef struct {
+    Region *begin, *end;
+} Arena;
+
+#define REGION_DEFAULT_CAPACITY (8*1024)
+
+Region *new_region(size_t capacity);
+void free_region(Region *r);
+
+// TODO: snapshot/rewind capability for the arena
+// - Snapshot should be combination of a->end and a->end->count.
+// - Rewinding should be restoring a->end and a->end->count from the snapshot and
+// setting count-s of all the Region-s after the remembered a->end to 0.
+void *arena_alloc(Arena *a, size_t size_bytes);
+void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz);
+
+void arena_reset(Arena *a);
+void arena_free(Arena *a);
+
+#endif // ARENA_H_
+
+#ifdef ARENA_IMPLEMENTATION
+
+#if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC
+#include <stdlib.h>
+
+// TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region
+// It should be up to new_region() to decide the actual capacity to allocate
+Region *new_region(size_t capacity)
+{
+    size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity;
+    // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned
+    Region *r = malloc(size_bytes);
+    ARENA_ASSERT(r);
+    r->next = NULL;
+    r->count = 0;
+    r->capacity = capacity;
+    return r;
+}
+
+void free_region(Region *r)
+{
+    free(r);
+}
+#elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP
+#  error "TODO: Linux mmap backend is not implemented yet"
+#elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC
+#  error "TODO: Win32 VirtualAlloc backend is not implemented yet"
+#elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE
+#  error "TODO: WASM __heap_base backend is not implemented yet"
+#else
+#  error "Unknown Arena backend"
+#endif
+
+// TODO: add debug statistic collection mode for arena
+// Should collect things like:
+// - How many times new_region was called
+// - How many times existing region was skipped
+// - How many times allocation exceeded REGION_DEFAULT_CAPACITY
+
+void *arena_alloc(Arena *a, size_t size_bytes)
+{
+    size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t);
+
+    if (a->end == NULL) {
+        ARENA_ASSERT(a->begin == NULL);
+        size_t capacity = REGION_DEFAULT_CAPACITY;
+        if (capacity < size) capacity = size;
+        a->end = new_region(capacity);
+        a->begin = a->end;
+    }
+
+    while (a->end->count + size > a->end->capacity && a->end->next != NULL) {
+        a->end = a->end->next;
+    }
+
+    if (a->end->count + size > a->end->capacity) {
+        ARENA_ASSERT(a->end->next == NULL);
+        size_t capacity = REGION_DEFAULT_CAPACITY;
+        if (capacity < size) capacity = size;
+        a->end->next = new_region(capacity);
+        a->end = a->end->next;
+    }
+
+    void *result = &a->end->data[a->end->count];
+    a->end->count += size;
+    return result;
+}
+
+void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz)
+{
+    if (newsz <= oldsz) return oldptr;
+    void *newptr = arena_alloc(a, newsz);
+    char *newptr_char = newptr;
+    char *oldptr_char = oldptr;
+    for (size_t i = 0; i < oldsz; ++i) {
+        newptr_char[i] = oldptr_char[i];
+    }
+    return newptr;
+}
+
+void arena_reset(Arena *a)
+{
+    for (Region *r = a->begin; r != NULL; r = r->next) {
+        r->count = 0;
+    }
+
+    a->end = a->begin;
+}
+
+void arena_free(Arena *a)
+{
+    Region *r = a->begin;
+    while (r) {
+        Region *r0 = r;
+        r = r->next;
+        free_region(r0);
+    }
+    a->begin = NULL;
+    a->end = NULL;
+}
+
+#endif // ARENA_IMPLEMENTATION

+ 104 - 0
src/common.c

@@ -0,0 +1,104 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef _WIN32
+#    define MINIRENT_IMPLEMENTATION
+#    include <minirent.h>
+#else
+#    include <dirent.h>
+#endif // _WIN32
+
+#include "common.h"
+#define ARENA_IMPLEMENTATION
+#include "./arena.h"
+
+static Arena temporary_arena = {0};
+
+char *temp_strdup(const char *s)
+{
+    size_t n = strlen(s);
+    char *ds = arena_alloc(&temporary_arena, n + 1);
+    memcpy(ds, s, n);
+    ds[n] = '\0';
+    return ds;
+}
+
+void temp_reset(void)
+{
+    arena_reset(&temporary_arena);
+}
+
+Errno read_entire_dir(const char *dir_path, Files *files)
+{
+    Errno result = 0;
+    DIR *dir = NULL;
+
+    dir = opendir(dir_path);
+    if (dir == NULL) {
+        return_defer(errno);
+    }
+
+    errno = 0;
+    struct dirent *ent = readdir(dir);
+    while (ent != NULL) {
+        da_append(files, temp_strdup(ent->d_name));
+        ent = readdir(dir);
+    }
+
+    if (errno != 0) {
+        return_defer(errno);
+    }
+
+defer:
+    if (dir) closedir(dir);
+    return result;
+}
+
+Errno write_entire_file(const char *file_path, const char *buf, size_t buf_size)
+{
+    Errno result = 0;
+    FILE *f = NULL;
+
+    f = fopen(file_path, "wb");
+    if (f == NULL) return_defer(errno);
+
+    fwrite(buf, 1, buf_size, f);
+    if (ferror(f)) return_defer(errno);
+
+defer:
+    if (f) fclose(f);
+    return result;
+}
+
+char *read_entire_file(const char *file_path)
+{
+#define SLURP_FILE_PANIC \
+    do { \
+        fprintf(stderr, "Could not read file `%s`: %s\n", file_path, strerror(errno)); \
+        exit(1); \
+    } while (0)
+
+    FILE *f = fopen(file_path, "r");
+    if (f == NULL) SLURP_FILE_PANIC;
+    if (fseek(f, 0, SEEK_END) < 0) SLURP_FILE_PANIC;
+
+    long size = ftell(f);
+    if (size < 0) SLURP_FILE_PANIC;
+
+    char *buffer = malloc(size + 1);
+    if (buffer == NULL) SLURP_FILE_PANIC;
+
+    if (fseek(f, 0, SEEK_SET) < 0) SLURP_FILE_PANIC;
+
+    fread(buffer, 1, size, f);
+    if (ferror(f) < 0) SLURP_FILE_PANIC;
+
+    buffer[size] = '\0';
+
+    if (fclose(f) < 0) SLURP_FILE_PANIC;
+
+    return buffer;
+#undef SLURP_FILE_PANIC
+}

+ 73 - 0
src/common.h

@@ -0,0 +1,73 @@
+#ifndef COMMON_H_
+#define COMMON_H_
+
+#include <stdlib.h>
+
+typedef int Errno;
+
+#define return_defer(value) do { result = (value); goto defer; } while (0)
+
+#define UNIMPLEMENTED(...)                                                      \
+    do {                                                                        \
+        printf("%s:%d: UNIMPLEMENTED: %s \n", __FILE__, __LINE__, __VA_ARGS__); \
+        exit(1);                                                                \
+    } while(0)
+#define UNUSED(x) (void)(x)
+
+#define DA_INIT_CAP 256
+
+#define da_append(da, item)                                                          \
+    do {                                                                             \
+        if ((da)->count >= (da)->capacity) {                                         \
+            (da)->capacity = (da)->capacity == 0 ? DA_INIT_CAP : (da)->capacity*2;   \
+            (da)->items = realloc((da)->items, (da)->capacity*sizeof(*(da)->items)); \
+            assert((da)->items != NULL && "Buy more RAM lol");                       \
+        }                                                                            \
+                                                                                     \
+        (da)->items[(da)->count++] = (item);                                         \
+    } while (0)
+
+#define da_append_many(da, new_items, new_items_count)                                      \
+    do {                                                                                    \
+        if ((da)->count + new_items_count > (da)->capacity) {                               \
+            if ((da)->capacity == 0) {                                                      \
+                (da)->capacity = DA_INIT_CAP;                                               \
+            }                                                                               \
+            while ((da)->count + new_items_count > (da)->capacity) {                        \
+                (da)->capacity *= 2;                                                        \
+            }                                                                               \
+            (da)->items = realloc((da)->items, (da)->capacity*sizeof(*(da)->items));        \
+            assert((da)->items != NULL && "Buy more RAM lol");                              \
+        }                                                                                   \
+        memcpy((da)->items + (da)->count, new_items, new_items_count*sizeof(*(da)->items)); \
+        (da)->count += new_items_count;                                                     \
+    } while (0)
+
+char *temp_strdup(const char *s);
+void temp_reset(void);
+
+typedef struct {
+    char *items;
+    size_t count;
+    size_t capacity;
+} String_Builder;
+
+#define sb_append_buf da_append_many
+#define sb_append_cstr(sb, cstr)     \
+    do {                             \
+        size_t n = strlen(cstr);     \
+        da_append_many(sb, cstr, n); \
+    } while (0)
+#define sb_append_null(sb) da_append_many(sb, "", 1)
+
+typedef struct {
+    const char **items;
+    size_t count;
+    size_t capacity;
+} Files;
+
+char *read_entire_file(const char *file_path);
+Errno write_entire_file(const char *file_path, const char *buf, size_t buf_size);
+Errno read_entire_dir(const char *dir_path, Files *files);
+
+#endif // COMMON_H_

+ 40 - 20
src/editor.c

@@ -5,6 +5,7 @@
 #include <string.h>
 #include "./editor.h"
 #include "./sv.h"
+#include "common.h"
 
 void editor_backspace(Editor *e)
 {
@@ -35,37 +36,47 @@ void editor_delete(Editor *e)
     editor_recompute_lines(e);
 }
 
-void editor_save_to_file(const Editor *editor, const char *file_path)
+Errno editor_save_as(Editor *editor, const char *file_path)
 {
-    FILE *f = fopen(file_path, "w");
-    if (f == NULL) {
-        fprintf(stdout, "ERROR: could not open file `%s`: %s\n",
-                file_path, strerror(errno));
-        exit(1);
-    }
+    Errno err = write_entire_file(file_path, editor->data.items, editor->data.count);
+    if (err != 0) return err;
+    editor->file_path.count = 0;
+    sb_append_cstr(&editor->file_path, file_path);
+    sb_append_null(&editor->file_path);
+    return 0;
+}
 
-    fwrite(editor->data.items, 1, editor->data.count, f);
-    fclose(f);
+Errno editor_save(const Editor *editor)
+{
+    assert(editor->file_path.count > 0);
+    return write_entire_file(editor->file_path.items, editor->data.items, editor->data.count);
 }
 
-static size_t file_size(FILE *file)
+static Errno file_size(FILE *file, size_t *size)
 {
     long saved = ftell(file);
-    assert(saved >= 0);
-    int err = fseek(file, 0, SEEK_END);
-    assert(err == 0);
+    if (saved < 0) return errno;
+    if (fseek(file, 0, SEEK_END) < 0) return errno;
     long result = ftell(file);
-    assert(result >= 0);
-    err = fseek(file, saved, SEEK_SET);
-    assert(err == 0);
-    return result;
+    if (result < 0) return errno;
+    if (fseek(file, saved, SEEK_SET) < 0) return errno;
+    *size = (size_t) result;
+    return 0;
 }
 
-void editor_load_from_file(Editor *e, FILE *file)
+Errno editor_load_from_file(Editor *e, const char *file_path)
 {
+    Errno result = 0;
+    FILE *file = NULL;
+
     e->data.count = 0;
 
-    size_t data_size = file_size(file);
+    file = fopen(file_path, "rb");
+    if (file == NULL) return_defer(errno);
+
+    size_t data_size;
+    Errno err = file_size(file, &data_size);
+    if (err != 0) return_defer(err);
 
     if (e->data.capacity < data_size) {
         e->data.capacity = data_size;
@@ -74,10 +85,19 @@ void editor_load_from_file(Editor *e, FILE *file)
     }
 
     fread(e->data.items, data_size, 1, file);
-    assert(!ferror(file));
+    if (ferror(file)) return_defer(errno);
     e->data.count = data_size;
 
     editor_recompute_lines(e);
+
+defer:
+    if (result == 0) {
+        e->file_path.count = 0;
+        sb_append_cstr(&e->file_path, file_path);
+        sb_append_null(&e->file_path);
+    }
+    if (file) fclose(file);
+    return result;
 }
 
 size_t editor_cursor_row(const Editor *e)

+ 5 - 15
src/editor.h

@@ -2,6 +2,7 @@
 #define EDITOR_H_
 
 #include <stdlib.h>
+#include "common.h"
 
 typedef struct {
     size_t begin;
@@ -20,27 +21,16 @@ typedef struct {
     size_t capacity;
 } Lines;
 
-#define DA_INIT_CAP 256
-
-#define da_append(da, item)                                                         \
-    do {                                                                            \
-        if ((da)->count >= (da)->capacity) {                                        \
-            (da)->capacity = (da)->capacity == 0 ? DA_INIT_CAP : (da)->capacity*2;  \
-            (da)->items = realloc((da)->items, (da)->capacity*sizeof(*(da)->items)); \
-            assert((da)->items != NULL && "Buy more RAM lol");                      \
-        }                                                                           \
-                                                                                    \
-        (da)->items[(da)->count++] = (item);                                        \
-    } while (0)
-
 typedef struct {
     Data data;
     Lines lines;
+    String_Builder file_path;
     size_t cursor;
 } Editor;
 
-void editor_save_to_file(const Editor *editor, const char *file_path);
-void editor_load_from_file(Editor *editor, FILE *file);
+Errno editor_save_as(Editor *editor, const char *file_path);
+Errno editor_save(const Editor *editor);
+Errno editor_load_from_file(Editor *editor, const char *file_path);
 
 void editor_backspace(Editor *editor);
 void editor_delete(Editor *editor);

+ 21 - 0
src/file_browser.c

@@ -0,0 +1,21 @@
+#include <string.h>
+#include "file_browser.h"
+
+int file_cmp(const void *ap, const void *bp)
+{
+    const char *a = *(const char**)ap;
+    const char *b = *(const char**)bp;
+    return strcmp(a, b);
+}
+
+Errno fb_open_dir(File_Browser *fb, const char *dir_path)
+{
+    fb->files.count = 0;
+    fb->cursor = 0;
+    Errno err = read_entire_dir(dir_path, &fb->files);
+    if (err != 0) {
+        return err;
+    }
+    qsort(fb->files.items, fb->files.count, sizeof(*fb->files.items), file_cmp);
+    return 0;
+}

+ 13 - 0
src/file_browser.h

@@ -0,0 +1,13 @@
+#ifndef FILE_BROWSER_H_
+#define FILE_BROWSER_H_
+
+#include "common.h"
+
+typedef struct {
+    Files files;
+    size_t cursor;
+} File_Browser;
+
+Errno fb_open_dir(File_Browser *fb, const char *dir_path);
+
+#endif // FILE_BROWSER_H_

+ 9 - 7
src/free_glyph.c

@@ -87,7 +87,7 @@ float free_glyph_atlas_cursor_pos(const Free_Glyph_Atlas *atlas, const char *tex
     return pos.x;
 }
 
-void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos)
+void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos, bool render)
 {
     for (size_t i = 0; i < text_size; ++i) {
         Glyph_Metric metric = atlas->metrics[(int) text[i]];
@@ -99,11 +99,13 @@ void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer
         pos->x += metric.ax;
         pos->y += metric.ay;
 
-        simple_renderer_image_rect(
-            sr,
-            vec2f(x2, -y2),
-            vec2f(w, -h),
-            vec2f(metric.tx, 0.0f),
-            vec2f(metric.bw / (float) atlas->atlas_width, metric.bh / (float) atlas->atlas_height));
+        if (render) {
+            simple_renderer_image_rect(
+                sr,
+                vec2f(x2, -y2),
+                vec2f(w, -h),
+                vec2f(metric.tx, 0.0f),
+                vec2f(metric.bw / (float) atlas->atlas_width, metric.bh / (float) atlas->atlas_height));
+        }
     }
 }

+ 1 - 1
src/free_glyph.h

@@ -39,6 +39,6 @@ typedef struct {
 
 void free_glyph_atlas_init(Free_Glyph_Atlas *atlas, FT_Face face);
 float free_glyph_atlas_cursor_pos(const Free_Glyph_Atlas *atlas, const char *text, size_t text_size, Vec2f pos, size_t col);
-void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos);
+void free_glyph_atlas_render_line_sized(Free_Glyph_Atlas *atlas, Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos, bool render);
 
 #endif // FREE_GLYPH_H_

+ 189 - 58
src/main.c

@@ -1,6 +1,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdbool.h>
+#include <errno.h>
+#include <string.h>
 
 #include <SDL2/SDL.h>
 #define GLEW_STATIC
@@ -15,9 +17,11 @@
 #include "./sv.h"
 
 #include "./editor.h"
+#include "./file_browser.h"
 #include "./la.h"
 #include "./free_glyph.h"
 #include "./simple_renderer.h"
+#include "./common.h"
 
 #define SCREEN_WIDTH 800
 #define SCREEN_HEIGHT 600
@@ -57,19 +61,81 @@ void MessageCallback(GLenum source,
 static Free_Glyph_Atlas atlas = {0};
 static Simple_Renderer sr = {0};
 static Editor editor = {0};
+static File_Browser fb = {0};
 static Uint32 last_stroke = 0;
 
 #define FREE_GLYPH_FONT_SIZE 64
 #define ZOOM_OUT_GLYPH_THRESHOLD 30
 
-void render_editor(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor)
+void render_file_browser(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr, const File_Browser *fb)
 {
+    Vec2f cursor_pos = vec2f(0, -(float)fb->cursor * FREE_GLYPH_FONT_SIZE);
+
     int w, h;
     SDL_GetWindowSize(window, &w, &h);
 
     float max_line_len = 0.0f;
 
-    simple_renderer_use(sr);
+    sr->resolution = vec2f(w, h);
+    sr->time = (float) SDL_GetTicks() / 1000.0f;
+
+    simple_renderer_set_shader(sr, SHADER_FOR_COLOR);
+    if (fb->cursor < fb->files.count) {
+        const Vec2f begin = vec2f(0, -(float)fb->cursor * FREE_GLYPH_FONT_SIZE);
+        Vec2f end = begin;
+        free_glyph_atlas_render_line_sized(
+            atlas, sr, fb->files.items[fb->cursor], strlen(fb->files.items[fb->cursor]),
+            &end,
+            false);
+        simple_renderer_solid_rect(sr, begin, vec2f(end.x - begin.x, FREE_GLYPH_FONT_SIZE), vec4f(.25, .25, .25, 1));
+    }
+    simple_renderer_flush(sr);
+
+    simple_renderer_set_shader(sr, SHADER_FOR_EPICNESS);
+    for (size_t row = 0; row < fb->files.count; ++row) {
+        const Vec2f begin = vec2f(0, -(float)row * FREE_GLYPH_FONT_SIZE);
+        Vec2f end = begin;
+        free_glyph_atlas_render_line_sized(
+            atlas, sr, fb->files.items[row], strlen(fb->files.items[row]),
+            &end,
+            true);
+        // TODO: the max_line_len should be calculated based on what's visible on the screen right now
+        float line_len = fabsf(end.x - begin.x);
+        if (line_len > max_line_len) {
+            max_line_len = line_len;
+        }
+    }
+
+    simple_renderer_flush(sr);
+
+    // Update camera
+    {
+        float target_scale = 3.0f;
+        if (max_line_len > 0.0f) {
+            target_scale = SCREEN_WIDTH / max_line_len;
+        }
+
+        if (target_scale > 3.0f) {
+            target_scale = 3.0f;
+        }
+
+
+        sr->camera_vel = vec2f_mul(
+                             vec2f_sub(cursor_pos, sr->camera_pos),
+                             vec2fs(2.0f));
+        sr->camera_scale_vel = (target_scale - sr->camera_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;
+    }
+}
+
+void render_editor(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer *sr, Editor *editor)
+{
+    int w, h;
+    SDL_GetWindowSize(window, &w, &h);
+
+    float max_line_len = 0.0f;
 
     sr->resolution = vec2f(w, h);
     sr->time = (float) SDL_GetTicks() / 1000.0f;
@@ -84,7 +150,8 @@ void render_editor(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer
             Vec2f end = begin;
             free_glyph_atlas_render_line_sized(
                 atlas, sr, editor->data.items + line.begin, line.end - line.begin,
-                &end);
+                &end,
+                true);
             // TODO: the max_line_len should be calculated based on what's visible on the screen right now
             float line_len = fabsf(end.x - begin.x);
             if (line_len > max_line_len) {
@@ -131,6 +198,9 @@ void render_editor(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer
     // Update camera
     {
         float target_scale = 3.0f;
+        if (max_line_len > 1000.0f) {
+            max_line_len = 1000.0f;
+        }
         if (max_line_len > 0.0f) {
             target_scale = SCREEN_WIDTH / max_line_len;
         }
@@ -149,8 +219,13 @@ void render_editor(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer
     }
 }
 
+// TODO: display errors reported via flash_error right in the text editor window somehow
+#define flash_error(...) fprintf(stderr, __VA_ARGS__)
+
 int main(int argc, char **argv)
 {
+    Errno err;
+
     editor_recompute_lines(&editor);
 
     FT_Library library = {0};
@@ -182,18 +257,21 @@ int main(int argc, char **argv)
         return 1;
     }
 
-    const char *file_path = NULL;
 
     if (argc > 1) {
-        file_path = argv[1];
+        const char *file_path = argv[1];
+        err = editor_load_from_file(&editor, file_path);
+        if (err != 0) {
+            fprintf(stderr, "ERROR: Could ont read file %s: %s\n", file_path, strerror(err));
+            return 1;
+        }
     }
 
-    if (file_path) {
-        FILE *file = fopen(file_path, "r");
-        if (file != NULL) {
-            editor_load_from_file(&editor, file);
-            fclose(file);
-        }
+    const char *dir_path = ".";
+    err = fb_open_dir(&fb, dir_path);
+    if (err != 0) {
+        fprintf(stderr, "ERROR: Could not read directory %s: %s\n", dir_path, strerror(err));
+        return 1;
     }
 
     if (SDL_Init(SDL_INIT_VIDEO) < 0) {
@@ -262,6 +340,7 @@ int main(int argc, char **argv)
     free_glyph_atlas_init(&atlas, face);
 
     bool quit = false;
+    bool file_browser = false;
     while (!quit) {
         const Uint32 start = SDL_GetTicks();
         SDL_Event event = {0};
@@ -273,66 +352,114 @@ int main(int argc, char **argv)
             break;
 
             case SDL_KEYDOWN: {
-                switch (event.key.keysym.sym) {
-                case SDLK_BACKSPACE: {
-                    editor_backspace(&editor);
-                    last_stroke = SDL_GetTicks();
-                }
-                break;
+                if (file_browser) {
+                    switch (event.key.keysym.sym) {
+                    case SDLK_F3: {
+                        file_browser = false;
+                    }
+                    break;
 
-                case SDLK_F2: {
-                    if (file_path) {
-                        editor_save_to_file(&editor, file_path);
+                    case SDLK_UP: {
+                        if (fb.cursor > 0) fb.cursor -= 1;
                     }
-                }
-                break;
+                    break;
 
-                case SDLK_RETURN: {
-                    editor_insert_char(&editor, '\n');
-                    last_stroke = SDL_GetTicks();
-                }
-                break;
+                    case SDLK_DOWN: {
+                        if (fb.cursor + 1 < fb.files.count) fb.cursor += 1;
+                    }
+                    break;
+
+                    case SDLK_RETURN: {
+                        if (fb.cursor < fb.files.count) {
+                            // TODO: go into folders
+                            const char *file_path = fb.files.items[fb.cursor];
+                            err = editor_load_from_file(&editor, file_path);
+                            if (err != 0) {
+                                flash_error("Could not open file %s: %s", file_path, strerror(err));
+                            } else {
+                                file_browser = false;
+                            }
+                        }
+                    }
+                    break;
+                    }
+                } else {
+                    switch (event.key.keysym.sym) {
+                    case SDLK_BACKSPACE: {
+                        editor_backspace(&editor);
+                        last_stroke = SDL_GetTicks();
+                    }
+                    break;
+
+                    case SDLK_F2: {
+                        if (editor.file_path.count > 0) {
+                            err = editor_save(&editor);
+                            if (err != 0) {
+                                flash_error("Could not save file currently edited file: %s", strerror(err));
+                            }
+                        } else {
+                            // TODO: as the user for the path to save to in this situation
+                            flash_error("No where to save the text");
+                        }
+                    }
+                    break;
 
-                case SDLK_DELETE: {
-                    editor_delete(&editor);
-                    last_stroke = SDL_GetTicks();
-                }
-                break;
+                    case SDLK_F3: {
+                        file_browser = true;
+                    }
+                    break;
 
-                case SDLK_UP: {
-                    editor_move_line_up(&editor);
-                    last_stroke = SDL_GetTicks();
-                }
-                break;
+                    case SDLK_RETURN: {
+                        editor_insert_char(&editor, '\n');
+                        last_stroke = SDL_GetTicks();
+                    }
+                    break;
 
-                case SDLK_DOWN: {
-                    editor_move_line_down(&editor);
-                    last_stroke = SDL_GetTicks();
-                }
-                break;
+                    case SDLK_DELETE: {
+                        editor_delete(&editor);
+                        last_stroke = SDL_GetTicks();
+                    }
+                    break;
 
-                case SDLK_LEFT: {
-                    editor_move_char_left(&editor);
-                    last_stroke = SDL_GetTicks();
-                }
-                break;
+                    case SDLK_UP: {
+                        editor_move_line_up(&editor);
+                        last_stroke = SDL_GetTicks();
+                    }
+                    break;
 
-                case SDLK_RIGHT: {
-                    editor_move_char_right(&editor);
-                    last_stroke = SDL_GetTicks();
-                }
-                break;
+                    case SDLK_DOWN: {
+                        editor_move_line_down(&editor);
+                        last_stroke = SDL_GetTicks();
+                    }
+                    break;
+
+                    case SDLK_LEFT: {
+                        editor_move_char_left(&editor);
+                        last_stroke = SDL_GetTicks();
+                    }
+                    break;
+
+                    case SDLK_RIGHT: {
+                        editor_move_char_right(&editor);
+                        last_stroke = SDL_GetTicks();
+                    }
+                    break;
+                    }
                 }
             }
             break;
 
             case SDL_TEXTINPUT: {
-                const char *text = event.text.text;
-                size_t text_len = strlen(text);
-                for (size_t i = 0; i < text_len; ++i) {
-                    editor_insert_char(&editor, text[i]);
+                if (file_browser) {
+                    // TODO: file browser keys
+                } else {
+                    const char *text = event.text.text;
+                    size_t text_len = strlen(text);
+                    for (size_t i = 0; i < text_len; ++i) {
+                        editor_insert_char(&editor, text[i]);
+                    }
+                    last_stroke = SDL_GetTicks();
                 }
-                last_stroke = SDL_GetTicks();
             }
             break;
             }
@@ -348,7 +475,11 @@ int main(int argc, char **argv)
         glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
         glClear(GL_COLOR_BUFFER_BIT);
 
-        render_editor(window, &atlas, &sr, &editor);
+        if (file_browser) {
+            render_file_browser(window, &atlas, &sr, &fb);
+        } else {
+            render_editor(window, &atlas, &sr, &editor);
+        }
 
         SDL_GL_SwapWindow(window);
 

+ 136 - 0
src/minirent.h

@@ -0,0 +1,136 @@
+// Copyright 2021 Alexey Kutepov <[email protected]>
+//
+// 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

+ 2 - 44
src/simple_renderer.c

@@ -5,37 +5,7 @@
 #include <string.h>
 #include <errno.h>
 #include "./simple_renderer.h"
-
-static char *slurp_file(const char *file_path)
-{
-#define SLURP_FILE_PANIC \
-    do { \
-        fprintf(stderr, "Could not read file `%s`: %s\n", file_path, strerror(errno)); \
-        exit(1); \
-    } while (0)
-
-    FILE *f = fopen(file_path, "r");
-    if (f == NULL) SLURP_FILE_PANIC;
-    if (fseek(f, 0, SEEK_END) < 0) SLURP_FILE_PANIC;
-
-    long size = ftell(f);
-    if (size < 0) SLURP_FILE_PANIC;
-
-    char *buffer = malloc(size + 1);
-    if (buffer == NULL) SLURP_FILE_PANIC;
-
-    if (fseek(f, 0, SEEK_SET) < 0) SLURP_FILE_PANIC;
-
-    fread(buffer, 1, size, f);
-    if (ferror(f) < 0) SLURP_FILE_PANIC;
-
-    buffer[size] = '\0';
-
-    if (fclose(f) < 0) SLURP_FILE_PANIC;
-
-    return buffer;
-#undef SLURP_FILE_PANIC
-}
+#include "./common.h"
 
 static const char *shader_type_as_cstr(GLuint shader)
 {
@@ -72,7 +42,7 @@ static bool compile_shader_source(const GLchar *source, GLenum shader_type, GLui
 
 static bool compile_shader_file(const char *file_path, GLenum shader_type, GLuint *shader)
 {
-    char *source = slurp_file(file_path);
+    char *source = read_entire_file(file_path);
     bool ok = compile_shader_source(source, shader_type, shader);
     if (!ok) {
         fprintf(stderr, "ERROR: failed to compile `%s` shader file\n", file_path);
@@ -130,12 +100,6 @@ static const Uniform_Def uniform_defs[COUNT_UNIFORM_SLOTS] = {
     },
 };
 
-#define UNIMPLEMENTED(...)               \
-    do {                                 \
-        printf("%s:%d: UNIMPLEMENTED: %s \n", __FILE__, __LINE__, __VA_ARGS__); \
-        exit(1);                         \
-    } while(0)
-#define UNUSED(x) (void)(x)
 
 static void get_uniform_location(GLuint program, GLint locations[COUNT_UNIFORM_SLOTS])
 {
@@ -234,12 +198,6 @@ void simple_renderer_init(Simple_Renderer *sr,
     }
 }
 
-void simple_renderer_use(const Simple_Renderer *sr)
-{
-    glBindVertexArray(sr->vao);
-    glBindBuffer(GL_ARRAY_BUFFER, sr->vbo);
-}
-
 void simple_renderer_vertex(Simple_Renderer *sr,
                             Vec2f p, Vec4f c, Vec2f uv)
 {

+ 0 - 1
src/simple_renderer.h

@@ -63,7 +63,6 @@ void simple_renderer_init(Simple_Renderer *sr,
                           const char *image_frag_file_path,
                           const char *epic_frag_file_path);
 
-void simple_renderer_use(const Simple_Renderer *sr);
 void simple_renderer_vertex(Simple_Renderer *sr,
                             Vec2f p, Vec4f c, Vec2f uv);
 void simple_renderer_set_shader(Simple_Renderer *sr, Simple_Shader shader);