summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile24
-rw-r--r--kon/konassets.c506
-rw-r--r--kon/konassets.h485
-rw-r--r--kon/konaudio.c277
-rw-r--r--kon/konaudio.h256
-rw-r--r--kon/kondebug.c739
-rw-r--r--kon/kondebug.h714
-rw-r--r--kon/konpack.c598
-rw-r--r--kon/konpack.h577
-rw-r--r--kon/kontile.c101
-rw-r--r--kon/kontile.h80
-rw-r--r--kon/konui.c556
-rw-r--r--kon/konui.h535
-rw-r--r--src/engine.c126
-rw-r--r--src/impl/konassets.c2
-rw-r--r--src/impl/konaudio.c2
-rw-r--r--src/impl/kondebug.c2
-rw-r--r--src/impl/konpack.c2
-rw-r--r--src/impl/kontile.c2
-rw-r--r--src/impl/konui.c2
20 files changed, 2845 insertions, 2741 deletions
diff --git a/Makefile b/Makefile
index 3916d7d..0900bbc 100644
--- a/Makefile
+++ b/Makefile
@@ -53,20 +53,9 @@ $(OBJDIR)/konfont.o: $(SRCDIR)/impl/konfont.c $(KONDIR)/konfont.h $(KONDIR)/kons
$(OBJDIR)/kontoml.o: $(SRCDIR)/impl/kontoml.c $(KONDIR)/kontoml.h
$(COMPILE) $(SRCDIR)/impl/kontoml.c -o $@
-$(OBJDIR)/kontile.o: $(SRCDIR)/impl/kontile.c $(KONDIR)/kontile.h $(KONDIR)/konsofren.h
- $(COMPILE) $(SRCDIR)/impl/kontile.c -o $@
-
-$(OBJDIR)/konassets.o: $(SRCDIR)/impl/konassets.c $(KONDIR)/konassets.h $(KONDIR)/konpack.h $(KONDIR)/konsofren.h $(EXTDIR)/*.h
- $(COMPILE) $(SRCDIR)/impl/konassets.c -o $@
-
-$(OBJDIR)/konui.o: $(SRCDIR)/impl/konui.c $(KONDIR)/konui.h $(KONDIR)/konfont.h $(KONDIR)/konsofren.h $(KONDIR)/konwinlib.h
- $(COMPILE) $(SRCDIR)/impl/konui.c -o $@
-
-$(OBJDIR)/kondebug.o: $(SRCDIR)/impl/kondebug.c $(KONDIR)/kondebug.h $(KONDIR)/konassets.h $(KONDIR)/konfont.h $(KONDIR)/konsofren.h $(KONDIR)/kontime.h
- $(COMPILE) $(SRCDIR)/impl/kondebug.c -o $@
-
-$(OBJDIR)/konaudio.o: $(SRCDIR)/impl/konaudio.c $(KONDIR)/konaudio.h $(KONDIR)/konassets.h $(EXTDIR)/miniaudio.h
- $(COMPILE) $(SRCDIR)/impl/konaudio.c -o $@
+# modules with a .c next to their header are normal modules, the single header libs go through src/impl
+$(OBJDIR)/%.o: $(KONDIR)/%.c $(KONDIR)/*.h $(EXTDIR)/*.h
+ $(COMPILE) $< -o $@
$(OBJDIR)/stb_image.o: $(SRCDIR)/impl/stb_image.c $(EXTDIR)/stb_image.h
$(COMPILEEXT) $(SRCDIR)/impl/stb_image.c -o $@
@@ -74,15 +63,12 @@ $(OBJDIR)/stb_image.o: $(SRCDIR)/impl/stb_image.c $(EXTDIR)/stb_image.h
$(OBJDIR)/miniaudio.o: $(SRCDIR)/impl/miniaudio.c $(EXTDIR)/miniaudio.h
$(COMPILEEXT) $(SRCDIR)/impl/miniaudio.c -o $@
-$(OBJDIR)/konpack.o: $(SRCDIR)/impl/konpack.c $(KONDIR)/konpack.h
- $(COMPILE) $(SRCDIR)/impl/konpack.c -o $@
-
tools: $(PACKER)
-$(PACKER): tools/konpack.c $(KONDIR)/konpack.h
+$(PACKER): tools/konpack.c $(KONDIR)/konpack.c $(KONDIR)/konpack.h
@mkdir -p $(OBJDIR)
@echo "# link $@"
- @$(CC) $(KONFLAGS) -o $@ tools/konpack.c
+ @$(CC) $(KONFLAGS) -DKONPACK_WRITER -o $@ tools/konpack.c $(KONDIR)/konpack.c
pack: $(PACKER)
@find $(ASSETDIR) -type f | sort > $(OBJDIR)/assets.lst
diff --git a/kon/konassets.c b/kon/konassets.c
new file mode 100644
index 0000000..173e085
--- /dev/null
+++ b/kon/konassets.c
@@ -0,0 +1,506 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konassets.c
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * 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.
+ */
+
+
+#include "konassets.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#ifdef KON_MODULE_IMAGE
+#include "stb_image.h"
+#endif
+
+#ifdef KON_MODULE_AUDIO
+#include "miniaudio.h"
+#endif
+
+#define KON_ASSET_LOADERS_ 32
+
+typedef struct kon_assetEntry_ {
+ char *name;
+ void *object;
+ int type;
+ int refs;
+ size_t bytes;
+ int loose;
+} kon_assetEntry_t;
+
+struct kon_assets {
+ kon_pack_t *pack;
+ char *looseDir;
+ kon_assetLoader_t loaders[KON_ASSET_LOADERS_];
+ int loaderUsed[KON_ASSET_LOADERS_];
+ int nextCustom;
+ kon_assetEntry_t *entries;
+ int count, capacity;
+ const char *error;
+};
+
+/*** built in loaders ***/
+
+static void *kon_loadBlob_(const uint8_t *data, size_t size, size_t *bytes) {
+ kon_blob_t *blob = malloc(sizeof(kon_blob_t));
+ if (!blob) return NULL;
+
+ blob->data = malloc(size + 1);
+ if (!blob->data) {
+ free(blob);
+ return NULL;
+ }
+
+ memcpy(blob->data, data, size);
+ blob->data[size] = 0;
+ blob->size = size;
+ *bytes = size;
+ return blob;
+}
+
+static void kon_unloadBlob_(void *object) {
+ kon_blob_t *blob = object;
+ free(blob->data);
+ free(blob);
+}
+
+#ifdef KON_MODULE_IMAGE
+
+static void *kon_loadImageAsset_(const uint8_t *data, size_t size, size_t *bytes) {
+ int width, height, channels;
+ unsigned char *pixels;
+ kon_image *image;
+
+ if (size > INT_MAX) return NULL;
+
+ pixels = stbi_load_from_memory(data, (int)size, &width, &height, &channels, 4);
+ if (!pixels) return NULL;
+
+ image = kon_loadImage(pixels, width, height, konFormatRGBA8);
+ stbi_image_free(pixels);
+
+ if (image) *bytes = (size_t)width * (size_t)height * 4;
+ return image;
+}
+
+static void kon_unloadImageAsset_(void *object) {
+ kon_freeImage(object);
+}
+
+#endif
+
+#ifdef KON_MODULE_AUDIO
+
+#define KON_SOUND_MAX_BYTES_ ((uint64_t)1 << 30)
+
+static void *kon_loadSoundAsset_(const uint8_t *data, size_t size, size_t *bytes) {
+ ma_decoder_config config = ma_decoder_config_init(ma_format_s16, 0, 0);
+ ma_decoder decoder;
+ kon_sound_t *sound;
+ int16_t *frames = NULL;
+ uint64_t total = 0, capacity = 0;
+ uint32_t channels;
+
+ if (ma_decoder_init_memory(data, size, &config, &decoder) != MA_SUCCESS) return NULL;
+
+ channels = (uint32_t)decoder.outputChannels;
+ if (channels == 0) {
+ ma_decoder_uninit(&decoder);
+ return NULL;
+ }
+
+ /* the length is not always known up front, so read until the decoder runs dry */
+ for (;;) {
+ ma_uint64 got = 0;
+
+ if (total == capacity) {
+ uint64_t grown = capacity ? capacity * 2 : 16384;
+ int16_t *more;
+
+ if (grown * channels * sizeof(int16_t) > KON_SOUND_MAX_BYTES_) break;
+ more = realloc(frames, (size_t)(grown * channels * sizeof(int16_t)));
+ if (!more) {
+ free(frames);
+ ma_decoder_uninit(&decoder);
+ return NULL;
+ }
+ frames = more;
+ capacity = grown;
+ }
+
+ if (ma_decoder_read_pcm_frames(&decoder, frames + total * channels, capacity - total, &got) != MA_SUCCESS && got == 0) break;
+ if (got == 0) break;
+ total += got;
+ }
+
+ sound = malloc(sizeof(kon_sound_t));
+ if (!sound || total == 0) {
+ free(sound);
+ free(frames);
+ ma_decoder_uninit(&decoder);
+ return NULL;
+ }
+
+ sound->frames = frames;
+ sound->frameCount = total;
+ sound->channels = channels;
+ sound->sampleRate = (uint32_t)decoder.outputSampleRate;
+ *bytes = (size_t)(total * channels * sizeof(int16_t));
+
+ ma_decoder_uninit(&decoder);
+ return sound;
+}
+
+static void kon_unloadSoundAsset_(void *object) {
+ kon_sound_t *sound = object;
+ free(sound->frames);
+ free(sound);
+}
+
+#endif
+
+/*** private helper ***/
+
+static int kon_extensionMatches_(const char *name, const char *extensions) {
+ const char *dot = strrchr(name, '.');
+ const char *slash = strrchr(name, '/');
+ size_t length;
+
+ if (!dot || (slash && dot < slash) || !extensions) return 0;
+ length = strlen(dot);
+
+ while (*extensions) {
+ const char *end;
+ size_t tokenLength;
+
+ while (*extensions == ' ') extensions++;
+ end = extensions;
+ while (*end && *end != ' ') end++;
+ tokenLength = (size_t)(end - extensions);
+
+ if (tokenLength == length) {
+ size_t i;
+ for (i = 0; i < length; i++) {
+ char c = dot[i];
+ if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
+ if (c != extensions[i]) break;
+ }
+ if (i == length) return 1;
+ }
+ extensions = end;
+ }
+ return 0;
+}
+
+static int kon_nameIsSafe_(const char *name) {
+ const char *segment = name;
+
+ if (!name || name[0] == '\0' || name[0] == '/') return 0;
+
+ for (const char *c = name; ; c++) {
+ if (*c == '\\' || *c == ':') return 0;
+ if (*c == '/' || *c == '\0') {
+ if (c - segment == 2 && segment[0] == '.' && segment[1] == '.') return 0;
+ if (*c == '\0') break;
+ segment = c + 1;
+ }
+ }
+ return 1;
+}
+
+static int kon_typeForName_(const kon_assets_t *assets, const char *name) {
+ for (int i = 1; i < KON_ASSET_LOADERS_; i++) {
+ if (assets->loaderUsed[i] && kon_extensionMatches_(name, assets->loaders[i].extensions)) return i;
+ }
+ return KON_ASSET_BLOB;
+}
+
+static kon_assetEntry_t *kon_findEntry_(const kon_assets_t *assets, const char *name) {
+ for (int i = 0; i < assets->count; i++) {
+ if (strcmp(assets->entries[i].name, name) == 0) return &assets->entries[i];
+ }
+ return NULL;
+}
+
+static uint8_t *kon_readFile_(kon_assets_t *assets, const char *name, size_t *size, int *loose) {
+ *loose = 0;
+
+#ifdef KON_DEVELOPER
+ if (assets->looseDir) {
+ size_t length = strlen(assets->looseDir) + strlen(name) + 2;
+ char *path = malloc(length);
+ FILE *f;
+
+ if (!path) {
+ assets->error = "out of memory";
+ return NULL;
+ }
+ snprintf(path, length, "%s/%s", assets->looseDir, name);
+ f = fopen(path, "rb");
+ free(path);
+
+ if (f) {
+ long end;
+ uint8_t *data = NULL;
+
+ if (fseek(f, 0, SEEK_END) == 0 && (end = ftell(f)) >= 0 && fseek(f, 0, SEEK_SET) == 0) {
+ data = malloc((size_t)end + 1);
+ if (data && fread(data, 1, (size_t)end, f) == (size_t)end) {
+ data[end] = 0;
+ *size = (size_t)end;
+ *loose = 1;
+ fclose(f);
+ return data;
+ }
+ free(data);
+ }
+ fclose(f);
+ assets->error = "could not read the loose file";
+ return NULL;
+ }
+ }
+#endif
+
+ if (assets->pack) {
+ int index = kon_packFind(assets->pack, name);
+ if (index >= 0) {
+ uint8_t *data = kon_packRead(assets->pack, index, size);
+ if (!data) assets->error = kon_packError();
+ return data;
+ }
+ }
+
+ assets->error = "file not found";
+ return NULL;
+}
+
+static void kon_unloadEntry_(kon_assets_t *assets, kon_assetEntry_t *entry) {
+ if (assets->loaderUsed[entry->type] && assets->loaders[entry->type].unload) {
+ assets->loaders[entry->type].unload(entry->object);
+ }
+ free(entry->name);
+}
+
+/*** assets ***/
+
+static void kon_addLoader_(kon_assets_t *assets, int type, const char *name, const char *extensions,
+ void *(*load)(const uint8_t *, size_t, size_t *), void (*unload)(void *)) {
+ assets->loaders[type].name = name;
+ assets->loaders[type].extensions = extensions;
+ assets->loaders[type].load = load;
+ assets->loaders[type].unload = unload;
+ assets->loaderUsed[type] = 1;
+}
+
+kon_assets_t *kon_assetsCreate(kon_pack_t *pack, const char *looseDir) {
+ kon_assets_t *assets = calloc(1, sizeof(kon_assets_t));
+ if (!assets) return NULL;
+
+ assets->pack = pack;
+ assets->error = "no error";
+ assets->nextCustom = KON_ASSET_CUSTOM;
+
+#ifdef KON_DEVELOPER
+ if (looseDir) {
+ assets->looseDir = malloc(strlen(looseDir) + 1);
+ if (!assets->looseDir) {
+ free(assets);
+ return NULL;
+ }
+ strcpy(assets->looseDir, looseDir);
+ }
+#else
+ (void)looseDir;
+#endif
+
+ kon_addLoader_(assets, KON_ASSET_BLOB, "blob", NULL, kon_loadBlob_, kon_unloadBlob_);
+#ifdef KON_MODULE_IMAGE
+ kon_addLoader_(assets, KON_ASSET_IMAGE, "image", ".png .jpg .jpeg .bmp .tga .gif", kon_loadImageAsset_, kon_unloadImageAsset_);
+#endif
+#ifdef KON_MODULE_AUDIO
+ kon_addLoader_(assets, KON_ASSET_SOUND, "sound", ".wav .mp3 .flac", kon_loadSoundAsset_, kon_unloadSoundAsset_);
+#endif
+
+ return assets;
+}
+
+void kon_assetsDestroy(kon_assets_t *assets) {
+ if (!assets) return;
+
+ for (int i = 0; i < assets->count; i++) kon_unloadEntry_(assets, &assets->entries[i]);
+ free(assets->entries);
+ free(assets->looseDir);
+ free(assets);
+}
+
+int kon_assetsRegister(kon_assets_t *assets, const kon_assetLoader_t *loader) {
+ int type;
+
+ if (!assets || !loader || !loader->load || !loader->extensions || !loader->name) return -1;
+ if (assets->nextCustom >= KON_ASSET_LOADERS_) return -1;
+
+ type = assets->nextCustom++;
+ kon_addLoader_(assets, type, loader->name, loader->extensions, loader->load, loader->unload);
+ return type;
+}
+
+void *kon_assetGet(kon_assets_t *assets, const char *name) {
+ return kon_assetGetAs(assets, name, -1);
+}
+
+void *kon_assetGetAs(kon_assets_t *assets, const char *name, int type) {
+ kon_assetEntry_t *entry;
+ uint8_t *data;
+ size_t size = 0, bytes = 0;
+ int loose, found;
+ void *object;
+
+ if (!assets) return NULL;
+ if (!kon_nameIsSafe_(name)) {
+ assets->error = "bad asset name";
+ return NULL;
+ }
+
+ entry = kon_findEntry_(assets, name);
+ if (entry) {
+ if (type >= 0 && entry->type != type) {
+ assets->error = "asset is a different type";
+ return NULL;
+ }
+ entry->refs++;
+ return entry->object;
+ }
+
+ found = kon_typeForName_(assets, name);
+ if (type >= 0 && found != type) {
+ assets->error = "asset is a different type";
+ return NULL;
+ }
+
+ data = kon_readFile_(assets, name, &size, &loose);
+ if (!data) return NULL;
+
+ object = assets->loaders[found].load(data, size, &bytes);
+ free(data);
+ if (!object) {
+ assets->error = "could not decode the file";
+ return NULL;
+ }
+
+ if (assets->count == assets->capacity) {
+ int capacity = assets->capacity ? assets->capacity * 2 : 32;
+ kon_assetEntry_t *grown = realloc(assets->entries, (size_t)capacity * sizeof(kon_assetEntry_t));
+ if (!grown) {
+ assets->loaders[found].unload(object);
+ assets->error = "out of memory";
+ return NULL;
+ }
+ assets->entries = grown;
+ assets->capacity = capacity;
+ }
+
+ entry = &assets->entries[assets->count];
+ entry->name = malloc(strlen(name) + 1);
+ if (!entry->name) {
+ assets->loaders[found].unload(object);
+ assets->error = "out of memory";
+ return NULL;
+ }
+ strcpy(entry->name, name);
+ entry->object = object;
+ entry->type = found;
+ entry->refs = 1;
+ entry->bytes = bytes;
+ entry->loose = loose;
+ assets->count++;
+
+ return object;
+}
+
+void kon_assetRelease(kon_assets_t *assets, const char *name) {
+ kon_assetEntry_t *entry;
+
+ if (!assets || !name) return;
+ entry = kon_findEntry_(assets, name);
+ if (entry && entry->refs > 0) entry->refs--;
+}
+
+int kon_assetsCollect(kon_assets_t *assets) {
+ int kept = 0, freed = 0;
+
+ if (!assets) return 0;
+
+ for (int i = 0; i < assets->count; i++) {
+ if (assets->entries[i].refs > 0) {
+ assets->entries[kept++] = assets->entries[i];
+ } else {
+ kon_unloadEntry_(assets, &assets->entries[i]);
+ freed++;
+ }
+ }
+ assets->count = kept;
+ return freed;
+}
+
+int kon_assetsCount(const kon_assets_t *assets) {
+ return assets ? assets->count : 0;
+}
+
+int kon_assetsInfo(const kon_assets_t *assets, int index, kon_assetInfo_t *info) {
+ const kon_assetEntry_t *entry;
+
+ if (!assets || !info || index < 0 || index >= assets->count) return 0;
+ entry = &assets->entries[index];
+
+ info->name = entry->name;
+ info->typeName = assets->loaderUsed[entry->type] ? assets->loaders[entry->type].name : "?";
+ info->type = entry->type;
+ info->refs = entry->refs;
+ info->bytes = entry->bytes;
+ info->loose = entry->loose;
+ return 1;
+}
+
+size_t kon_assetsBytes(const kon_assets_t *assets) {
+ size_t total = 0;
+
+ if (!assets) return 0;
+ for (int i = 0; i < assets->count; i++) total += assets->entries[i].bytes;
+ return total;
+}
+
+void kon_assetsDump(const kon_assets_t *assets, FILE *out) {
+ kon_assetInfo_t info;
+
+ if (!assets || !out) return;
+
+ fprintf(out, "%d assets, %lu bytes\n", assets->count, (unsigned long)kon_assetsBytes(assets));
+ for (int i = 0; kon_assetsInfo(assets, i, &info); i++) {
+ fprintf(out, "%-32s %-8s refs %-3d %10lu bytes%s\n", info.name, info.typeName, info.refs, (unsigned long)info.bytes, info.loose ? " loose" : "");
+ }
+}
+
+const char *kon_assetsError(const kon_assets_t *assets) {
+ return assets ? assets->error : "no assets";
+}
diff --git a/kon/konassets.h b/kon/konassets.h
index 0971166..6dd65eb 100644
--- a/kon/konassets.h
+++ b/kon/konassets.h
@@ -110,489 +110,4 @@ void kon_assetsDump(const kon_assets_t *assets, FILE *out);
const char *kon_assetsError(const kon_assets_t *assets);
-/*** implementation ***/
-
-#ifdef KONASSETS_IMPLEMENTATION
-
-#include <stdlib.h>
-#include <string.h>
-#include <limits.h>
-
-#ifdef KON_MODULE_IMAGE
-#include "stb_image.h"
-#endif
-
-#ifdef KON_MODULE_AUDIO
-#include "miniaudio.h"
-#endif
-
-#define KON_ASSET_LOADERS_ 32
-
-typedef struct kon_assetEntry_ {
- char *name;
- void *object;
- int type;
- int refs;
- size_t bytes;
- int loose;
-} kon_assetEntry_t;
-
-struct kon_assets {
- kon_pack_t *pack;
- char *looseDir;
- kon_assetLoader_t loaders[KON_ASSET_LOADERS_];
- int loaderUsed[KON_ASSET_LOADERS_];
- int nextCustom;
- kon_assetEntry_t *entries;
- int count, capacity;
- const char *error;
-};
-
-/*** built in loaders ***/
-
-static void *kon_loadBlob_(const uint8_t *data, size_t size, size_t *bytes) {
- kon_blob_t *blob = malloc(sizeof(kon_blob_t));
- if (!blob) return NULL;
-
- blob->data = malloc(size + 1);
- if (!blob->data) {
- free(blob);
- return NULL;
- }
-
- memcpy(blob->data, data, size);
- blob->data[size] = 0;
- blob->size = size;
- *bytes = size;
- return blob;
-}
-
-static void kon_unloadBlob_(void *object) {
- kon_blob_t *blob = object;
- free(blob->data);
- free(blob);
-}
-
-#ifdef KON_MODULE_IMAGE
-
-static void *kon_loadImageAsset_(const uint8_t *data, size_t size, size_t *bytes) {
- int width, height, channels;
- unsigned char *pixels;
- kon_image *image;
-
- if (size > INT_MAX) return NULL;
-
- pixels = stbi_load_from_memory(data, (int)size, &width, &height, &channels, 4);
- if (!pixels) return NULL;
-
- image = kon_loadImage(pixels, width, height, konFormatRGBA8);
- stbi_image_free(pixels);
-
- if (image) *bytes = (size_t)width * (size_t)height * 4;
- return image;
-}
-
-static void kon_unloadImageAsset_(void *object) {
- kon_freeImage(object);
-}
-
-#endif
-
-#ifdef KON_MODULE_AUDIO
-
-#define KON_SOUND_MAX_BYTES_ ((uint64_t)1 << 30)
-
-static void *kon_loadSoundAsset_(const uint8_t *data, size_t size, size_t *bytes) {
- ma_decoder_config config = ma_decoder_config_init(ma_format_s16, 0, 0);
- ma_decoder decoder;
- kon_sound_t *sound;
- int16_t *frames = NULL;
- uint64_t total = 0, capacity = 0;
- uint32_t channels;
-
- if (ma_decoder_init_memory(data, size, &config, &decoder) != MA_SUCCESS) return NULL;
-
- channels = (uint32_t)decoder.outputChannels;
- if (channels == 0) {
- ma_decoder_uninit(&decoder);
- return NULL;
- }
-
- /* the length is not always known up front, so read until the decoder runs dry */
- for (;;) {
- ma_uint64 got = 0;
-
- if (total == capacity) {
- uint64_t grown = capacity ? capacity * 2 : 16384;
- int16_t *more;
-
- if (grown * channels * sizeof(int16_t) > KON_SOUND_MAX_BYTES_) break;
- more = realloc(frames, (size_t)(grown * channels * sizeof(int16_t)));
- if (!more) {
- free(frames);
- ma_decoder_uninit(&decoder);
- return NULL;
- }
- frames = more;
- capacity = grown;
- }
-
- if (ma_decoder_read_pcm_frames(&decoder, frames + total * channels, capacity - total, &got) != MA_SUCCESS && got == 0) break;
- if (got == 0) break;
- total += got;
- }
-
- sound = malloc(sizeof(kon_sound_t));
- if (!sound || total == 0) {
- free(sound);
- free(frames);
- ma_decoder_uninit(&decoder);
- return NULL;
- }
-
- sound->frames = frames;
- sound->frameCount = total;
- sound->channels = channels;
- sound->sampleRate = (uint32_t)decoder.outputSampleRate;
- *bytes = (size_t)(total * channels * sizeof(int16_t));
-
- ma_decoder_uninit(&decoder);
- return sound;
-}
-
-static void kon_unloadSoundAsset_(void *object) {
- kon_sound_t *sound = object;
- free(sound->frames);
- free(sound);
-}
-
-#endif
-
-/*** private helper ***/
-
-static int kon_extensionMatches_(const char *name, const char *extensions) {
- const char *dot = strrchr(name, '.');
- const char *slash = strrchr(name, '/');
- size_t length;
-
- if (!dot || (slash && dot < slash) || !extensions) return 0;
- length = strlen(dot);
-
- while (*extensions) {
- const char *end;
- size_t tokenLength;
-
- while (*extensions == ' ') extensions++;
- end = extensions;
- while (*end && *end != ' ') end++;
- tokenLength = (size_t)(end - extensions);
-
- if (tokenLength == length) {
- size_t i;
- for (i = 0; i < length; i++) {
- char c = dot[i];
- if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
- if (c != extensions[i]) break;
- }
- if (i == length) return 1;
- }
- extensions = end;
- }
- return 0;
-}
-
-static int kon_nameIsSafe_(const char *name) {
- const char *segment = name;
-
- if (!name || name[0] == '\0' || name[0] == '/') return 0;
-
- for (const char *c = name; ; c++) {
- if (*c == '\\' || *c == ':') return 0;
- if (*c == '/' || *c == '\0') {
- if (c - segment == 2 && segment[0] == '.' && segment[1] == '.') return 0;
- if (*c == '\0') break;
- segment = c + 1;
- }
- }
- return 1;
-}
-
-static int kon_typeForName_(const kon_assets_t *assets, const char *name) {
- for (int i = 1; i < KON_ASSET_LOADERS_; i++) {
- if (assets->loaderUsed[i] && kon_extensionMatches_(name, assets->loaders[i].extensions)) return i;
- }
- return KON_ASSET_BLOB;
-}
-
-static kon_assetEntry_t *kon_findEntry_(const kon_assets_t *assets, const char *name) {
- for (int i = 0; i < assets->count; i++) {
- if (strcmp(assets->entries[i].name, name) == 0) return &assets->entries[i];
- }
- return NULL;
-}
-
-static uint8_t *kon_readFile_(kon_assets_t *assets, const char *name, size_t *size, int *loose) {
- *loose = 0;
-
-#ifdef KON_DEVELOPER
- if (assets->looseDir) {
- size_t length = strlen(assets->looseDir) + strlen(name) + 2;
- char *path = malloc(length);
- FILE *f;
-
- if (!path) {
- assets->error = "out of memory";
- return NULL;
- }
- snprintf(path, length, "%s/%s", assets->looseDir, name);
- f = fopen(path, "rb");
- free(path);
-
- if (f) {
- long end;
- uint8_t *data = NULL;
-
- if (fseek(f, 0, SEEK_END) == 0 && (end = ftell(f)) >= 0 && fseek(f, 0, SEEK_SET) == 0) {
- data = malloc((size_t)end + 1);
- if (data && fread(data, 1, (size_t)end, f) == (size_t)end) {
- data[end] = 0;
- *size = (size_t)end;
- *loose = 1;
- fclose(f);
- return data;
- }
- free(data);
- }
- fclose(f);
- assets->error = "could not read the loose file";
- return NULL;
- }
- }
-#endif
-
- if (assets->pack) {
- int index = kon_packFind(assets->pack, name);
- if (index >= 0) {
- uint8_t *data = kon_packRead(assets->pack, index, size);
- if (!data) assets->error = kon_packError();
- return data;
- }
- }
-
- assets->error = "file not found";
- return NULL;
-}
-
-static void kon_unloadEntry_(kon_assets_t *assets, kon_assetEntry_t *entry) {
- if (assets->loaderUsed[entry->type] && assets->loaders[entry->type].unload) {
- assets->loaders[entry->type].unload(entry->object);
- }
- free(entry->name);
-}
-
-/*** assets ***/
-
-static void kon_addLoader_(kon_assets_t *assets, int type, const char *name, const char *extensions,
- void *(*load)(const uint8_t *, size_t, size_t *), void (*unload)(void *)) {
- assets->loaders[type].name = name;
- assets->loaders[type].extensions = extensions;
- assets->loaders[type].load = load;
- assets->loaders[type].unload = unload;
- assets->loaderUsed[type] = 1;
-}
-
-kon_assets_t *kon_assetsCreate(kon_pack_t *pack, const char *looseDir) {
- kon_assets_t *assets = calloc(1, sizeof(kon_assets_t));
- if (!assets) return NULL;
-
- assets->pack = pack;
- assets->error = "no error";
- assets->nextCustom = KON_ASSET_CUSTOM;
-
-#ifdef KON_DEVELOPER
- if (looseDir) {
- assets->looseDir = malloc(strlen(looseDir) + 1);
- if (!assets->looseDir) {
- free(assets);
- return NULL;
- }
- strcpy(assets->looseDir, looseDir);
- }
-#else
- (void)looseDir;
-#endif
-
- kon_addLoader_(assets, KON_ASSET_BLOB, "blob", NULL, kon_loadBlob_, kon_unloadBlob_);
-#ifdef KON_MODULE_IMAGE
- kon_addLoader_(assets, KON_ASSET_IMAGE, "image", ".png .jpg .jpeg .bmp .tga .gif", kon_loadImageAsset_, kon_unloadImageAsset_);
-#endif
-#ifdef KON_MODULE_AUDIO
- kon_addLoader_(assets, KON_ASSET_SOUND, "sound", ".wav .mp3 .flac", kon_loadSoundAsset_, kon_unloadSoundAsset_);
-#endif
-
- return assets;
-}
-
-void kon_assetsDestroy(kon_assets_t *assets) {
- if (!assets) return;
-
- for (int i = 0; i < assets->count; i++) kon_unloadEntry_(assets, &assets->entries[i]);
- free(assets->entries);
- free(assets->looseDir);
- free(assets);
-}
-
-int kon_assetsRegister(kon_assets_t *assets, const kon_assetLoader_t *loader) {
- int type;
-
- if (!assets || !loader || !loader->load || !loader->extensions || !loader->name) return -1;
- if (assets->nextCustom >= KON_ASSET_LOADERS_) return -1;
-
- type = assets->nextCustom++;
- kon_addLoader_(assets, type, loader->name, loader->extensions, loader->load, loader->unload);
- return type;
-}
-
-void *kon_assetGet(kon_assets_t *assets, const char *name) {
- return kon_assetGetAs(assets, name, -1);
-}
-
-void *kon_assetGetAs(kon_assets_t *assets, const char *name, int type) {
- kon_assetEntry_t *entry;
- uint8_t *data;
- size_t size = 0, bytes = 0;
- int loose, found;
- void *object;
-
- if (!assets) return NULL;
- if (!kon_nameIsSafe_(name)) {
- assets->error = "bad asset name";
- return NULL;
- }
-
- entry = kon_findEntry_(assets, name);
- if (entry) {
- if (type >= 0 && entry->type != type) {
- assets->error = "asset is a different type";
- return NULL;
- }
- entry->refs++;
- return entry->object;
- }
-
- found = kon_typeForName_(assets, name);
- if (type >= 0 && found != type) {
- assets->error = "asset is a different type";
- return NULL;
- }
-
- data = kon_readFile_(assets, name, &size, &loose);
- if (!data) return NULL;
-
- object = assets->loaders[found].load(data, size, &bytes);
- free(data);
- if (!object) {
- assets->error = "could not decode the file";
- return NULL;
- }
-
- if (assets->count == assets->capacity) {
- int capacity = assets->capacity ? assets->capacity * 2 : 32;
- kon_assetEntry_t *grown = realloc(assets->entries, (size_t)capacity * sizeof(kon_assetEntry_t));
- if (!grown) {
- assets->loaders[found].unload(object);
- assets->error = "out of memory";
- return NULL;
- }
- assets->entries = grown;
- assets->capacity = capacity;
- }
-
- entry = &assets->entries[assets->count];
- entry->name = malloc(strlen(name) + 1);
- if (!entry->name) {
- assets->loaders[found].unload(object);
- assets->error = "out of memory";
- return NULL;
- }
- strcpy(entry->name, name);
- entry->object = object;
- entry->type = found;
- entry->refs = 1;
- entry->bytes = bytes;
- entry->loose = loose;
- assets->count++;
-
- return object;
-}
-
-void kon_assetRelease(kon_assets_t *assets, const char *name) {
- kon_assetEntry_t *entry;
-
- if (!assets || !name) return;
- entry = kon_findEntry_(assets, name);
- if (entry && entry->refs > 0) entry->refs--;
-}
-
-int kon_assetsCollect(kon_assets_t *assets) {
- int kept = 0, freed = 0;
-
- if (!assets) return 0;
-
- for (int i = 0; i < assets->count; i++) {
- if (assets->entries[i].refs > 0) {
- assets->entries[kept++] = assets->entries[i];
- } else {
- kon_unloadEntry_(assets, &assets->entries[i]);
- freed++;
- }
- }
- assets->count = kept;
- return freed;
-}
-
-int kon_assetsCount(const kon_assets_t *assets) {
- return assets ? assets->count : 0;
-}
-
-int kon_assetsInfo(const kon_assets_t *assets, int index, kon_assetInfo_t *info) {
- const kon_assetEntry_t *entry;
-
- if (!assets || !info || index < 0 || index >= assets->count) return 0;
- entry = &assets->entries[index];
-
- info->name = entry->name;
- info->typeName = assets->loaderUsed[entry->type] ? assets->loaders[entry->type].name : "?";
- info->type = entry->type;
- info->refs = entry->refs;
- info->bytes = entry->bytes;
- info->loose = entry->loose;
- return 1;
-}
-
-size_t kon_assetsBytes(const kon_assets_t *assets) {
- size_t total = 0;
-
- if (!assets) return 0;
- for (int i = 0; i < assets->count; i++) total += assets->entries[i].bytes;
- return total;
-}
-
-void kon_assetsDump(const kon_assets_t *assets, FILE *out) {
- kon_assetInfo_t info;
-
- if (!assets || !out) return;
-
- fprintf(out, "%d assets, %lu bytes\n", assets->count, (unsigned long)kon_assetsBytes(assets));
- for (int i = 0; kon_assetsInfo(assets, i, &info); i++) {
- fprintf(out, "%-32s %-8s refs %-3d %10lu bytes%s\n", info.name, info.typeName, info.refs, (unsigned long)info.bytes, info.loose ? " loose" : "");
- }
-}
-
-const char *kon_assetsError(const kon_assets_t *assets) {
- return assets ? assets->error : "no assets";
-}
-
-#endif /* KONASSETS_IMPLEMENTATION */
-
#endif
diff --git a/kon/konaudio.c b/kon/konaudio.c
new file mode 100644
index 0000000..94fb811
--- /dev/null
+++ b/kon/konaudio.c
@@ -0,0 +1,277 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konaudio.c
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * 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.
+ */
+
+
+#include "konaudio.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "miniaudio.h"
+
+typedef struct kon_voiceSlot {
+ const kon_sound_t *sound;
+ double position;
+ double step;
+ float volume;
+ float pan;
+ int loop;
+ int active;
+ uint32_t generation;
+} kon_voiceSlot_t;
+
+struct kon_audio {
+ kon_voiceSlot_t voices[KON_AUDIO_VOICES];
+ float master;
+ uint32_t rate;
+ int hasDevice;
+ ma_mutex lock;
+ ma_device device;
+};
+
+static void kon_audioCallback_(ma_device *device, void *out, const void *in, ma_uint32 frames)
+{
+ kon_audio_t *audio = device->pUserData;
+
+ (void)in;
+ kon_audioMix(audio, out, frames);
+}
+
+static kon_audio_t *kon_audioNew_(uint32_t sampleRate)
+{
+ kon_audio_t *audio = calloc(1, sizeof(kon_audio_t));
+
+ if (!audio) return NULL;
+ if (ma_mutex_init(&audio->lock) != MA_SUCCESS) {
+ free(audio);
+ return NULL;
+ }
+ audio->master = 1.0f;
+ audio->rate = sampleRate ? sampleRate : 48000;
+ return audio;
+}
+
+kon_audio_t *kon_audioCreateOffline(uint32_t sampleRate)
+{
+ return kon_audioNew_(sampleRate);
+}
+
+kon_audio_t *kon_audioCreate(uint32_t sampleRate)
+{
+ kon_audio_t *audio = kon_audioNew_(sampleRate);
+ ma_device_config config;
+
+ if (!audio) return NULL;
+
+ config = ma_device_config_init(ma_device_type_playback);
+ config.playback.format = ma_format_f32;
+ config.playback.channels = 2;
+ config.sampleRate = audio->rate;
+ config.dataCallback = kon_audioCallback_;
+ config.pUserData = audio;
+
+ if (ma_device_init(NULL, &config, &audio->device) != MA_SUCCESS) {
+ ma_mutex_uninit(&audio->lock);
+ free(audio);
+ return NULL;
+ }
+ audio->rate = audio->device.sampleRate;
+ audio->hasDevice = 1;
+
+ if (ma_device_start(&audio->device) != MA_SUCCESS) {
+ kon_audioDestroy(audio);
+ return NULL;
+ }
+ return audio;
+}
+
+void kon_audioDestroy(kon_audio_t *audio)
+{
+ if (!audio) return;
+ if (audio->hasDevice) ma_device_uninit(&audio->device);
+ ma_mutex_uninit(&audio->lock);
+ free(audio);
+}
+
+uint32_t kon_audioRate(const kon_audio_t *audio)
+{
+ return audio->rate;
+}
+
+kon_voice_t kon_audioPlay(kon_audio_t *audio, const kon_sound_t *sound, float volume, float pan, int loop)
+{
+ kon_voice_t handle = 0;
+ int i;
+
+ if (!audio || !sound || !sound->frames || sound->frameCount == 0 || sound->channels == 0 || sound->sampleRate == 0) return 0;
+
+ if (pan < -1.0f) pan = -1.0f;
+ if (pan > 1.0f) pan = 1.0f;
+
+ ma_mutex_lock(&audio->lock);
+ for (i = 0; i < KON_AUDIO_VOICES; i++) {
+ kon_voiceSlot_t *v = &audio->voices[i];
+
+ if (v->active) continue;
+ v->generation++;
+ if (v->generation == 0) v->generation = 1;
+ v->sound = sound;
+ v->position = 0.0;
+ v->step = (double)sound->sampleRate / (double)audio->rate;
+ v->volume = volume;
+ v->pan = pan;
+ v->loop = loop;
+ v->active = 1;
+ handle = (v->generation << 8) | (uint32_t)(i + 1);
+ break;
+ }
+ ma_mutex_unlock(&audio->lock);
+ return handle;
+}
+
+static kon_voiceSlot_t *kon_audioFind_(kon_audio_t *audio, kon_voice_t handle)
+{
+ uint32_t index = handle & 0xFF;
+ kon_voiceSlot_t *v;
+
+ if (index == 0 || index > KON_AUDIO_VOICES) return NULL;
+ v = &audio->voices[index - 1];
+ if (!v->active || v->generation != (handle >> 8)) return NULL;
+ return v;
+}
+
+void kon_audioStop(kon_audio_t *audio, kon_voice_t voice)
+{
+ kon_voiceSlot_t *v;
+
+ ma_mutex_lock(&audio->lock);
+ v = kon_audioFind_(audio, voice);
+ if (v) v->active = 0;
+ ma_mutex_unlock(&audio->lock);
+}
+
+void kon_audioStopSound(kon_audio_t *audio, const kon_sound_t *sound)
+{
+ int i;
+
+ ma_mutex_lock(&audio->lock);
+ for (i = 0; i < KON_AUDIO_VOICES; i++) {
+ if (audio->voices[i].active && audio->voices[i].sound == sound) audio->voices[i].active = 0;
+ }
+ ma_mutex_unlock(&audio->lock);
+}
+
+void kon_audioStopAll(kon_audio_t *audio)
+{
+ int i;
+
+ ma_mutex_lock(&audio->lock);
+ for (i = 0; i < KON_AUDIO_VOICES; i++) audio->voices[i].active = 0;
+ ma_mutex_unlock(&audio->lock);
+}
+
+int kon_audioIsPlaying(kon_audio_t *audio, kon_voice_t voice)
+{
+ int playing;
+
+ ma_mutex_lock(&audio->lock);
+ playing = kon_audioFind_(audio, voice) != NULL;
+ ma_mutex_unlock(&audio->lock);
+ return playing;
+}
+
+int kon_audioVoiceCount(kon_audio_t *audio)
+{
+ int i, count = 0;
+
+ ma_mutex_lock(&audio->lock);
+ for (i = 0; i < KON_AUDIO_VOICES; i++) count += audio->voices[i].active;
+ ma_mutex_unlock(&audio->lock);
+ return count;
+}
+
+void kon_audioSetVolume(kon_audio_t *audio, float volume)
+{
+ if (volume < 0.0f) volume = 0.0f;
+ ma_mutex_lock(&audio->lock);
+ audio->master = volume;
+ ma_mutex_unlock(&audio->lock);
+}
+
+void kon_audioMix(kon_audio_t *audio, float *out, uint32_t frames)
+{
+ uint32_t f;
+ int i;
+
+ memset(out, 0, (size_t)frames * 2 * sizeof(float));
+
+ ma_mutex_lock(&audio->lock);
+ for (i = 0; i < KON_AUDIO_VOICES; i++) {
+ kon_voiceSlot_t *v = &audio->voices[i];
+ const kon_sound_t *s = v->sound;
+ float gainL, gainR;
+
+ if (!v->active) continue;
+
+ gainL = v->volume * (v->pan > 0.0f ? 1.0f - v->pan : 1.0f);
+ gainR = v->volume * (v->pan < 0.0f ? 1.0f + v->pan : 1.0f);
+
+ for (f = 0; f < frames; f++) {
+ uint64_t a = (uint64_t)v->position, b;
+ float t, l0, r0, l1, r1;
+ const int16_t *p;
+
+ if (a >= s->frameCount) {
+ if (!v->loop) {
+ v->active = 0;
+ break;
+ }
+ v->position -= (double)s->frameCount;
+ a = (uint64_t)v->position;
+ }
+ b = a + 1;
+ if (b >= s->frameCount) b = v->loop ? 0 : a;
+ t = (float)(v->position - (double)a);
+
+ p = s->frames + a * s->channels;
+ l0 = p[0] / 32768.0f;
+ r0 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f;
+ p = s->frames + b * s->channels;
+ l1 = p[0] / 32768.0f;
+ r1 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f;
+
+ out[f * 2] += (l0 + (l1 - l0) * t) * gainL;
+ out[f * 2 + 1] += (r0 + (r1 - r0) * t) * gainR;
+ v->position += v->step;
+ }
+ }
+
+ for (f = 0; f < frames * 2; f++) {
+ float x = out[f] * audio->master;
+
+ out[f] = x > 1.0f ? 1.0f : (x < -1.0f ? -1.0f : x);
+ }
+ ma_mutex_unlock(&audio->lock);
+}
diff --git a/kon/konaudio.h b/kon/konaudio.h
index fcb675e..f40e190 100644
--- a/kon/konaudio.h
+++ b/kon/konaudio.h
@@ -62,259 +62,3 @@ void kon_audioSetVolume(kon_audio_t *audio, float volume);
void kon_audioMix(kon_audio_t *audio, float *out, uint32_t frames);
#endif
-
-/*** implementation ***/
-
-#ifdef KONAUDIO_IMPLEMENTATION
-
-#include <stdlib.h>
-#include <string.h>
-
-#include "miniaudio.h"
-
-typedef struct kon_voiceSlot {
- const kon_sound_t *sound;
- double position;
- double step;
- float volume;
- float pan;
- int loop;
- int active;
- uint32_t generation;
-} kon_voiceSlot_t;
-
-struct kon_audio {
- kon_voiceSlot_t voices[KON_AUDIO_VOICES];
- float master;
- uint32_t rate;
- int hasDevice;
- ma_mutex lock;
- ma_device device;
-};
-
-static void kon_audioCallback_(ma_device *device, void *out, const void *in, ma_uint32 frames)
-{
- kon_audio_t *audio = device->pUserData;
-
- (void)in;
- kon_audioMix(audio, out, frames);
-}
-
-static kon_audio_t *kon_audioNew_(uint32_t sampleRate)
-{
- kon_audio_t *audio = calloc(1, sizeof(kon_audio_t));
-
- if (!audio) return NULL;
- if (ma_mutex_init(&audio->lock) != MA_SUCCESS) {
- free(audio);
- return NULL;
- }
- audio->master = 1.0f;
- audio->rate = sampleRate ? sampleRate : 48000;
- return audio;
-}
-
-kon_audio_t *kon_audioCreateOffline(uint32_t sampleRate)
-{
- return kon_audioNew_(sampleRate);
-}
-
-kon_audio_t *kon_audioCreate(uint32_t sampleRate)
-{
- kon_audio_t *audio = kon_audioNew_(sampleRate);
- ma_device_config config;
-
- if (!audio) return NULL;
-
- config = ma_device_config_init(ma_device_type_playback);
- config.playback.format = ma_format_f32;
- config.playback.channels = 2;
- config.sampleRate = audio->rate;
- config.dataCallback = kon_audioCallback_;
- config.pUserData = audio;
-
- if (ma_device_init(NULL, &config, &audio->device) != MA_SUCCESS) {
- ma_mutex_uninit(&audio->lock);
- free(audio);
- return NULL;
- }
- audio->rate = audio->device.sampleRate;
- audio->hasDevice = 1;
-
- if (ma_device_start(&audio->device) != MA_SUCCESS) {
- kon_audioDestroy(audio);
- return NULL;
- }
- return audio;
-}
-
-void kon_audioDestroy(kon_audio_t *audio)
-{
- if (!audio) return;
- if (audio->hasDevice) ma_device_uninit(&audio->device);
- ma_mutex_uninit(&audio->lock);
- free(audio);
-}
-
-uint32_t kon_audioRate(const kon_audio_t *audio)
-{
- return audio->rate;
-}
-
-kon_voice_t kon_audioPlay(kon_audio_t *audio, const kon_sound_t *sound, float volume, float pan, int loop)
-{
- kon_voice_t handle = 0;
- int i;
-
- if (!audio || !sound || !sound->frames || sound->frameCount == 0 || sound->channels == 0 || sound->sampleRate == 0) return 0;
-
- if (pan < -1.0f) pan = -1.0f;
- if (pan > 1.0f) pan = 1.0f;
-
- ma_mutex_lock(&audio->lock);
- for (i = 0; i < KON_AUDIO_VOICES; i++) {
- kon_voiceSlot_t *v = &audio->voices[i];
-
- if (v->active) continue;
- v->generation++;
- if (v->generation == 0) v->generation = 1;
- v->sound = sound;
- v->position = 0.0;
- v->step = (double)sound->sampleRate / (double)audio->rate;
- v->volume = volume;
- v->pan = pan;
- v->loop = loop;
- v->active = 1;
- handle = (v->generation << 8) | (uint32_t)(i + 1);
- break;
- }
- ma_mutex_unlock(&audio->lock);
- return handle;
-}
-
-static kon_voiceSlot_t *kon_audioFind_(kon_audio_t *audio, kon_voice_t handle)
-{
- uint32_t index = handle & 0xFF;
- kon_voiceSlot_t *v;
-
- if (index == 0 || index > KON_AUDIO_VOICES) return NULL;
- v = &audio->voices[index - 1];
- if (!v->active || v->generation != (handle >> 8)) return NULL;
- return v;
-}
-
-void kon_audioStop(kon_audio_t *audio, kon_voice_t voice)
-{
- kon_voiceSlot_t *v;
-
- ma_mutex_lock(&audio->lock);
- v = kon_audioFind_(audio, voice);
- if (v) v->active = 0;
- ma_mutex_unlock(&audio->lock);
-}
-
-void kon_audioStopSound(kon_audio_t *audio, const kon_sound_t *sound)
-{
- int i;
-
- ma_mutex_lock(&audio->lock);
- for (i = 0; i < KON_AUDIO_VOICES; i++) {
- if (audio->voices[i].active && audio->voices[i].sound == sound) audio->voices[i].active = 0;
- }
- ma_mutex_unlock(&audio->lock);
-}
-
-void kon_audioStopAll(kon_audio_t *audio)
-{
- int i;
-
- ma_mutex_lock(&audio->lock);
- for (i = 0; i < KON_AUDIO_VOICES; i++) audio->voices[i].active = 0;
- ma_mutex_unlock(&audio->lock);
-}
-
-int kon_audioIsPlaying(kon_audio_t *audio, kon_voice_t voice)
-{
- int playing;
-
- ma_mutex_lock(&audio->lock);
- playing = kon_audioFind_(audio, voice) != NULL;
- ma_mutex_unlock(&audio->lock);
- return playing;
-}
-
-int kon_audioVoiceCount(kon_audio_t *audio)
-{
- int i, count = 0;
-
- ma_mutex_lock(&audio->lock);
- for (i = 0; i < KON_AUDIO_VOICES; i++) count += audio->voices[i].active;
- ma_mutex_unlock(&audio->lock);
- return count;
-}
-
-void kon_audioSetVolume(kon_audio_t *audio, float volume)
-{
- if (volume < 0.0f) volume = 0.0f;
- ma_mutex_lock(&audio->lock);
- audio->master = volume;
- ma_mutex_unlock(&audio->lock);
-}
-
-void kon_audioMix(kon_audio_t *audio, float *out, uint32_t frames)
-{
- uint32_t f;
- int i;
-
- memset(out, 0, (size_t)frames * 2 * sizeof(float));
-
- ma_mutex_lock(&audio->lock);
- for (i = 0; i < KON_AUDIO_VOICES; i++) {
- kon_voiceSlot_t *v = &audio->voices[i];
- const kon_sound_t *s = v->sound;
- float gainL, gainR;
-
- if (!v->active) continue;
-
- gainL = v->volume * (v->pan > 0.0f ? 1.0f - v->pan : 1.0f);
- gainR = v->volume * (v->pan < 0.0f ? 1.0f + v->pan : 1.0f);
-
- for (f = 0; f < frames; f++) {
- uint64_t a = (uint64_t)v->position, b;
- float t, l0, r0, l1, r1;
- const int16_t *p;
-
- if (a >= s->frameCount) {
- if (!v->loop) {
- v->active = 0;
- break;
- }
- v->position -= (double)s->frameCount;
- a = (uint64_t)v->position;
- }
- b = a + 1;
- if (b >= s->frameCount) b = v->loop ? 0 : a;
- t = (float)(v->position - (double)a);
-
- p = s->frames + a * s->channels;
- l0 = p[0] / 32768.0f;
- r0 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f;
- p = s->frames + b * s->channels;
- l1 = p[0] / 32768.0f;
- r1 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f;
-
- out[f * 2] += (l0 + (l1 - l0) * t) * gainL;
- out[f * 2 + 1] += (r0 + (r1 - r0) * t) * gainR;
- v->position += v->step;
- }
- }
-
- for (f = 0; f < frames * 2; f++) {
- float x = out[f] * audio->master;
-
- out[f] = x > 1.0f ? 1.0f : (x < -1.0f ? -1.0f : x);
- }
- ma_mutex_unlock(&audio->lock);
-}
-
-#endif
diff --git a/kon/kondebug.c b/kon/kondebug.c
new file mode 100644
index 0000000..a905adc
--- /dev/null
+++ b/kon/kondebug.c
@@ -0,0 +1,739 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * kondebug.c
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * 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.
+ */
+
+
+#include "kondebug.h"
+
+#ifdef KON_DEVELOPER
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "kontime.h"
+
+#define KON_DEBUG_LOGLINES 256
+#define KON_DEBUG_LINELEN 160
+#define KON_DEBUG_INPUT 256
+#define KON_DEBUG_HISTORY 16
+#define KON_DEBUG_COMMANDS 64
+#define KON_DEBUG_VARS 64
+#define KON_DEBUG_ARGS 16
+#define KON_DEBUG_GRAPH 120
+#define KON_DEBUG_TYPES 8
+
+typedef struct kon_debugCmd {
+ const char *name;
+ const char *help;
+ kon_debugCommandFn fn;
+ void *user;
+} kon_debugCmd_t;
+
+typedef struct kon_debugVarEntry {
+ const char *name;
+ kon_debugVarType_t type;
+ void *value;
+ int watched;
+} kon_debugVarEntry_t;
+
+struct kon_debug {
+ kon_font_t *font;
+ kon_assets_t *assets;
+ int lineH, ascent;
+ float baseSize, size;
+ int ctrl;
+
+ int overlayKey, consoleKey;
+ int overlay;
+ int open;
+ float slide;
+ int swallowTick;
+ int quit;
+
+ char log[KON_DEBUG_LOGLINES][KON_DEBUG_LINELEN];
+ int logCount, logHead, scroll;
+
+ char input[KON_DEBUG_INPUT];
+ int length, cursor;
+ char history[KON_DEBUG_HISTORY][KON_DEBUG_INPUT];
+ int historyCount, historyPos;
+
+ kon_debugCmd_t commands[KON_DEBUG_COMMANDS];
+ int commandCount;
+ kon_debugVarEntry_t vars[KON_DEBUG_VARS];
+ int varCount;
+
+ kon_time last;
+ double frameDt;
+ double accumTime;
+ int accumFrames;
+ float fps;
+ float graph[KON_DEBUG_GRAPH];
+ int graphHead;
+};
+
+/*** private helpers ***/
+
+static void kon_debugMetrics_(kon_debug_t *d)
+{
+ int h = 0;
+
+ kon_measureText(d->font, "A", NULL, &h);
+ d->ascent = h * 4 / 5;
+ d->lineH = h + 2;
+}
+
+static void kon_debugResize_(kon_debug_t *d, float size)
+{
+ if (size < 8.0f) size = 8.0f;
+ if (size > 64.0f) size = 64.0f;
+ if (size == d->size) return;
+ if (kon_fontResize(d->font, size)) {
+ d->size = size;
+ kon_debugMetrics_(d);
+ }
+}
+
+static void kon_debugText_(const kon_debug_t *d, kon_framebuffer_t *fb, int x, int y, const char *s, uint32_t color)
+{
+ kon_drawText(fb, d->font, x, y + d->ascent, s, color);
+}
+
+static void kon_debugPushLine_(kon_debug_t *d, const char *line)
+{
+ int slot = (d->logHead + d->logCount) % KON_DEBUG_LOGLINES;
+
+ if (d->logCount == KON_DEBUG_LOGLINES) {
+ slot = d->logHead;
+ d->logHead = (d->logHead + 1) % KON_DEBUG_LOGLINES;
+ } else {
+ d->logCount++;
+ }
+ snprintf(d->log[slot], KON_DEBUG_LINELEN, "%s", line);
+ if (d->scroll > 0 && d->scroll < d->logCount) d->scroll++;
+}
+
+static const char *kon_debugVarText_(const kon_debugVarEntry_t *v, char *buffer, size_t size)
+{
+ switch (v->type) {
+ case KON_VAR_BOOL: snprintf(buffer, size, "%s", *(int *)v->value ? "true" : "false"); break;
+ case KON_VAR_INT: snprintf(buffer, size, "%d", *(int *)v->value); break;
+ default: snprintf(buffer, size, "%g", (double)*(float *)v->value); break;
+ }
+ return buffer;
+}
+
+static int kon_debugTokenize_(char *line, char **argv, int max)
+{
+ int argc = 0;
+
+ while (*line && argc < max) {
+ while (*line == ' ' || *line == '\t') line++;
+ if (!*line) break;
+ if (*line == '"') {
+ argv[argc++] = ++line;
+ while (*line && *line != '"') line++;
+ } else {
+ argv[argc++] = line;
+ while (*line && *line != ' ' && *line != '\t') line++;
+ }
+ if (*line) *line++ = 0;
+ }
+ return argc;
+}
+
+static void kon_debugSetVar_(kon_debug_t *d, kon_debugVarEntry_t *v, const char *text)
+{
+ char out[64];
+
+ switch (v->type) {
+ case KON_VAR_BOOL:
+ if (!strcmp(text, "1") || !strcmp(text, "true") || !strcmp(text, "on")) *(int *)v->value = 1;
+ else if (!strcmp(text, "0") || !strcmp(text, "false") || !strcmp(text, "off")) *(int *)v->value = 0;
+ else {
+ kon_debugLog(d, "%s expects 0 or 1", v->name);
+ return;
+ }
+ break;
+ case KON_VAR_INT: *(int *)v->value = atoi(text); break;
+ default: *(float *)v->value = (float)atof(text); break;
+ }
+ kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out)));
+}
+
+static void kon_debugExecute_(kon_debug_t *d, const char *line)
+{
+ char copy[KON_DEBUG_INPUT], *argv[KON_DEBUG_ARGS];
+ int argc, i;
+
+ snprintf(copy, sizeof(copy), "%s", line);
+ argc = kon_debugTokenize_(copy, argv, KON_DEBUG_ARGS);
+ if (argc == 0) return;
+
+ for (i = 0; i < d->commandCount; i++) {
+ if (!strcmp(argv[0], d->commands[i].name)) {
+ d->commands[i].fn(d, d->commands[i].user, argc, argv);
+ return;
+ }
+ }
+ for (i = 0; i < d->varCount; i++) {
+ kon_debugVarEntry_t *v = &d->vars[i];
+ char out[64];
+
+ if (strcmp(argv[0], v->name)) continue;
+ if (argc > 1) kon_debugSetVar_(d, v, argv[1]);
+ else kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out)));
+ return;
+ }
+ kon_debugLog(d, "unknown command '%s', try help", argv[0]);
+}
+
+static void kon_debugComplete_(kon_debug_t *d)
+{
+ const char *matches[KON_DEBUG_COMMANDS + KON_DEBUG_VARS];
+ int count = 0, i, common;
+ size_t prefix = (size_t)d->cursor;
+
+ for (i = 0; i < d->cursor; i++) if (d->input[i] == ' ') return;
+ if (prefix == 0) return;
+
+ for (i = 0; i < d->commandCount; i++) if (!strncmp(d->commands[i].name, d->input, prefix)) matches[count++] = d->commands[i].name;
+ for (i = 0; i < d->varCount; i++) if (!strncmp(d->vars[i].name, d->input, prefix)) matches[count++] = d->vars[i].name;
+ if (count == 0) return;
+
+ common = (int)strlen(matches[0]);
+ for (i = 1; i < count; i++) {
+ int n = 0;
+
+ while (n < common && matches[i][n] == matches[0][n]) n++;
+ common = n;
+ }
+
+ if (count > 1) {
+ char line[KON_DEBUG_LINELEN] = "";
+
+ for (i = 0; i < count; i++) {
+ if (strlen(line) + strlen(matches[i]) + 2 >= sizeof(line)) break;
+ strcat(line, matches[i]);
+ strcat(line, " ");
+ }
+ kon_debugLog(d, "%s", line);
+ }
+
+ {
+ char rest[KON_DEBUG_INPUT];
+ int add = common - d->cursor + (count == 1 ? 1 : 0);
+
+ snprintf(rest, sizeof(rest), "%s", d->input + d->cursor);
+ if (d->length + add >= KON_DEBUG_INPUT) return;
+ memcpy(d->input, matches[0], (size_t)common);
+ if (count == 1) d->input[common] = ' ';
+ memcpy(d->input + common + (count == 1 ? 1 : 0), rest, strlen(rest) + 1);
+ d->length = (int)strlen(d->input);
+ d->cursor = common + (count == 1 ? 1 : 0);
+ }
+}
+
+static void kon_debugInsert_(kon_debug_t *d, char c)
+{
+ if (d->length + 1 >= KON_DEBUG_INPUT) return;
+ memmove(d->input + d->cursor + 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1);
+ d->input[d->cursor++] = c;
+ d->length++;
+}
+
+static void kon_debugSetInput_(kon_debug_t *d, const char *text)
+{
+ snprintf(d->input, sizeof(d->input), "%s", text);
+ d->length = (int)strlen(d->input);
+ d->cursor = d->length;
+}
+
+static void kon_debugSubmit_(kon_debug_t *d)
+{
+ char line[KON_DEBUG_INPUT + 4];
+
+ if (d->length == 0) return;
+ snprintf(line, sizeof(line), "> %s", d->input);
+ kon_debugPushLine_(d, line);
+
+ if (d->historyCount == 0 || strcmp(d->history[d->historyCount - 1], d->input)) {
+ if (d->historyCount == KON_DEBUG_HISTORY) {
+ memmove(d->history[0], d->history[1], sizeof(d->history[0]) * (KON_DEBUG_HISTORY - 1));
+ d->historyCount--;
+ }
+ snprintf(d->history[d->historyCount++], KON_DEBUG_INPUT, "%s", d->input);
+ }
+ d->historyPos = d->historyCount;
+
+ {
+ char copy[KON_DEBUG_INPUT];
+
+ snprintf(copy, sizeof(copy), "%s", d->input);
+ d->input[0] = 0;
+ d->length = d->cursor = 0;
+ d->scroll = 0;
+ kon_debugExecute_(d, copy);
+ }
+}
+
+/*** builtin commands ***/
+
+static void kon_debugCmdHelp_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ int i;
+
+ (void)user;
+ if (argc > 1) {
+ for (i = 0; i < d->commandCount; i++) {
+ if (!strcmp(argv[1], d->commands[i].name)) {
+ kon_debugLog(d, "%s: %s", d->commands[i].name, d->commands[i].help);
+ return;
+ }
+ }
+ kon_debugLog(d, "no help for '%s'", argv[1]);
+ return;
+ }
+ kon_debugLog(d, "commands:");
+ for (i = 0; i < d->commandCount; i++) kon_debugLog(d, " %-10s %s", d->commands[i].name, d->commands[i].help);
+ kon_debugLog(d, "variables (type the name to read, name value to set):");
+ for (i = 0; i < d->varCount; i++) kon_debugLog(d, " %s", d->vars[i].name);
+}
+
+static void kon_debugCmdClear_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ (void)user; (void)argc; (void)argv;
+ d->logCount = d->logHead = d->scroll = 0;
+}
+
+static void kon_debugCmdEcho_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ char line[KON_DEBUG_LINELEN] = "";
+ int i;
+
+ (void)user;
+ for (i = 1; i < argc; i++) {
+ if (strlen(line) + strlen(argv[i]) + 2 >= sizeof(line)) break;
+ strcat(line, argv[i]);
+ strcat(line, " ");
+ }
+ kon_debugLog(d, "%s", line);
+}
+
+static void kon_debugCmdFps_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ (void)user;
+ d->overlay = argc > 1 ? atoi(argv[1]) & 3 : (d->overlay + 1) & 3;
+}
+
+static void kon_debugCmdWatch_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ int i;
+
+ (void)user;
+ if (argc < 2) {
+ kon_debugLog(d, "usage: watch <variable>");
+ return;
+ }
+ for (i = 0; i < d->varCount; i++) {
+ if (!strcmp(argv[1], d->vars[i].name)) {
+ d->vars[i].watched = !d->vars[i].watched;
+ if (d->overlay == 0) d->overlay = 1;
+ return;
+ }
+ }
+ kon_debugLog(d, "no variable '%s'", argv[1]);
+}
+
+static void kon_debugCmdQuit_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ (void)user; (void)argc; (void)argv;
+ d->quit = 1;
+}
+
+static void kon_debugCmdFontSize_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ (void)user;
+ if (argc > 1) kon_debugResize_(d, (float)atof(argv[1]));
+ kon_debugLog(d, "fontsize = %g", (double)d->size);
+}
+
+static void kon_debugCmdAssets_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ int i, count;
+ kon_assetInfo_t info;
+
+ (void)user; (void)argc; (void)argv;
+ if (!d->assets) {
+ kon_debugLog(d, "no asset manager attached");
+ return;
+ }
+ count = kon_assetsCount(d->assets);
+ for (i = 0; i < count; i++) {
+ if (!kon_assetsInfo(d->assets, i, &info)) continue;
+ kon_debugLog(d, "%-7s %3d ref %8lu b %s%s", info.typeName, info.refs, (unsigned long)info.bytes, info.name, info.loose ? " (loose)" : "");
+ }
+ kon_debugLog(d, "%d assets, %lu bytes", count, (unsigned long)kon_assetsBytes(d->assets));
+}
+
+/*** debug ***/
+
+kon_debug_t *kon_debugCreate(kon_font_t *font)
+{
+ kon_debug_t *d;
+
+ if (!font) return NULL;
+ d = calloc(1, sizeof(kon_debug_t));
+ if (!d) return NULL;
+
+ d->font = font;
+ d->baseSize = d->size = kon_fontPixelHeight(font);
+ kon_debugMetrics_(d);
+ d->overlayKey = KON_KEY_F1;
+ d->consoleKey = KON_KEY_GRAVE;
+ d->last = kon_getTime();
+
+ kon_debugCommand(d, "help", "list commands, or help <command>", kon_debugCmdHelp_, NULL);
+ kon_debugCommand(d, "clear", "clear the console", kon_debugCmdClear_, NULL);
+ kon_debugCommand(d, "echo", "print text", kon_debugCmdEcho_, NULL);
+ kon_debugCommand(d, "fps", "overlay mode 0-3, or cycle", kon_debugCmdFps_, NULL);
+ kon_debugCommand(d, "watch", "show a variable in the overlay", kon_debugCmdWatch_, NULL);
+ kon_debugCommand(d, "assets", "list loaded assets", kon_debugCmdAssets_, NULL);
+ kon_debugCommand(d, "fontsize", "text size in pixels, ctrl + and ctrl - too", kon_debugCmdFontSize_, NULL);
+ kon_debugCommand(d, "quit", "close the game", kon_debugCmdQuit_, NULL);
+ kon_debugCommand(d, "exit", "close the game", kon_debugCmdQuit_, NULL);
+ kon_debugLog(d, "type help for commands, tab completes");
+ return d;
+}
+
+void kon_debugDestroy(kon_debug_t *d)
+{
+ free(d);
+}
+
+void kon_debugSetKeys(kon_debug_t *d, int overlayKey, int consoleKey)
+{
+ if (!d) return;
+ d->overlayKey = overlayKey;
+ d->consoleKey = consoleKey;
+}
+
+void kon_debugSetAssets(kon_debug_t *d, kon_assets_t *assets)
+{
+ if (!d) return;
+ d->assets = assets;
+}
+
+int kon_debugCommand(kon_debug_t *d, const char *name, const char *help, kon_debugCommandFn fn, void *user)
+{
+ if (!d) return 0;
+ kon_debugCmd_t *c;
+
+ if (d->commandCount == KON_DEBUG_COMMANDS) return 0;
+ c = &d->commands[d->commandCount++];
+ c->name = name;
+ c->help = help;
+ c->fn = fn;
+ c->user = user;
+ return 1;
+}
+
+int kon_debugVar(kon_debug_t *d, const char *name, kon_debugVarType_t type, void *value)
+{
+ if (!d) return 0;
+ kon_debugVarEntry_t *v;
+
+ if (d->varCount == KON_DEBUG_VARS) return 0;
+ v = &d->vars[d->varCount++];
+ v->name = name;
+ v->type = type;
+ v->value = value;
+ v->watched = 0;
+ return 1;
+}
+
+void kon_debugLog(kon_debug_t *d, const char *format, ...)
+{
+ if (!d) return;
+ char line[KON_DEBUG_LINELEN];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(line, sizeof(line), format, args);
+ va_end(args);
+ kon_debugPushLine_(d, line);
+}
+
+int kon_debugConsoleOpen(const kon_debug_t *d)
+{
+ if (!d) return 0;
+ return d->open;
+}
+
+int kon_debugQuitRequested(const kon_debug_t *d)
+{
+ return d ? d->quit : 0;
+}
+
+float kon_debugFps(const kon_debug_t *d)
+{
+ if (!d) return 0.0f;
+ return d->fps;
+}
+
+int kon_debugFeedEvent(kon_debug_t *d, const kon_event_t *e)
+{
+ if (!d) return 0;
+
+ if (e->key == KON_KEY_LEFT_CTRL || e->key == KON_KEY_RIGHT_CTRL) {
+ if (e->type == KON_EVENT_KEY_DOWN) d->ctrl = 1;
+ if (e->type == KON_EVENT_KEY_UP) d->ctrl = 0;
+ }
+
+ if (e->type == KON_EVENT_KEY_DOWN) {
+ if (d->ctrl && (d->open || d->overlay)) {
+ if (e->key == KON_KEY_EQUAL) { kon_debugResize_(d, d->size + 2.0f); return 1; }
+ if (e->key == KON_KEY_MINUS) { kon_debugResize_(d, d->size - 2.0f); return 1; }
+ if (e->key == KON_KEY_0) { kon_debugResize_(d, d->baseSize); return 1; }
+ }
+ if (e->key == d->consoleKey) {
+ d->open = !d->open;
+ d->swallowTick = 1;
+ return 1;
+ }
+ d->swallowTick = 0;
+ if (e->key == d->overlayKey) {
+ d->overlay = (d->overlay + 1) & 3;
+ return 1;
+ }
+ }
+ if (!d->open) return 0;
+
+ if (e->type == KON_EVENT_TEXT) {
+ if (d->ctrl) return 1;
+ if (d->swallowTick && e->codepoint == '`') {
+ d->swallowTick = 0;
+ return 1;
+ }
+ if (e->codepoint >= 32 && e->codepoint <= 126) kon_debugInsert_(d, (char)e->codepoint);
+ return 1;
+ }
+ if (e->type == KON_EVENT_KEY_UP) return 1;
+ if (e->type != KON_EVENT_KEY_DOWN) return 0;
+
+ switch (e->key) {
+ case KON_KEY_ENTER: kon_debugSubmit_(d); break;
+ case KON_KEY_ESCAPE: d->open = 0; break;
+ case KON_KEY_TAB: kon_debugComplete_(d); break;
+ case KON_KEY_LEFT: if (d->cursor > 0) d->cursor--; break;
+ case KON_KEY_RIGHT: if (d->cursor < d->length) d->cursor++; break;
+ case KON_KEY_BACKSPACE:
+ if (d->cursor > 0) {
+ memmove(d->input + d->cursor - 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1);
+ d->cursor--;
+ d->length--;
+ }
+ break;
+ case KON_KEY_DELETE:
+ if (d->cursor < d->length) {
+ memmove(d->input + d->cursor, d->input + d->cursor + 1, (size_t)(d->length - d->cursor));
+ d->length--;
+ }
+ break;
+ case KON_KEY_UP:
+ if (d->historyPos > 0) kon_debugSetInput_(d, d->history[--d->historyPos]);
+ break;
+ case KON_KEY_DOWN:
+ if (d->historyPos < d->historyCount - 1) kon_debugSetInput_(d, d->history[++d->historyPos]);
+ else {
+ d->historyPos = d->historyCount;
+ kon_debugSetInput_(d, "");
+ }
+ break;
+ case KON_KEY_PAGE_UP: d->scroll += 8; break;
+ case KON_KEY_PAGE_DOWN: d->scroll -= 8; break;
+ default: break;
+ }
+ if (d->scroll < 0) d->scroll = 0;
+ if (d->scroll > d->logCount - 1) d->scroll = d->logCount > 0 ? d->logCount - 1 : 0;
+ return 1;
+}
+
+void kon_debugFrame(kon_debug_t *d)
+{
+ if (!d) return;
+ kon_time now = kon_getTime();
+ double dt = now - d->last;
+
+ d->last = now;
+ d->frameDt = dt;
+ d->accumTime += dt;
+ d->accumFrames++;
+ if (d->accumTime >= 0.5) {
+ d->fps = (float)((double)d->accumFrames / d->accumTime);
+ d->accumTime = 0.0;
+ d->accumFrames = 0;
+ }
+ d->graph[d->graphHead] = (float)(dt * 1000.0);
+ d->graphHead = (d->graphHead + 1) % KON_DEBUG_GRAPH;
+}
+
+static void kon_debugBox_(const kon_debug_t *d, kon_framebuffer_t *fb, int x, int y, const char *s, uint32_t color)
+{
+ int w = 0;
+
+ kon_measureText(d->font, s, &w, NULL);
+ kon_fillRectangle(fb, x - 2, y - 1, w + 4, d->lineH, 0xB0000000);
+ kon_debugText_(d, fb, x, y, s, color);
+}
+
+static void kon_debugDrawOverlay_(kon_debug_t *d, kon_framebuffer_t *fb)
+{
+ char line[128];
+ int x = 4, y = 4, i, rows;
+ uint32_t white = 0xFFFFFFFF, dim = 0xFFB0B0C0;
+ float worst = 0.0f;
+
+ snprintf(line, sizeof(line), "%.0f fps %.2f ms", (double)d->fps, d->frameDt * 1000.0);
+ kon_debugBox_(d, fb, x, y, line, white);
+ y += d->lineH + 3;
+
+ if (d->overlay >= 1) {
+ kon_fillRectangle(fb, x - 2, y - 2, KON_DEBUG_GRAPH + 4, 34, 0xB0000000);
+ for (i = 0; i < KON_DEBUG_GRAPH; i++) {
+ float ms = d->graph[(d->graphHead + i) % KON_DEBUG_GRAPH];
+ int h = (int)(ms * 30.0f / 33.3f);
+ uint32_t color = ms < 17.5f ? 0xFF50D060 : (ms < 34.0f ? 0xFFE0C040 : 0xFFE05050);
+
+ if (ms > worst) worst = ms;
+ if (h > 30) h = 30;
+ if (h < 1) h = 1;
+ kon_fillRectangle(fb, x + i, y + 30 - h, 1, h, color);
+ }
+ y += 36;
+ }
+
+ for (i = 0; i < d->varCount; i++) {
+ char value[64];
+
+ if (!d->vars[i].watched) continue;
+ snprintf(line, sizeof(line), "%s = %s", d->vars[i].name, kon_debugVarText_(&d->vars[i], value, sizeof(value)));
+ kon_debugBox_(d, fb, x, y, line, dim);
+ y += d->lineH;
+ }
+
+ if (d->overlay >= 2 && d->assets) {
+ const char *names[KON_DEBUG_TYPES];
+ int counts[KON_DEBUG_TYPES], types = 0, total = kon_assetsCount(d->assets);
+ size_t bytes[KON_DEBUG_TYPES];
+ kon_assetInfo_t info;
+
+ for (i = 0; i < total; i++) {
+ int t;
+
+ if (!kon_assetsInfo(d->assets, i, &info)) continue;
+ for (t = 0; t < types; t++) if (names[t] == info.typeName) break;
+ if (t == types) {
+ if (types == KON_DEBUG_TYPES) continue;
+ names[types] = info.typeName;
+ counts[types] = 0;
+ bytes[types] = 0;
+ types++;
+ }
+ counts[t]++;
+ bytes[t] += info.bytes;
+ }
+
+ y += 2;
+ snprintf(line, sizeof(line), "assets %d, %.1f KB", total, (double)kon_assetsBytes(d->assets) / 1024.0);
+ kon_debugBox_(d, fb, x, y, line, white);
+ y += d->lineH;
+ for (i = 0; i < types; i++) {
+ snprintf(line, sizeof(line), " %-7s %3d %8.1f KB", names[i], counts[i], (double)bytes[i] / 1024.0);
+ kon_debugBox_(d, fb, x, y, line, dim);
+ y += d->lineH;
+ }
+
+ if (d->overlay >= 3) {
+ rows = (fb->height - y) / d->lineH - 1;
+ for (i = 0; i < total && i < rows; i++) {
+ if (!kon_assetsInfo(d->assets, i, &info)) continue;
+ snprintf(line, sizeof(line), "%-6s x%d %7.1fK %s%s", info.typeName, info.refs, (double)info.bytes / 1024.0, info.name, info.loose ? " *" : "");
+ kon_debugBox_(d, fb, x, y, line, dim);
+ y += d->lineH;
+ }
+ }
+ } else if (d->overlay >= 2) {
+ kon_debugBox_(d, fb, x, y + 2, "no asset manager attached", dim);
+ }
+}
+
+static void kon_debugDrawConsole_(kon_debug_t *d, kon_framebuffer_t *fb)
+{
+ int full = fb->height / 2, height = (int)((float)full * d->slide);
+ int inputY, rows, i, line, cx, w = 0;
+ char shown[KON_DEBUG_INPUT + 4];
+
+ if (height < d->lineH + 6) return;
+
+ kon_fillRectangle(fb, 0, 0, fb->width, height, 0xE0101018);
+ kon_fillRectangle(fb, 0, height - 1, fb->width, 1, 0xFF7088E0);
+
+ inputY = height - d->lineH - 4;
+ kon_fillRectangle(fb, 0, inputY - 2, fb->width, d->lineH + 4, 0xFF181824);
+
+ rows = (inputY - 4) / d->lineH;
+ for (i = 0; i < rows; i++) {
+ int index = d->logCount - 1 - d->scroll - i;
+
+ if (index < 0) break;
+ line = (d->logHead + index) % KON_DEBUG_LOGLINES;
+ kon_debugText_(d, fb, 6, inputY - 4 - (i + 1) * d->lineH, d->log[line], d->log[line][0] == '>' ? 0xFF9098C0 : 0xFFE0E0E8);
+ }
+
+ snprintf(shown, sizeof(shown), "> %s", d->input);
+ kon_debugText_(d, fb, 6, inputY, shown, 0xFFFFFFFF);
+
+ snprintf(shown, sizeof(shown), "> %.*s", d->cursor, d->input);
+ kon_measureText(d->font, shown, &w, NULL);
+ cx = 6 + w;
+ kon_fillRectangle(fb, cx, inputY, 2, d->lineH - 2, 0xFFFFFFFF);
+}
+
+void kon_debugDraw(kon_debug_t *d, kon_framebuffer_t *fb)
+{
+ if (!d) return;
+ float target = d->open ? 1.0f : 0.0f;
+ float step = (float)d->frameDt * 8.0f;
+
+ if (d->slide < target) d->slide = d->slide + step > target ? target : d->slide + step;
+ else if (d->slide > target) d->slide = d->slide - step < target ? target : d->slide - step;
+
+ if (d->overlay) kon_debugDrawOverlay_(d, fb);
+ if (d->slide > 0.0f) kon_debugDrawConsole_(d, fb);
+}
+
+#endif
diff --git a/kon/kondebug.h b/kon/kondebug.h
index 6ac51c8..ecee066 100644
--- a/kon/kondebug.h
+++ b/kon/kondebug.h
@@ -93,717 +93,3 @@ static inline int kon_debugQuitRequested(const kon_debug_t *debug) { (void)debug
#endif
#endif
-
-/*** implementation ***/
-
-#if defined(KONDEBUG_IMPLEMENTATION) && defined(KON_DEVELOPER)
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdarg.h>
-
-#include "kontime.h"
-
-#define KON_DEBUG_LOGLINES 256
-#define KON_DEBUG_LINELEN 160
-#define KON_DEBUG_INPUT 256
-#define KON_DEBUG_HISTORY 16
-#define KON_DEBUG_COMMANDS 64
-#define KON_DEBUG_VARS 64
-#define KON_DEBUG_ARGS 16
-#define KON_DEBUG_GRAPH 120
-#define KON_DEBUG_TYPES 8
-
-typedef struct kon_debugCmd {
- const char *name;
- const char *help;
- kon_debugCommandFn fn;
- void *user;
-} kon_debugCmd_t;
-
-typedef struct kon_debugVarEntry {
- const char *name;
- kon_debugVarType_t type;
- void *value;
- int watched;
-} kon_debugVarEntry_t;
-
-struct kon_debug {
- kon_font_t *font;
- kon_assets_t *assets;
- int lineH, ascent;
- float baseSize, size;
- int ctrl;
-
- int overlayKey, consoleKey;
- int overlay;
- int open;
- float slide;
- int swallowTick;
- int quit;
-
- char log[KON_DEBUG_LOGLINES][KON_DEBUG_LINELEN];
- int logCount, logHead, scroll;
-
- char input[KON_DEBUG_INPUT];
- int length, cursor;
- char history[KON_DEBUG_HISTORY][KON_DEBUG_INPUT];
- int historyCount, historyPos;
-
- kon_debugCmd_t commands[KON_DEBUG_COMMANDS];
- int commandCount;
- kon_debugVarEntry_t vars[KON_DEBUG_VARS];
- int varCount;
-
- kon_time last;
- double frameDt;
- double accumTime;
- int accumFrames;
- float fps;
- float graph[KON_DEBUG_GRAPH];
- int graphHead;
-};
-
-/*** private helpers ***/
-
-static void kon_debugMetrics_(kon_debug_t *d)
-{
- int h = 0;
-
- kon_measureText(d->font, "A", NULL, &h);
- d->ascent = h * 4 / 5;
- d->lineH = h + 2;
-}
-
-static void kon_debugResize_(kon_debug_t *d, float size)
-{
- if (size < 8.0f) size = 8.0f;
- if (size > 64.0f) size = 64.0f;
- if (size == d->size) return;
- if (kon_fontResize(d->font, size)) {
- d->size = size;
- kon_debugMetrics_(d);
- }
-}
-
-static void kon_debugText_(const kon_debug_t *d, kon_framebuffer_t *fb, int x, int y, const char *s, uint32_t color)
-{
- kon_drawText(fb, d->font, x, y + d->ascent, s, color);
-}
-
-static void kon_debugPushLine_(kon_debug_t *d, const char *line)
-{
- int slot = (d->logHead + d->logCount) % KON_DEBUG_LOGLINES;
-
- if (d->logCount == KON_DEBUG_LOGLINES) {
- slot = d->logHead;
- d->logHead = (d->logHead + 1) % KON_DEBUG_LOGLINES;
- } else {
- d->logCount++;
- }
- snprintf(d->log[slot], KON_DEBUG_LINELEN, "%s", line);
- if (d->scroll > 0 && d->scroll < d->logCount) d->scroll++;
-}
-
-static const char *kon_debugVarText_(const kon_debugVarEntry_t *v, char *buffer, size_t size)
-{
- switch (v->type) {
- case KON_VAR_BOOL: snprintf(buffer, size, "%s", *(int *)v->value ? "true" : "false"); break;
- case KON_VAR_INT: snprintf(buffer, size, "%d", *(int *)v->value); break;
- default: snprintf(buffer, size, "%g", (double)*(float *)v->value); break;
- }
- return buffer;
-}
-
-static int kon_debugTokenize_(char *line, char **argv, int max)
-{
- int argc = 0;
-
- while (*line && argc < max) {
- while (*line == ' ' || *line == '\t') line++;
- if (!*line) break;
- if (*line == '"') {
- argv[argc++] = ++line;
- while (*line && *line != '"') line++;
- } else {
- argv[argc++] = line;
- while (*line && *line != ' ' && *line != '\t') line++;
- }
- if (*line) *line++ = 0;
- }
- return argc;
-}
-
-static void kon_debugSetVar_(kon_debug_t *d, kon_debugVarEntry_t *v, const char *text)
-{
- char out[64];
-
- switch (v->type) {
- case KON_VAR_BOOL:
- if (!strcmp(text, "1") || !strcmp(text, "true") || !strcmp(text, "on")) *(int *)v->value = 1;
- else if (!strcmp(text, "0") || !strcmp(text, "false") || !strcmp(text, "off")) *(int *)v->value = 0;
- else {
- kon_debugLog(d, "%s expects 0 or 1", v->name);
- return;
- }
- break;
- case KON_VAR_INT: *(int *)v->value = atoi(text); break;
- default: *(float *)v->value = (float)atof(text); break;
- }
- kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out)));
-}
-
-static void kon_debugExecute_(kon_debug_t *d, const char *line)
-{
- char copy[KON_DEBUG_INPUT], *argv[KON_DEBUG_ARGS];
- int argc, i;
-
- snprintf(copy, sizeof(copy), "%s", line);
- argc = kon_debugTokenize_(copy, argv, KON_DEBUG_ARGS);
- if (argc == 0) return;
-
- for (i = 0; i < d->commandCount; i++) {
- if (!strcmp(argv[0], d->commands[i].name)) {
- d->commands[i].fn(d, d->commands[i].user, argc, argv);
- return;
- }
- }
- for (i = 0; i < d->varCount; i++) {
- kon_debugVarEntry_t *v = &d->vars[i];
- char out[64];
-
- if (strcmp(argv[0], v->name)) continue;
- if (argc > 1) kon_debugSetVar_(d, v, argv[1]);
- else kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out)));
- return;
- }
- kon_debugLog(d, "unknown command '%s', try help", argv[0]);
-}
-
-static void kon_debugComplete_(kon_debug_t *d)
-{
- const char *matches[KON_DEBUG_COMMANDS + KON_DEBUG_VARS];
- int count = 0, i, common;
- size_t prefix = (size_t)d->cursor;
-
- for (i = 0; i < d->cursor; i++) if (d->input[i] == ' ') return;
- if (prefix == 0) return;
-
- for (i = 0; i < d->commandCount; i++) if (!strncmp(d->commands[i].name, d->input, prefix)) matches[count++] = d->commands[i].name;
- for (i = 0; i < d->varCount; i++) if (!strncmp(d->vars[i].name, d->input, prefix)) matches[count++] = d->vars[i].name;
- if (count == 0) return;
-
- common = (int)strlen(matches[0]);
- for (i = 1; i < count; i++) {
- int n = 0;
-
- while (n < common && matches[i][n] == matches[0][n]) n++;
- common = n;
- }
-
- if (count > 1) {
- char line[KON_DEBUG_LINELEN] = "";
-
- for (i = 0; i < count; i++) {
- if (strlen(line) + strlen(matches[i]) + 2 >= sizeof(line)) break;
- strcat(line, matches[i]);
- strcat(line, " ");
- }
- kon_debugLog(d, "%s", line);
- }
-
- {
- char rest[KON_DEBUG_INPUT];
- int add = common - d->cursor + (count == 1 ? 1 : 0);
-
- snprintf(rest, sizeof(rest), "%s", d->input + d->cursor);
- if (d->length + add >= KON_DEBUG_INPUT) return;
- memcpy(d->input, matches[0], (size_t)common);
- if (count == 1) d->input[common] = ' ';
- memcpy(d->input + common + (count == 1 ? 1 : 0), rest, strlen(rest) + 1);
- d->length = (int)strlen(d->input);
- d->cursor = common + (count == 1 ? 1 : 0);
- }
-}
-
-static void kon_debugInsert_(kon_debug_t *d, char c)
-{
- if (d->length + 1 >= KON_DEBUG_INPUT) return;
- memmove(d->input + d->cursor + 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1);
- d->input[d->cursor++] = c;
- d->length++;
-}
-
-static void kon_debugSetInput_(kon_debug_t *d, const char *text)
-{
- snprintf(d->input, sizeof(d->input), "%s", text);
- d->length = (int)strlen(d->input);
- d->cursor = d->length;
-}
-
-static void kon_debugSubmit_(kon_debug_t *d)
-{
- char line[KON_DEBUG_INPUT + 4];
-
- if (d->length == 0) return;
- snprintf(line, sizeof(line), "> %s", d->input);
- kon_debugPushLine_(d, line);
-
- if (d->historyCount == 0 || strcmp(d->history[d->historyCount - 1], d->input)) {
- if (d->historyCount == KON_DEBUG_HISTORY) {
- memmove(d->history[0], d->history[1], sizeof(d->history[0]) * (KON_DEBUG_HISTORY - 1));
- d->historyCount--;
- }
- snprintf(d->history[d->historyCount++], KON_DEBUG_INPUT, "%s", d->input);
- }
- d->historyPos = d->historyCount;
-
- {
- char copy[KON_DEBUG_INPUT];
-
- snprintf(copy, sizeof(copy), "%s", d->input);
- d->input[0] = 0;
- d->length = d->cursor = 0;
- d->scroll = 0;
- kon_debugExecute_(d, copy);
- }
-}
-
-/*** builtin commands ***/
-
-static void kon_debugCmdHelp_(kon_debug_t *d, void *user, int argc, char **argv)
-{
- int i;
-
- (void)user;
- if (argc > 1) {
- for (i = 0; i < d->commandCount; i++) {
- if (!strcmp(argv[1], d->commands[i].name)) {
- kon_debugLog(d, "%s: %s", d->commands[i].name, d->commands[i].help);
- return;
- }
- }
- kon_debugLog(d, "no help for '%s'", argv[1]);
- return;
- }
- kon_debugLog(d, "commands:");
- for (i = 0; i < d->commandCount; i++) kon_debugLog(d, " %-10s %s", d->commands[i].name, d->commands[i].help);
- kon_debugLog(d, "variables (type the name to read, name value to set):");
- for (i = 0; i < d->varCount; i++) kon_debugLog(d, " %s", d->vars[i].name);
-}
-
-static void kon_debugCmdClear_(kon_debug_t *d, void *user, int argc, char **argv)
-{
- (void)user; (void)argc; (void)argv;
- d->logCount = d->logHead = d->scroll = 0;
-}
-
-static void kon_debugCmdEcho_(kon_debug_t *d, void *user, int argc, char **argv)
-{
- char line[KON_DEBUG_LINELEN] = "";
- int i;
-
- (void)user;
- for (i = 1; i < argc; i++) {
- if (strlen(line) + strlen(argv[i]) + 2 >= sizeof(line)) break;
- strcat(line, argv[i]);
- strcat(line, " ");
- }
- kon_debugLog(d, "%s", line);
-}
-
-static void kon_debugCmdFps_(kon_debug_t *d, void *user, int argc, char **argv)
-{
- (void)user;
- d->overlay = argc > 1 ? atoi(argv[1]) & 3 : (d->overlay + 1) & 3;
-}
-
-static void kon_debugCmdWatch_(kon_debug_t *d, void *user, int argc, char **argv)
-{
- int i;
-
- (void)user;
- if (argc < 2) {
- kon_debugLog(d, "usage: watch <variable>");
- return;
- }
- for (i = 0; i < d->varCount; i++) {
- if (!strcmp(argv[1], d->vars[i].name)) {
- d->vars[i].watched = !d->vars[i].watched;
- if (d->overlay == 0) d->overlay = 1;
- return;
- }
- }
- kon_debugLog(d, "no variable '%s'", argv[1]);
-}
-
-static void kon_debugCmdQuit_(kon_debug_t *d, void *user, int argc, char **argv)
-{
- (void)user; (void)argc; (void)argv;
- d->quit = 1;
-}
-
-static void kon_debugCmdFontSize_(kon_debug_t *d, void *user, int argc, char **argv)
-{
- (void)user;
- if (argc > 1) kon_debugResize_(d, (float)atof(argv[1]));
- kon_debugLog(d, "fontsize = %g", (double)d->size);
-}
-
-static void kon_debugCmdAssets_(kon_debug_t *d, void *user, int argc, char **argv)
-{
- int i, count;
- kon_assetInfo_t info;
-
- (void)user; (void)argc; (void)argv;
- if (!d->assets) {
- kon_debugLog(d, "no asset manager attached");
- return;
- }
- count = kon_assetsCount(d->assets);
- for (i = 0; i < count; i++) {
- if (!kon_assetsInfo(d->assets, i, &info)) continue;
- kon_debugLog(d, "%-7s %3d ref %8lu b %s%s", info.typeName, info.refs, (unsigned long)info.bytes, info.name, info.loose ? " (loose)" : "");
- }
- kon_debugLog(d, "%d assets, %lu bytes", count, (unsigned long)kon_assetsBytes(d->assets));
-}
-
-/*** debug ***/
-
-kon_debug_t *kon_debugCreate(kon_font_t *font)
-{
- kon_debug_t *d;
-
- if (!font) return NULL;
- d = calloc(1, sizeof(kon_debug_t));
- if (!d) return NULL;
-
- d->font = font;
- d->baseSize = d->size = kon_fontPixelHeight(font);
- kon_debugMetrics_(d);
- d->overlayKey = KON_KEY_F1;
- d->consoleKey = KON_KEY_GRAVE;
- d->last = kon_getTime();
-
- kon_debugCommand(d, "help", "list commands, or help <command>", kon_debugCmdHelp_, NULL);
- kon_debugCommand(d, "clear", "clear the console", kon_debugCmdClear_, NULL);
- kon_debugCommand(d, "echo", "print text", kon_debugCmdEcho_, NULL);
- kon_debugCommand(d, "fps", "overlay mode 0-3, or cycle", kon_debugCmdFps_, NULL);
- kon_debugCommand(d, "watch", "show a variable in the overlay", kon_debugCmdWatch_, NULL);
- kon_debugCommand(d, "assets", "list loaded assets", kon_debugCmdAssets_, NULL);
- kon_debugCommand(d, "fontsize", "text size in pixels, ctrl + and ctrl - too", kon_debugCmdFontSize_, NULL);
- kon_debugCommand(d, "quit", "close the game", kon_debugCmdQuit_, NULL);
- kon_debugCommand(d, "exit", "close the game", kon_debugCmdQuit_, NULL);
- kon_debugLog(d, "type help for commands, tab completes");
- return d;
-}
-
-void kon_debugDestroy(kon_debug_t *d)
-{
- free(d);
-}
-
-void kon_debugSetKeys(kon_debug_t *d, int overlayKey, int consoleKey)
-{
- if (!d) return;
- d->overlayKey = overlayKey;
- d->consoleKey = consoleKey;
-}
-
-void kon_debugSetAssets(kon_debug_t *d, kon_assets_t *assets)
-{
- if (!d) return;
- d->assets = assets;
-}
-
-int kon_debugCommand(kon_debug_t *d, const char *name, const char *help, kon_debugCommandFn fn, void *user)
-{
- if (!d) return 0;
- kon_debugCmd_t *c;
-
- if (d->commandCount == KON_DEBUG_COMMANDS) return 0;
- c = &d->commands[d->commandCount++];
- c->name = name;
- c->help = help;
- c->fn = fn;
- c->user = user;
- return 1;
-}
-
-int kon_debugVar(kon_debug_t *d, const char *name, kon_debugVarType_t type, void *value)
-{
- if (!d) return 0;
- kon_debugVarEntry_t *v;
-
- if (d->varCount == KON_DEBUG_VARS) return 0;
- v = &d->vars[d->varCount++];
- v->name = name;
- v->type = type;
- v->value = value;
- v->watched = 0;
- return 1;
-}
-
-void kon_debugLog(kon_debug_t *d, const char *format, ...)
-{
- if (!d) return;
- char line[KON_DEBUG_LINELEN];
- va_list args;
-
- va_start(args, format);
- vsnprintf(line, sizeof(line), format, args);
- va_end(args);
- kon_debugPushLine_(d, line);
-}
-
-int kon_debugConsoleOpen(const kon_debug_t *d)
-{
- if (!d) return 0;
- return d->open;
-}
-
-int kon_debugQuitRequested(const kon_debug_t *d)
-{
- return d ? d->quit : 0;
-}
-
-float kon_debugFps(const kon_debug_t *d)
-{
- if (!d) return 0.0f;
- return d->fps;
-}
-
-int kon_debugFeedEvent(kon_debug_t *d, const kon_event_t *e)
-{
- if (!d) return 0;
-
- if (e->key == KON_KEY_LEFT_CTRL || e->key == KON_KEY_RIGHT_CTRL) {
- if (e->type == KON_EVENT_KEY_DOWN) d->ctrl = 1;
- if (e->type == KON_EVENT_KEY_UP) d->ctrl = 0;
- }
-
- if (e->type == KON_EVENT_KEY_DOWN) {
- if (d->ctrl && (d->open || d->overlay)) {
- if (e->key == KON_KEY_EQUAL) { kon_debugResize_(d, d->size + 2.0f); return 1; }
- if (e->key == KON_KEY_MINUS) { kon_debugResize_(d, d->size - 2.0f); return 1; }
- if (e->key == KON_KEY_0) { kon_debugResize_(d, d->baseSize); return 1; }
- }
- if (e->key == d->consoleKey) {
- d->open = !d->open;
- d->swallowTick = 1;
- return 1;
- }
- d->swallowTick = 0;
- if (e->key == d->overlayKey) {
- d->overlay = (d->overlay + 1) & 3;
- return 1;
- }
- }
- if (!d->open) return 0;
-
- if (e->type == KON_EVENT_TEXT) {
- if (d->ctrl) return 1;
- if (d->swallowTick && e->codepoint == '`') {
- d->swallowTick = 0;
- return 1;
- }
- if (e->codepoint >= 32 && e->codepoint <= 126) kon_debugInsert_(d, (char)e->codepoint);
- return 1;
- }
- if (e->type == KON_EVENT_KEY_UP) return 1;
- if (e->type != KON_EVENT_KEY_DOWN) return 0;
-
- switch (e->key) {
- case KON_KEY_ENTER: kon_debugSubmit_(d); break;
- case KON_KEY_ESCAPE: d->open = 0; break;
- case KON_KEY_TAB: kon_debugComplete_(d); break;
- case KON_KEY_LEFT: if (d->cursor > 0) d->cursor--; break;
- case KON_KEY_RIGHT: if (d->cursor < d->length) d->cursor++; break;
- case KON_KEY_BACKSPACE:
- if (d->cursor > 0) {
- memmove(d->input + d->cursor - 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1);
- d->cursor--;
- d->length--;
- }
- break;
- case KON_KEY_DELETE:
- if (d->cursor < d->length) {
- memmove(d->input + d->cursor, d->input + d->cursor + 1, (size_t)(d->length - d->cursor));
- d->length--;
- }
- break;
- case KON_KEY_UP:
- if (d->historyPos > 0) kon_debugSetInput_(d, d->history[--d->historyPos]);
- break;
- case KON_KEY_DOWN:
- if (d->historyPos < d->historyCount - 1) kon_debugSetInput_(d, d->history[++d->historyPos]);
- else {
- d->historyPos = d->historyCount;
- kon_debugSetInput_(d, "");
- }
- break;
- case KON_KEY_PAGE_UP: d->scroll += 8; break;
- case KON_KEY_PAGE_DOWN: d->scroll -= 8; break;
- default: break;
- }
- if (d->scroll < 0) d->scroll = 0;
- if (d->scroll > d->logCount - 1) d->scroll = d->logCount > 0 ? d->logCount - 1 : 0;
- return 1;
-}
-
-void kon_debugFrame(kon_debug_t *d)
-{
- if (!d) return;
- kon_time now = kon_getTime();
- double dt = now - d->last;
-
- d->last = now;
- d->frameDt = dt;
- d->accumTime += dt;
- d->accumFrames++;
- if (d->accumTime >= 0.5) {
- d->fps = (float)((double)d->accumFrames / d->accumTime);
- d->accumTime = 0.0;
- d->accumFrames = 0;
- }
- d->graph[d->graphHead] = (float)(dt * 1000.0);
- d->graphHead = (d->graphHead + 1) % KON_DEBUG_GRAPH;
-}
-
-static void kon_debugBox_(const kon_debug_t *d, kon_framebuffer_t *fb, int x, int y, const char *s, uint32_t color)
-{
- int w = 0;
-
- kon_measureText(d->font, s, &w, NULL);
- kon_fillRectangle(fb, x - 2, y - 1, w + 4, d->lineH, 0xB0000000);
- kon_debugText_(d, fb, x, y, s, color);
-}
-
-static void kon_debugDrawOverlay_(kon_debug_t *d, kon_framebuffer_t *fb)
-{
- char line[128];
- int x = 4, y = 4, i, rows;
- uint32_t white = 0xFFFFFFFF, dim = 0xFFB0B0C0;
- float worst = 0.0f;
-
- snprintf(line, sizeof(line), "%.0f fps %.2f ms", (double)d->fps, d->frameDt * 1000.0);
- kon_debugBox_(d, fb, x, y, line, white);
- y += d->lineH + 3;
-
- if (d->overlay >= 1) {
- kon_fillRectangle(fb, x - 2, y - 2, KON_DEBUG_GRAPH + 4, 34, 0xB0000000);
- for (i = 0; i < KON_DEBUG_GRAPH; i++) {
- float ms = d->graph[(d->graphHead + i) % KON_DEBUG_GRAPH];
- int h = (int)(ms * 30.0f / 33.3f);
- uint32_t color = ms < 17.5f ? 0xFF50D060 : (ms < 34.0f ? 0xFFE0C040 : 0xFFE05050);
-
- if (ms > worst) worst = ms;
- if (h > 30) h = 30;
- if (h < 1) h = 1;
- kon_fillRectangle(fb, x + i, y + 30 - h, 1, h, color);
- }
- y += 36;
- }
-
- for (i = 0; i < d->varCount; i++) {
- char value[64];
-
- if (!d->vars[i].watched) continue;
- snprintf(line, sizeof(line), "%s = %s", d->vars[i].name, kon_debugVarText_(&d->vars[i], value, sizeof(value)));
- kon_debugBox_(d, fb, x, y, line, dim);
- y += d->lineH;
- }
-
- if (d->overlay >= 2 && d->assets) {
- const char *names[KON_DEBUG_TYPES];
- int counts[KON_DEBUG_TYPES], types = 0, total = kon_assetsCount(d->assets);
- size_t bytes[KON_DEBUG_TYPES];
- kon_assetInfo_t info;
-
- for (i = 0; i < total; i++) {
- int t;
-
- if (!kon_assetsInfo(d->assets, i, &info)) continue;
- for (t = 0; t < types; t++) if (names[t] == info.typeName) break;
- if (t == types) {
- if (types == KON_DEBUG_TYPES) continue;
- names[types] = info.typeName;
- counts[types] = 0;
- bytes[types] = 0;
- types++;
- }
- counts[t]++;
- bytes[t] += info.bytes;
- }
-
- y += 2;
- snprintf(line, sizeof(line), "assets %d, %.1f KB", total, (double)kon_assetsBytes(d->assets) / 1024.0);
- kon_debugBox_(d, fb, x, y, line, white);
- y += d->lineH;
- for (i = 0; i < types; i++) {
- snprintf(line, sizeof(line), " %-7s %3d %8.1f KB", names[i], counts[i], (double)bytes[i] / 1024.0);
- kon_debugBox_(d, fb, x, y, line, dim);
- y += d->lineH;
- }
-
- if (d->overlay >= 3) {
- rows = (fb->height - y) / d->lineH - 1;
- for (i = 0; i < total && i < rows; i++) {
- if (!kon_assetsInfo(d->assets, i, &info)) continue;
- snprintf(line, sizeof(line), "%-6s x%d %7.1fK %s%s", info.typeName, info.refs, (double)info.bytes / 1024.0, info.name, info.loose ? " *" : "");
- kon_debugBox_(d, fb, x, y, line, dim);
- y += d->lineH;
- }
- }
- } else if (d->overlay >= 2) {
- kon_debugBox_(d, fb, x, y + 2, "no asset manager attached", dim);
- }
-}
-
-static void kon_debugDrawConsole_(kon_debug_t *d, kon_framebuffer_t *fb)
-{
- int full = fb->height / 2, height = (int)((float)full * d->slide);
- int inputY, rows, i, line, cx, w = 0;
- char shown[KON_DEBUG_INPUT + 4];
-
- if (height < d->lineH + 6) return;
-
- kon_fillRectangle(fb, 0, 0, fb->width, height, 0xE0101018);
- kon_fillRectangle(fb, 0, height - 1, fb->width, 1, 0xFF7088E0);
-
- inputY = height - d->lineH - 4;
- kon_fillRectangle(fb, 0, inputY - 2, fb->width, d->lineH + 4, 0xFF181824);
-
- rows = (inputY - 4) / d->lineH;
- for (i = 0; i < rows; i++) {
- int index = d->logCount - 1 - d->scroll - i;
-
- if (index < 0) break;
- line = (d->logHead + index) % KON_DEBUG_LOGLINES;
- kon_debugText_(d, fb, 6, inputY - 4 - (i + 1) * d->lineH, d->log[line], d->log[line][0] == '>' ? 0xFF9098C0 : 0xFFE0E0E8);
- }
-
- snprintf(shown, sizeof(shown), "> %s", d->input);
- kon_debugText_(d, fb, 6, inputY, shown, 0xFFFFFFFF);
-
- snprintf(shown, sizeof(shown), "> %.*s", d->cursor, d->input);
- kon_measureText(d->font, shown, &w, NULL);
- cx = 6 + w;
- kon_fillRectangle(fb, cx, inputY, 2, d->lineH - 2, 0xFFFFFFFF);
-}
-
-void kon_debugDraw(kon_debug_t *d, kon_framebuffer_t *fb)
-{
- if (!d) return;
- float target = d->open ? 1.0f : 0.0f;
- float step = (float)d->frameDt * 8.0f;
-
- if (d->slide < target) d->slide = d->slide + step > target ? target : d->slide + step;
- else if (d->slide > target) d->slide = d->slide - step < target ? target : d->slide - step;
-
- if (d->overlay) kon_debugDrawOverlay_(d, fb);
- if (d->slide > 0.0f) kon_debugDrawConsole_(d, fb);
-}
-
-#endif
diff --git a/kon/konpack.c b/kon/konpack.c
new file mode 100644
index 0000000..0dfe4a8
--- /dev/null
+++ b/kon/konpack.c
@@ -0,0 +1,598 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konpack.c
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * 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.
+ */
+
+
+#include "konpack.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#define KON_PACK_MAGIC_ "KPAK"
+#define KON_PACK_VERSION_ 1u
+#define KON_PACK_HEADER_SIZE_ 36u
+#define KON_PACK_FLAG_ENCRYPTED_ 1u
+#define KON_PACK_TABLE_INDEX_ 0xFFFFFFFFu
+
+static const char *kon_packErrorText_ = "no error";
+
+const char *kon_packError(void) {
+ return kon_packErrorText_;
+}
+
+/*** private helper ***/
+
+static uint32_t kon_load32_(const uint8_t *p) {
+ return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
+}
+
+static void kon_store32_(uint8_t *p, uint32_t v) {
+ p[0] = (uint8_t)(v & 0xFF);
+ p[1] = (uint8_t)((v >> 8) & 0xFF);
+ p[2] = (uint8_t)((v >> 16) & 0xFF);
+ p[3] = (uint8_t)((v >> 24) & 0xFF);
+}
+
+/*** crc32 ***/
+
+uint32_t kon_crc32(const uint8_t *data, size_t length) {
+ static uint32_t table[256];
+ static int ready;
+
+ if (!ready) {
+ for (uint32_t i = 0; i < 256; i++) {
+ uint32_t c = i;
+ for (int k = 0; k < 8; k++) c = (c & 1u) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
+ table[i] = c;
+ }
+ ready = 1;
+ }
+
+ uint32_t crc = 0xFFFFFFFFu;
+ for (size_t i = 0; i < length; i++) crc = table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
+ return crc ^ 0xFFFFFFFFu;
+}
+
+/*** chacha20 ***/
+
+#define KON_ROTL32_(v, n) (((v) << (n)) | ((v) >> (32 - (n))))
+
+#define KON_QUARTER_(a, b, c, d) \
+ a += b; d ^= a; d = KON_ROTL32_(d, 16); \
+ c += d; b ^= c; b = KON_ROTL32_(b, 12); \
+ a += b; d ^= a; d = KON_ROTL32_(d, 8); \
+ c += d; b ^= c; b = KON_ROTL32_(b, 7);
+
+static void kon_chachaBlock_(const uint32_t state[16], uint8_t out[64]) {
+ uint32_t x[16];
+
+ for (int i = 0; i < 16; i++) x[i] = state[i];
+
+ for (int round = 0; round < 10; round++) {
+ KON_QUARTER_(x[0], x[4], x[8], x[12])
+ KON_QUARTER_(x[1], x[5], x[9], x[13])
+ KON_QUARTER_(x[2], x[6], x[10], x[14])
+ KON_QUARTER_(x[3], x[7], x[11], x[15])
+ KON_QUARTER_(x[0], x[5], x[10], x[15])
+ KON_QUARTER_(x[1], x[6], x[11], x[12])
+ KON_QUARTER_(x[2], x[7], x[8], x[13])
+ KON_QUARTER_(x[3], x[4], x[9], x[14])
+ }
+
+ for (int i = 0; i < 16; i++) kon_store32_(&out[i * 4], x[i] + state[i]);
+}
+
+void kon_chacha20Xor(const uint8_t key[KON_PACK_KEY_SIZE], const uint8_t nonce[12], uint32_t counter, const uint8_t *in, uint8_t *out, size_t length) {
+ uint32_t state[16];
+ uint8_t block[64];
+
+ state[0] = 0x61707865u;
+ state[1] = 0x3320646Eu;
+ state[2] = 0x79622D32u;
+ state[3] = 0x6B206574u;
+ for (int i = 0; i < 8; i++) state[4 + i] = kon_load32_(&key[i * 4]);
+ state[12] = counter;
+ for (int i = 0; i < 3; i++) state[13 + i] = kon_load32_(&nonce[i * 4]);
+
+ while (length > 0) {
+ size_t n = length < 64 ? length : 64;
+
+ kon_chachaBlock_(state, block);
+ for (size_t i = 0; i < n; i++) out[i] = (uint8_t)(in[i] ^ block[i]);
+
+ in += n;
+ out += n;
+ length -= n;
+ state[12]++;
+ }
+}
+
+static void kon_fileNonce_(const uint8_t packNonce[8], uint32_t index, uint8_t out[12]) {
+ memcpy(out, packNonce, 8);
+ kon_store32_(&out[8], index);
+}
+
+/*** reading ***/
+
+typedef struct kon_packEntry {
+ const char *name;
+ uint32_t offset, size, crc;
+} kon_packEntry_t;
+
+struct kon_pack {
+ FILE *file;
+ uint8_t key[KON_PACK_KEY_SIZE];
+ uint8_t nonce[8];
+ int encrypted;
+ int count;
+ kon_packEntry_t *entries;
+ char *names;
+};
+
+void kon_packClose(kon_pack_t *pack) {
+ if (!pack) return;
+
+ if (pack->file) fclose(pack->file);
+ free(pack->entries);
+ free(pack->names);
+ free(pack);
+}
+
+kon_pack_t *kon_packOpen(const char *path, const uint8_t *key) {
+ uint8_t header[KON_PACK_HEADER_SIZE_];
+ uint8_t *table = NULL;
+ kon_pack_t *pack = NULL;
+ uint32_t flags, count, tableOffset, tableSize, tableCrc;
+ uint64_t fileSize;
+ long end;
+
+ if (!path) {
+ kon_packErrorText_ = "no path";
+ return NULL;
+ }
+
+ pack = calloc(1, sizeof(kon_pack_t));
+ if (!pack) {
+ kon_packErrorText_ = "out of memory";
+ return NULL;
+ }
+
+ pack->file = fopen(path, "rb");
+ if (!pack->file) {
+ kon_packErrorText_ = "could not open the pack file";
+ goto fail;
+ }
+
+ if (fread(header, 1, sizeof(header), pack->file) != sizeof(header)) {
+ kon_packErrorText_ = "pack is too small";
+ goto fail;
+ }
+ if (memcmp(header, KON_PACK_MAGIC_, 4) != 0) {
+ kon_packErrorText_ = "not a pack file";
+ goto fail;
+ }
+ if (kon_load32_(&header[4]) != KON_PACK_VERSION_) {
+ kon_packErrorText_ = "unsupported pack version";
+ goto fail;
+ }
+
+ flags = kon_load32_(&header[8]);
+ count = kon_load32_(&header[12]);
+ tableOffset = kon_load32_(&header[16]);
+ tableSize = kon_load32_(&header[20]);
+ tableCrc = kon_load32_(&header[24]);
+ memcpy(pack->nonce, &header[28], 8);
+
+ pack->encrypted = (flags & KON_PACK_FLAG_ENCRYPTED_) != 0;
+ if (pack->encrypted) {
+ if (!key) {
+ kon_packErrorText_ = "pack is encrypted and no key was given";
+ goto fail;
+ }
+ memcpy(pack->key, key, KON_PACK_KEY_SIZE);
+ }
+
+ if (fseek(pack->file, 0, SEEK_END) != 0 || (end = ftell(pack->file)) < 0) {
+ kon_packErrorText_ = "could not get the pack size";
+ goto fail;
+ }
+ fileSize = (uint64_t)end;
+
+ if (tableOffset < KON_PACK_HEADER_SIZE_ || (uint64_t)tableOffset + tableSize > fileSize ||
+ (uint64_t)count * 15u > tableSize || count > 0x7FFFFFFFu) {
+ kon_packErrorText_ = "pack table is damaged";
+ goto fail;
+ }
+
+ table = malloc(tableSize ? tableSize : 1);
+ pack->entries = calloc(count ? count : 1, sizeof(kon_packEntry_t));
+ pack->names = malloc((size_t)tableSize + count + 1);
+ if (!table || !pack->entries || !pack->names) {
+ kon_packErrorText_ = "out of memory";
+ goto fail;
+ }
+
+ if (fseek(pack->file, (long)tableOffset, SEEK_SET) != 0 || fread(table, 1, tableSize, pack->file) != tableSize) {
+ kon_packErrorText_ = "could not read the pack table";
+ goto fail;
+ }
+
+ if (pack->encrypted) {
+ uint8_t nonce[12];
+ kon_fileNonce_(pack->nonce, KON_PACK_TABLE_INDEX_, nonce);
+ kon_chacha20Xor(pack->key, nonce, 0, table, table, tableSize);
+ }
+
+ if (kon_crc32(table, tableSize) != tableCrc) {
+ kon_packErrorText_ = pack->encrypted ? "wrong key or damaged pack" : "pack table is damaged";
+ goto fail;
+ }
+
+ {
+ size_t at = 0, nameAt = 0;
+
+ for (uint32_t i = 0; i < count; i++) {
+ size_t nameLength;
+
+ if (at + 2 > tableSize) goto damaged;
+ nameLength = (size_t)table[at] | ((size_t)table[at + 1] << 8);
+ at += 2;
+ if (nameLength == 0 || at + nameLength + 12 > tableSize) goto damaged;
+
+ memcpy(&pack->names[nameAt], &table[at], nameLength);
+ pack->names[nameAt + nameLength] = '\0';
+ if (strlen(&pack->names[nameAt]) != nameLength) goto damaged;
+ pack->entries[i].name = &pack->names[nameAt];
+ nameAt += nameLength + 1;
+ at += nameLength;
+
+ pack->entries[i].offset = kon_load32_(&table[at]);
+ pack->entries[i].size = kon_load32_(&table[at + 4]);
+ pack->entries[i].crc = kon_load32_(&table[at + 8]);
+ at += 12;
+
+ if (pack->entries[i].offset < KON_PACK_HEADER_SIZE_ ||
+ (uint64_t)pack->entries[i].offset + pack->entries[i].size > tableOffset) goto damaged;
+ if (i > 0 && strcmp(pack->entries[i - 1].name, pack->entries[i].name) >= 0) goto damaged;
+ }
+ if (at != tableSize) goto damaged;
+ }
+
+ pack->count = (int)count;
+ free(table);
+ return pack;
+
+damaged:
+ kon_packErrorText_ = "pack table is damaged";
+fail:
+ free(table);
+ kon_packClose(pack);
+ return NULL;
+}
+
+int kon_packCount(const kon_pack_t *pack) {
+ return pack ? pack->count : 0;
+}
+
+const char *kon_packName(const kon_pack_t *pack, int index) {
+ if (!pack || index < 0 || index >= pack->count) return NULL;
+ return pack->entries[index].name;
+}
+
+uint32_t kon_packSize(const kon_pack_t *pack, int index) {
+ if (!pack || index < 0 || index >= pack->count) return 0;
+ return pack->entries[index].size;
+}
+
+int kon_packFind(const kon_pack_t *pack, const char *name) {
+ int low = 0, high;
+
+ if (!pack || !name) return -1;
+ high = pack->count - 1;
+
+ while (low <= high) {
+ int mid = low + (high - low) / 2;
+ int cmp = strcmp(name, pack->entries[mid].name);
+
+ if (cmp == 0) return mid;
+ if (cmp < 0) high = mid - 1;
+ else low = mid + 1;
+ }
+ return -1;
+}
+
+uint8_t *kon_packRead(kon_pack_t *pack, int index, size_t *size) {
+ kon_packEntry_t *entry;
+ uint8_t *data;
+
+ if (size) *size = 0;
+ if (!pack || index < 0 || index >= pack->count) {
+ kon_packErrorText_ = "no such file in the pack";
+ return NULL;
+ }
+ entry = &pack->entries[index];
+
+ data = malloc((size_t)entry->size + 1);
+ if (!data) {
+ kon_packErrorText_ = "out of memory";
+ return NULL;
+ }
+
+ if (fseek(pack->file, (long)entry->offset, SEEK_SET) != 0 || fread(data, 1, entry->size, pack->file) != entry->size) {
+ kon_packErrorText_ = "could not read from the pack";
+ free(data);
+ return NULL;
+ }
+
+ if (pack->encrypted) {
+ uint8_t nonce[12];
+ kon_fileNonce_(pack->nonce, (uint32_t)index, nonce);
+ kon_chacha20Xor(pack->key, nonce, 0, data, data, entry->size);
+ }
+
+ if (kon_crc32(data, entry->size) != entry->crc) {
+ kon_packErrorText_ = "file is damaged";
+ free(data);
+ return NULL;
+ }
+
+ data[entry->size] = 0;
+ if (size) *size = entry->size;
+ return data;
+}
+
+/*** writing ***/
+
+#ifdef KONPACK_WRITER
+
+typedef struct kon_packSource_ {
+ const char *path;
+ char *name;
+} kon_packSource_t;
+
+static int kon_packSourceCompare_(const void *a, const void *b) {
+ return strcmp(((const kon_packSource_t *)a)->name, ((const kon_packSource_t *)b)->name);
+}
+
+static void kon_packMakeNonce_(uint8_t nonce[8]) {
+ FILE *f = fopen("/dev/urandom", "rb");
+
+ if (f) {
+ size_t got = fread(nonce, 1, 8, f);
+ fclose(f);
+ if (got == 8) return;
+ }
+
+ /* nothing better around, mix what changes between runs. only has to never repeat, it is not secret */
+ {
+ uint64_t h = 1469598103934665603ull;
+ uint64_t parts[3];
+ int local;
+
+ parts[0] = (uint64_t)time(NULL);
+ parts[1] = (uint64_t)clock();
+ parts[2] = (uint64_t)(size_t)&local;
+
+ for (int i = 0; i < 3; i++) {
+ for (int b = 0; b < 8; b++) {
+ h ^= (parts[i] >> (b * 8)) & 0xFF;
+ h *= 1099511628211ull;
+ }
+ }
+ for (int i = 0; i < 8; i++) nonce[i] = (uint8_t)(h >> (i * 8));
+ }
+}
+
+static char *kon_packMakeName_(const char *path, const char *baseDir) {
+ size_t baseLength = baseDir ? strlen(baseDir) : 0;
+ const char *rest = path;
+ char *name;
+
+ if (baseLength > 0 && strncmp(path, baseDir, baseLength) == 0) {
+ rest = path + baseLength;
+ while (*rest == '/' || *rest == '\\') rest++;
+ }
+ while (rest[0] == '.' && (rest[1] == '/' || rest[1] == '\\')) rest += 2;
+
+ name = malloc(strlen(rest) + 1);
+ if (!name) return NULL;
+
+ strcpy(name, rest);
+ for (char *c = name; *c; c++) {
+ if (*c == '\\') *c = '/';
+ }
+ return name;
+}
+
+int kon_packWrite(const char *outPath, const uint8_t *key, const char *baseDir, const char *const *files, int count) {
+ kon_packSource_t *sources = NULL;
+ FILE *out = NULL;
+ uint8_t *table = NULL;
+ uint32_t *offsets = NULL, *sizes = NULL, *crcs = NULL;
+ uint8_t header[KON_PACK_HEADER_SIZE_];
+ uint8_t packNonce[8];
+ uint64_t position = KON_PACK_HEADER_SIZE_;
+ size_t tableSize = 0, at = 0;
+ int result = -1, made = 0;
+
+ if (!outPath || !files || count < 0) {
+ kon_packErrorText_ = "bad arguments";
+ return -1;
+ }
+
+ sources = calloc(count ? (size_t)count : 1, sizeof(kon_packSource_t));
+ offsets = calloc(count ? (size_t)count : 1, sizeof(uint32_t));
+ sizes = calloc(count ? (size_t)count : 1, sizeof(uint32_t));
+ crcs = calloc(count ? (size_t)count : 1, sizeof(uint32_t));
+ if (!sources || !offsets || !sizes || !crcs) {
+ kon_packErrorText_ = "out of memory";
+ goto done;
+ }
+
+ for (int i = 0; i < count; i++) {
+ sources[i].path = files[i];
+ sources[i].name = kon_packMakeName_(files[i], baseDir);
+ made++;
+ if (!sources[i].name || sources[i].name[0] == '\0' || strlen(sources[i].name) > 65535) {
+ kon_packErrorText_ = "bad file name";
+ goto done;
+ }
+ }
+
+ qsort(sources, (size_t)count, sizeof(kon_packSource_t), kon_packSourceCompare_);
+ for (int i = 1; i < count; i++) {
+ if (strcmp(sources[i - 1].name, sources[i].name) == 0) {
+ kon_packErrorText_ = "two files have the same name in the pack";
+ goto done;
+ }
+ }
+
+ kon_packMakeNonce_(packNonce);
+
+ out = fopen(outPath, "wb");
+ if (!out) {
+ kon_packErrorText_ = "could not create the pack file";
+ goto done;
+ }
+
+ memset(header, 0, sizeof(header));
+ if (fwrite(header, 1, sizeof(header), out) != sizeof(header)) {
+ kon_packErrorText_ = "could not write the pack";
+ goto done;
+ }
+
+ for (int i = 0; i < count; i++) {
+ FILE *in = fopen(sources[i].path, "rb");
+ uint8_t *data = NULL;
+ long length;
+
+ if (!in) {
+ kon_packErrorText_ = "could not open an input file";
+ goto done;
+ }
+ if (fseek(in, 0, SEEK_END) != 0 || (length = ftell(in)) < 0 || fseek(in, 0, SEEK_SET) != 0) {
+ kon_packErrorText_ = "could not get an input file size";
+ fclose(in);
+ goto done;
+ }
+ if ((uint64_t)length > 0xFFFFFFFFu || position + (uint64_t)length > 0xFFFFFFFFu) {
+ kon_packErrorText_ = "pack would be over 4 GB";
+ fclose(in);
+ goto done;
+ }
+
+ data = malloc((size_t)length ? (size_t)length : 1);
+ if (!data || fread(data, 1, (size_t)length, in) != (size_t)length) {
+ kon_packErrorText_ = "could not read an input file";
+ free(data);
+ fclose(in);
+ goto done;
+ }
+ fclose(in);
+
+ offsets[i] = (uint32_t)position;
+ sizes[i] = (uint32_t)length;
+ crcs[i] = kon_crc32(data, (size_t)length);
+
+ if (key) {
+ uint8_t nonce[12];
+ kon_fileNonce_(packNonce, (uint32_t)i, nonce);
+ kon_chacha20Xor(key, nonce, 0, data, data, (size_t)length);
+ }
+
+ if (fwrite(data, 1, (size_t)length, out) != (size_t)length) {
+ kon_packErrorText_ = "could not write the pack";
+ free(data);
+ goto done;
+ }
+ free(data);
+ position += (uint64_t)length;
+ }
+
+ for (int i = 0; i < count; i++) tableSize += 2 + strlen(sources[i].name) + 12;
+ if (position + tableSize > 0xFFFFFFFFu) {
+ kon_packErrorText_ = "pack would be over 4 GB";
+ goto done;
+ }
+
+ table = malloc(tableSize ? tableSize : 1);
+ if (!table) {
+ kon_packErrorText_ = "out of memory";
+ goto done;
+ }
+ for (int i = 0; i < count; i++) {
+ size_t nameLength = strlen(sources[i].name);
+
+ table[at] = (uint8_t)(nameLength & 0xFF);
+ table[at + 1] = (uint8_t)((nameLength >> 8) & 0xFF);
+ memcpy(&table[at + 2], sources[i].name, nameLength);
+ at += 2 + nameLength;
+ kon_store32_(&table[at], offsets[i]);
+ kon_store32_(&table[at + 4], sizes[i]);
+ kon_store32_(&table[at + 8], crcs[i]);
+ at += 12;
+ }
+
+ memcpy(header, KON_PACK_MAGIC_, 4);
+ kon_store32_(&header[4], KON_PACK_VERSION_);
+ kon_store32_(&header[8], key ? KON_PACK_FLAG_ENCRYPTED_ : 0u);
+ kon_store32_(&header[12], (uint32_t)count);
+ kon_store32_(&header[16], (uint32_t)position);
+ kon_store32_(&header[20], (uint32_t)tableSize);
+ kon_store32_(&header[24], kon_crc32(table, tableSize));
+ memcpy(&header[28], packNonce, 8);
+
+ if (key) {
+ uint8_t nonce[12];
+ kon_fileNonce_(packNonce, KON_PACK_TABLE_INDEX_, nonce);
+ kon_chacha20Xor(key, nonce, 0, table, table, tableSize);
+ }
+
+ if (fwrite(table, 1, tableSize, out) != tableSize || fseek(out, 0, SEEK_SET) != 0 ||
+ fwrite(header, 1, sizeof(header), out) != sizeof(header)) {
+ kon_packErrorText_ = "could not write the pack";
+ goto done;
+ }
+
+ result = 0;
+
+done:
+ if (out && fclose(out) != 0 && result == 0) {
+ kon_packErrorText_ = "could not write the pack";
+ result = -1;
+ }
+ if (sources) {
+ for (int i = 0; i < made; i++) free(sources[i].name);
+ }
+ free(sources);
+ free(offsets);
+ free(sizes);
+ free(crcs);
+ free(table);
+ return result;
+}
+
+#endif /* KONPACK_WRITER */
diff --git a/kon/konpack.h b/kon/konpack.h
index da7464c..3700900 100644
--- a/kon/konpack.h
+++ b/kon/konpack.h
@@ -63,581 +63,4 @@ void kon_chacha20Xor(const uint8_t key[KON_PACK_KEY_SIZE], const uint8_t nonce[1
int kon_packWrite(const char *outPath, const uint8_t *key, const char *baseDir, const char *const *files, int count);
#endif
-/*** implementation ***/
-
-#ifdef KONPACK_IMPLEMENTATION
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#define KON_PACK_MAGIC_ "KPAK"
-#define KON_PACK_VERSION_ 1u
-#define KON_PACK_HEADER_SIZE_ 36u
-#define KON_PACK_FLAG_ENCRYPTED_ 1u
-#define KON_PACK_TABLE_INDEX_ 0xFFFFFFFFu
-
-static const char *kon_packErrorText_ = "no error";
-
-const char *kon_packError(void) {
- return kon_packErrorText_;
-}
-
-/*** private helper ***/
-
-static uint32_t kon_load32_(const uint8_t *p) {
- return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
-}
-
-static void kon_store32_(uint8_t *p, uint32_t v) {
- p[0] = (uint8_t)(v & 0xFF);
- p[1] = (uint8_t)((v >> 8) & 0xFF);
- p[2] = (uint8_t)((v >> 16) & 0xFF);
- p[3] = (uint8_t)((v >> 24) & 0xFF);
-}
-
-/*** crc32 ***/
-
-uint32_t kon_crc32(const uint8_t *data, size_t length) {
- static uint32_t table[256];
- static int ready;
-
- if (!ready) {
- for (uint32_t i = 0; i < 256; i++) {
- uint32_t c = i;
- for (int k = 0; k < 8; k++) c = (c & 1u) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
- table[i] = c;
- }
- ready = 1;
- }
-
- uint32_t crc = 0xFFFFFFFFu;
- for (size_t i = 0; i < length; i++) crc = table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
- return crc ^ 0xFFFFFFFFu;
-}
-
-/*** chacha20 ***/
-
-#define KON_ROTL32_(v, n) (((v) << (n)) | ((v) >> (32 - (n))))
-
-#define KON_QUARTER_(a, b, c, d) \
- a += b; d ^= a; d = KON_ROTL32_(d, 16); \
- c += d; b ^= c; b = KON_ROTL32_(b, 12); \
- a += b; d ^= a; d = KON_ROTL32_(d, 8); \
- c += d; b ^= c; b = KON_ROTL32_(b, 7);
-
-static void kon_chachaBlock_(const uint32_t state[16], uint8_t out[64]) {
- uint32_t x[16];
-
- for (int i = 0; i < 16; i++) x[i] = state[i];
-
- for (int round = 0; round < 10; round++) {
- KON_QUARTER_(x[0], x[4], x[8], x[12])
- KON_QUARTER_(x[1], x[5], x[9], x[13])
- KON_QUARTER_(x[2], x[6], x[10], x[14])
- KON_QUARTER_(x[3], x[7], x[11], x[15])
- KON_QUARTER_(x[0], x[5], x[10], x[15])
- KON_QUARTER_(x[1], x[6], x[11], x[12])
- KON_QUARTER_(x[2], x[7], x[8], x[13])
- KON_QUARTER_(x[3], x[4], x[9], x[14])
- }
-
- for (int i = 0; i < 16; i++) kon_store32_(&out[i * 4], x[i] + state[i]);
-}
-
-void kon_chacha20Xor(const uint8_t key[KON_PACK_KEY_SIZE], const uint8_t nonce[12], uint32_t counter, const uint8_t *in, uint8_t *out, size_t length) {
- uint32_t state[16];
- uint8_t block[64];
-
- state[0] = 0x61707865u;
- state[1] = 0x3320646Eu;
- state[2] = 0x79622D32u;
- state[3] = 0x6B206574u;
- for (int i = 0; i < 8; i++) state[4 + i] = kon_load32_(&key[i * 4]);
- state[12] = counter;
- for (int i = 0; i < 3; i++) state[13 + i] = kon_load32_(&nonce[i * 4]);
-
- while (length > 0) {
- size_t n = length < 64 ? length : 64;
-
- kon_chachaBlock_(state, block);
- for (size_t i = 0; i < n; i++) out[i] = (uint8_t)(in[i] ^ block[i]);
-
- in += n;
- out += n;
- length -= n;
- state[12]++;
- }
-}
-
-static void kon_fileNonce_(const uint8_t packNonce[8], uint32_t index, uint8_t out[12]) {
- memcpy(out, packNonce, 8);
- kon_store32_(&out[8], index);
-}
-
-/*** reading ***/
-
-typedef struct kon_packEntry {
- const char *name;
- uint32_t offset, size, crc;
-} kon_packEntry_t;
-
-struct kon_pack {
- FILE *file;
- uint8_t key[KON_PACK_KEY_SIZE];
- uint8_t nonce[8];
- int encrypted;
- int count;
- kon_packEntry_t *entries;
- char *names;
-};
-
-void kon_packClose(kon_pack_t *pack) {
- if (!pack) return;
-
- if (pack->file) fclose(pack->file);
- free(pack->entries);
- free(pack->names);
- free(pack);
-}
-
-kon_pack_t *kon_packOpen(const char *path, const uint8_t *key) {
- uint8_t header[KON_PACK_HEADER_SIZE_];
- uint8_t *table = NULL;
- kon_pack_t *pack = NULL;
- uint32_t flags, count, tableOffset, tableSize, tableCrc;
- uint64_t fileSize;
- long end;
-
- if (!path) {
- kon_packErrorText_ = "no path";
- return NULL;
- }
-
- pack = calloc(1, sizeof(kon_pack_t));
- if (!pack) {
- kon_packErrorText_ = "out of memory";
- return NULL;
- }
-
- pack->file = fopen(path, "rb");
- if (!pack->file) {
- kon_packErrorText_ = "could not open the pack file";
- goto fail;
- }
-
- if (fread(header, 1, sizeof(header), pack->file) != sizeof(header)) {
- kon_packErrorText_ = "pack is too small";
- goto fail;
- }
- if (memcmp(header, KON_PACK_MAGIC_, 4) != 0) {
- kon_packErrorText_ = "not a pack file";
- goto fail;
- }
- if (kon_load32_(&header[4]) != KON_PACK_VERSION_) {
- kon_packErrorText_ = "unsupported pack version";
- goto fail;
- }
-
- flags = kon_load32_(&header[8]);
- count = kon_load32_(&header[12]);
- tableOffset = kon_load32_(&header[16]);
- tableSize = kon_load32_(&header[20]);
- tableCrc = kon_load32_(&header[24]);
- memcpy(pack->nonce, &header[28], 8);
-
- pack->encrypted = (flags & KON_PACK_FLAG_ENCRYPTED_) != 0;
- if (pack->encrypted) {
- if (!key) {
- kon_packErrorText_ = "pack is encrypted and no key was given";
- goto fail;
- }
- memcpy(pack->key, key, KON_PACK_KEY_SIZE);
- }
-
- if (fseek(pack->file, 0, SEEK_END) != 0 || (end = ftell(pack->file)) < 0) {
- kon_packErrorText_ = "could not get the pack size";
- goto fail;
- }
- fileSize = (uint64_t)end;
-
- if (tableOffset < KON_PACK_HEADER_SIZE_ || (uint64_t)tableOffset + tableSize > fileSize ||
- (uint64_t)count * 15u > tableSize || count > 0x7FFFFFFFu) {
- kon_packErrorText_ = "pack table is damaged";
- goto fail;
- }
-
- table = malloc(tableSize ? tableSize : 1);
- pack->entries = calloc(count ? count : 1, sizeof(kon_packEntry_t));
- pack->names = malloc((size_t)tableSize + count + 1);
- if (!table || !pack->entries || !pack->names) {
- kon_packErrorText_ = "out of memory";
- goto fail;
- }
-
- if (fseek(pack->file, (long)tableOffset, SEEK_SET) != 0 || fread(table, 1, tableSize, pack->file) != tableSize) {
- kon_packErrorText_ = "could not read the pack table";
- goto fail;
- }
-
- if (pack->encrypted) {
- uint8_t nonce[12];
- kon_fileNonce_(pack->nonce, KON_PACK_TABLE_INDEX_, nonce);
- kon_chacha20Xor(pack->key, nonce, 0, table, table, tableSize);
- }
-
- if (kon_crc32(table, tableSize) != tableCrc) {
- kon_packErrorText_ = pack->encrypted ? "wrong key or damaged pack" : "pack table is damaged";
- goto fail;
- }
-
- {
- size_t at = 0, nameAt = 0;
-
- for (uint32_t i = 0; i < count; i++) {
- size_t nameLength;
-
- if (at + 2 > tableSize) goto damaged;
- nameLength = (size_t)table[at] | ((size_t)table[at + 1] << 8);
- at += 2;
- if (nameLength == 0 || at + nameLength + 12 > tableSize) goto damaged;
-
- memcpy(&pack->names[nameAt], &table[at], nameLength);
- pack->names[nameAt + nameLength] = '\0';
- if (strlen(&pack->names[nameAt]) != nameLength) goto damaged;
- pack->entries[i].name = &pack->names[nameAt];
- nameAt += nameLength + 1;
- at += nameLength;
-
- pack->entries[i].offset = kon_load32_(&table[at]);
- pack->entries[i].size = kon_load32_(&table[at + 4]);
- pack->entries[i].crc = kon_load32_(&table[at + 8]);
- at += 12;
-
- if (pack->entries[i].offset < KON_PACK_HEADER_SIZE_ ||
- (uint64_t)pack->entries[i].offset + pack->entries[i].size > tableOffset) goto damaged;
- if (i > 0 && strcmp(pack->entries[i - 1].name, pack->entries[i].name) >= 0) goto damaged;
- }
- if (at != tableSize) goto damaged;
- }
-
- pack->count = (int)count;
- free(table);
- return pack;
-
-damaged:
- kon_packErrorText_ = "pack table is damaged";
-fail:
- free(table);
- kon_packClose(pack);
- return NULL;
-}
-
-int kon_packCount(const kon_pack_t *pack) {
- return pack ? pack->count : 0;
-}
-
-const char *kon_packName(const kon_pack_t *pack, int index) {
- if (!pack || index < 0 || index >= pack->count) return NULL;
- return pack->entries[index].name;
-}
-
-uint32_t kon_packSize(const kon_pack_t *pack, int index) {
- if (!pack || index < 0 || index >= pack->count) return 0;
- return pack->entries[index].size;
-}
-
-int kon_packFind(const kon_pack_t *pack, const char *name) {
- int low = 0, high;
-
- if (!pack || !name) return -1;
- high = pack->count - 1;
-
- while (low <= high) {
- int mid = low + (high - low) / 2;
- int cmp = strcmp(name, pack->entries[mid].name);
-
- if (cmp == 0) return mid;
- if (cmp < 0) high = mid - 1;
- else low = mid + 1;
- }
- return -1;
-}
-
-uint8_t *kon_packRead(kon_pack_t *pack, int index, size_t *size) {
- kon_packEntry_t *entry;
- uint8_t *data;
-
- if (size) *size = 0;
- if (!pack || index < 0 || index >= pack->count) {
- kon_packErrorText_ = "no such file in the pack";
- return NULL;
- }
- entry = &pack->entries[index];
-
- data = malloc((size_t)entry->size + 1);
- if (!data) {
- kon_packErrorText_ = "out of memory";
- return NULL;
- }
-
- if (fseek(pack->file, (long)entry->offset, SEEK_SET) != 0 || fread(data, 1, entry->size, pack->file) != entry->size) {
- kon_packErrorText_ = "could not read from the pack";
- free(data);
- return NULL;
- }
-
- if (pack->encrypted) {
- uint8_t nonce[12];
- kon_fileNonce_(pack->nonce, (uint32_t)index, nonce);
- kon_chacha20Xor(pack->key, nonce, 0, data, data, entry->size);
- }
-
- if (kon_crc32(data, entry->size) != entry->crc) {
- kon_packErrorText_ = "file is damaged";
- free(data);
- return NULL;
- }
-
- data[entry->size] = 0;
- if (size) *size = entry->size;
- return data;
-}
-
-/*** writing ***/
-
-#ifdef KONPACK_WRITER
-
-typedef struct kon_packSource_ {
- const char *path;
- char *name;
-} kon_packSource_t;
-
-static int kon_packSourceCompare_(const void *a, const void *b) {
- return strcmp(((const kon_packSource_t *)a)->name, ((const kon_packSource_t *)b)->name);
-}
-
-static void kon_packMakeNonce_(uint8_t nonce[8]) {
- FILE *f = fopen("/dev/urandom", "rb");
-
- if (f) {
- size_t got = fread(nonce, 1, 8, f);
- fclose(f);
- if (got == 8) return;
- }
-
- /* nothing better around, mix what changes between runs. only has to never repeat, it is not secret */
- {
- uint64_t h = 1469598103934665603ull;
- uint64_t parts[3];
- int local;
-
- parts[0] = (uint64_t)time(NULL);
- parts[1] = (uint64_t)clock();
- parts[2] = (uint64_t)(size_t)&local;
-
- for (int i = 0; i < 3; i++) {
- for (int b = 0; b < 8; b++) {
- h ^= (parts[i] >> (b * 8)) & 0xFF;
- h *= 1099511628211ull;
- }
- }
- for (int i = 0; i < 8; i++) nonce[i] = (uint8_t)(h >> (i * 8));
- }
-}
-
-static char *kon_packMakeName_(const char *path, const char *baseDir) {
- size_t baseLength = baseDir ? strlen(baseDir) : 0;
- const char *rest = path;
- char *name;
-
- if (baseLength > 0 && strncmp(path, baseDir, baseLength) == 0) {
- rest = path + baseLength;
- while (*rest == '/' || *rest == '\\') rest++;
- }
- while (rest[0] == '.' && (rest[1] == '/' || rest[1] == '\\')) rest += 2;
-
- name = malloc(strlen(rest) + 1);
- if (!name) return NULL;
-
- strcpy(name, rest);
- for (char *c = name; *c; c++) {
- if (*c == '\\') *c = '/';
- }
- return name;
-}
-
-int kon_packWrite(const char *outPath, const uint8_t *key, const char *baseDir, const char *const *files, int count) {
- kon_packSource_t *sources = NULL;
- FILE *out = NULL;
- uint8_t *table = NULL;
- uint32_t *offsets = NULL, *sizes = NULL, *crcs = NULL;
- uint8_t header[KON_PACK_HEADER_SIZE_];
- uint8_t packNonce[8];
- uint64_t position = KON_PACK_HEADER_SIZE_;
- size_t tableSize = 0, at = 0;
- int result = -1, made = 0;
-
- if (!outPath || !files || count < 0) {
- kon_packErrorText_ = "bad arguments";
- return -1;
- }
-
- sources = calloc(count ? (size_t)count : 1, sizeof(kon_packSource_t));
- offsets = calloc(count ? (size_t)count : 1, sizeof(uint32_t));
- sizes = calloc(count ? (size_t)count : 1, sizeof(uint32_t));
- crcs = calloc(count ? (size_t)count : 1, sizeof(uint32_t));
- if (!sources || !offsets || !sizes || !crcs) {
- kon_packErrorText_ = "out of memory";
- goto done;
- }
-
- for (int i = 0; i < count; i++) {
- sources[i].path = files[i];
- sources[i].name = kon_packMakeName_(files[i], baseDir);
- made++;
- if (!sources[i].name || sources[i].name[0] == '\0' || strlen(sources[i].name) > 65535) {
- kon_packErrorText_ = "bad file name";
- goto done;
- }
- }
-
- qsort(sources, (size_t)count, sizeof(kon_packSource_t), kon_packSourceCompare_);
- for (int i = 1; i < count; i++) {
- if (strcmp(sources[i - 1].name, sources[i].name) == 0) {
- kon_packErrorText_ = "two files have the same name in the pack";
- goto done;
- }
- }
-
- kon_packMakeNonce_(packNonce);
-
- out = fopen(outPath, "wb");
- if (!out) {
- kon_packErrorText_ = "could not create the pack file";
- goto done;
- }
-
- memset(header, 0, sizeof(header));
- if (fwrite(header, 1, sizeof(header), out) != sizeof(header)) {
- kon_packErrorText_ = "could not write the pack";
- goto done;
- }
-
- for (int i = 0; i < count; i++) {
- FILE *in = fopen(sources[i].path, "rb");
- uint8_t *data = NULL;
- long length;
-
- if (!in) {
- kon_packErrorText_ = "could not open an input file";
- goto done;
- }
- if (fseek(in, 0, SEEK_END) != 0 || (length = ftell(in)) < 0 || fseek(in, 0, SEEK_SET) != 0) {
- kon_packErrorText_ = "could not get an input file size";
- fclose(in);
- goto done;
- }
- if ((uint64_t)length > 0xFFFFFFFFu || position + (uint64_t)length > 0xFFFFFFFFu) {
- kon_packErrorText_ = "pack would be over 4 GB";
- fclose(in);
- goto done;
- }
-
- data = malloc((size_t)length ? (size_t)length : 1);
- if (!data || fread(data, 1, (size_t)length, in) != (size_t)length) {
- kon_packErrorText_ = "could not read an input file";
- free(data);
- fclose(in);
- goto done;
- }
- fclose(in);
-
- offsets[i] = (uint32_t)position;
- sizes[i] = (uint32_t)length;
- crcs[i] = kon_crc32(data, (size_t)length);
-
- if (key) {
- uint8_t nonce[12];
- kon_fileNonce_(packNonce, (uint32_t)i, nonce);
- kon_chacha20Xor(key, nonce, 0, data, data, (size_t)length);
- }
-
- if (fwrite(data, 1, (size_t)length, out) != (size_t)length) {
- kon_packErrorText_ = "could not write the pack";
- free(data);
- goto done;
- }
- free(data);
- position += (uint64_t)length;
- }
-
- for (int i = 0; i < count; i++) tableSize += 2 + strlen(sources[i].name) + 12;
- if (position + tableSize > 0xFFFFFFFFu) {
- kon_packErrorText_ = "pack would be over 4 GB";
- goto done;
- }
-
- table = malloc(tableSize ? tableSize : 1);
- if (!table) {
- kon_packErrorText_ = "out of memory";
- goto done;
- }
- for (int i = 0; i < count; i++) {
- size_t nameLength = strlen(sources[i].name);
-
- table[at] = (uint8_t)(nameLength & 0xFF);
- table[at + 1] = (uint8_t)((nameLength >> 8) & 0xFF);
- memcpy(&table[at + 2], sources[i].name, nameLength);
- at += 2 + nameLength;
- kon_store32_(&table[at], offsets[i]);
- kon_store32_(&table[at + 4], sizes[i]);
- kon_store32_(&table[at + 8], crcs[i]);
- at += 12;
- }
-
- memcpy(header, KON_PACK_MAGIC_, 4);
- kon_store32_(&header[4], KON_PACK_VERSION_);
- kon_store32_(&header[8], key ? KON_PACK_FLAG_ENCRYPTED_ : 0u);
- kon_store32_(&header[12], (uint32_t)count);
- kon_store32_(&header[16], (uint32_t)position);
- kon_store32_(&header[20], (uint32_t)tableSize);
- kon_store32_(&header[24], kon_crc32(table, tableSize));
- memcpy(&header[28], packNonce, 8);
-
- if (key) {
- uint8_t nonce[12];
- kon_fileNonce_(packNonce, KON_PACK_TABLE_INDEX_, nonce);
- kon_chacha20Xor(key, nonce, 0, table, table, tableSize);
- }
-
- if (fwrite(table, 1, tableSize, out) != tableSize || fseek(out, 0, SEEK_SET) != 0 ||
- fwrite(header, 1, sizeof(header), out) != sizeof(header)) {
- kon_packErrorText_ = "could not write the pack";
- goto done;
- }
-
- result = 0;
-
-done:
- if (out && fclose(out) != 0 && result == 0) {
- kon_packErrorText_ = "could not write the pack";
- result = -1;
- }
- if (sources) {
- for (int i = 0; i < made; i++) free(sources[i].name);
- }
- free(sources);
- free(offsets);
- free(sizes);
- free(crcs);
- free(table);
- return result;
-}
-
-#endif /* KONPACK_WRITER */
-
-#endif /* KONPACK_IMPLEMENTATION */
-
#endif
diff --git a/kon/kontile.c b/kon/kontile.c
new file mode 100644
index 0000000..b133656
--- /dev/null
+++ b/kon/kontile.c
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * kontile.c
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * 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.
+ */
+
+
+#include "kontile.h"
+
+static int kon_floorDiv_(int a, int b) {
+ int q = a / b;
+ return (a % b != 0 && (a < 0) != (b < 0)) ? q - 1 : q;
+}
+
+uint16_t kon_tilemapAt(const kon_tilemap_t *map, int column, int row) {
+ if (!map || !map->tiles) return 0;
+ if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return 0;
+ return map->tiles[(size_t)row * (size_t)map->columns + (size_t)column];
+}
+
+void kon_tilemapSet(kon_tilemap_t *map, int column, int row, uint16_t tile) {
+ if (!map || !map->tiles) return;
+ if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return;
+ map->tiles[(size_t)row * (size_t)map->columns + (size_t)column] = tile;
+}
+
+int kon_tilemapPixelToTile(const kon_tilemap_t *map, int x, int y, int *column, int *row) {
+ if (!map || map->tileWidth <= 0 || map->tileHeight <= 0) return 0;
+
+ int c = kon_floorDiv_(x, map->tileWidth);
+ int r = kon_floorDiv_(y, map->tileHeight);
+ if (column) *column = c;
+ if (row) *row = r;
+
+ return c >= 0 && r >= 0 && c < map->columns && r < map->rows;
+}
+
+int kon_tilemapBoxHits(const kon_tilemap_t *map, int x, int y, int width, int height, const uint8_t *solid, int solidCount) {
+ if (!map || !solid || solidCount <= 0 || map->tileWidth <= 0 || map->tileHeight <= 0 || width <= 0 || height <= 0) return 0;
+
+ int firstColumn = kon_floorDiv_(x, map->tileWidth);
+ int firstRow = kon_floorDiv_(y, map->tileHeight);
+ int lastColumn = kon_floorDiv_(x + width - 1, map->tileWidth);
+ int lastRow = kon_floorDiv_(y + height - 1, map->tileHeight);
+
+ for (int row = firstRow; row <= lastRow; row++) {
+ for (int column = firstColumn; column <= lastColumn; column++) {
+ int tile = kon_tilemapAt(map, column, row);
+ if (tile < solidCount && solid[tile]) return 1;
+ }
+ }
+ return 0;
+}
+
+void kon_drawTilemap(kon_framebuffer_t *fb, const kon_tilemap_t *map, int cameraX, int cameraY) {
+ if (!fb || !map || !map->sheet || !map->tiles) return;
+ if (map->tileWidth <= 0 || map->tileHeight <= 0) return;
+
+ int sheetColumns = map->sheetColumns > 0 ? map->sheetColumns : map->sheet->width / map->tileWidth;
+ if (sheetColumns <= 0) return;
+
+ int firstColumn = kon_floorDiv_(cameraX, map->tileWidth);
+ int firstRow = kon_floorDiv_(cameraY, map->tileHeight);
+ int lastColumn = kon_floorDiv_(cameraX + fb->width - 1, map->tileWidth);
+ int lastRow = kon_floorDiv_(cameraY + fb->height - 1, map->tileHeight);
+
+ if (firstColumn < 0) firstColumn = 0;
+ if (firstRow < 0) firstRow = 0;
+ if (lastColumn > map->columns - 1) lastColumn = map->columns - 1;
+ if (lastRow > map->rows - 1) lastRow = map->rows - 1;
+
+ for (int row = firstRow; row <= lastRow; row++) {
+ for (int column = firstColumn; column <= lastColumn; column++) {
+ int tile = map->tiles[(size_t)row * (size_t)map->columns + (size_t)column];
+ if (tile == 0) continue;
+ tile--;
+
+ kon_rect_t src = KON_RECT((tile % sheetColumns) * map->tileWidth, (tile / sheetColumns) * map->tileHeight, map->tileWidth, map->tileHeight);
+ kon_drawImageRegion(fb, column * map->tileWidth - cameraX, row * map->tileHeight - cameraY, map->sheet, src);
+ }
+ }
+}
diff --git a/kon/kontile.h b/kon/kontile.h
index d416d34..f671da9 100644
--- a/kon/kontile.h
+++ b/kon/kontile.h
@@ -55,84 +55,4 @@ int kon_tilemapBoxHits(const kon_tilemap_t *map, int x, int y, int width, int he
/* cameraX, cameraY is the map pixel shown at the top left of fb */
void kon_drawTilemap(kon_framebuffer_t *fb, const kon_tilemap_t *map, int cameraX, int cameraY);
-/*** implementation ***/
-
-#ifdef KONTILE_IMPLEMENTATION
-
-static int kon_floorDiv_(int a, int b) {
- int q = a / b;
- return (a % b != 0 && (a < 0) != (b < 0)) ? q - 1 : q;
-}
-
-uint16_t kon_tilemapAt(const kon_tilemap_t *map, int column, int row) {
- if (!map || !map->tiles) return 0;
- if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return 0;
- return map->tiles[(size_t)row * (size_t)map->columns + (size_t)column];
-}
-
-void kon_tilemapSet(kon_tilemap_t *map, int column, int row, uint16_t tile) {
- if (!map || !map->tiles) return;
- if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return;
- map->tiles[(size_t)row * (size_t)map->columns + (size_t)column] = tile;
-}
-
-int kon_tilemapPixelToTile(const kon_tilemap_t *map, int x, int y, int *column, int *row) {
- if (!map || map->tileWidth <= 0 || map->tileHeight <= 0) return 0;
-
- int c = kon_floorDiv_(x, map->tileWidth);
- int r = kon_floorDiv_(y, map->tileHeight);
- if (column) *column = c;
- if (row) *row = r;
-
- return c >= 0 && r >= 0 && c < map->columns && r < map->rows;
-}
-
-int kon_tilemapBoxHits(const kon_tilemap_t *map, int x, int y, int width, int height, const uint8_t *solid, int solidCount) {
- if (!map || !solid || solidCount <= 0 || map->tileWidth <= 0 || map->tileHeight <= 0 || width <= 0 || height <= 0) return 0;
-
- int firstColumn = kon_floorDiv_(x, map->tileWidth);
- int firstRow = kon_floorDiv_(y, map->tileHeight);
- int lastColumn = kon_floorDiv_(x + width - 1, map->tileWidth);
- int lastRow = kon_floorDiv_(y + height - 1, map->tileHeight);
-
- for (int row = firstRow; row <= lastRow; row++) {
- for (int column = firstColumn; column <= lastColumn; column++) {
- int tile = kon_tilemapAt(map, column, row);
- if (tile < solidCount && solid[tile]) return 1;
- }
- }
- return 0;
-}
-
-void kon_drawTilemap(kon_framebuffer_t *fb, const kon_tilemap_t *map, int cameraX, int cameraY) {
- if (!fb || !map || !map->sheet || !map->tiles) return;
- if (map->tileWidth <= 0 || map->tileHeight <= 0) return;
-
- int sheetColumns = map->sheetColumns > 0 ? map->sheetColumns : map->sheet->width / map->tileWidth;
- if (sheetColumns <= 0) return;
-
- int firstColumn = kon_floorDiv_(cameraX, map->tileWidth);
- int firstRow = kon_floorDiv_(cameraY, map->tileHeight);
- int lastColumn = kon_floorDiv_(cameraX + fb->width - 1, map->tileWidth);
- int lastRow = kon_floorDiv_(cameraY + fb->height - 1, map->tileHeight);
-
- if (firstColumn < 0) firstColumn = 0;
- if (firstRow < 0) firstRow = 0;
- if (lastColumn > map->columns - 1) lastColumn = map->columns - 1;
- if (lastRow > map->rows - 1) lastRow = map->rows - 1;
-
- for (int row = firstRow; row <= lastRow; row++) {
- for (int column = firstColumn; column <= lastColumn; column++) {
- int tile = map->tiles[(size_t)row * (size_t)map->columns + (size_t)column];
- if (tile == 0) continue;
- tile--;
-
- kon_rect_t src = KON_RECT((tile % sheetColumns) * map->tileWidth, (tile / sheetColumns) * map->tileHeight, map->tileWidth, map->tileHeight);
- kon_drawImageRegion(fb, column * map->tileWidth - cameraX, row * map->tileHeight - cameraY, map->sheet, src);
- }
- }
-}
-
-#endif /* KONTILE_IMPLEMENTATION */
-
#endif
diff --git a/kon/konui.c b/kon/konui.c
new file mode 100644
index 0000000..777f24d
--- /dev/null
+++ b/kon/konui.c
@@ -0,0 +1,556 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konui.c
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * 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.
+ */
+
+
+#include "konui.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#define KON_UI_IDS 16
+#define KON_UI_TEXTQUEUE 32
+
+struct kon_ui {
+ kon_font_t *font;
+ kon_framebuffer_t *fb;
+ kon_uiTheme_t theme;
+ int lineH, ascent;
+
+ int mouseX, mouseY;
+ int mouseDown, mousePressed, mouseReleased;
+ int keyPressed[KON_KEY_COUNT];
+ unsigned int text[KON_UI_TEXTQUEUE];
+ int textCount;
+
+ uint32_t active, focus;
+ uint32_t topPanel, topPanelNext;
+ int wantsMouse;
+ int focusKept;
+ int dragX, dragY;
+
+ uint32_t ids[KON_UI_IDS];
+ int idCount;
+
+ kon_uiPanel_t *panel;
+ uint32_t panelId;
+ int cx, cy, availW;
+ int sameLine;
+ kon_rect_t last;
+ int cursor;
+};
+
+/*** private helpers ***/
+
+static uint32_t kon_uiHash_(const kon_ui_t *ui, const char *s)
+{
+ uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u;
+
+ while (*s) {
+ h ^= (unsigned char)*s++;
+ h *= 16777619u;
+ }
+ return h ? h : 1;
+}
+
+static size_t kon_uiVisibleLength_(const char *s)
+{
+ size_t n = 0;
+
+ while (s[n]) {
+ if (s[n] == '#' && s[n + 1] == '#') break;
+ n++;
+ }
+ return n;
+}
+
+static int kon_uiInside_(const kon_ui_t *ui, kon_rect_t r)
+{
+ return ui->mouseX >= r.x && ui->mouseX < r.x + r.w && ui->mouseY >= r.y && ui->mouseY < r.y + r.h;
+}
+
+/* the mouse can only reach this widget if its panel is the topmost one under it */
+static int kon_uiReachable_(const kon_ui_t *ui)
+{
+ return ui->topPanel == ui->panelId;
+}
+
+static int kon_uiTextWidth_(const kon_ui_t *ui, const char *s, size_t n)
+{
+ char buffer[256];
+ int w = 0;
+
+ if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1;
+ memcpy(buffer, s, n);
+ buffer[n] = 0;
+ kon_measureText(ui->font, buffer, &w, NULL);
+ return w;
+}
+
+static void kon_uiDrawText_(const kon_ui_t *ui, int x, int y, const char *s, size_t n, uint32_t color)
+{
+ char buffer[256];
+
+ if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1;
+ memcpy(buffer, s, n);
+ buffer[n] = 0;
+ kon_drawText(ui->fb, ui->font, x, y + ui->ascent, buffer, color);
+}
+
+static kon_rect_t kon_uiNextRect_(kon_ui_t *ui, int w, int h)
+{
+ kon_rect_t r;
+
+ if (ui->sameLine) {
+ r.x = ui->last.x + ui->last.w + ui->theme.spacing;
+ r.y = ui->last.y;
+ } else {
+ r.x = ui->cx;
+ r.y = ui->cy;
+ }
+ r.w = w > 0 ? w : ui->availW;
+ r.h = h;
+
+ if (!ui->sameLine) ui->cy += h + ui->theme.spacing;
+ ui->sameLine = 0;
+ ui->last = r;
+ return r;
+}
+
+static void kon_uiFill_(const kon_ui_t *ui, kon_rect_t r, uint32_t color)
+{
+ kon_fillRectangle(ui->fb, r.x, r.y, r.w, r.h, color);
+}
+
+static void kon_uiOutline_(const kon_ui_t *ui, kon_rect_t r, uint32_t color)
+{
+ kon_drawRectangle(ui->fb, r.x, r.y, r.w - 1, r.h - 1, color);
+}
+
+/* hover and press handling shared by everything clickable. returns 1 when it was clicked */
+static int kon_uiBehave_(kon_ui_t *ui, uint32_t id, kon_rect_t r, int *hovered)
+{
+ int over = kon_uiReachable_(ui) && kon_uiInside_(ui, r) && (ui->active == 0 || ui->active == id);
+ int clicked = 0;
+
+ if (over && ui->mousePressed) ui->active = id;
+ if (ui->active == id && ui->mouseReleased) {
+ clicked = over;
+ ui->active = 0;
+ }
+ if (hovered) *hovered = over;
+ return clicked;
+}
+
+static uint32_t kon_uiWidgetColor_(const kon_ui_t *ui, uint32_t id, int hovered)
+{
+ if (ui->active == id) return ui->theme.active;
+ return hovered ? ui->theme.hover : ui->theme.widget;
+}
+
+/*** ui ***/
+
+kon_ui_t *kon_uiCreate(kon_font_t *font)
+{
+ kon_ui_t *ui;
+ int h = 0;
+
+ if (!font) return NULL;
+ ui = calloc(1, sizeof(kon_ui_t));
+ if (!ui) return NULL;
+
+ ui->font = font;
+ kon_measureText(font, "A", NULL, &h);
+ ui->ascent = h * 4 / 5;
+ ui->lineH = h + 4;
+
+ ui->theme.panel = 0xE0202028;
+ ui->theme.title = 0xFF383848;
+ ui->theme.text = 0xFFE8E8F0;
+ ui->theme.textDim = 0xFF8888A0;
+ ui->theme.widget = 0xFF404058;
+ ui->theme.hover = 0xFF505070;
+ ui->theme.active = 0xFF6060A0;
+ ui->theme.accent = 0xFF7088E0;
+ ui->theme.border = 0xFF101018;
+ ui->theme.padding = 4;
+ ui->theme.spacing = 3;
+ return ui;
+}
+
+void kon_uiDestroy(kon_ui_t *ui)
+{
+ free(ui);
+}
+
+kon_uiTheme_t *kon_uiTheme(kon_ui_t *ui)
+{
+ return &ui->theme;
+}
+
+void kon_uiFeedEvent(kon_ui_t *ui, const kon_event_t *event)
+{
+ switch (event->type) {
+ case KON_EVENT_MOUSE_MOVE:
+ ui->mouseX = event->mouseX;
+ ui->mouseY = event->mouseY;
+ break;
+ case KON_EVENT_MOUSE_DOWN:
+ ui->mouseX = event->mouseX;
+ ui->mouseY = event->mouseY;
+ if (event->mouseButton == KON_MOUSE_LEFT) {
+ ui->mouseDown = 1;
+ ui->mousePressed = 1;
+ }
+ break;
+ case KON_EVENT_MOUSE_UP:
+ ui->mouseX = event->mouseX;
+ ui->mouseY = event->mouseY;
+ if (event->mouseButton == KON_MOUSE_LEFT) {
+ ui->mouseDown = 0;
+ ui->mouseReleased = 1;
+ }
+ break;
+ case KON_EVENT_KEY_DOWN:
+ if (event->key >= 0 && event->key < KON_KEY_COUNT) ui->keyPressed[event->key] = 1;
+ break;
+ case KON_EVENT_TEXT:
+ if (ui->textCount < KON_UI_TEXTQUEUE) ui->text[ui->textCount++] = event->codepoint;
+ break;
+ default:
+ break;
+ }
+}
+
+void kon_uiBegin(kon_ui_t *ui, kon_framebuffer_t *fb)
+{
+ ui->fb = fb;
+ ui->topPanel = ui->topPanelNext;
+ ui->topPanelNext = 0;
+ ui->focusKept = 0;
+ ui->panel = NULL;
+ ui->panelId = 0;
+ ui->idCount = 0;
+ ui->sameLine = 0;
+ ui->cx = ui->theme.padding;
+ ui->cy = ui->theme.padding;
+ ui->availW = fb->width - ui->theme.padding * 2;
+}
+
+void kon_uiEnd(kon_ui_t *ui)
+{
+ if (ui->mousePressed && !ui->focusKept) ui->focus = 0;
+ if (!ui->mouseDown && ui->active) ui->active = 0;
+
+ ui->wantsMouse = ui->topPanelNext != 0 || ui->active != 0;
+
+ ui->mousePressed = 0;
+ ui->mouseReleased = 0;
+ memset(ui->keyPressed, 0, sizeof(ui->keyPressed));
+ ui->textCount = 0;
+ ui->fb = NULL;
+}
+
+int kon_uiWantsMouse(const kon_ui_t *ui)
+{
+ return ui->wantsMouse;
+}
+
+int kon_uiWantsKeyboard(const kon_ui_t *ui)
+{
+ return ui->focus != 0;
+}
+
+void kon_uiPushId(kon_ui_t *ui, int id)
+{
+ uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u;
+
+ if (ui->idCount == KON_UI_IDS) return;
+ h ^= (uint32_t)id;
+ h *= 16777619u;
+ ui->ids[ui->idCount++] = h;
+}
+
+void kon_uiPopId(kon_ui_t *ui)
+{
+ if (ui->idCount) ui->idCount--;
+}
+
+kon_rect_t kon_uiLastRect(const kon_ui_t *ui)
+{
+ return ui->last;
+}
+
+/*** panels ***/
+
+int kon_uiBeginPanel(kon_ui_t *ui, const char *title, kon_uiPanel_t *panel)
+{
+ uint32_t id = kon_uiHash_(ui, title);
+ uint32_t dragId = id ^ 0x9E3779B9u;
+ int pad = ui->theme.padding, titleH = ui->lineH + pad;
+ kon_rect_t whole, bar, arrow;
+ int h, hovered;
+
+ if (ui->panel) return 0;
+ if (panel->w <= 0) panel->w = 220;
+
+ h = panel->collapsed ? titleH : (panel->h > 0 ? panel->h : panel->contentH);
+ if (h < titleH) h = titleH;
+
+ whole = KON_RECT(panel->x, panel->y, panel->w, h);
+ bar = KON_RECT(panel->x, panel->y, panel->w, titleH);
+ arrow = KON_RECT(panel->x, panel->y, titleH, titleH);
+
+ if (kon_uiInside_(ui, whole)) ui->topPanelNext = id;
+
+ ui->panelId = id;
+ ui->panel = panel;
+
+ if (kon_uiBehave_(ui, id ^ 1u, arrow, &hovered)) panel->collapsed = !panel->collapsed;
+
+ if (ui->active == 0 && ui->mousePressed && kon_uiReachable_(ui) && kon_uiInside_(ui, bar) && !kon_uiInside_(ui, arrow)) {
+ ui->active = dragId;
+ ui->dragX = ui->mouseX - panel->x;
+ ui->dragY = ui->mouseY - panel->y;
+ }
+ if (ui->active == dragId) {
+ panel->x = ui->mouseX - ui->dragX;
+ panel->y = ui->mouseY - ui->dragY;
+ if (panel->x < 40 - panel->w) panel->x = 40 - panel->w;
+ if (panel->x > ui->fb->width - 40) panel->x = ui->fb->width - 40;
+ if (panel->y < 0) panel->y = 0;
+ if (panel->y > ui->fb->height - titleH) panel->y = ui->fb->height - titleH;
+ if (ui->mouseReleased) ui->active = 0;
+ whole.x = bar.x = arrow.x = panel->x;
+ whole.y = bar.y = arrow.y = panel->y;
+ }
+
+ kon_uiFill_(ui, whole, ui->theme.panel);
+ kon_uiFill_(ui, bar, ui->theme.title);
+ kon_uiOutline_(ui, whole, ui->theme.border);
+ kon_uiDrawText_(ui, arrow.x + pad, arrow.y + pad / 2, panel->collapsed ? "+" : "-", 1, ui->theme.textDim);
+ kon_uiDrawText_(ui, panel->x + titleH, panel->y + pad / 2, title, kon_uiVisibleLength_(title), ui->theme.text);
+
+ ui->cx = panel->x + pad;
+ ui->cy = panel->y + titleH + pad;
+ ui->availW = panel->w - pad * 2;
+ ui->sameLine = 0;
+ return !panel->collapsed;
+}
+
+void kon_uiEndPanel(kon_ui_t *ui)
+{
+ if (!ui->panel) return;
+ ui->panel->contentH = ui->cy - ui->panel->y + ui->theme.padding - ui->theme.spacing;
+ ui->panel = NULL;
+ ui->panelId = 0;
+ ui->cx = ui->theme.padding;
+ ui->cy = ui->theme.padding;
+ ui->availW = ui->fb->width - ui->theme.padding * 2;
+}
+
+/*** widgets ***/
+
+void kon_uiLabel(kon_ui_t *ui, const char *text)
+{
+ size_t n = kon_uiVisibleLength_(text);
+ kon_rect_t r = kon_uiNextRect_(ui, kon_uiTextWidth_(ui, text, n) + 1, ui->lineH);
+
+ kon_uiDrawText_(ui, r.x, r.y, text, n, ui->theme.text);
+}
+
+void kon_uiLabelf(kon_ui_t *ui, const char *format, ...)
+{
+ char buffer[256];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
+ kon_uiLabel(ui, buffer);
+}
+
+int kon_uiButton(kon_ui_t *ui, const char *label)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int pad = ui->theme.padding, textW = kon_uiTextWidth_(ui, label, n);
+ kon_rect_t r = kon_uiNextRect_(ui, ui->sameLine ? textW + pad * 2 : 0, ui->lineH + pad);
+ int hovered, clicked;
+
+ clicked = kon_uiBehave_(ui, id, r, &hovered);
+ kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered));
+ kon_uiOutline_(ui, r, ui->theme.border);
+ kon_uiDrawText_(ui, r.x + (r.w - textW) / 2, r.y + pad / 2 + 1, label, n, ui->theme.text);
+ return clicked;
+}
+
+int kon_uiCheckbox(kon_ui_t *ui, const char *label, int *value)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int box = ui->lineH, pad = ui->theme.padding;
+ kon_rect_t r = kon_uiNextRect_(ui, box + pad + kon_uiTextWidth_(ui, label, n) + 1, box);
+ kon_rect_t b = KON_RECT(r.x, r.y, box, box);
+ int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered);
+
+ if (clicked) *value = !*value;
+ kon_uiFill_(ui, b, kon_uiWidgetColor_(ui, id, hovered));
+ kon_uiOutline_(ui, b, ui->theme.border);
+ if (*value) kon_uiFill_(ui, KON_RECT(b.x + 3, b.y + 3, b.w - 6, b.h - 6), ui->theme.accent);
+ kon_uiDrawText_(ui, r.x + box + pad, r.y, label, n, ui->theme.text);
+ return clicked;
+}
+
+int kon_uiSliderFloat(kon_ui_t *ui, const char *label, float *value, float min, float max)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int pad = ui->theme.padding;
+ kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad);
+ char text[64];
+ int hovered, changed = 0, fill;
+ float t;
+
+ kon_uiBehave_(ui, id, r, &hovered);
+
+ if (ui->active == id && r.w > 1 && max > min) {
+ float v;
+
+ t = (float)(ui->mouseX - r.x) / (float)(r.w - 1);
+ if (t < 0.0f) t = 0.0f;
+ if (t > 1.0f) t = 1.0f;
+ v = min + t * (max - min);
+ if (v != *value) {
+ *value = v;
+ changed = 1;
+ }
+ }
+
+ t = max > min ? (*value - min) / (max - min) : 0.0f;
+ if (t < 0.0f) t = 0.0f;
+ if (t > 1.0f) t = 1.0f;
+ fill = (int)(t * (float)(r.w - 2));
+
+ kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered));
+ kon_uiFill_(ui, KON_RECT(r.x + 1, r.y + 1, fill, r.h - 2), ui->theme.accent);
+ kon_uiOutline_(ui, r, ui->theme.border);
+
+ snprintf(text, sizeof(text), "%.*s: %.2f", (int)(n > 40 ? 40 : n), label, (double)*value);
+ kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, text, strlen(text), ui->theme.text);
+ return changed;
+}
+
+int kon_uiSliderInt(kon_ui_t *ui, const char *label, int *value, int min, int max)
+{
+ float v = (float)*value;
+ int changed = kon_uiSliderFloat(ui, label, &v, (float)min, (float)max);
+
+ if (changed) *value = (int)(v + (v < 0 ? -0.5f : 0.5f));
+ return changed;
+}
+
+int kon_uiSelectable(kon_ui_t *ui, const char *label, int selected)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int pad = ui->theme.padding;
+ kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH);
+ int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered);
+
+ if (selected) kon_uiFill_(ui, r, ui->theme.active);
+ else if (hovered) kon_uiFill_(ui, r, ui->theme.hover);
+ kon_uiDrawText_(ui, r.x + pad, r.y, label, n, ui->theme.text);
+ return clicked;
+}
+
+int kon_uiTextInput(kon_ui_t *ui, const char *label, char *buffer, size_t size)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int pad = ui->theme.padding, hovered, entered = 0, i;
+ size_t length = strlen(buffer);
+ kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad);
+
+ kon_uiBehave_(ui, id, r, &hovered);
+ if (hovered && ui->mousePressed) {
+ ui->focus = id;
+ ui->cursor = (int)length;
+ }
+ if (ui->focus == id) {
+ ui->focusKept = ui->focusKept || (hovered && ui->mousePressed);
+ if (ui->cursor > (int)length) ui->cursor = (int)length;
+
+ for (i = 0; i < ui->textCount; i++) {
+ unsigned int c = ui->text[i];
+
+ if (c < 32 || c > 126 || length + 1 >= size) continue;
+ memmove(buffer + ui->cursor + 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1);
+ buffer[ui->cursor++] = (char)c;
+ length++;
+ }
+ if (ui->keyPressed[KON_KEY_BACKSPACE] && ui->cursor > 0) {
+ memmove(buffer + ui->cursor - 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1);
+ ui->cursor--;
+ length--;
+ }
+ if (ui->keyPressed[KON_KEY_DELETE] && ui->cursor < (int)length) {
+ memmove(buffer + ui->cursor, buffer + ui->cursor + 1, length - (size_t)ui->cursor);
+ length--;
+ }
+ if (ui->keyPressed[KON_KEY_LEFT] && ui->cursor > 0) ui->cursor--;
+ if (ui->keyPressed[KON_KEY_RIGHT] && ui->cursor < (int)length) ui->cursor++;
+ if (ui->keyPressed[KON_KEY_ENTER]) entered = 1;
+ if (ui->keyPressed[KON_KEY_ESCAPE]) ui->focus = 0;
+ }
+
+ kon_uiFill_(ui, r, ui->focus == id ? ui->theme.hover : ui->theme.widget);
+ kon_uiOutline_(ui, r, ui->focus == id ? ui->theme.accent : ui->theme.border);
+
+ if (length == 0 && ui->focus != id) {
+ kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, label, n, ui->theme.textDim);
+ } else {
+ kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, buffer, length, ui->theme.text);
+ if (ui->focus == id) {
+ int cx = r.x + pad + kon_uiTextWidth_(ui, buffer, (size_t)ui->cursor);
+
+ kon_uiFill_(ui, KON_RECT(cx, r.y + 2, 1, r.h - 4), ui->theme.text);
+ }
+ }
+ return entered;
+}
+
+void kon_uiSeparator(kon_ui_t *ui)
+{
+ kon_rect_t r = kon_uiNextRect_(ui, 0, 1);
+
+ kon_uiFill_(ui, r, ui->theme.border);
+}
+
+void kon_uiSameLine(kon_ui_t *ui)
+{
+ ui->sameLine = 1;
+}
diff --git a/kon/konui.h b/kon/konui.h
index e03b7fe..1a5e581 100644
--- a/kon/konui.h
+++ b/kon/konui.h
@@ -87,538 +87,3 @@ void kon_uiPopId(kon_ui_t *ui);
kon_rect_t kon_uiLastRect(const kon_ui_t *ui);
#endif
-
-/*** implementation ***/
-
-#ifdef KONUI_IMPLEMENTATION
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdarg.h>
-
-#define KON_UI_IDS 16
-#define KON_UI_TEXTQUEUE 32
-
-struct kon_ui {
- kon_font_t *font;
- kon_framebuffer_t *fb;
- kon_uiTheme_t theme;
- int lineH, ascent;
-
- int mouseX, mouseY;
- int mouseDown, mousePressed, mouseReleased;
- int keyPressed[KON_KEY_COUNT];
- unsigned int text[KON_UI_TEXTQUEUE];
- int textCount;
-
- uint32_t active, focus;
- uint32_t topPanel, topPanelNext;
- int wantsMouse;
- int focusKept;
- int dragX, dragY;
-
- uint32_t ids[KON_UI_IDS];
- int idCount;
-
- kon_uiPanel_t *panel;
- uint32_t panelId;
- int cx, cy, availW;
- int sameLine;
- kon_rect_t last;
- int cursor;
-};
-
-/*** private helpers ***/
-
-static uint32_t kon_uiHash_(const kon_ui_t *ui, const char *s)
-{
- uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u;
-
- while (*s) {
- h ^= (unsigned char)*s++;
- h *= 16777619u;
- }
- return h ? h : 1;
-}
-
-static size_t kon_uiVisibleLength_(const char *s)
-{
- size_t n = 0;
-
- while (s[n]) {
- if (s[n] == '#' && s[n + 1] == '#') break;
- n++;
- }
- return n;
-}
-
-static int kon_uiInside_(const kon_ui_t *ui, kon_rect_t r)
-{
- return ui->mouseX >= r.x && ui->mouseX < r.x + r.w && ui->mouseY >= r.y && ui->mouseY < r.y + r.h;
-}
-
-/* the mouse can only reach this widget if its panel is the topmost one under it */
-static int kon_uiReachable_(const kon_ui_t *ui)
-{
- return ui->topPanel == ui->panelId;
-}
-
-static int kon_uiTextWidth_(const kon_ui_t *ui, const char *s, size_t n)
-{
- char buffer[256];
- int w = 0;
-
- if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1;
- memcpy(buffer, s, n);
- buffer[n] = 0;
- kon_measureText(ui->font, buffer, &w, NULL);
- return w;
-}
-
-static void kon_uiDrawText_(const kon_ui_t *ui, int x, int y, const char *s, size_t n, uint32_t color)
-{
- char buffer[256];
-
- if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1;
- memcpy(buffer, s, n);
- buffer[n] = 0;
- kon_drawText(ui->fb, ui->font, x, y + ui->ascent, buffer, color);
-}
-
-static kon_rect_t kon_uiNextRect_(kon_ui_t *ui, int w, int h)
-{
- kon_rect_t r;
-
- if (ui->sameLine) {
- r.x = ui->last.x + ui->last.w + ui->theme.spacing;
- r.y = ui->last.y;
- } else {
- r.x = ui->cx;
- r.y = ui->cy;
- }
- r.w = w > 0 ? w : ui->availW;
- r.h = h;
-
- if (!ui->sameLine) ui->cy += h + ui->theme.spacing;
- ui->sameLine = 0;
- ui->last = r;
- return r;
-}
-
-static void kon_uiFill_(const kon_ui_t *ui, kon_rect_t r, uint32_t color)
-{
- kon_fillRectangle(ui->fb, r.x, r.y, r.w, r.h, color);
-}
-
-static void kon_uiOutline_(const kon_ui_t *ui, kon_rect_t r, uint32_t color)
-{
- kon_drawRectangle(ui->fb, r.x, r.y, r.w - 1, r.h - 1, color);
-}
-
-/* hover and press handling shared by everything clickable. returns 1 when it was clicked */
-static int kon_uiBehave_(kon_ui_t *ui, uint32_t id, kon_rect_t r, int *hovered)
-{
- int over = kon_uiReachable_(ui) && kon_uiInside_(ui, r) && (ui->active == 0 || ui->active == id);
- int clicked = 0;
-
- if (over && ui->mousePressed) ui->active = id;
- if (ui->active == id && ui->mouseReleased) {
- clicked = over;
- ui->active = 0;
- }
- if (hovered) *hovered = over;
- return clicked;
-}
-
-static uint32_t kon_uiWidgetColor_(const kon_ui_t *ui, uint32_t id, int hovered)
-{
- if (ui->active == id) return ui->theme.active;
- return hovered ? ui->theme.hover : ui->theme.widget;
-}
-
-/*** ui ***/
-
-kon_ui_t *kon_uiCreate(kon_font_t *font)
-{
- kon_ui_t *ui;
- int h = 0;
-
- if (!font) return NULL;
- ui = calloc(1, sizeof(kon_ui_t));
- if (!ui) return NULL;
-
- ui->font = font;
- kon_measureText(font, "A", NULL, &h);
- ui->ascent = h * 4 / 5;
- ui->lineH = h + 4;
-
- ui->theme.panel = 0xE0202028;
- ui->theme.title = 0xFF383848;
- ui->theme.text = 0xFFE8E8F0;
- ui->theme.textDim = 0xFF8888A0;
- ui->theme.widget = 0xFF404058;
- ui->theme.hover = 0xFF505070;
- ui->theme.active = 0xFF6060A0;
- ui->theme.accent = 0xFF7088E0;
- ui->theme.border = 0xFF101018;
- ui->theme.padding = 4;
- ui->theme.spacing = 3;
- return ui;
-}
-
-void kon_uiDestroy(kon_ui_t *ui)
-{
- free(ui);
-}
-
-kon_uiTheme_t *kon_uiTheme(kon_ui_t *ui)
-{
- return &ui->theme;
-}
-
-void kon_uiFeedEvent(kon_ui_t *ui, const kon_event_t *event)
-{
- switch (event->type) {
- case KON_EVENT_MOUSE_MOVE:
- ui->mouseX = event->mouseX;
- ui->mouseY = event->mouseY;
- break;
- case KON_EVENT_MOUSE_DOWN:
- ui->mouseX = event->mouseX;
- ui->mouseY = event->mouseY;
- if (event->mouseButton == KON_MOUSE_LEFT) {
- ui->mouseDown = 1;
- ui->mousePressed = 1;
- }
- break;
- case KON_EVENT_MOUSE_UP:
- ui->mouseX = event->mouseX;
- ui->mouseY = event->mouseY;
- if (event->mouseButton == KON_MOUSE_LEFT) {
- ui->mouseDown = 0;
- ui->mouseReleased = 1;
- }
- break;
- case KON_EVENT_KEY_DOWN:
- if (event->key >= 0 && event->key < KON_KEY_COUNT) ui->keyPressed[event->key] = 1;
- break;
- case KON_EVENT_TEXT:
- if (ui->textCount < KON_UI_TEXTQUEUE) ui->text[ui->textCount++] = event->codepoint;
- break;
- default:
- break;
- }
-}
-
-void kon_uiBegin(kon_ui_t *ui, kon_framebuffer_t *fb)
-{
- ui->fb = fb;
- ui->topPanel = ui->topPanelNext;
- ui->topPanelNext = 0;
- ui->focusKept = 0;
- ui->panel = NULL;
- ui->panelId = 0;
- ui->idCount = 0;
- ui->sameLine = 0;
- ui->cx = ui->theme.padding;
- ui->cy = ui->theme.padding;
- ui->availW = fb->width - ui->theme.padding * 2;
-}
-
-void kon_uiEnd(kon_ui_t *ui)
-{
- if (ui->mousePressed && !ui->focusKept) ui->focus = 0;
- if (!ui->mouseDown && ui->active) ui->active = 0;
-
- ui->wantsMouse = ui->topPanelNext != 0 || ui->active != 0;
-
- ui->mousePressed = 0;
- ui->mouseReleased = 0;
- memset(ui->keyPressed, 0, sizeof(ui->keyPressed));
- ui->textCount = 0;
- ui->fb = NULL;
-}
-
-int kon_uiWantsMouse(const kon_ui_t *ui)
-{
- return ui->wantsMouse;
-}
-
-int kon_uiWantsKeyboard(const kon_ui_t *ui)
-{
- return ui->focus != 0;
-}
-
-void kon_uiPushId(kon_ui_t *ui, int id)
-{
- uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u;
-
- if (ui->idCount == KON_UI_IDS) return;
- h ^= (uint32_t)id;
- h *= 16777619u;
- ui->ids[ui->idCount++] = h;
-}
-
-void kon_uiPopId(kon_ui_t *ui)
-{
- if (ui->idCount) ui->idCount--;
-}
-
-kon_rect_t kon_uiLastRect(const kon_ui_t *ui)
-{
- return ui->last;
-}
-
-/*** panels ***/
-
-int kon_uiBeginPanel(kon_ui_t *ui, const char *title, kon_uiPanel_t *panel)
-{
- uint32_t id = kon_uiHash_(ui, title);
- uint32_t dragId = id ^ 0x9E3779B9u;
- int pad = ui->theme.padding, titleH = ui->lineH + pad;
- kon_rect_t whole, bar, arrow;
- int h, hovered;
-
- if (ui->panel) return 0;
- if (panel->w <= 0) panel->w = 220;
-
- h = panel->collapsed ? titleH : (panel->h > 0 ? panel->h : panel->contentH);
- if (h < titleH) h = titleH;
-
- whole = KON_RECT(panel->x, panel->y, panel->w, h);
- bar = KON_RECT(panel->x, panel->y, panel->w, titleH);
- arrow = KON_RECT(panel->x, panel->y, titleH, titleH);
-
- if (kon_uiInside_(ui, whole)) ui->topPanelNext = id;
-
- ui->panelId = id;
- ui->panel = panel;
-
- if (kon_uiBehave_(ui, id ^ 1u, arrow, &hovered)) panel->collapsed = !panel->collapsed;
-
- if (ui->active == 0 && ui->mousePressed && kon_uiReachable_(ui) && kon_uiInside_(ui, bar) && !kon_uiInside_(ui, arrow)) {
- ui->active = dragId;
- ui->dragX = ui->mouseX - panel->x;
- ui->dragY = ui->mouseY - panel->y;
- }
- if (ui->active == dragId) {
- panel->x = ui->mouseX - ui->dragX;
- panel->y = ui->mouseY - ui->dragY;
- if (panel->x < 40 - panel->w) panel->x = 40 - panel->w;
- if (panel->x > ui->fb->width - 40) panel->x = ui->fb->width - 40;
- if (panel->y < 0) panel->y = 0;
- if (panel->y > ui->fb->height - titleH) panel->y = ui->fb->height - titleH;
- if (ui->mouseReleased) ui->active = 0;
- whole.x = bar.x = arrow.x = panel->x;
- whole.y = bar.y = arrow.y = panel->y;
- }
-
- kon_uiFill_(ui, whole, ui->theme.panel);
- kon_uiFill_(ui, bar, ui->theme.title);
- kon_uiOutline_(ui, whole, ui->theme.border);
- kon_uiDrawText_(ui, arrow.x + pad, arrow.y + pad / 2, panel->collapsed ? "+" : "-", 1, ui->theme.textDim);
- kon_uiDrawText_(ui, panel->x + titleH, panel->y + pad / 2, title, kon_uiVisibleLength_(title), ui->theme.text);
-
- ui->cx = panel->x + pad;
- ui->cy = panel->y + titleH + pad;
- ui->availW = panel->w - pad * 2;
- ui->sameLine = 0;
- return !panel->collapsed;
-}
-
-void kon_uiEndPanel(kon_ui_t *ui)
-{
- if (!ui->panel) return;
- ui->panel->contentH = ui->cy - ui->panel->y + ui->theme.padding - ui->theme.spacing;
- ui->panel = NULL;
- ui->panelId = 0;
- ui->cx = ui->theme.padding;
- ui->cy = ui->theme.padding;
- ui->availW = ui->fb->width - ui->theme.padding * 2;
-}
-
-/*** widgets ***/
-
-void kon_uiLabel(kon_ui_t *ui, const char *text)
-{
- size_t n = kon_uiVisibleLength_(text);
- kon_rect_t r = kon_uiNextRect_(ui, kon_uiTextWidth_(ui, text, n) + 1, ui->lineH);
-
- kon_uiDrawText_(ui, r.x, r.y, text, n, ui->theme.text);
-}
-
-void kon_uiLabelf(kon_ui_t *ui, const char *format, ...)
-{
- char buffer[256];
- va_list args;
-
- va_start(args, format);
- vsnprintf(buffer, sizeof(buffer), format, args);
- va_end(args);
- kon_uiLabel(ui, buffer);
-}
-
-int kon_uiButton(kon_ui_t *ui, const char *label)
-{
- uint32_t id = kon_uiHash_(ui, label);
- size_t n = kon_uiVisibleLength_(label);
- int pad = ui->theme.padding, textW = kon_uiTextWidth_(ui, label, n);
- kon_rect_t r = kon_uiNextRect_(ui, ui->sameLine ? textW + pad * 2 : 0, ui->lineH + pad);
- int hovered, clicked;
-
- clicked = kon_uiBehave_(ui, id, r, &hovered);
- kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered));
- kon_uiOutline_(ui, r, ui->theme.border);
- kon_uiDrawText_(ui, r.x + (r.w - textW) / 2, r.y + pad / 2 + 1, label, n, ui->theme.text);
- return clicked;
-}
-
-int kon_uiCheckbox(kon_ui_t *ui, const char *label, int *value)
-{
- uint32_t id = kon_uiHash_(ui, label);
- size_t n = kon_uiVisibleLength_(label);
- int box = ui->lineH, pad = ui->theme.padding;
- kon_rect_t r = kon_uiNextRect_(ui, box + pad + kon_uiTextWidth_(ui, label, n) + 1, box);
- kon_rect_t b = KON_RECT(r.x, r.y, box, box);
- int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered);
-
- if (clicked) *value = !*value;
- kon_uiFill_(ui, b, kon_uiWidgetColor_(ui, id, hovered));
- kon_uiOutline_(ui, b, ui->theme.border);
- if (*value) kon_uiFill_(ui, KON_RECT(b.x + 3, b.y + 3, b.w - 6, b.h - 6), ui->theme.accent);
- kon_uiDrawText_(ui, r.x + box + pad, r.y, label, n, ui->theme.text);
- return clicked;
-}
-
-int kon_uiSliderFloat(kon_ui_t *ui, const char *label, float *value, float min, float max)
-{
- uint32_t id = kon_uiHash_(ui, label);
- size_t n = kon_uiVisibleLength_(label);
- int pad = ui->theme.padding;
- kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad);
- char text[64];
- int hovered, changed = 0, fill;
- float t;
-
- kon_uiBehave_(ui, id, r, &hovered);
-
- if (ui->active == id && r.w > 1 && max > min) {
- float v;
-
- t = (float)(ui->mouseX - r.x) / (float)(r.w - 1);
- if (t < 0.0f) t = 0.0f;
- if (t > 1.0f) t = 1.0f;
- v = min + t * (max - min);
- if (v != *value) {
- *value = v;
- changed = 1;
- }
- }
-
- t = max > min ? (*value - min) / (max - min) : 0.0f;
- if (t < 0.0f) t = 0.0f;
- if (t > 1.0f) t = 1.0f;
- fill = (int)(t * (float)(r.w - 2));
-
- kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered));
- kon_uiFill_(ui, KON_RECT(r.x + 1, r.y + 1, fill, r.h - 2), ui->theme.accent);
- kon_uiOutline_(ui, r, ui->theme.border);
-
- snprintf(text, sizeof(text), "%.*s: %.2f", (int)(n > 40 ? 40 : n), label, (double)*value);
- kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, text, strlen(text), ui->theme.text);
- return changed;
-}
-
-int kon_uiSliderInt(kon_ui_t *ui, const char *label, int *value, int min, int max)
-{
- float v = (float)*value;
- int changed = kon_uiSliderFloat(ui, label, &v, (float)min, (float)max);
-
- if (changed) *value = (int)(v + (v < 0 ? -0.5f : 0.5f));
- return changed;
-}
-
-int kon_uiSelectable(kon_ui_t *ui, const char *label, int selected)
-{
- uint32_t id = kon_uiHash_(ui, label);
- size_t n = kon_uiVisibleLength_(label);
- int pad = ui->theme.padding;
- kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH);
- int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered);
-
- if (selected) kon_uiFill_(ui, r, ui->theme.active);
- else if (hovered) kon_uiFill_(ui, r, ui->theme.hover);
- kon_uiDrawText_(ui, r.x + pad, r.y, label, n, ui->theme.text);
- return clicked;
-}
-
-int kon_uiTextInput(kon_ui_t *ui, const char *label, char *buffer, size_t size)
-{
- uint32_t id = kon_uiHash_(ui, label);
- size_t n = kon_uiVisibleLength_(label);
- int pad = ui->theme.padding, hovered, entered = 0, i;
- size_t length = strlen(buffer);
- kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad);
-
- kon_uiBehave_(ui, id, r, &hovered);
- if (hovered && ui->mousePressed) {
- ui->focus = id;
- ui->cursor = (int)length;
- }
- if (ui->focus == id) {
- ui->focusKept = ui->focusKept || (hovered && ui->mousePressed);
- if (ui->cursor > (int)length) ui->cursor = (int)length;
-
- for (i = 0; i < ui->textCount; i++) {
- unsigned int c = ui->text[i];
-
- if (c < 32 || c > 126 || length + 1 >= size) continue;
- memmove(buffer + ui->cursor + 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1);
- buffer[ui->cursor++] = (char)c;
- length++;
- }
- if (ui->keyPressed[KON_KEY_BACKSPACE] && ui->cursor > 0) {
- memmove(buffer + ui->cursor - 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1);
- ui->cursor--;
- length--;
- }
- if (ui->keyPressed[KON_KEY_DELETE] && ui->cursor < (int)length) {
- memmove(buffer + ui->cursor, buffer + ui->cursor + 1, length - (size_t)ui->cursor);
- length--;
- }
- if (ui->keyPressed[KON_KEY_LEFT] && ui->cursor > 0) ui->cursor--;
- if (ui->keyPressed[KON_KEY_RIGHT] && ui->cursor < (int)length) ui->cursor++;
- if (ui->keyPressed[KON_KEY_ENTER]) entered = 1;
- if (ui->keyPressed[KON_KEY_ESCAPE]) ui->focus = 0;
- }
-
- kon_uiFill_(ui, r, ui->focus == id ? ui->theme.hover : ui->theme.widget);
- kon_uiOutline_(ui, r, ui->focus == id ? ui->theme.accent : ui->theme.border);
-
- if (length == 0 && ui->focus != id) {
- kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, label, n, ui->theme.textDim);
- } else {
- kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, buffer, length, ui->theme.text);
- if (ui->focus == id) {
- int cx = r.x + pad + kon_uiTextWidth_(ui, buffer, (size_t)ui->cursor);
-
- kon_uiFill_(ui, KON_RECT(cx, r.y + 2, 1, r.h - 4), ui->theme.text);
- }
- }
- return entered;
-}
-
-void kon_uiSeparator(kon_ui_t *ui)
-{
- kon_rect_t r = kon_uiNextRect_(ui, 0, 1);
-
- kon_uiFill_(ui, r, ui->theme.border);
-}
-
-void kon_uiSameLine(kon_ui_t *ui)
-{
- ui->sameLine = 1;
-}
-
-#endif /* KONUI_IMPLEMENTATION */
diff --git a/src/engine.c b/src/engine.c
index 3dc442c..9e7b602 100644
--- a/src/engine.c
+++ b/src/engine.c
@@ -13,7 +13,7 @@
#define GAME_WIDTH 320
#define GAME_HEIGHT 180
-typedef struct app {
+typedef struct program {
kon_window_t *window;
kon_display_t *display;
kon_camera_t camera;
@@ -28,39 +28,39 @@ typedef struct app {
float shakeLeft;
int baseX, baseY;
#endif
-} app_t;
+} program_t;
#ifdef KON_MODULE_DEBUG
static void cmdOpacity(kon_debug_t *debug, void *user, int argc, char **argv)
{
- app_t *app = user;
+ program_t *program = user;
(void)debug;
- if (argc > 1) kon_setWindowOpacity(app->window, (float)atof(argv[1]));
+ if (argc > 1) kon_setWindowOpacity(program->window, (float)atof(argv[1]));
}
static void cmdTopmost(kon_debug_t *debug, void *user, int argc, char **argv)
{
- app_t *app = user;
+ program_t *program = user;
(void)debug;
- if (argc > 1) kon_setWindowTopmost(app->window, atoi(argv[1]) != 0);
+ if (argc > 1) kon_setWindowTopmost(program->window, atoi(argv[1]) != 0);
}
static void cmdBorderless(kon_debug_t *debug, void *user, int argc, char **argv)
{
- app_t *app = user;
+ program_t *program = user;
(void)debug;
- if (argc > 1) kon_setWindowDecorated(app->window, atoi(argv[1]) == 0);
+ if (argc > 1) kon_setWindowDecorated(program->window, atoi(argv[1]) == 0);
}
static void cmdClickThrough(kon_debug_t *debug, void *user, int argc, char **argv)
{
- app_t *app = user;
+ program_t *program = user;
(void)debug;
- if (argc > 1) kon_setWindowClickThrough(app->window, atoi(argv[1]) != 0);
+ if (argc > 1) kon_setWindowClickThrough(program->window, atoi(argv[1]) != 0);
}
static void cmdMessage(kon_debug_t *debug, void *user, int argc, char **argv)
@@ -71,116 +71,116 @@ static void cmdMessage(kon_debug_t *debug, void *user, int argc, char **argv)
static void cmdShake(kon_debug_t *debug, void *user, int argc, char **argv)
{
- app_t *app = user;
+ program_t *program = user;
(void)debug; (void)argc; (void)argv;
- kon_getWindowPos(app->window, &app->baseX, &app->baseY);
- app->shakeLeft = 0.5f;
+ kon_getWindowPos(program->window, &program->baseX, &program->baseY);
+ program->shakeLeft = 0.5f;
}
#endif
static void frame(void *data)
{
- app_t *app = (app_t *)data;
- kon_display_t *display = app->display;
+ program_t *program = (program_t *)data;
+ kon_display_t *display = program->display;
kon_event_t ev;
kon_time now, dt;
kon_mat4_t model, viewProjection;
- while (kon_pollEvent(app->window, &ev)) {
+ while (kon_pollEvent(program->window, &ev)) {
#ifdef KON_MODULE_DEBUG
- if (kon_debugFeedEvent(app->debug, &ev)) continue;
+ if (kon_debugFeedEvent(program->debug, &ev)) continue;
#endif
if (ev.type == KON_EVENT_RESIZE) {
kon_resizeDisplay(display, ev.width, ev.height);
}
if (ev.type == KON_EVENT_KEY_DOWN && ev.key == KON_KEY_O) {
- app->camera.projection = (app->camera.projection == KON_PROJECTION_PERSPECTIVE)
+ program->camera.projection = (program->camera.projection == KON_PROJECTION_PERSPECTIVE)
? KON_PROJECTION_ORTHOGRAPHIC : KON_PROJECTION_PERSPECTIVE;
}
}
now = kon_getTime();
- dt = now - app->last;
- app->angle += (float)dt;
- app->last = now;
+ dt = now - program->last;
+ program->angle += (float)dt;
+ program->last = now;
#ifdef KON_MODULE_DEBUG
- if (app->shakeLeft > 0.0f) {
- app->shakeLeft -= (float)dt;
- if (app->shakeLeft > 0.0f) kon_setWindowPos(app->window, app->baseX + rand() % 21 - 10, app->baseY + rand() % 21 - 10);
- else kon_setWindowPos(app->window, app->baseX, app->baseY);
+ if (program->shakeLeft > 0.0f) {
+ program->shakeLeft -= (float)dt;
+ if (program->shakeLeft > 0.0f) kon_setWindowPos(program->window, program->baseX + rand() % 21 - 10, program->baseY + rand() % 21 - 10);
+ else kon_setWindowPos(program->window, program->baseX, program->baseY);
}
#endif
model = kon_mat4Identity();
- model = kon_mat4Rotate(model, KON_VEC3(app->angle * 0.5f, app->angle, 0));
- viewProjection = kon_cameraViewProjection(&app->camera, display->game->width, display->game->height);
+ model = kon_mat4Rotate(model, KON_VEC3(program->angle * 0.5f, program->angle, 0));
+ viewProjection = kon_cameraViewProjection(&program->camera, display->game->width, display->game->height);
kon_clearFramebuffer(display->game, KON_BACKGROUND_COLOR);
- kon_drawMesh(display->game, viewProjection, model, &kon_cubeMesh, &app->material, &app->light, KON_RENDER_DEFAULT);
+ kon_drawMesh(display->game, viewProjection, model, &kon_cubeMesh, &program->material, &program->light, KON_RENDER_DEFAULT);
kon_presentDisplay(display);
#ifdef KON_MODULE_DEBUG
- kon_debugFrame(app->debug);
- if (kon_debugQuitRequested(app->debug)) kon_setShouldClose(app->window, true);
- kon_debugDraw(app->debug, display->screen);
+ kon_debugFrame(program->debug);
+ if (kon_debugQuitRequested(program->debug)) kon_setShouldClose(program->window, true);
+ kon_debugDraw(program->debug, display->screen);
#endif
- kon_blitPixels(app->window, display->screen->data, display->screen->width, display->screen->height);
+ kon_blitPixels(program->window, display->screen->data, display->screen->width, display->screen->height);
kon_sleep(0.008);
}
int main(void)
{
- app_t app = {0};
+ program_t program = {0};
if (!kon_init()) return 1;
- app.window = kon_createWindow("engine", 0, 0, 800, 600, KON_WINDOW_CENTER | KON_WINDOW_RESIZABLE);
- if (!app.window) return 1;
+ program.window = kon_createWindow("engine", 0, 0, 800, 600, KON_WINDOW_CENTER | KON_WINDOW_RESIZABLE);
+ if (!program.window) return 1;
#ifdef KON_DEVELOPER
- kon_setExitKey(app.window, KON_KEY_F10);
+ kon_setExitKey(program.window, KON_KEY_F10);
#else
- kon_setExitKey(app.window, KON_KEY_ESCAPE);
+ kon_setExitKey(program.window, KON_KEY_ESCAPE);
#endif
- app.display = kon_createDisplay(GAME_WIDTH, GAME_HEIGHT, 800, 600);
- if (!app.display) return 1;
+ program.display = kon_createDisplay(GAME_WIDTH, GAME_HEIGHT, 800, 600);
+ if (!program.display) return 1;
- app.camera = kon_cameraDefault();
- app.camera.position = KON_VEC3(3, 3, 6);
- app.material = kon_materialColor(0xFFC8C8FF);
- app.light = kon_lightDefault();
- app.last = kon_getTime();
+ program.camera = kon_cameraDefault();
+ program.camera.position = KON_VEC3(3, 3, 6);
+ program.material = kon_materialColor(0xFFC8C8FF);
+ program.light = kon_lightDefault();
+ program.last = kon_getTime();
#ifdef KON_MODULE_DEBUG
- app.assets = kon_assetsCreate(NULL, "assets");
+ program.assets = kon_assetsCreate(NULL, "assets");
{
- kon_blob_t *ttf = kon_assetBlob(app.assets, "font.ttf");
-
- if (ttf) app.font = kon_loadFont(ttf->data, ttf->size, 14.0f);
- app.debug = kon_debugCreate(app.font);
- kon_debugSetAssets(app.debug, app.assets);
- kon_debugCommand(app.debug, "opacity", "window opacity 0 to 1", cmdOpacity, &app);
- kon_debugCommand(app.debug, "topmost", "topmost 0 or 1", cmdTopmost, &app);
- kon_debugCommand(app.debug, "borderless", "borderless 0 or 1", cmdBorderless, &app);
- kon_debugCommand(app.debug, "clickthrough", "clickthrough 0 or 1", cmdClickThrough, &app);
- kon_debugCommand(app.debug, "message", "message \"text\"", cmdMessage, &app);
- kon_debugCommand(app.debug, "shake", "shake the window", cmdShake, &app);
- kon_debugLog(app.debug, ttf ? "loaded font.ttf" : "put a font.ttf in assets/ for the console");
+ kon_blob_t *ttf = kon_assetBlob(program.assets, "font.ttf");
+
+ if (ttf) program.font = kon_loadFont(ttf->data, ttf->size, 14.0f);
+ program.debug = kon_debugCreate(program.font);
+ kon_debugSetAssets(program.debug, program.assets);
+ kon_debugCommand(program.debug, "opacity", "window opacity 0 to 1", cmdOpacity, &program);
+ kon_debugCommand(program.debug, "topmost", "topmost 0 or 1", cmdTopmost, &program);
+ kon_debugCommand(program.debug, "borderless", "borderless 0 or 1", cmdBorderless, &program);
+ kon_debugCommand(program.debug, "clickthrough", "clickthrough 0 or 1", cmdClickThrough, &program);
+ kon_debugCommand(program.debug, "message", "message \"text\"", cmdMessage, &program);
+ kon_debugCommand(program.debug, "shake", "shake the window", cmdShake, &program);
+ kon_debugLog(program.debug, ttf ? "loaded font.ttf" : "put a font.ttf in assets/ for the console");
}
#endif
- kon_runMainLoop(app.window, frame, &app);
+ kon_runMainLoop(program.window, frame, &program);
#ifdef KON_MODULE_DEBUG
- kon_debugDestroy(app.debug);
- kon_freeFont(app.font);
- kon_assetsDestroy(app.assets);
+ kon_debugDestroy(program.debug);
+ kon_freeFont(program.font);
+ kon_assetsDestroy(program.assets);
#endif
- kon_freeDisplay(app.display);
- kon_destroyWindow(app.window);
+ kon_freeDisplay(program.display);
+ kon_destroyWindow(program.window);
kon_deinit();
return 0;
}
diff --git a/src/impl/konassets.c b/src/impl/konassets.c
deleted file mode 100644
index af67fb1..0000000
--- a/src/impl/konassets.c
+++ /dev/null
@@ -1,2 +0,0 @@
-#define KONASSETS_IMPLEMENTATION
-#include "konassets.h"
diff --git a/src/impl/konaudio.c b/src/impl/konaudio.c
deleted file mode 100644
index 0e5a651..0000000
--- a/src/impl/konaudio.c
+++ /dev/null
@@ -1,2 +0,0 @@
-#define KONAUDIO_IMPLEMENTATION
-#include "konaudio.h"
diff --git a/src/impl/kondebug.c b/src/impl/kondebug.c
deleted file mode 100644
index 3a9abb1..0000000
--- a/src/impl/kondebug.c
+++ /dev/null
@@ -1,2 +0,0 @@
-#define KONDEBUG_IMPLEMENTATION
-#include "kondebug.h"
diff --git a/src/impl/konpack.c b/src/impl/konpack.c
deleted file mode 100644
index d48b9b7..0000000
--- a/src/impl/konpack.c
+++ /dev/null
@@ -1,2 +0,0 @@
-#define KONPACK_IMPLEMENTATION
-#include "konpack.h"
diff --git a/src/impl/kontile.c b/src/impl/kontile.c
deleted file mode 100644
index 8f57579..0000000
--- a/src/impl/kontile.c
+++ /dev/null
@@ -1,2 +0,0 @@
-#define KONTILE_IMPLEMENTATION
-#include "kontile.h"
diff --git a/src/impl/konui.c b/src/impl/konui.c
deleted file mode 100644
index d7290c3..0000000
--- a/src/impl/konui.c
+++ /dev/null
@@ -1,2 +0,0 @@
-#define KONUI_IMPLEMENTATION
-#include "konui.h"