summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkdx <kikoodx@paranoici.org>2023-03-04 18:09:04 +0100
committerkdx <kikoodx@paranoici.org>2023-03-04 18:10:32 +0100
commit2d7cc7f8218ce7d8edec9b2009a2d38086a40644 (patch)
tree8519a80deaf4a0a382d74cfa8003d7f320b2a7d3
download7drl2023-2d7cc7f8218ce7d8edec9b2009a2d38086a40644.tar.gz
initial commit
-rw-r--r--.gitignore4
-rw-r--r--Makefile30
-rw-r--r--TZR.c682
-rw-r--r--TZR.h244
-rw-r--r--compile_flags.txt5
-rw-r--r--main.c25
6 files changed, 990 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bbf637d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.o
+7drl2023
+tags
+a.out
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9224fc5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+CC := gcc
+LD := $(CC)
+SDL2-CONFIG := sdl2-config
+DEBUGFLAGS := -g -O0
+CFLAGS := $(DEBUGFLAGS) -std=c99 -Wall -Wextra -Wno-override-init \
+ $(shell $(SDL2-CONFIG) --cflags)
+LDFLAGS := $(DEBUGFLAGS) -lm $(shell $(SDL2-CONFIG) --libs) -lSDL2_image
+SRC := $(wildcard *.c)
+OBJ := $(patsubst %.c,%.o,$(SRC))
+NAME := 7drl2023
+
+all: $(NAME)
+
+$(NAME): $(OBJ)
+ $(LD) -o $(NAME) $(OBJ) $(LDFLAGS)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+run: all
+ ./$(NAME)
+
+clean:
+ rm -f $(NAME) $(OBJ)
+
+re:
+ make --no-print-directory clean
+ make --no-print-directory all
+
+.PHONY: all run clean re
diff --git a/TZR.c b/TZR.c
new file mode 100644
index 0000000..8ee0ec1
--- /dev/null
+++ b/TZR.c
@@ -0,0 +1,682 @@
+#include <math.h>
+#include <SDL2/SDL_error.h>
+#include <SDL2/SDL_events.h>
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_log.h>
+#include <SDL2/SDL_render.h>
+#include <SDL2/SDL_rwops.h>
+#include <SDL2/SDL_scancode.h>
+#include <SDL2/SDL_timer.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+#include "TZR.h"
+#include <unistd.h>
+
+/* sources/drain.h */
+
+static void *drain_error(void)
+{
+ perror("TZR_LoadResourceTyped:drain");
+ return NULL;
+}
+
+static char *drain(FILE *fp, size_t *size)
+{
+ if (fseek(fp, 0, SEEK_END) < 0)
+ return drain_error();
+ *size = ftell(fp);
+ if (fseek(fp, 0, SEEK_SET) < 0)
+ return drain_error();
+ char *data = malloc(*size);
+ if (data == NULL)
+ return drain_error();
+ if (fread(data, 1, *size, fp) != *size)
+ return free(data), drain_error();
+ return data;
+}
+
+/* sources/sdl_error.h */
+
+static int sdl_error(int rv)
+{
+ SDL_Log("%s\n", SDL_GetError());
+ return rv;
+}
+/* sources/sdl_rect_to_rect.h */
+
+static void TZR_RectFromSDLRect(TZR_Rect *dest, const SDL_Rect *src)
+{
+ dest->from.x = src->x;
+ dest->from.y = src->y;
+ dest->to.x = src->x + src->w - 1;
+ dest->to.y = src->y + src->h - 1;
+}
+/* sources/globals.c */
+
+TZR_Config ___tzr_config = {0};
+TZR_Color ___tzr_color = {0, 0.0f, 0.0f, 0.0f, 1.0f};
+TZR_Resource *___tzr_resources = NULL;
+size_t ___tzr_resources_capacity = 0;
+size_t ___tzr_resources_size = 0;
+SDL_Window *___tzr_window = NULL;
+SDL_Renderer *___tzr_renderer = NULL;
+SDL_Texture *___tzr_target = NULL;
+unsigned long ___tzr_tick = 0;
+unsigned long ___tzr_next_time = 0;
+unsigned long ___tzr_min_dt = 0;
+int ___tzr_should_quit = 0;
+int ___tzr_mouse_x = 0;
+int ___tzr_mouse_y = 0;
+const char *___tzr_command[___TZR_RES_COUNT] = {0};
+TZR_KeyState ___tzr_keystates[SDL_NUM_SCANCODES] = {0};
+/* sources/TZR_CycleEvents.c */
+
+void TZR_CycleEvents(void)
+{
+ TZR_ResourcesWatch();
+ for (int i = 0; i < SDL_NUM_SCANCODES; i++) {
+ TZR_KeyState *const keystate = &___tzr_keystates[i];
+ switch (*keystate) {
+ case TZR_KEYSTATE_RELEASE:
+ case TZR_KEYSTATE_UP:
+ *keystate = TZR_KEYSTATE_UP;
+ break;
+ case TZR_KEYSTATE_PRESS:
+ case TZR_KEYSTATE_DOWN:
+ *keystate = TZR_KEYSTATE_DOWN;
+ break;
+ default:
+ break;
+ }
+ }
+ TZR_Event e;
+ while (TZR_PollEvent(&e))
+ ;
+}
+/* sources/TZR_DestroyResource.c */
+
+void TZR_DestroyResource(TZR_Resource *res, int free_path)
+{
+ if (res->path != NULL && free_path)
+ free(res->path);
+ switch (res->type) {
+ case TZR_RES_RAW:
+ if (res->raw.data != NULL)
+ free(res->raw.data);
+ break;
+ case TZR_RES_IMAGE:
+ if (res->image.ptr != NULL)
+ SDL_DestroyTexture(res->image.ptr);
+ break;
+ default:
+ fprintf(stderr, "unknown resource type %u\n", res->type);
+ break;
+ }
+}
+
+/* sources/TZR_DirectResourceLoad.c */
+
+int TZR_DirectResourceLoad(TZR_Resource *res, const void *data, int size)
+{
+ switch (res->type) {
+ case TZR_RES_RAW:
+ res->raw.size = size;
+ res->raw.data = malloc(size);
+ if (res->raw.data == NULL) {
+ perror("TZR_DirectResourceLoad");
+ return -1;
+ }
+ memcpy(res->raw.data, data, size);
+ break;
+ case TZR_RES_IMAGE: {
+ SDL_RWops *const rw = SDL_RWFromConstMem(data, size);
+ if (rw == NULL)
+ return sdl_error(-1);
+ SDL_Surface *const surf = IMG_Load_RW(rw, 0);
+ SDL_RWclose(rw);
+ if (surf == NULL)
+ return sdl_error(-1);
+ SDL_Texture *const tex =
+ SDL_CreateTextureFromSurface(___tzr_renderer, surf);
+ SDL_FreeSurface(surf);
+ if (tex == NULL)
+ return sdl_error(-1);
+ if (SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND) < 0) {
+ SDL_DestroyTexture(tex);
+ return sdl_error(-1);
+ }
+ if (SDL_QueryTexture(tex, NULL, NULL,
+ &res->image.width, &res->image.height)) {
+ SDL_DestroyTexture(tex);
+ return sdl_error(-1);
+ }
+ res->image.ptr = tex;
+ } break;
+ default:
+ fprintf(stderr, "invalid type\n");
+ return -1;
+ }
+ return 0;
+}
+/* sources/TZR_DrawBegin.c */
+
+int TZR_DrawBegin(void)
+{
+ if (SDL_SetRenderTarget(___tzr_renderer, ___tzr_target) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_DrawClear.c */
+
+int TZR_DrawClear(void)
+{
+ if (SDL_RenderClear(___tzr_renderer) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_DrawEnd.c */
+
+int TZR_DrawEnd(void)
+{
+ if (SDL_SetRenderTarget(___tzr_renderer, NULL) < 0)
+ return sdl_error(-1);
+ if (SDL_SetRenderDrawColor(___tzr_renderer, 0, 0, 0, 255) < 0)
+ return sdl_error(-1);
+ if (SDL_RenderClear(___tzr_renderer) < 0)
+ return sdl_error(-1);
+
+ int win_w, win_h;
+ SDL_GetWindowSize(___tzr_window, &win_w, &win_h);
+ win_w = win_w ? win_w : 1;
+ win_h = win_h ? win_h : 1;
+
+ const float ratio_w = win_w / (float)___tzr_config.width;
+ const float ratio_h = win_h / (float)___tzr_config.height;
+ float scale = (ratio_w < ratio_h) ? ratio_w : ratio_h;
+ if (scale > 1.0f && ___tzr_config.pixel_perfect)
+ scale = (int)scale;
+ const int off_x = (win_w - ___tzr_config.width * scale) / 2;
+ const int off_y = (win_h - ___tzr_config.height * scale) / 2;
+ const SDL_Rect dest = {
+ off_x,
+ off_y,
+ ___tzr_config.width * scale,
+ ___tzr_config.height * scale
+ };
+
+ if (SDL_RenderCopy(___tzr_renderer, ___tzr_target, NULL, &dest) < 0)
+ return sdl_error(-1);
+ if (___tzr_config.target_fps > 0) {
+ ___tzr_next_time += ___tzr_min_dt;
+ const unsigned long cur_time = SDL_GetTicks64();
+ if (___tzr_next_time <= cur_time)
+ ___tzr_next_time = cur_time;
+ else
+ SDL_Delay(___tzr_next_time - cur_time);
+ }
+ SDL_RenderPresent(___tzr_renderer);
+ ___tzr_tick += 1;
+ return 0;
+}
+/* sources/TZR_DrawImage.c */
+
+int _TZR_DrawImage(const TZR_DrawImageArgs *args)
+{
+ if (TZR_GetResourceType(args->id) != TZR_RES_IMAGE) {
+ fprintf(stderr, "%u isn't an image\n", args->id);
+ return -1;
+ }
+ TZR_Resource *const res = TZR_GetResourcePointer(args->id);
+ const TZR_Image *const img = &res->image;
+
+ if (SDL_SetTextureColorMod(img->ptr, ___tzr_color.r * 255,
+ ___tzr_color.g * 255, ___tzr_color.b * 255))
+ return sdl_error(-1);
+ if (SDL_SetTextureAlphaMod(img->ptr, ___tzr_color.a * 255))
+ return sdl_error(-1);
+
+ float scale_x = args->sx;
+ float scale_y = args->sy;
+
+ int simg_width = (args->w == INT_MIN) ? img->width : args->w;
+ if (simg_width < 0) {
+ simg_width *= -1;
+ scale_x *= -1;
+ }
+ if (simg_width + args->ix > img->width)
+ simg_width = img->width - args->ix;
+
+ int simg_height = (args->h == INT_MIN) ? img->height : args->h;
+ if (simg_height < 0) {
+ simg_height *= -1;
+ scale_y *= -1;
+ }
+ if (simg_height + args->iy > img->height)
+ simg_height = img->height - args->iy;
+
+ const int flip = (SDL_FLIP_HORIZONTAL * (scale_x < 0.0f)) |
+ (SDL_FLIP_VERTICAL * (scale_y < 0.0f));
+ const SDL_Rect src = {
+ args->ix,
+ args->iy,
+ simg_width,
+ simg_height
+ };
+
+ const int width = simg_width * fabs(scale_x);
+ const int height = simg_height * fabs(scale_y);
+ const int x = args->x - (scale_x < 0.0f ? width : 0);
+ const int y = args->y - (scale_y < 0.0f ? height : 0);
+
+ const SDL_Rect dest = {
+ x - (args->center ? (simg_width * scale_x / 2) : 0),
+ y - (args->center ? (simg_height * scale_y / 2) : 0),
+ width,
+ height
+ };
+ if (SDL_RenderCopyEx(___tzr_renderer, img->ptr, &src, &dest,
+ args->r * 360, NULL, flip) < 0)
+ return sdl_error(-1);
+ TZR_Rect area;
+ TZR_RectFromSDLRect(&area, &dest);
+ return 0;
+}
+/* sources/TZR_DrawLine.c */
+
+int TZR_DrawLine(int x0, int y0, int x1, int y1)
+{
+ if (SDL_RenderDrawLine(___tzr_renderer, x0, y0, x1, y1) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_DrawPoint.c */
+
+int TZR_DrawPoint(int x, int y)
+{
+ if (SDL_RenderDrawPoint(___tzr_renderer, x, y) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_DrawRectangle.c */
+
+int TZR_DrawRectangle(bool fill, int x, int y, int w, int h)
+{
+ const SDL_Rect rect = { x, y, w, h };
+ if (fill) {
+ if (SDL_RenderFillRect(___tzr_renderer, &rect) < 0)
+ return sdl_error(-1);
+ } else {
+ if (SDL_RenderDrawRect(___tzr_renderer, &rect) < 0)
+ return sdl_error(-1);
+ }
+ return 0;
+}
+/* sources/TZR_DrawSetColor.c */
+
+int _TZR_DrawSetColor(const TZR_Color *color)
+{
+ ___tzr_color.r = (color->r < 0.0f) ? ___tzr_color.r : color->r;
+ ___tzr_color.g = (color->g < 0.0f) ? ___tzr_color.g : color->g;
+ ___tzr_color.b = (color->b < 0.0f) ? ___tzr_color.b : color->b;
+ ___tzr_color.a = (color->a < 0.0f) ? ___tzr_color.a : color->a;
+ const uint8_t r = ___tzr_color.r * 255;
+ const uint8_t g = ___tzr_color.g * 255;
+ const uint8_t b = ___tzr_color.b * 255;
+ const uint8_t a = ___tzr_color.a * 255;
+ if (SDL_SetRenderDrawColor(___tzr_renderer, r, g, b, a) < 0)
+ return sdl_error(-1);
+ return 0;
+}
+/* sources/TZR_GetImageHeight.c */
+
+int TZR_GetImageHeight(TZR_Uint id)
+{
+ return TZR_GetResourcePointer(id)->image.height;
+}
+/* sources/TZR_GetImageWidth.c */
+
+int TZR_GetImageWidth(TZR_Uint id)
+{
+ return TZR_GetResourcePointer(id)->image.width;
+}
+/* sources/TZR_GetKeyState.c */
+
+TZR_KeyState TZR_GetKeyState(int scancode)
+{
+ return ___tzr_keystates[scancode];
+}
+/* sources/TZR_GetRawResource.c */
+
+TZR_Raw *TZR_GetRawResource(TZR_Uint id)
+{
+ return &TZR_GetResourcePointer(id)->raw;
+}
+/* sources/TZR_GetResourcePath.c */
+
+const char *TZR_GetResourcePath(TZR_Uint id)
+{
+ return TZR_GetResourcePointer(id)->path;
+}
+/* sources/TZR_GetResourcePointer.c */
+
+TZR_Resource *TZR_GetResourcePointer(TZR_Uint id)
+{
+ return &___tzr_resources[id - 1];
+}
+/* sources/TZR_GetResourceType.c */
+
+TZR_ResourceType TZR_GetResourceType(TZR_Uint id)
+{
+ return TZR_GetResourcePointer(id)->type;
+}
+/* sources/TZR_GetTick.c */
+
+unsigned long TZR_GetTick(void)
+{
+ return ___tzr_tick;
+}
+/* sources/TZR_Init.c */
+
+static int _sdl_error(void)
+{
+ sdl_error(-1);
+ TZR_Quit();
+ return -1;
+}
+
+int _TZR_Init(const TZR_Config *config)
+{
+ if (SDL_Init(SDL_INIT_VIDEO) < 0)
+ return _sdl_error();
+ if (IMG_Init(IMG_INIT_PNG) < 0)
+ return _sdl_error();
+
+ memcpy(&___tzr_config, config, sizeof(TZR_Config));
+ char *const basepath = SDL_GetBasePath();
+ if (basepath == NULL)
+ return _sdl_error();
+ const int chdir_rv = chdir(basepath);
+ SDL_free(basepath);
+ if (chdir_rv < 0)
+ return perror("TZR_Init"), -1;
+
+ ___tzr_window = SDL_CreateWindow("TZR", SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ ___tzr_config.width,
+ ___tzr_config.height,
+ SDL_WINDOW_RESIZABLE);
+ if (___tzr_window == NULL)
+ return _sdl_error();
+
+ ___tzr_renderer = SDL_CreateRenderer(___tzr_window, -1,
+ SDL_RENDERER_ACCELERATED);
+ if (___tzr_renderer == NULL)
+ return _sdl_error();
+
+ ___tzr_target = SDL_CreateTexture(___tzr_renderer,
+ SDL_PIXELFORMAT_RGB888,
+ SDL_TEXTUREACCESS_TARGET,
+ ___tzr_config.width,
+ ___tzr_config.height);
+ if (___tzr_target == NULL)
+ return _sdl_error();
+
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0);
+ ___tzr_tick = 0;
+
+ if (___tzr_config.target_fps > 0) {
+ ___tzr_min_dt = 1000 / ___tzr_config.target_fps;
+ ___tzr_next_time = SDL_GetTicks64();
+ }
+
+ ___tzr_command[TZR_RES_RAW] = getenv("TZR_EDIT_RAW");
+ if (___tzr_command[TZR_RES_RAW] == NULL)
+ ___tzr_command[TZR_RES_RAW] = "foot nvim";
+ ___tzr_command[TZR_RES_IMAGE] = getenv("TZR_EDIT_IMAGE");
+ if (___tzr_command[TZR_RES_IMAGE] == NULL)
+ ___tzr_command[TZR_RES_IMAGE] = "gimp";
+ return 0;
+}
+/* sources/TZR_IsKeyDown.c */
+
+bool TZR_IsKeyDown(int scancode)
+{
+ const TZR_KeyState state = TZR_GetKeyState(scancode);
+ return (state == TZR_KEYSTATE_DOWN || state == TZR_KEYSTATE_PRESS);
+}
+/* sources/TZR_IsKeyPressed.c */
+
+bool TZR_IsKeyPressed(int scancode)
+{
+ return (TZR_GetKeyState(scancode) == TZR_KEYSTATE_PRESS);
+}
+/* sources/TZR_IsKeyReleased.c */
+
+bool TZR_IsKeyReleased(int scancode)
+{
+ if (TZR_GetKeyState(scancode) == TZR_KEYSTATE_RELEASE)
+ return true;
+ return false;
+}
+/* sources/TZR_IsKeyUp.c */
+
+bool TZR_IsKeyUp(int scancode)
+{
+ const TZR_KeyState state = TZR_GetKeyState(scancode);
+ return (state == TZR_KEYSTATE_UP || state == TZR_KEYSTATE_RELEASE);
+}
+/* sources/TZR_LoadResource.c */
+
+static TZR_ResourceType deduce_type(const char *path)
+{
+ const char *const path_extension = strrchr(path, '.');
+ if (path_extension == NULL)
+ return TZR_RES_RAW;
+ if (strcasecmp(path_extension, ".bmp") == 0)
+ return TZR_RES_IMAGE;
+ return TZR_RES_RAW;
+}
+
+TZR_Uint TZR_LoadResource(const char *path)
+{
+ return TZR_LoadResourceTyped(deduce_type(path), path);
+}
+/* sources/TZR_LoadResourceFromMemory.c */
+
+static int reserve_ressources(size_t size)
+{
+ if (size <= ___tzr_resources_capacity)
+ return 0;
+ size_t target = 16;
+ while (target < size * sizeof(TZR_Resource))
+ target *= 2;
+ TZR_Resource *new_resources;
+ if (___tzr_resources == NULL)
+ new_resources = malloc(target);
+ else
+ new_resources = realloc(___tzr_resources, target);
+ if (new_resources == NULL) {
+ perror("TZR_LoadResourceFromMemory:reserve_ressources");
+ return 1;
+ }
+ ___tzr_resources = new_resources;
+ return 0;
+}
+
+TZR_Uint TZR_LoadResourceFromMemory(TZR_ResourceType type, const void *data, int size)
+{
+ if (reserve_ressources(___tzr_resources_size + 1)) {
+ fprintf(stderr, "failed to reserve for new ressource\n");
+ return 0;
+ }
+ TZR_Resource *const res = &___tzr_resources[___tzr_resources_size];
+ res->type = type;
+ res->path = NULL;
+ if (TZR_DirectResourceLoad(res, data, size))
+ return 0;
+ ___tzr_resources_size += 1;
+ return ___tzr_resources_size;
+}
+/* sources/TZR_LoadResourceTyped.c */
+
+TZR_Uint TZR_LoadResourceTyped(TZR_ResourceType type, const char *path)
+{
+ for (TZR_Uint i = 0; i < ___tzr_resources_size; i++)
+ if (___tzr_resources[i].type == type &&
+ strcmp(___tzr_resources[i].path, path) == 0)
+ return i;
+ FILE *const fp = fopen(path, "rb");
+ if (fp == NULL) {
+ perror(path);
+ return 0;
+ }
+ size_t size;
+ char *const data = drain(fp, &size);
+ fclose(fp);
+ if (data == NULL)
+ return 0;
+ const TZR_Uint id = TZR_LoadResourceFromMemory(type, data, size);
+ free(data);
+ if (id == 0)
+ return 0;
+ TZR_Resource *const res = TZR_GetResourcePointer(id);
+
+ const size_t path_len = strlen(path);
+ struct stat st = {0};
+ (void)stat(path, &st);
+ res->path = malloc(path_len + 1);
+ res->mtime = 0;
+ if (res->path == NULL)
+ perror("TZR_LoadResourceTyped");
+ else
+ strcpy(res->path, path);
+ return id;
+}
+/* sources/TZR_PollEvent.c */
+
+int TZR_PollEvent(TZR_Event *e)
+{
+ TZR_Event discard;
+ if (e == NULL)
+ e = &discard;
+
+ SDL_Event se;
+ while (SDL_PollEvent(&se)) switch (se.type) {
+ case SDL_QUIT:
+ e->type = TZR_EV_QUIT;
+ ___tzr_should_quit = 1;
+ return 1;
+ case SDL_KEYDOWN:
+ if (se.key.repeat)
+ break;
+ e->type = TZR_EV_KEYDOWN;
+ e->button = se.key.keysym.scancode;
+ ___tzr_keystates[e->button] = TZR_KEYSTATE_PRESS;
+ break;
+ case SDL_KEYUP:
+ e->type = TZR_EV_KEYUP;
+ e->button = se.key.keysym.scancode;
+ ___tzr_keystates[e->button] = TZR_KEYSTATE_RELEASE;
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ ___tzr_mouse_x = se.button.x;
+ ___tzr_mouse_y = se.button.y;
+ break;
+ case SDL_MOUSEBUTTONUP:
+ ___tzr_mouse_x = se.button.x;
+ ___tzr_mouse_y = se.button.y;
+ break;
+ case SDL_MOUSEMOTION:
+ ___tzr_mouse_x = se.motion.x;
+ ___tzr_mouse_y = se.motion.y;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+/* sources/TZR_Quit.c */
+
+void TZR_Quit(void)
+{
+ for (size_t i = 0; i < ___tzr_resources_size; i++)
+ TZR_DestroyResource(&___tzr_resources[i], 1);
+ if (___tzr_resources != NULL)
+ free(___tzr_resources);
+ ___tzr_resources = NULL;
+ ___tzr_resources_size = 0;
+ ___tzr_resources_capacity = 0;
+ if (___tzr_target != NULL) {
+ SDL_DestroyTexture(___tzr_target);
+ ___tzr_target = NULL;
+ }
+ if (___tzr_renderer != NULL) {
+ SDL_DestroyRenderer(___tzr_renderer);
+ ___tzr_renderer = NULL;
+ }
+ if (___tzr_window != NULL) {
+ SDL_DestroyWindow(___tzr_window);
+ ___tzr_window = NULL;
+ }
+ IMG_Quit();
+ SDL_Quit();
+}
+/* sources/TZR_ReloadResource.c */
+
+int TZR_ReloadResource(TZR_Uint id)
+{
+ TZR_Resource *const res = TZR_GetResourcePointer(id);
+ if (res->path == NULL) {
+ fprintf(stderr, "resource path of %u is NULL\n", id);
+ return -1;
+ }
+ FILE *const fp = fopen(res->path, "rb");
+ if (fp == NULL) {
+ perror(res->path);
+ return -1;
+ }
+ size_t size;
+ char *const data = drain(fp, &size);
+ fclose(fp);
+ if (data == NULL)
+ return -1;
+ TZR_Resource new_res;
+ memcpy(&new_res, res, sizeof(TZR_Resource));
+ const int load_rv = TZR_DirectResourceLoad(&new_res, data, size);
+ free(data);
+ if (load_rv)
+ return -1;
+ TZR_DestroyResource(res, 0);
+ memcpy(res, &new_res, sizeof(TZR_Resource));
+ return 0;
+}
+/* sources/TZR_ResourcesWatch.c */
+
+void TZR_ResourcesWatch(void)
+{
+ for (TZR_Uint i = 0; i < ___tzr_resources_size; i++) {
+ TZR_Resource *const res = TZR_GetResourcePointer(i + 1);
+ if (res->path == NULL)
+ continue;
+ struct stat st = {0};
+ (void)stat(res->path, &st);
+ if (st.st_mtime != res->mtime) {
+ if (res->mtime != 0)
+ TZR_ReloadResource(i + 1);
+ res->mtime = st.st_mtime;
+ }
+ }
+}
+/* sources/TZR_ShouldQuit.c */
+
+int TZR_ShouldQuit(void)
+{
+ return ___tzr_should_quit;
+}
+
+/* commit hash: 41400167398f1ab5f5e8ee67a76358189085f17f */
diff --git a/TZR.h b/TZR.h
new file mode 100644
index 0000000..2c1c1d2
--- /dev/null
+++ b/TZR.h
@@ -0,0 +1,244 @@
+#pragma once
+#include <limits.h>
+#include <SDL2/SDL_main.h>
+#include <SDL2/SDL_render.h>
+#include <SDL2/SDL_scancode.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* headers/TZR_types.h */
+
+enum TZR_ResourceType {
+ TZR_RES_RAW,
+ TZR_RES_IMAGE,
+ ___TZR_RES_COUNT
+};
+
+enum TZR_EventType {
+ TZR_EV_QUIT,
+ TZR_EV_KEYDOWN,
+ TZR_EV_KEYUP
+};
+
+enum TZR_KeyState {
+ TZR_KEYSTATE_UP,
+ TZR_KEYSTATE_DOWN,
+ TZR_KEYSTATE_RELEASE,
+ TZR_KEYSTATE_PRESS
+};
+
+typedef unsigned int TZR_Uint;
+typedef struct TZR_Config TZR_Config;
+typedef struct TZR_Point TZR_Point;
+typedef struct TZR_Color TZR_Color;
+typedef struct TZR_Rect TZR_Rect;
+typedef enum TZR_ResourceType TZR_ResourceType;
+typedef struct TZR_Raw TZR_Raw;
+typedef struct TZR_Image TZR_Image;
+typedef struct TZR_Resource TZR_Resource;
+typedef enum TZR_EventType TZR_EventType;
+typedef struct TZR_Event TZR_Event;
+typedef struct TZR_DrawImageArgs TZR_DrawImageArgs;
+typedef enum TZR_KeyState TZR_KeyState;
+
+struct TZR_Config {
+ int _;
+ int width;
+ int height;
+ int target_fps;
+ bool pixel_perfect;
+ const char *title;
+};
+
+struct TZR_Point {
+ int x;
+ int y;
+};
+
+struct TZR_Color {
+ int _;
+ float r;
+ float g;
+ float b;
+ float a;
+};
+
+struct TZR_Rect {
+ TZR_Point from;
+ TZR_Point to;
+};
+
+struct TZR_Raw {
+ void *data;
+ size_t size;
+};
+
+struct TZR_Image {
+ int width;
+ int height;
+ SDL_Texture *ptr;
+};
+
+struct TZR_Resource {
+ TZR_ResourceType type;
+ char *path; /* allocated and freed by TZR; can be NULL */
+ long mtime;
+ union {
+ TZR_Raw raw; /* raw file data */
+ TZR_Image image;
+ };
+};
+
+struct TZR_Event {
+ TZR_EventType type;
+ int button;
+};
+
+struct TZR_DrawImageArgs {
+ int _;
+ TZR_Uint id;
+ int x;
+ int y;
+ int ix;
+ int iy;
+ int w;
+ int h;
+ float r;
+ float sx;
+ float sy;
+ bool center;
+};
+/* headers/TZR_globals.h */
+
+extern TZR_Config ___tzr_config;
+extern TZR_Color ___tzr_color;
+extern TZR_Resource *___tzr_resources;
+extern size_t ___tzr_resources_capacity;
+extern size_t ___tzr_resources_size;
+extern SDL_Window *___tzr_window;
+extern SDL_Renderer *___tzr_renderer;
+extern SDL_Texture *___tzr_target;
+extern unsigned long ___tzr_tick;
+extern unsigned long ___tzr_next_time;
+extern unsigned long ___tzr_min_dt;
+extern int ___tzr_should_quit;
+extern int ___tzr_mouse_x;
+extern int ___tzr_mouse_y;
+extern const char *___tzr_command[___TZR_RES_COUNT];
+extern TZR_KeyState ___tzr_keystates[SDL_NUM_SCANCODES];
+/* headers/TZR_resource.h */
+
+/* Return 0 on error. */
+TZR_Uint TZR_LoadResourceFromMemory(TZR_ResourceType type, const void *data, int size);
+TZR_Uint TZR_LoadResourceTyped(TZR_ResourceType type, const char *path);
+
+/* Return 0 on error; try to autodetect resource type from file extension. */
+TZR_Uint TZR_LoadResource(const char *path);
+
+/* Doesn't handle invalid ID. Pointer might get invalidated by resource
+ * creation calls, use with caution. */
+TZR_Resource *TZR_GetResourcePointer(TZR_Uint id);
+
+/* Doesn't handle invalid ID. Pointer might get invalidated by resource
+ * creation calls, use with caution. */
+TZR_Raw *TZR_GetRawResource(TZR_Uint id);
+
+/* Doesn't handle invalid ID. */
+TZR_ResourceType TZR_GetResourceType(TZR_Uint id);
+
+/* Doesn't handle invalid ID. */
+const char *TZR_GetResourcePath(TZR_Uint id);
+
+/* Doesn't handle invalid ID. */
+int TZR_GetImageWidth(TZR_Uint id);
+
+/* Doesn't handle invalid ID. */
+int TZR_GetImageHeight(TZR_Uint id);
+
+/* Reload ressource. Returns -1 on error and doesn't apply changes. */
+int TZR_ReloadResource(TZR_Uint id);
+
+/* Watch for changes on managed resources and hotreload if appropriate. */
+void TZR_ResourcesWatch(void);
+
+/* Used internaly. */
+int TZR_DirectResourceLoad(TZR_Resource *res, const void *data, int size);
+void TZR_DestroyResource(TZR_Resource *res, int free_path);
+/* headers/TZR_events.h */
+
+/* Write event data to `e`. Return 0 when event queue is empty. */
+int TZR_PollEvent(TZR_Event *e);
+
+/* Drain queued events with TZR_PollEvent and call TZR_ResourcesWatch. */
+void TZR_CycleEvents(void);
+/* headers/TZR_render.h */
+
+/* All draw calls should happen between TZR_DrawBegin and TZR_DrawEnd.
+ * Return -1 on error. */
+int TZR_DrawBegin(void);
+
+/* Return -1 on error. */
+int TZR_DrawEnd(void);
+
+/* Return -1 on error. */
+#define TZR_DrawSetColor(...) _TZR_DrawSetColor(&(const TZR_Color){ \
+ .r=-1.0f, .g=-1.0f, .b=-1.0f, .a=-1.0f, ._=0, __VA_ARGS__ })
+int _TZR_DrawSetColor(const TZR_Color *color);
+
+/* Return -1 on error. */
+int TZR_DrawClear(void);
+
+/* Return -1 on error. Draw point on `x`;`y` in the framebuffer. */
+int TZR_DrawPoint(int x, int y);
+
+/* Return -1 on error. Draw line between `x0`;`y0` and `x1`;`y1` in the
+ * framebuffer. */
+int TZR_DrawLine(int x0, int y0, int x1, int y1);
+
+/* Return -1 on error. Draw rectangle at `x`;`y` position of size `w`x`h` in the
+ * framebuffer. */
+int TZR_DrawRectangle(bool fill, int x, int y, int w, int h);
+
+/* Return -1 on error. Draw texture ressource `id` at `x`;`y` position of
+ * the framebuffer. */
+#define TZR_DrawImage(...) _TZR_DrawImage(&(const TZR_DrawImageArgs){ \
+ .x=0, .y=0, .ix=0, .iy=0, .w=INT_MIN, .h=INT_MIN, .r=0.0f, .sx=1.0f, \
+ .sy=1.0f, .center=false, ._=0, __VA_ARGS__ })
+int _TZR_DrawImage(const TZR_DrawImageArgs *args);
+/* headers/TZR_keystate.h */
+
+TZR_KeyState TZR_GetKeyState(int scancode);
+bool TZR_IsKeyDown(int scancode);
+bool TZR_IsKeyUp(int scancode);
+bool TZR_IsKeyReleased(int scancode);
+bool TZR_IsKeyPressed(int scancode);
+/* headers/TZR.h */
+
+/* TZR manages all loaded resources internally and tries to avoid duplicate.
+ * A resource can be loaded multiple times if asked, identification doesn't rely
+ * on filepath. In doubt, use TZR_LoadResource ONCE per asset and store the ID.
+ * Resources are exposed as ID instead of pointer, as a future proof measure in
+ * case TZR needs to rearrange memory at runtime in the future. */
+
+#define TZR_Init(...) _TZR_Init(&(const TZR_Config){ \
+ .width=256, \
+ .height=224, \
+ .target_fps=60, \
+ .pixel_perfect=true, \
+ .title="TZR", \
+ ._=0, __VA_ARGS__ })
+
+/* Should be called only once before usage of any other function unless
+ * otherwise specified. On error this calls TZR_Quit and returns -1.
+ * `config` will be duplicated internally and can be safely discarded. */
+int _TZR_Init(const TZR_Config *config);
+
+/* Destroy and free everything created by TZR. */
+void TZR_Quit(void);
+
+/* Return non-zero value when a quit event has been received. */
+int TZR_ShouldQuit(void);
+
+unsigned long TZR_GetTick(void);
+
+/* commit hash: 41400167398f1ab5f5e8ee67a76358189085f17f */
diff --git a/compile_flags.txt b/compile_flags.txt
new file mode 100644
index 0000000..7077ba2
--- /dev/null
+++ b/compile_flags.txt
@@ -0,0 +1,5 @@
+-Wall
+-Wextra
+-Wno-initializer-overrides
+-std=c99
+-Iheaders
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..cea5f76
--- /dev/null
+++ b/main.c
@@ -0,0 +1,25 @@
+#include "TZR.h"
+
+int main(int argc, char **argv)
+{
+ (void)argc, (void)argv;
+
+ if (TZR_Init(.target_fps=30, .pixel_perfect=false, .title="7DRL 2023"))
+ return 1;
+ if (atexit(TZR_Quit)) {
+ printf("atexit failed\n");
+ TZR_Quit();
+ return 1;
+ }
+
+ while (!TZR_ShouldQuit()) {
+ TZR_CycleEvents();
+
+ TZR_DrawBegin();
+ TZR_DrawSetColor(0, 0, 0, 1);
+ TZR_DrawClear();
+ TZR_DrawEnd();
+ }
+
+ return 0;
+}