commit a9f34c1e708b30734800beb9e514000ae95ea094
parent d7cd9a81779c2b08988a06abe6575301c457046e
Author: rexim <reximkut@gmail.com>
Date: Tue, 10 Jan 2023 13:08:30 +0700
Initial implementation of the file browser
Diffstat:
12 files changed, 645 insertions(+), 108 deletions(-)
diff --git a/build.sh b/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/free_glyph.c src/simple_renderer.c src/common.c"
if [ `uname` = "Darwin" ]; then
CFLAGS+=" -framework OpenGL"
diff --git a/build_msvc.bat b/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\free_glyph.c src\simple_renderer.c src\common.c /link %LIBS% -SUBSYSTEM:windows
diff --git a/src/arena.h b/src/arena.h
@@ -0,0 +1,173 @@
+// Copyright 2022 Alexey Kutepov <reximkut@gmail.com>
+
+// 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
diff --git a/src/common.c b/src/common.c
@@ -0,0 +1,88 @@
+#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;
+}
+
+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
+}
diff --git a/src/common.h b/src/common.h
@@ -0,0 +1,42 @@
+#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)
+
+char *temp_strdup(const char *s);
+void temp_reset(void);
+
+typedef struct {
+ const char **items;
+ size_t count;
+ size_t capacity;
+} Files;
+
+char *read_entire_file(const char *file_path);
+Errno read_entire_dir(const char *dir_path, Files *files);
+
+#endif // COMMON_H_
diff --git a/src/editor.c b/src/editor.c
@@ -5,6 +5,7 @@
#include <string.h>
#include "./editor.h"
#include "./sv.h"
+#include "common.h"
void editor_backspace(Editor *e)
{
diff --git a/src/editor.h b/src/editor.h
@@ -20,18 +20,6 @@ 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;
diff --git a/src/free_glyph.c b/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));
+ }
}
}
diff --git a/src/free_glyph.h b/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_
diff --git a/src/main.c b/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
@@ -18,6 +20,7 @@
#include "./la.h"
#include "./free_glyph.h"
#include "./simple_renderer.h"
+#include "./common.h"
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
@@ -62,6 +65,76 @@ static Uint32 last_stroke = 0;
#define FREE_GLYPH_FONT_SIZE 64
#define ZOOM_OUT_GLYPH_THRESHOLD 30
+typedef struct {
+ Files files;
+ size_t cursor;
+} File_Browser;
+
+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;
@@ -84,7 +157,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 +205,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,6 +226,16 @@ void render_editor(SDL_Window *window, Free_Glyph_Atlas *atlas, Simple_Renderer
}
}
+// TODO: display errors reported via flush_error right in the text editor window somehow
+#define flush_error(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
+
+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);
+}
+
int main(int argc, char **argv)
{
editor_recompute_lines(&editor);
@@ -262,6 +349,8 @@ int main(int argc, char **argv)
free_glyph_atlas_init(&atlas, face);
bool quit = false;
+ bool file_browser = false;
+ File_Browser fb = {0};
while (!quit) {
const Uint32 start = SDL_GetTicks();
SDL_Event event = {0};
@@ -273,66 +362,116 @@ 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) {
+ file_path = fb.files.items[fb.cursor];
+ FILE *file = fopen(file_path, "r");
+ if (file != NULL) {
+ editor_load_from_file(&editor, file);
+ file_browser = false;
+ fclose(file);
+ }
+ }
+ }
+ break;
+ }
+ } else {
+ switch (event.key.keysym.sym) {
+ case SDLK_BACKSPACE: {
+ editor_backspace(&editor);
+ last_stroke = SDL_GetTicks();
+ }
+ break;
- case SDLK_DELETE: {
- editor_delete(&editor);
- last_stroke = SDL_GetTicks();
- }
- break;
+ case SDLK_F2: {
+ if (file_path) {
+ editor_save_to_file(&editor, file_path);
+ }
+ }
+ break;
+
+ case SDLK_F3: {
+ fb.files.count = 0;
+ fb.cursor = 0;
+ const char *dir_path = ".";
+ Errno err = read_entire_dir(dir_path, &fb.files);
+ if (err != 0) {
+ flush_error("Could not read directory %s: %s", dir_path, strerror(errno));
+ } else {
+ qsort(fb.files.items, fb.files.count, sizeof(*fb.files.items), file_cmp);
+ 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 +487,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);
diff --git a/src/minirent.h b/src/minirent.h
@@ -0,0 +1,136 @@
+// Copyright 2021 Alexey Kutepov <reximkut@gmail.com>
+//
+// 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
@@ -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])
{