summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-07 04:11:02 +0800
committerKonAki <konaki@anokonaki.org>2026-10-07 04:11:02 +0800
commitf4a0c7936a57bc63381556188fa3de2903b2dc99 (patch)
tree5e0d072504e0cb675ff76ebfcbe30b2f19c72a06 /src
initial commit
Diffstat (limited to 'src')
-rw-r--r--src/konfont.h197
-rw-r--r--src/koninput.h142
-rw-r--r--src/konmath.h295
-rw-r--r--src/konsofren.h402
-rw-r--r--src/kontime.h85
-rw-r--r--src/kontoml.h649
-rw-r--r--src/konwinlib.h1349
7 files changed, 3119 insertions, 0 deletions
diff --git a/src/konfont.h b/src/konfont.h
new file mode 100644
index 0000000..822e1f3
--- /dev/null
+++ b/src/konfont.h
@@ -0,0 +1,197 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konfont.h
+ *
+ * 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.
+ */
+
+#ifndef KONFONT_H
+#define KONFONT_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "konsofren.h"
+
+#define KON_FONT_FIRST_CHAR 32
+#define KON_FONT_NUM_CHARS 95
+
+typedef struct kon_font kon_font_t;
+
+/*** font declarations ***/
+
+kon_font_t *kon_loadFont(const uint8_t *ttf_data, size_t size, float pixelHeight);
+void kon_freeFont(kon_font_t *font);
+
+kon_image *kon_fontGetAtlas(kon_font_t *font);
+
+/*** text draw declarations ***/
+
+void kon_drawText(kon_framebuffer_t *fb, kon_font_t *font, int x, int y, const char *text, uint32_t color);
+void kon_measureText(kon_font_t *font, const char *text, int *outWidth, int *outHeight);
+
+/*** implementatoin ***/
+
+#ifdef KONFONT_IMPLEMENTATION
+
+#include "stb_truetype.h"
+
+struct kon_font {
+ stbtt_bakedchar chardata[KON_FONT_NUM_CHARS];
+ uint8_t *atlasPixels;
+ int atlasSize;
+ float pixelHeight;
+};
+
+/*** private helpers ***/
+
+static bool kon_fontBakeAtlas(kon_font_t *font, const uint8_t *ttf_data, float pixelHeight) {
+ int sizes[] = {256, 512, 1024, 2048};
+
+ for (int i = 0; i < 4; i++) {
+ int atlasSize = sizes[i];
+
+ uint8_t *bitmap = malloc((size_t)atlasSize * (size_t)atlasSize);
+ if (!bitmap) return false;
+
+ int result = stbtt_BakeFontBitmap(ttf_data, 0, pixelHeight, bitmap, atlasSize, atlasSize, KON_FONT_FIRST_CHAR, KON_FONT_NUM_CHARS, font->chardata);
+
+ if (result > 0) {
+ font->atlasPixels = bitmap;
+ font->atlasSize = atlasSize;
+ return true;
+ }
+
+ free(bitmap);
+ }
+
+ return false;
+}
+
+/*** font implementation ***/
+
+kon_font_t *kon_loadFont(const uint8_t *ttf_data, size_t size, float pixelHeight) {
+ (void)size;
+
+ if (!ttf_data || pixelHeight <= 0.0f) return NULL;
+
+ kon_font_t *font = malloc(sizeof(kon_font_t));
+ if (!font) return NULL;
+
+ font->atlasPixels = NULL;
+ font->pixelHeight = pixelHeight;
+
+ if (!kon_fontBakeAtlas(font, ttf_data, pixelHeight)) {
+ free(font);
+ return NULL;
+ }
+
+ return font;
+}
+
+void kon_freeFont(kon_font_t *font) {
+ if (!font) return;
+
+ free(font->atlasPixels);
+ free(font);
+}
+
+kon_image *kon_fontGetAtlas(kon_font_t *font) {
+ if (!font) return NULL;
+
+ int n = font->atlasSize * font->atlasSize;
+ uint8_t *rgba = malloc((size_t)n * 4);
+ if (!rgba) return NULL;
+
+ for (int i = 0; i < n; i++) {
+ uint8_t a = font->atlasPixels[i];
+ rgba[i * 4 + 0] = 255;
+ rgba[i * 4 + 1] = 255;
+ rgba[i * 4 + 2] = 255;
+ rgba[i * 4 + 3] = a;
+ }
+
+ kon_image *img = kon_loadImage(rgba, font->atlasSize, font->atlasSize, konFormatRGBA8);
+ free(rgba);
+ return img;
+}
+
+/*** text draw implementation ***/
+
+void kon_drawText(kon_framebuffer_t *fb, kon_font_t *font, int x, int y, const char *text, uint32_t color) {
+ if (!fb || !font || !text) return;
+
+ uint32_t baseColor = color & 0x00FFFFFF;
+
+ float cursorX = (float)x;
+ float cursorY = (float)y;
+
+ for (const char *p = text; *p; p++) {
+ unsigned char c = (unsigned char)*p;
+
+ if (c < KON_FONT_FIRST_CHAR || c >= KON_FONT_FIRST_CHAR + KON_FONT_NUM_CHARS) continue;
+
+ stbtt_aligned_quad q;
+ stbtt_GetBakedQuad(font->chardata, font->atlasSize, font->atlasSize, c - KON_FONT_FIRST_CHAR, &cursorX, &cursorY, &q, 0);
+
+ int src_x0 = (int)(q.s0 * font->atlasSize);
+ int src_y0 = (int)(q.t0 * font->atlasSize);
+ int glyphW = (int)(q.x1 - q.x0);
+ int glyphH = (int)(q.y1 - q.y0);
+
+ for (int gy = 0; gy < glyphH; gy++) {
+ for (int gx = 0; gx < glyphW; gx++) {
+ uint8_t alpha = font->atlasPixels[(src_y0 + gy) * font->atlasSize + (src_x0 + gx)];
+ if (alpha == 0) continue;
+
+ kon_putPixel(fb, (int)q.x0 + gx, (int)q.y0 + gy, ((uint32_t)alpha << 24) | baseColor);
+ }
+ }
+ }
+}
+
+void kon_measureText(kon_font_t *font, const char *text, int *outWidth, int *outHeight) {
+ if (!font || !text) return;
+
+ float cursorX = 0.0f;
+ float cursorY = 0.0f;
+ float maxX = 0.0f;
+
+ for (const char *p = text; *p; p++) {
+ unsigned char c = (unsigned char)*p;
+
+ if (c < KON_FONT_FIRST_CHAR || c >= KON_FONT_FIRST_CHAR + KON_FONT_NUM_CHARS) continue;
+
+ stbtt_aligned_quad q;
+ stbtt_GetBakedQuad(font->chardata, font->atlasSize, font->atlasSize, c - KON_FONT_FIRST_CHAR, &cursorX, &cursorY, &q, 0);
+
+ if (cursorX > maxX) maxX = cursorX;
+ }
+
+ if (outWidth) *outWidth = (int)maxX;
+ if (outHeight) *outHeight = (int)font->pixelHeight;
+}
+
+#endif /* KONFONT_IMPLEMENTATION */
+
+#endif /* KONFONT_H */
diff --git a/src/koninput.h b/src/koninput.h
new file mode 100644
index 0000000..5008c06
--- /dev/null
+++ b/src/koninput.h
@@ -0,0 +1,142 @@
+/*
+
+MIT License
+
+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.
+
+*/
+
+#ifndef KONINPUT_H
+#define KONINPUT_H
+
+#include "konwinlib.h"
+
+typedef struct kon_input {
+ bool down[KON_KEY_COUNT];
+ bool downPrev[KON_KEY_COUNT];
+
+ bool mouseDown[3];
+ bool mouseDownPrev[3];
+
+ int mouseX, mouseY;
+} kon_input_t;
+
+void kon_inputBeginFrame(kon_input_t *input);
+
+void kon_inputFeedEvent(kon_input_t *input, const kon_event_t *event);
+
+bool kon_isKeyDown(const kon_input_t *input, kon_key_t key);
+bool kon_isKeyPressed(const kon_input_t *input, kon_key_t key);
+bool kon_isKeyReleased(const kon_input_t *input, kon_key_t key);
+
+bool kon_isMouseDown(const kon_input_t *input, kon_mouseButton_t button);
+bool kon_isMousePressed(const kon_input_t *input, kon_mouseButton_t button);
+bool kon_isMouseReleased(const kon_input_t *input, kon_mouseButton_t button);
+
+void kon_getMousePos(const kon_input_t *input, int *x, int *y);
+
+/*** implementation ***/
+
+#ifdef KONINPUT_IMPLEMENTATION
+
+void kon_inputBeginFrame(kon_input_t *input) {
+ if (!input) return;
+
+ for (int i = 0; i < KON_KEY_COUNT; i++) {
+ input->downPrev[i] = input->down[i];
+ }
+ for (int i = 0; i < 3; i++) {
+ input->mouseDownPrev[i] = input->mouseDown[i];
+ }
+}
+
+void kon_inputFeedEvent(kon_input_t *input, const kon_event_t *event) {
+ if (!input || !event) return;
+
+ switch (event->type) {
+ case KON_EVENT_KEY_DOWN:
+ if (event->key >= 0 && event->key < KON_KEY_COUNT)
+ input->down[event->key] = true;
+ break;
+ case KON_EVENT_KEY_UP:
+ if (event->key >= 0 && event->key < KON_KEY_COUNT)
+ input->down[event->key] = false;
+ break;
+ case KON_EVENT_MOUSE_DOWN:
+ if (event->mouseButton >= 0 && event->mouseButton < 3)
+ input->mouseDown[event->mouseButton] = true;
+ input->mouseX = event->mouseX;
+ input->mouseY = event->mouseY;
+ break;
+ case KON_EVENT_MOUSE_UP:
+ if (event->mouseButton >= 0 && event->mouseButton < 3)
+ input->mouseDown[event->mouseButton] = false;
+ input->mouseX = event->mouseX;
+ input->mouseY = event->mouseY;
+ break;
+ case KON_EVENT_MOUSE_MOVE:
+ input->mouseX = event->mouseX;
+ input->mouseY = event->mouseY;
+ break;
+ default:
+ break;
+ }
+}
+
+bool kon_isKeyDown(const kon_input_t *input, kon_key_t key) {
+ if (!input || key < 0 || key >= KON_KEY_COUNT) return false;
+ return input->down[key];
+}
+
+bool kon_isKeyPressed(const kon_input_t *input, kon_key_t key) {
+ if (!input || key < 0 || key >= KON_KEY_COUNT) return false;
+ return input->down[key] && !input->downPrev[key];
+}
+
+bool kon_isKeyReleased(const kon_input_t *input, kon_key_t key) {
+ if (!input || key < 0 || key >= KON_KEY_COUNT) return false;
+ return !input->down[key] && input->downPrev[key];
+}
+
+bool kon_isMouseDown(const kon_input_t *input, kon_mouseButton_t button) {
+ if (!input || button < 0 || button >= 3) return false;
+ return input->mouseDown[button];
+}
+
+bool kon_isMousePressed(const kon_input_t *input, kon_mouseButton_t button) {
+ if (!input || button < 0 || button >= 3) return false;
+ return input->mouseDown[button] && !input->mouseDownPrev[button];
+}
+
+bool kon_isMouseReleased(const kon_input_t *input, kon_mouseButton_t button) {
+ if (!input || button < 0 || button >= 3) return false;
+ return !input->mouseDown[button] && input->mouseDownPrev[button];
+}
+
+void kon_getMousePos(const kon_input_t *input, int *x, int *y) {
+ if (!input) return;
+ if (x) *x = input->mouseX;
+ if (y) *y = input->mouseY;
+}
+
+#endif /* KONINPUT_IMPLEMENTATION */
+
+#endif /* KONINPUT_H */
diff --git a/src/konmath.h b/src/konmath.h
new file mode 100644
index 0000000..339be48
--- /dev/null
+++ b/src/konmath.h
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konmath.h
+ *
+ * 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.
+ */
+
+
+#ifndef KONMATH_H
+#define KONMATH_H
+
+#include <math.h>
+
+#define KON_PI 3.14159265358979323846f
+#define KON_TAU (2.0f * KON_PI)
+
+#define KON_DEG2RAD(d) ((float)(d) * (KON_PI / 180.0f))
+#define KON_RAD2DEG(r) ((float)(r) * (180.0f / KON_PI))
+
+/* kon_vec3_t built from degrees, handy for rotations: KON_DEG3(0, 45, 0) */
+#define KON_DEG3(x, y, z) ((kon_vec3_t){KON_DEG2RAD(x), KON_DEG2RAD(y), KON_DEG2RAD(z)})
+
+#define KON_VEC2(x, y) ((kon_vec2_t){(float)(x), (float)(y)})
+#define KON_VEC3(x, y, z) ((kon_vec3_t){(float)(x), (float)(y), (float)(z)})
+#define KON_VEC4(x, y, z, w) ((kon_vec4_t){(float)(x), (float)(y), (float)(z), (float)(w)})
+
+#define KON_VEC3_ZERO KON_VEC3(0, 0, 0)
+#define KON_VEC3_ONE KON_VEC3(1, 1, 1)
+#define KON_VEC3_UP KON_VEC3(0, 1, 0)
+
+/* right-handed, +Y up, camera looks down -Z, column-major: m[column * 4 + row] */
+
+typedef struct kon_vec2 { float x, y; } kon_vec2_t;
+typedef struct kon_vec3 { float x, y, z; } kon_vec3_t;
+typedef struct kon_vec4 { float x, y, z, w; } kon_vec4_t;
+typedef struct kon_mat4 { float m[16]; } kon_mat4_t;
+
+/*** scalar declarations ***/
+
+float kon_clamp(float v, float min, float max);
+float kon_lerp(float a, float b, float t);
+
+/*** vec2 declarations ***/
+
+kon_vec2_t kon_vec2Add(kon_vec2_t a, kon_vec2_t b);
+kon_vec2_t kon_vec2Sub(kon_vec2_t a, kon_vec2_t b);
+kon_vec2_t kon_vec2Scale(kon_vec2_t a, float s);
+float kon_vec2Dot(kon_vec2_t a, kon_vec2_t b);
+
+/*** vec3 declarations ***/
+
+kon_vec3_t kon_vec3Add(kon_vec3_t a, kon_vec3_t b);
+kon_vec3_t kon_vec3Sub(kon_vec3_t a, kon_vec3_t b);
+kon_vec3_t kon_vec3Scale(kon_vec3_t a, float s);
+float kon_vec3Dot(kon_vec3_t a, kon_vec3_t b);
+kon_vec3_t kon_vec3Cross(kon_vec3_t a, kon_vec3_t b);
+float kon_vec3Length(kon_vec3_t a);
+kon_vec3_t kon_vec3Normalize(kon_vec3_t a);
+kon_vec3_t kon_vec3Lerp(kon_vec3_t a, kon_vec3_t b, float t);
+
+/*** vec4 declarations ***/
+
+
+/*** mat4 declarations ***/
+
+kon_mat4_t kon_mat4Identity(void);
+kon_mat4_t kon_mat4Mul(kon_mat4_t a, kon_mat4_t b);
+kon_vec4_t kon_mat4MulVec4(kon_mat4_t m, kon_vec4_t v);
+kon_vec3_t kon_mat4MulPoint(kon_mat4_t m, kon_vec3_t p);
+
+/* like blender's local/global: the plain versions work along the model's own axes,
+ the World versions along the world's axes. neither rotate nor scale ever moves the model's position.
+ angles are euler angles in radians (x = pitch, y = yaw, z = roll), see KON_DEG3 */
+kon_mat4_t kon_mat4Translate(kon_mat4_t m, kon_vec3_t v);
+kon_mat4_t kon_mat4Rotate(kon_mat4_t m, kon_vec3_t angles);
+kon_mat4_t kon_mat4Scale(kon_mat4_t m, kon_vec3_t v);
+kon_mat4_t kon_mat4TranslateWorld(kon_mat4_t m, kon_vec3_t v);
+kon_mat4_t kon_mat4RotateWorld(kon_mat4_t m, kon_vec3_t angles);
+kon_mat4_t kon_mat4ScaleWorld(kon_mat4_t m, kon_vec3_t v);
+
+kon_mat4_t kon_mat4FromTranslation(kon_vec3_t v);
+kon_mat4_t kon_mat4FromRotation(kon_vec3_t angles);
+kon_mat4_t kon_mat4FromAxisAngle(kon_vec3_t axis, float angle);
+kon_mat4_t kon_mat4FromScale(kon_vec3_t v);
+kon_mat4_t kon_mat4FromTransform(kon_vec3_t position, kon_vec3_t angles, kon_vec3_t scale);
+
+kon_mat4_t kon_mat4LookAt(kon_vec3_t eye, kon_vec3_t target, kon_vec3_t up);
+kon_mat4_t kon_mat4Perspective(float fovY, float aspect, float nearZ, float farZ);
+kon_mat4_t kon_mat4Ortho(float left, float right, float bottom, float top, float nearZ, float farZ);
+
+/*** implementation ***/
+
+#ifdef KONMATH_IMPLEMENTATION
+
+/*** scalar ***/
+
+float kon_clamp(float v, float min, float max) { return v < min ? min : (v > max ? max : v); }
+float kon_lerp(float a, float b, float t) { return a + (b - a) * t; }
+
+/*** vec2 ***/
+
+kon_vec2_t kon_vec2Add(kon_vec2_t a, kon_vec2_t b) { return (kon_vec2_t){a.x + b.x, a.y + b.y}; }
+kon_vec2_t kon_vec2Sub(kon_vec2_t a, kon_vec2_t b) { return (kon_vec2_t){a.x - b.x, a.y - b.y}; }
+kon_vec2_t kon_vec2Scale(kon_vec2_t a, float s) { return (kon_vec2_t){a.x * s, a.y * s}; }
+float kon_vec2Dot(kon_vec2_t a, kon_vec2_t b) { return a.x * b.x + a.y * b.y; }
+
+/*** vec3 ***/
+
+kon_vec3_t kon_vec3Add(kon_vec3_t a, kon_vec3_t b) { return (kon_vec3_t){a.x + b.x, a.y + b.y, a.z + b.z}; }
+kon_vec3_t kon_vec3Sub(kon_vec3_t a, kon_vec3_t b) { return (kon_vec3_t){a.x - b.x, a.y - b.y, a.z - b.z}; }
+kon_vec3_t kon_vec3Scale(kon_vec3_t a, float s) { return (kon_vec3_t){a.x * s, a.y * s, a.z * s}; }
+float kon_vec3Dot(kon_vec3_t a, kon_vec3_t b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
+
+kon_vec3_t kon_vec3Cross(kon_vec3_t a, kon_vec3_t b) {
+ return (kon_vec3_t){
+ a.y * b.z - a.z * b.y,
+ a.z * b.x - a.x * b.z,
+ a.x * b.y - a.y * b.x
+ };
+}
+
+float kon_vec3Length(kon_vec3_t a) { return sqrtf(kon_vec3Dot(a, a)); }
+
+kon_vec3_t kon_vec3Normalize(kon_vec3_t a) {
+ float len = kon_vec3Length(a);
+ return len > 0.0f ? kon_vec3Scale(a, 1.0f / len) : a;
+}
+
+kon_vec3_t kon_vec3Lerp(kon_vec3_t a, kon_vec3_t b, float t) {
+ return (kon_vec3_t){kon_lerp(a.x, b.x, t), kon_lerp(a.y, b.y, t), kon_lerp(a.z, b.z, t)};
+}
+
+/*** vec4 ***/
+
+
+/*** mat4 ***/
+
+kon_mat4_t kon_mat4Identity(void) {
+ kon_mat4_t r = {{0}};
+ r.m[0] = r.m[5] = r.m[10] = r.m[15] = 1.0f;
+ return r;
+}
+
+/* returns a * b, b is applied first */
+kon_mat4_t kon_mat4Mul(kon_mat4_t a, kon_mat4_t b) {
+ kon_mat4_t r;
+ for (int c = 0; c < 4; c++) {
+ for (int row = 0; row < 4; row++) {
+ float sum = 0.0f;
+ for (int k = 0; k < 4; k++) {
+ sum += a.m[k * 4 + row] * b.m[c * 4 + k];
+ }
+ r.m[c * 4 + row] = sum;
+ }
+ }
+ return r;
+}
+
+kon_vec4_t kon_mat4MulVec4(kon_mat4_t m, kon_vec4_t v) {
+ return (kon_vec4_t){
+ m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w,
+ m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w,
+ m.m[2] * v.x + m.m[6] * v.y + m.m[10] * v.z + m.m[14] * v.w,
+ m.m[3] * v.x + m.m[7] * v.y + m.m[11] * v.z + m.m[15] * v.w
+ };
+}
+
+kon_vec3_t kon_mat4MulPoint(kon_mat4_t m, kon_vec3_t p) {
+ kon_vec4_t r = kon_mat4MulVec4(m, (kon_vec4_t){p.x, p.y, p.z, 1.0f});
+ return (kon_vec3_t){r.x, r.y, r.z};
+}
+
+kon_mat4_t kon_mat4FromTranslation(kon_vec3_t v) {
+ kon_mat4_t r = kon_mat4Identity();
+ r.m[12] = v.x;
+ r.m[13] = v.y;
+ r.m[14] = v.z;
+ return r;
+}
+
+kon_mat4_t kon_mat4FromScale(kon_vec3_t v) {
+ kon_mat4_t r = kon_mat4Identity();
+ r.m[0] = v.x;
+ r.m[5] = v.y;
+ r.m[10] = v.z;
+ return r;
+}
+
+kon_mat4_t kon_mat4FromAxisAngle(kon_vec3_t axis, float angle) {
+ kon_vec3_t a = kon_vec3Normalize(axis);
+ float c = cosf(angle), s = sinf(angle), t = 1.0f - c;
+ kon_mat4_t r = kon_mat4Identity();
+ r.m[0] = t * a.x * a.x + c;
+ r.m[1] = t * a.x * a.y + s * a.z;
+ r.m[2] = t * a.x * a.z - s * a.y;
+ r.m[4] = t * a.x * a.y - s * a.z;
+ r.m[5] = t * a.y * a.y + c;
+ r.m[6] = t * a.y * a.z + s * a.x;
+ r.m[8] = t * a.x * a.z + s * a.y;
+ r.m[9] = t * a.y * a.z - s * a.x;
+ r.m[10] = t * a.z * a.z + c;
+ return r;
+}
+
+/* applied in order roll (z), pitch (x), yaw (y) */
+kon_mat4_t kon_mat4FromRotation(kon_vec3_t angles) {
+ kon_mat4_t x = kon_mat4FromAxisAngle(KON_VEC3(1, 0, 0), angles.x);
+ kon_mat4_t y = kon_mat4FromAxisAngle(KON_VEC3(0, 1, 0), angles.y);
+ kon_mat4_t z = kon_mat4FromAxisAngle(KON_VEC3(0, 0, 1), angles.z);
+ return kon_mat4Mul(y, kon_mat4Mul(x, z));
+}
+
+kon_mat4_t kon_mat4FromTransform(kon_vec3_t position, kon_vec3_t angles, kon_vec3_t scale) {
+ kon_mat4_t r = kon_mat4Mul(kon_mat4FromRotation(angles), kon_mat4FromScale(scale));
+ return kon_mat4Mul(kon_mat4FromTranslation(position), r);
+}
+
+kon_mat4_t kon_mat4Translate(kon_mat4_t m, kon_vec3_t v) { return kon_mat4Mul(m, kon_mat4FromTranslation(v)); }
+kon_mat4_t kon_mat4Rotate(kon_mat4_t m, kon_vec3_t angles) { return kon_mat4Mul(m, kon_mat4FromRotation(angles)); }
+kon_mat4_t kon_mat4Scale(kon_mat4_t m, kon_vec3_t v) { return kon_mat4Mul(m, kon_mat4FromScale(v)); }
+kon_mat4_t kon_mat4TranslateWorld(kon_mat4_t m, kon_vec3_t v) { return kon_mat4Mul(kon_mat4FromTranslation(v), m); }
+
+/* pre-multiplying would also swing the position around the world origin, so put it back */
+kon_mat4_t kon_mat4RotateWorld(kon_mat4_t m, kon_vec3_t angles) {
+ kon_mat4_t r = kon_mat4Mul(kon_mat4FromRotation(angles), m);
+ r.m[12] = m.m[12];
+ r.m[13] = m.m[13];
+ r.m[14] = m.m[14];
+ return r;
+}
+
+kon_mat4_t kon_mat4ScaleWorld(kon_mat4_t m, kon_vec3_t v) {
+ kon_mat4_t r = kon_mat4Mul(kon_mat4FromScale(v), m);
+ r.m[12] = m.m[12];
+ r.m[13] = m.m[13];
+ r.m[14] = m.m[14];
+ return r;
+}
+
+kon_mat4_t kon_mat4LookAt(kon_vec3_t eye, kon_vec3_t target, kon_vec3_t up) {
+ kon_vec3_t f = kon_vec3Normalize(kon_vec3Sub(target, eye));
+ kon_vec3_t s = kon_vec3Normalize(kon_vec3Cross(f, up));
+ kon_vec3_t u = kon_vec3Cross(s, f);
+
+ kon_mat4_t r = kon_mat4Identity();
+ r.m[0] = s.x; r.m[4] = s.y; r.m[8] = s.z; r.m[12] = -kon_vec3Dot(s, eye);
+ r.m[1] = u.x; r.m[5] = u.y; r.m[9] = u.z; r.m[13] = -kon_vec3Dot(u, eye);
+ r.m[2] = -f.x; r.m[6] = -f.y; r.m[10] = -f.z; r.m[14] = kon_vec3Dot(f, eye);
+ return r;
+}
+
+/* fovY in radians, aspect = width / height */
+kon_mat4_t kon_mat4Perspective(float fovY, float aspect, float nearZ, float farZ) {
+ kon_mat4_t r = {{0}};
+ float f = 1.0f / tanf(fovY * 0.5f);
+ r.m[0] = f / aspect;
+ r.m[5] = f;
+ r.m[10] = (farZ + nearZ) / (nearZ - farZ);
+ r.m[11] = -1.0f;
+ r.m[14] = (2.0f * farZ * nearZ) / (nearZ - farZ);
+ return r;
+}
+
+kon_mat4_t kon_mat4Ortho(float left, float right, float bottom, float top, float nearZ, float farZ) {
+ kon_mat4_t r = kon_mat4Identity();
+ r.m[0] = 2.0f / (right - left);
+ r.m[5] = 2.0f / (top - bottom);
+ r.m[10] = -2.0f / (farZ - nearZ);
+ r.m[12] = -(right + left) / (right - left);
+ r.m[13] = -(top + bottom) / (top - bottom);
+ r.m[14] = -(farZ + nearZ) / (farZ - nearZ);
+ return r;
+}
+
+#endif /* end of KONMATH_IMPLEMENTATION */
+
+#endif
diff --git a/src/konsofren.h b/src/konsofren.h
new file mode 100644
index 0000000..a209a88
--- /dev/null
+++ b/src/konsofren.h
@@ -0,0 +1,402 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konsofren.h
+ *
+ * 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.
+ */
+
+#ifndef KONSOFREN_H
+#define KONSOFREN_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#define KON_BACKGROUND_COLOR 0xFF05050A
+
+typedef struct kon_framebuffer {
+ int width, height;
+ uint32_t *data;
+} kon_framebuffer_t;
+
+typedef kon_framebuffer_t kon_image;
+
+typedef enum kon_imageFormat {
+ konFormatRGBA8 = 0,
+ konFormatABGR8,
+ konFormatARGB8,
+ konFormatBGRA8
+} kon_imageFormat_t;
+
+/*** framebuffer declarations ***/
+
+kon_framebuffer_t *kon_createFramebuffer(int width, int height);
+void kon_freeFramebuffer(kon_framebuffer_t *fb);
+void kon_putPixel(kon_framebuffer_t *fb, int x, int y, uint32_t color);
+void kon_clearFramebuffer(kon_framebuffer_t *fb, uint32_t color);
+void kon_resizeFramebuffer(kon_framebuffer_t *fb, int width, int height);
+void kon_exportPixels(kon_framebuffer_t *fb, kon_imageFormat_t format, uint8_t *out);
+
+/*** image declarations ***/
+
+kon_image *kon_loadImage(const uint8_t *pixels, int width, int height, kon_imageFormat_t format);
+void kon_freeImage(kon_image *image);
+
+void kon_drawImage(kon_framebuffer_t *fb, int x, int y, int width, int height, kon_image *image);
+
+/*** draw function declarations ***/
+
+void kon_drawRectangle(kon_framebuffer_t *fb, int x, int y, int width, int height, uint32_t color);
+void kon_fillRectangle(kon_framebuffer_t *fb, int x, int y, int width, int height, uint32_t color);
+void kon_drawLine(kon_framebuffer_t *fb, int x0, int y0, int x1, int y1, uint32_t color);
+void kon_drawCircle(kon_framebuffer_t *fb, int center_x, int center_y, int radius, uint32_t color);
+void kon_fillCircle(kon_framebuffer_t *fb, int center_x, int center_y, int radius, uint32_t color);
+
+/*** implementation ***/
+
+#ifdef KONSOFREN_IMPLEMENTATION
+
+/*** private helper ***/
+
+static inline uint32_t kon_blendColor(uint32_t dst, uint32_t src) {
+
+ uint8_t src_a = (src >> 24) & 0xFF;
+ uint8_t src_r = (src >> 16) & 0xFF;
+ uint8_t src_g = (src >> 8) & 0xFF;
+ uint8_t src_b = (src >> 0) & 0xFF;
+
+ if (src_a == 0xFF) {
+ return src;
+ }
+
+ uint8_t dst_a = (dst >> 24) & 0xFF;
+ uint8_t dst_r = (dst >> 16) & 0xFF;
+ uint8_t dst_g = (dst >> 8) & 0xFF;
+ uint8_t dst_b = (dst >> 0) & 0xFF;
+
+ uint8_t inv_a = 255 - src_a;
+
+ uint8_t out_r = (src_r * src_a + dst_r * inv_a) / 255;
+ uint8_t out_g = (src_g * src_a + dst_g * inv_a) / 255;
+ uint8_t out_b = (src_b * src_a + dst_b * inv_a) / 255;
+
+ return ((uint32_t)dst_a << 24) | ((uint32_t)out_r << 16) | ((uint32_t)out_g << 8) | (uint32_t)out_b;
+}
+
+/*** framebuffer implementation ***/
+
+kon_framebuffer_t *kon_createFramebuffer(int width, int height) {
+ kon_framebuffer_t *fb = malloc(sizeof(kon_framebuffer_t));
+ if (!fb) return NULL;
+
+ fb->data = calloc((size_t)width * height, sizeof(uint32_t));
+ if (!fb->data) {
+ free(fb);
+ return NULL;
+ }
+
+ fb->width = width;
+ fb->height = height;
+ return fb;
+}
+
+void kon_freeFramebuffer(kon_framebuffer_t *fb) {
+ if (!fb) return;
+
+ free(fb->data);
+ free(fb);
+}
+
+void kon_putPixel(kon_framebuffer_t *fb, int x, int y, uint32_t color) {
+ if (!fb) return;
+
+ if (x < 0 || x >= fb->width || y < 0 || y >= fb->height) return;
+
+ uint32_t dst = fb->data[y * fb->width + x];
+ fb->data[y * fb->width + x] = kon_blendColor(dst, color);
+}
+
+void kon_clearFramebuffer(kon_framebuffer_t *fb, uint32_t color) {
+ if (!fb) return;
+
+ for (int i = 0; i < fb->width * fb->height; i++) {
+ /* did it directly to not check overhead because of the if in bounds check */
+ fb->data[i] = color;
+ }
+}
+
+void kon_resizeFramebuffer(kon_framebuffer_t *fb, int width, int height) {
+ if (!fb) return;
+
+ uint32_t *tmp = realloc(fb->data, (size_t)width * height * sizeof(uint32_t));
+ if (!tmp) return;
+
+ fb->data = tmp;
+ fb->width = width;
+ fb->height = height;
+
+ kon_clearFramebuffer(fb, KON_BACKGROUND_COLOR);
+}
+
+void kon_exportPixels(kon_framebuffer_t *fb, kon_imageFormat_t format, uint8_t *out) {
+ if (!fb || !out) return;
+
+ int fbSize = fb->width * fb->height;
+
+ for (int i = 0; i < fbSize; i++) {
+ uint32_t color = fb->data[i];
+
+ uint8_t a = (color >> 24) & 0xFF;
+ uint8_t r = (color >> 16) & 0xFF;
+ uint8_t g = (color >> 8) & 0xFF;
+ uint8_t b = (color >> 0) & 0xFF;
+
+ switch (format) {
+ case konFormatRGBA8:
+ out[i * 4 + 0] = r;
+ out[i * 4 + 1] = g;
+ out[i * 4 + 2] = b;
+ out[i * 4 + 3] = a;
+ break;
+ case konFormatABGR8:
+ out[i * 4 + 0] = a;
+ out[i * 4 + 1] = b;
+ out[i * 4 + 2] = g;
+ out[i * 4 + 3] = r;
+ break;
+ case konFormatARGB8:
+ out[i * 4 + 0] = a;
+ out[i * 4 + 1] = r;
+ out[i * 4 + 2] = g;
+ out[i * 4 + 3] = b;
+ break;
+ case konFormatBGRA8:
+ out[i * 4 + 0] = b;
+ out[i * 4 + 1] = g;
+ out[i * 4 + 2] = r;
+ out[i * 4 + 3] = a;
+ break;
+ default:
+ return;
+ }
+ }
+}
+
+/*** draw functions implementation ***/
+
+void kon_drawRectangle(kon_framebuffer_t *fb, int x, int y, int width, int height, uint32_t color) {
+ if (!fb) return;
+
+ kon_drawLine(fb, x, y, x + width, y, color);
+ kon_drawLine(fb, x, y + height, x + width, y + height, color);
+ kon_drawLine(fb, x, y, x, y + height, color);
+ kon_drawLine(fb, x + width, y, x + width, y + height, color);
+}
+
+void kon_fillRectangle(kon_framebuffer_t *fb, int x, int y, int width, int height, uint32_t color) {
+ if (!fb) return;
+
+ if (width < 0 || height < 0) return;
+
+ if (x < 0) {
+ width += x;
+ x = 0;
+ }
+ if (y < 0) {
+ height += y;
+ y = 0;
+ }
+
+ if (x + width > fb->width ) width = fb->width - x;
+ if (y + height > fb->height) height = fb->height - y;
+
+ for (int offset_y = 0; offset_y < height; offset_y++) {
+ for (int offset_x = 0; offset_x < width; offset_x++) {
+ uint32_t dst = fb->data[(offset_y + y) * fb->width + (offset_x + x)];
+ fb->data[(y + offset_y) * fb->width + (x + offset_x)] = kon_blendColor(dst, color);
+
+ }
+ }
+}
+
+void kon_drawLine(kon_framebuffer_t *fb, int x0, int y0, int x1, int y1, uint32_t color) {
+ if (!fb) return;
+
+ int dx = abs(x1 - x0);
+ int dy = abs(y1 - y0);
+
+ int sx = (x0 < x1) ? 1 : -1;
+ int sy = (y0 < y1) ? 1 : -1;
+
+ int err = dx - dy;
+
+ for (;;) {
+ kon_putPixel(fb, x0, y0, color);
+
+ if (x0 == x1 && y0 == y1) break;
+
+ int e2 = 2 * err;
+
+ if (e2 > -dy) {
+ err -= dy;
+ x0 += sx;
+ }
+
+ if (e2 < dx) {
+ err += dx;
+ y0 += sy;
+ }
+ }
+}
+
+void kon_drawCircle(kon_framebuffer_t *fb, int center_x, int center_y, int radius, uint32_t color) {
+ if (!fb) return;
+ if (radius <= 0) return;
+
+ int x = radius;
+ int y = 0;
+ int err = 1 - radius;
+
+ while (x >= y) {
+ kon_putPixel(fb, center_x + x, center_y + y, color);
+ kon_putPixel(fb, center_x + y, center_y + x, color);
+ kon_putPixel(fb, center_x - y, center_y + x, color);
+ kon_putPixel(fb, center_x - x, center_y + y, color);
+ kon_putPixel(fb, center_x - x, center_y - y, color);
+ kon_putPixel(fb, center_x - y, center_y - x, color);
+ kon_putPixel(fb, center_x + y, center_y - x, color);
+ kon_putPixel(fb, center_x + x, center_y - y, color);
+
+ y += 1;
+
+ if (err < 0) {
+ err += 2 * y + 1;
+ } else {
+ x -= 1;
+ err += 2 * (y - x) + 1;
+ }
+ }
+}
+
+void kon_fillCircle(kon_framebuffer_t *fb, int center_x, int center_y, int radius, uint32_t color) {
+ if (!fb) return;
+ if (radius <= 0) return;
+
+ int x = radius;
+ int y = 0;
+ int err = 1 - radius;
+
+ while (x >= y) {
+ kon_drawLine(fb, center_x - x, center_y + y, center_x + x, center_y + y, color);
+ kon_drawLine(fb, center_x - x, center_y - y, center_x + x, center_y - y, color);
+ kon_drawLine(fb, center_x - y, center_y + x, center_x + y, center_y + x, color);
+ kon_drawLine(fb, center_x - y, center_y - x, center_x + y, center_y - x, color);
+
+ y += 1;
+
+ if (err < 0) {
+ err += 2 * y + 1;
+ } else {
+ x -= 1;
+ err += 2 * (y - x) + 1;
+ }
+ }
+}
+
+/*** image implementation ***/
+
+kon_image *kon_loadImage(const uint8_t *pixels, int width, int height, kon_imageFormat_t format) {
+ kon_image *image = malloc(sizeof(kon_image));
+ if (!image) return NULL;
+
+ image->data = malloc((size_t)width * height * sizeof(uint32_t));
+ if (!image->data) {
+ free(image);
+ return NULL;
+ }
+
+ image->width = width;
+ image->height = height;
+
+ for (int i = 0; i < width * height; i++) {
+ uint8_t r, g, b, a;
+
+ switch(format) {
+ case konFormatRGBA8:
+ r = pixels[i * 4 + 0];
+ g = pixels[i * 4 + 1];
+ b = pixels[i * 4 + 2];
+ a = pixels[i * 4 + 3];
+ break;
+ case konFormatABGR8:
+ a = pixels[i * 4 + 0];
+ b = pixels[i * 4 + 1];
+ g = pixels[i * 4 + 2];
+ r = pixels[i * 4 + 3];
+ break;
+ case konFormatARGB8:
+ a = pixels[i * 4 + 0];
+ r = pixels[i * 4 + 1];
+ g = pixels[i * 4 + 2];
+ b = pixels[i * 4 + 3];
+ break;
+ case konFormatBGRA8:
+ b = pixels[i * 4 + 0];
+ g = pixels[i * 4 + 1];
+ r = pixels[i * 4 + 2];
+ a = pixels[i * 4 + 3];
+ break;
+ default:
+ free(image->data);
+ free(image);
+ return NULL;
+ }
+
+ image->data[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
+ }
+
+ return image;
+}
+void kon_freeImage(kon_image *image) {
+ kon_freeFramebuffer(image);
+}
+
+void kon_drawImage(kon_framebuffer_t *fb, int x, int y, int width, int height, kon_image *image) {
+ if (!fb || !image) return;
+
+ /* just trying to not draw when it's unnecessary */
+ if (image->width <= 0 || image->height <= 0) return;
+ if (width <= 0 || height <= 0) return;
+ if (x >= fb->width || y >= fb->height) return;
+
+ /* using this loop to make sure we only the parts of the image that are on screen */
+ for (int iy = 0; iy < height; iy++) {
+ for (int ix = 0; ix < width; ix++) {
+ int src_x = ix * image->width / width;
+ int src_y = iy * image->height / height;
+
+ uint32_t color = image->data[src_y * image->width + src_x];
+ kon_putPixel(fb, x+ix, y+iy, color);
+ }
+ }
+}
+
+#endif
+#endif
diff --git a/src/kontime.h b/src/kontime.h
new file mode 100644
index 0000000..da46ddd
--- /dev/null
+++ b/src/kontime.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * kontime.h
+ *
+ * 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.
+ */
+
+#ifndef KONTIME_H
+#define KONTIME_H
+
+/*** definition ***/
+
+typedef double kon_time;
+
+kon_time kon_getTime(void);
+
+void kon_sleep(kon_time seconds);
+
+/*** implementation ***/
+
+#ifdef KONTIME_IMPLEMENTATION
+
+#if defined(__linux__) || defined(__unix__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+
+#define _POSIX_C_SOURCE 200809L
+#include <time.h>
+
+kon_time kon_getTime(void) {
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ return (kon_time)ts.tv_sec + (kon_time)ts.tv_nsec / 1e9;
+}
+
+void kon_sleep(kon_time seconds) {
+ struct timespec ts;
+ ts.tv_sec = (time_t)seconds;
+ ts.tv_nsec = (long)((seconds - ts.tv_sec) * 1e9);
+
+ nanosleep(&ts, NULL);
+}
+
+#elif defined(_WIN32)
+
+#include <windows.h>
+
+kon_time kon_getTime(void) {
+ LARGE_INTEGER freq, now;
+ QueryPerformanceFrequency(&freq);
+ QueryPerformanceCounter(&now);
+
+ return (kon_time)now.QuadPart / (kon_time)freq.QuadPart;
+}
+
+void kon_sleep(kon_time seconds) {
+ Sleep((DWORD)(seconds * 1000.0));
+}
+
+#else
+#error "kontime.h: unsupported platform!"
+
+#endif
+
+#endif /* end of KONTIME_IMPLEMENTATION */
+
+
+#endif
diff --git a/src/kontoml.h b/src/kontoml.h
new file mode 100644
index 0000000..88a7e68
--- /dev/null
+++ b/src/kontoml.h
@@ -0,0 +1,649 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * kontoml.h
+ *
+ * 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.
+ */
+
+#ifndef KONTOML_H
+#define KONTOML_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+typedef enum kon_tomlType {
+ konTomlString,
+ konTomlInt,
+ konTomlFloat,
+ konTomlBool,
+ konTomlArray
+} kon_tomlType_t;
+
+typedef struct kon_tomlEntry {
+ char *section;
+ char *key;
+ kon_tomlType_t type;
+ char *str_value;
+ long long int_value;
+ double float_value;
+ bool bool_value;
+
+ kon_tomlType_t array_type;
+ int array_count;
+ char **array_str;
+ long long *array_int;
+ double *array_float;
+ bool *array_bool;
+} kon_tomlEntry_t;
+
+typedef struct kon_toml {
+ kon_tomlEntry_t *entries;
+ int count;
+ int capacity;
+} kon_toml_t;
+
+kon_toml_t *kon_parseToml(const char *text);
+kon_toml_t *kon_parseTomlFile(const char *path);
+void kon_freeToml(kon_toml_t *toml);
+
+const char *kon_tomlGetString(kon_toml_t *toml, const char *section, const char *key, const char *def);
+long long kon_tomlGetInt(kon_toml_t *toml, const char *section, const char *key, long long def);
+double kon_tomlGetFloat(kon_toml_t *toml, const char *section, const char *key, double def);
+bool kon_tomlGetBool(kon_toml_t *toml, const char *section, const char *key, bool def);
+
+const char *const *kon_tomlGetStringArray(kon_toml_t *toml, const char *section, const char *key, int *out_count);
+const long long *kon_tomlGetIntArray(kon_toml_t *toml, const char *section, const char *key, int *out_count);
+const double *kon_tomlGetFloatArray(kon_toml_t *toml, const char *section, const char *key, int *out_count);
+const bool *kon_tomlGetBoolArray(kon_toml_t *toml, const char *section, const char *key, int *out_count);
+
+/*** implementation ***/
+
+#ifdef KONTOML_IMPLEMENTATION
+
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+
+static char *kon__tomlStrdup(const char *s) {
+ size_t len = strlen(s) + 1;
+ char *out = malloc(len);
+ if (!out) return NULL;
+ memcpy(out, s, len);
+ return out;
+}
+
+static char *kon__tomlStrndup(const char *s, size_t n) {
+ char *out = malloc(n + 1);
+ if (!out) return NULL;
+ memcpy(out, s, n);
+ out[n] = '\0';
+ return out;
+}
+
+static char *kon__tomlTrim(char *s) {
+ char *end;
+
+ while (*s == ' ' || *s == '\t') s++;
+ if (*s == '\0') return s;
+
+ end = s + strlen(s) - 1;
+ while (end > s && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) {
+ *end = '\0';
+ end--;
+ }
+
+ return s;
+}
+
+static kon_tomlEntry_t *kon__tomlPushEntry(kon_toml_t *toml) {
+ if (toml->count == toml->capacity) {
+ int newCap = toml->capacity ? toml->capacity * 2 : 16;
+ kon_tomlEntry_t *tmp = realloc(toml->entries, (size_t)newCap * sizeof(kon_tomlEntry_t));
+ if (!tmp) return NULL;
+
+ toml->entries = tmp;
+ toml->capacity = newCap;
+ }
+
+ kon_tomlEntry_t *entry = &toml->entries[toml->count];
+ memset(entry, 0, sizeof(kon_tomlEntry_t));
+ toml->count++;
+
+ return entry;
+}
+
+static int kon__tomlParseString(const char **p, char **out) {
+ const char *s = *p;
+ char quote = *s;
+ int literal = (quote == '\'');
+ s++;
+
+ char *buf = malloc(strlen(s) + 1);
+ if (!buf) return -1;
+
+ size_t bi = 0;
+
+ while (*s && *s != quote) {
+ if (!literal && *s == '\\') {
+ s++;
+ switch (*s) {
+ case 'n':
+ buf[bi++] = '\n';
+ break;
+ case 't':
+ buf[bi++] = '\t';
+ break;
+ case 'r':
+ buf[bi++] = '\r';
+ break;
+ case '"':
+ buf[bi++] = '"';
+ break;
+ case '\\':
+ buf[bi++] = '\\';
+ break;
+ case '\0':
+ free(buf);
+ return -1;
+ default:
+ buf[bi++] = *s;
+ break;
+ }
+ s++;
+ } else {
+ buf[bi++] = *s;
+ s++;
+ }
+ }
+
+ if (*s != quote) {
+ free(buf);
+ return -1;
+ }
+ s++;
+
+ buf[bi] = '\0';
+ *out = buf;
+ *p = s;
+
+ return 0;
+}
+
+static char *kon__tomlStripUnderscores(const char *s) {
+ char *out = malloc(strlen(s) + 1);
+ if (!out) return NULL;
+
+ size_t oi = 0;
+ for (size_t i = 0; s[i]; i++) {
+ if (s[i] != '_') out[oi++] = s[i];
+ }
+ out[oi] = '\0';
+
+ return out;
+}
+
+static char **kon__tomlSplitArrayElements(const char *content, int *outCount) {
+ int cap = 8;
+ int count = 0;
+ char **elems = malloc((size_t)cap * sizeof(char *));
+ if (!elems) return NULL;
+
+ const char *start = content;
+ const char *p = content;
+ char inQuote = 0;
+
+ while (*p) {
+ if (inQuote) {
+ if (inQuote == '"' && *p == '\\' && *(p + 1) != '\0') {
+ p += 2;
+ continue;
+ }
+ if (*p == inQuote) inQuote = 0;
+ p++;
+ continue;
+ }
+
+ if (*p == '"' || *p == '\'') {
+ inQuote = *p;
+ p++;
+ continue;
+ }
+
+ if (*p == ',') {
+ if (count == cap) {
+ cap *= 2;
+ char **tmp = realloc(elems, (size_t)cap * sizeof(char *));
+ if (!tmp) { free(elems); return NULL; }
+ elems = tmp;
+ }
+ elems[count++] = kon__tomlStrndup(start, (size_t)(p - start));
+ p++;
+ start = p;
+ continue;
+ }
+
+ p++;
+ }
+
+ {
+ char *raw = kon__tomlStrndup(start, (size_t)(p - start));
+ char *checkTrim = raw ? kon__tomlTrim(raw) : NULL;
+
+ if (raw && *checkTrim != '\0') {
+ if (count == cap) {
+ cap += 1;
+ char **tmp = realloc(elems, (size_t)cap * sizeof(char *));
+ if (!tmp) { free(raw); free(elems); return NULL; }
+ elems = tmp;
+ }
+ elems[count++] = raw;
+ } else {
+ free(raw);
+ }
+ }
+
+ *outCount = count;
+ return elems;
+}
+
+static int kon__tomlParseArrayValue(kon_tomlEntry_t *entry, const char *valueStr) {
+ const char *open = strchr(valueStr, '[');
+ const char *close = strchr(valueStr, ']');
+ if (!open || !close || close <= open) return -1;
+
+ char *content = kon__tomlStrndup(open + 1, (size_t)(close - open - 1));
+ if (!content) return -1;
+
+ int rawCount = 0;
+ char **rawElems = kon__tomlSplitArrayElements(content, &rawCount);
+ free(content);
+ if (!rawElems) return -1;
+
+ if (rawCount == 0) {
+ free(rawElems);
+ entry->type = konTomlArray;
+ entry->array_type = konTomlInt;
+ entry->array_count = 0;
+ return 0;
+ }
+
+ kon_tomlType_t elemType = konTomlInt;
+ char **strArr = NULL;
+ long long *intArr = NULL;
+ double *floatArr = NULL;
+ bool *boolArr = NULL;
+ int ok = 1;
+
+ for (int i = 0; i < rawCount && ok; i++) {
+ char *trimmed = kon__tomlTrim(rawElems[i]);
+
+ kon_tomlType_t thisType;
+ char *decodedStr = NULL;
+ long long thisInt = 0;
+ double thisFloat = 0.0;
+ bool thisBool = false;
+
+ if (*trimmed == '"' || *trimmed == '\'') {
+ const char *vp = trimmed;
+ thisType = konTomlString;
+ if (kon__tomlParseString(&vp, &decodedStr) != 0) { ok = 0; break; }
+ } else if (strcmp(trimmed, "true") == 0) {
+ thisType = konTomlBool;
+ thisBool = true;
+ } else if (strcmp(trimmed, "false") == 0) {
+ thisType = konTomlBool;
+ thisBool = false;
+ } else {
+ char *clean = kon__tomlStripUnderscores(trimmed);
+ if (!clean) { ok = 0; break; }
+
+ int isFloat = (strchr(clean, '.') || strchr(clean, 'e') || strchr(clean, 'E'));
+ if (isFloat) {
+ thisType = konTomlFloat;
+ thisFloat = strtod(clean, NULL);
+ } else {
+ thisType = konTomlInt;
+ thisInt = strtoll(clean, NULL, 10);
+ }
+ free(clean);
+ }
+
+ if (i == 0) {
+ elemType = thisType;
+ switch (elemType) {
+ case konTomlString:
+ strArr = malloc((size_t)rawCount * sizeof(char *));
+ break;
+ case konTomlInt:
+ intArr = malloc((size_t)rawCount * sizeof(long long));
+ break;
+ case konTomlFloat:
+ floatArr = malloc((size_t)rawCount * sizeof(double));
+ break;
+ case konTomlBool:
+ boolArr = malloc((size_t)rawCount * sizeof(bool));
+ break;
+ default:
+ break;
+ }
+ } else if (thisType != elemType) {
+ free(decodedStr);
+ ok = 0;
+ break;
+ }
+
+ switch (elemType) {
+ case konTomlString:
+ strArr[i] = decodedStr;
+ break;
+ case konTomlInt:
+ intArr[i] = thisInt;
+ break;
+ case konTomlFloat:
+ floatArr[i] = thisFloat;
+ break;
+ case konTomlBool:
+ boolArr[i] = thisBool;
+ break;
+ default:
+ break;
+ }
+ }
+
+ for (int i = 0; i < rawCount; i++) free(rawElems[i]);
+ free(rawElems);
+
+ if (!ok) {
+ if (strArr) {
+ for (int i = 0; i < rawCount; i++)
+ free(strArr[i]);
+
+ free(strArr);
+ }
+ free(intArr);
+ free(floatArr);
+ free(boolArr);
+ return -1;
+ }
+
+ entry->type = konTomlArray;
+ entry->array_type = elemType;
+ entry->array_count = rawCount;
+ entry->array_str = strArr;
+ entry->array_int = intArr;
+ entry->array_float = floatArr;
+ entry->array_bool = boolArr;
+
+ return 0;
+}
+
+static int kon__tomlParseLine(kon_toml_t *toml, char *line, char **currentSection) {
+ char *trimmed = kon__tomlTrim(line);
+
+ if (*trimmed == '\0' || *trimmed == '#') return 0;
+
+ if (*trimmed == '[') {
+ char *close = strchr(trimmed, ']');
+ if (!close) return -1;
+
+ free(*currentSection);
+ *currentSection = kon__tomlStrndup(trimmed + 1, (size_t)(close - trimmed - 1));
+
+ return 0;
+ }
+
+ char *eq = strchr(trimmed, '=');
+ if (!eq) return -1;
+
+ *eq = '\0';
+ char *key = kon__tomlTrim(trimmed);
+ char *valueStr = kon__tomlTrim(eq + 1);
+
+ if (*valueStr != '"' && *valueStr != '\'') {
+ char *hash = strchr(valueStr, '#');
+ if (hash) {
+ *hash = '\0';
+ valueStr = kon__tomlTrim(valueStr);
+ }
+ }
+
+ if (*key == '\0' || *valueStr == '\0') return -1;
+
+ kon_tomlEntry_t *entry = kon__tomlPushEntry(toml);
+ if (!entry) return -1;
+
+ entry->section = *currentSection ? kon__tomlStrdup(*currentSection) : kon__tomlStrdup("");
+ entry->key = kon__tomlStrdup(key);
+
+ if (*valueStr == '[') {
+ if (kon__tomlParseArrayValue(entry, valueStr) != 0) return -1;
+ } else if (*valueStr == '"' || *valueStr == '\'') {
+ const char *vp = valueStr;
+ char *decoded = NULL;
+
+ if (kon__tomlParseString(&vp, &decoded) != 0) return -1;
+
+ entry->type = konTomlString;
+ entry->str_value = decoded;
+ } else if (strcmp(valueStr, "true") == 0) {
+ entry->type = konTomlBool;
+ entry->bool_value = true;
+ } else if (strcmp(valueStr, "false") == 0) {
+ entry->type = konTomlBool;
+ entry->bool_value = false;
+ } else {
+ char *clean = kon__tomlStripUnderscores(valueStr);
+ if (!clean) return -1;
+
+ int isFloat = (strchr(clean, '.') || strchr(clean, 'e') || strchr(clean, 'E'));
+
+ if (isFloat) {
+ entry->type = konTomlFloat;
+ entry->float_value = strtod(clean, NULL);
+ } else {
+ entry->type = konTomlInt;
+ entry->int_value = strtoll(clean, NULL, 10);
+ }
+
+ free(clean);
+ }
+
+ return 0;
+}
+
+kon_toml_t *kon_parseToml(const char *text) {
+ if (!text) return NULL;
+
+ kon_toml_t *toml = malloc(sizeof(kon_toml_t));
+ if (!toml) return NULL;
+
+ toml->entries = NULL;
+ toml->count = 0;
+ toml->capacity = 0;
+
+ char *currentSection = NULL;
+
+ const char *lineStart = text;
+ while (*lineStart) {
+ const char *lineEnd = strchr(lineStart, '\n');
+ size_t len = lineEnd ? (size_t)(lineEnd - lineStart) : strlen(lineStart);
+
+ char *line = kon__tomlStrndup(lineStart, len);
+ if (line) {
+ kon__tomlParseLine(toml, line, &currentSection);
+ free(line);
+ }
+
+ if (!lineEnd) break;
+ lineStart = lineEnd + 1;
+ }
+
+ free(currentSection);
+
+ return toml;
+}
+
+kon_toml_t *kon_parseTomlFile(const char *path) {
+ FILE *f = fopen(path, "rb");
+ if (!f) return NULL;
+
+ fseek(f, 0, SEEK_END);
+ long size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (size < 0) {
+ fclose(f);
+ return NULL;
+ }
+
+ char *buf = malloc((size_t)size + 1);
+ if (!buf) {
+ fclose(f);
+ return NULL;
+ }
+
+ size_t read = fread(buf, 1, (size_t)size, f);
+ buf[read] = '\0';
+ fclose(f);
+
+ kon_toml_t *toml = kon_parseToml(buf);
+ free(buf);
+
+ return toml;
+}
+
+void kon_freeToml(kon_toml_t *toml) {
+ if (!toml) return;
+
+ for (int i = 0; i < toml->count; i++) {
+ kon_tomlEntry_t *e = &toml->entries[i];
+
+ free(e->section);
+ free(e->key);
+
+ if (e->type == konTomlString) {
+ free(e->str_value);
+ } else if (e->type == konTomlArray) {
+ if (e->array_str) {
+ for (int j = 0; j < e->array_count; j++) free(e->array_str[j]);
+ free(e->array_str);
+ }
+ free(e->array_int);
+ free(e->array_float);
+ free(e->array_bool);
+ }
+ }
+
+ free(toml->entries);
+ free(toml);
+}
+
+static kon_tomlEntry_t *kon__tomlFind(kon_toml_t *toml, const char *section, const char *key) {
+ if (!toml) return NULL;
+
+ const char *sec = section ? section : "";
+
+ for (int i = 0; i < toml->count; i++) {
+ kon_tomlEntry_t *e = &toml->entries[i];
+ if (strcmp(e->section, sec) == 0 && strcmp(e->key, key) == 0) {
+ return e;
+ }
+ }
+
+ return NULL;
+}
+
+const char *kon_tomlGetString(kon_toml_t *toml, const char *section, const char *key, const char *def) {
+ kon_tomlEntry_t *e = kon__tomlFind(toml, section, key);
+ if (!e || e->type != konTomlString) return def;
+ return e->str_value;
+}
+
+long long kon_tomlGetInt(kon_toml_t *toml, const char *section, const char *key, long long def) {
+ kon_tomlEntry_t *e = kon__tomlFind(toml, section, key);
+ if (!e) return def;
+
+ if (e->type == konTomlInt) return e->int_value;
+ if (e->type == konTomlFloat) return (long long)e->float_value;
+
+ return def;
+}
+
+double kon_tomlGetFloat(kon_toml_t *toml, const char *section, const char *key, double def) {
+ kon_tomlEntry_t *e = kon__tomlFind(toml, section, key);
+ if (!e) return def;
+
+ if (e->type == konTomlFloat) return e->float_value;
+ if (e->type == konTomlInt) return (double)e->int_value;
+
+ return def;
+}
+
+bool kon_tomlGetBool(kon_toml_t *toml, const char *section, const char *key, bool def) {
+ kon_tomlEntry_t *e = kon__tomlFind(toml, section, key);
+ if (!e || e->type != konTomlBool) return def;
+ return e->bool_value;
+}
+
+const char *const *kon_tomlGetStringArray(kon_toml_t *toml, const char *section, const char *key, int *out_count) {
+ kon_tomlEntry_t *e = kon__tomlFind(toml, section, key);
+ if (!e || e->type != konTomlArray || e->array_type != konTomlString) {
+ if (out_count) *out_count = 0;
+ return NULL;
+ }
+ if (out_count) *out_count = e->array_count;
+ return (const char *const *)e->array_str;
+}
+
+const long long *kon_tomlGetIntArray(kon_toml_t *toml, const char *section, const char *key, int *out_count) {
+ kon_tomlEntry_t *e = kon__tomlFind(toml, section, key);
+ if (!e || e->type != konTomlArray || e->array_type != konTomlInt) {
+ if (out_count) *out_count = 0;
+ return NULL;
+ }
+ if (out_count) *out_count = e->array_count;
+ return e->array_int;
+}
+
+const double *kon_tomlGetFloatArray(kon_toml_t *toml, const char *section, const char *key, int *out_count) {
+ kon_tomlEntry_t *e = kon__tomlFind(toml, section, key);
+ if (!e || e->type != konTomlArray || e->array_type != konTomlFloat) {
+ if (out_count) *out_count = 0;
+ return NULL;
+ }
+ if (out_count) *out_count = e->array_count;
+ return e->array_float;
+}
+
+const bool *kon_tomlGetBoolArray(kon_toml_t *toml, const char *section, const char *key, int *out_count) {
+ kon_tomlEntry_t *e = kon__tomlFind(toml, section, key);
+ if (!e || e->type != konTomlArray || e->array_type != konTomlBool) {
+ if (out_count) *out_count = 0;
+ return NULL;
+ }
+ if (out_count) *out_count = e->array_count;
+ return e->array_bool;
+}
+
+#endif /* END of KONTOML_IMPLEMENTATION */
+
+#endif
diff --git a/src/konwinlib.h b/src/konwinlib.h
new file mode 100644
index 0000000..66ac5ba
--- /dev/null
+++ b/src/konwinlib.h
@@ -0,0 +1,1349 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konwinlib.h
+ *
+ * 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.
+ */
+
+#ifndef KONWINLIB_H
+#define KONWINLIB_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/*** definitions ***/
+
+typedef struct kon_context kon_context_t;
+
+bool kon_init(void);
+void kon_deinit(void);
+
+typedef struct kon_window kon_window_t;
+
+typedef enum kon_windowFlags {
+ KON_WINDOW_NONE = 0,
+ KON_WINDOW_TRANSPARENT = 1 << 0,
+ KON_WINDOW_NO_DECOR = 1 << 1,
+ KON_WINDOW_ALWAYS_ONTOP = 1 << 2,
+ KON_WINDOW_CENTER = 1 << 3,
+ KON_WINDOW_RESIZABLE = 1 << 4
+} kon_windowFlags_t;
+
+typedef enum kon_eventType {
+ KON_EVENT_NONE = 0,
+ KON_EVENT_CLOSE,
+ KON_EVENT_RESIZE,
+ KON_EVENT_KEY_DOWN,
+ KON_EVENT_KEY_UP,
+ KON_EVENT_MOUSE_DOWN,
+ KON_EVENT_MOUSE_UP,
+ KON_EVENT_MOUSE_MOVE
+} kon_eventType_t;
+
+typedef enum kon_mouseButton {
+ KON_MOUSE_LEFT = 0,
+ KON_MOUSE_MIDDLE,
+ KON_MOUSE_RIGHT
+} kon_mouseButton_t;
+
+typedef enum kon_key {
+ KON_KEY_UNKNOWN = 0,
+
+ KON_KEY_A, KON_KEY_B, KON_KEY_C, KON_KEY_D, KON_KEY_E, KON_KEY_F, KON_KEY_G,
+ KON_KEY_H, KON_KEY_I, KON_KEY_J, KON_KEY_K, KON_KEY_L, KON_KEY_M, KON_KEY_N, KON_KEY_O, KON_KEY_P,
+ KON_KEY_Q, KON_KEY_R, KON_KEY_S, KON_KEY_T, KON_KEY_U, KON_KEY_V,
+ KON_KEY_W, KON_KEY_X, KON_KEY_Y, KON_KEY_Z,
+
+ KON_KEY_0, KON_KEY_1, KON_KEY_2, KON_KEY_3, KON_KEY_4, KON_KEY_5, KON_KEY_6, KON_KEY_7, KON_KEY_8, KON_KEY_9,
+
+ KON_KEY_F1, KON_KEY_F2, KON_KEY_F3, KON_KEY_F4, KON_KEY_F5, KON_KEY_F6,
+ KON_KEY_F7, KON_KEY_F8, KON_KEY_F9, KON_KEY_F10, KON_KEY_F11, KON_KEY_F12,
+
+ KON_KEY_LEFT, KON_KEY_RIGHT, KON_KEY_UP, KON_KEY_DOWN,
+
+ KON_KEY_SPACE, KON_KEY_ENTER, KON_KEY_TAB, KON_KEY_BACKSPACE,
+ KON_KEY_ESCAPE, KON_KEY_DELETE,
+
+ KON_KEY_LEFT_SHIFT, KON_KEY_RIGHT_SHIFT,
+ KON_KEY_LEFT_CTRL, KON_KEY_RIGHT_CTRL,
+ KON_KEY_LEFT_ALT, KON_KEY_RIGHT_ALT,
+
+ KON_KEY_MINUS, KON_KEY_EQUAL,
+ KON_KEY_LEFT_BRACKET, KON_KEY_RIGHT_BRACKET,
+ KON_KEY_SEMICOLON, KON_KEY_APOSTROPHE,
+ KON_KEY_COMMA, KON_KEY_PERIOD, KON_KEY_SLASH, KON_KEY_BACKSLASH,
+ KON_KEY_GRAVE,
+
+ KON_KEY_COUNT
+} kon_key_t;
+
+typedef struct kon_event {
+ kon_eventType_t type;
+ int width, height; /* will be used to store data for resize events */
+ int key;
+ int mouseX, mouseY;
+ kon_mouseButton_t mouseButton;
+} kon_event_t;
+
+kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int height, kon_windowFlags_t flags);
+void kon_destroyWindow(kon_window_t *window);
+
+bool kon_windowShouldClose(kon_window_t *window);
+
+void kon_setExitKey(kon_window_t *window, int key);
+
+void kon_setWindowPos(kon_window_t *window, int x, int y);
+void kon_setWindowSize(kon_window_t *window, int width, int height);
+
+void kon_getWindowPos(kon_window_t *window, int *x, int *y);
+void kon_getWindowSize(kon_window_t *window, int *width, int *height);
+
+int kon_pollEvent(kon_window_t *window, kon_event_t *event);
+void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int height);
+
+// added these for web dev
+typedef void (*kon_mainLoopFn)(void *userData);
+void kon_runMainLoop(kon_window_t *window, kon_mainLoopFn callback, void *userData);
+
+/*** implementation ***/
+
+#ifdef KONWINLIB_IMPLEMENTATION
+
+static kon_context_t *kon_ctx;
+
+#ifdef __EMSCRIPTEN__
+
+#include <emscripten/emscripten.h>
+#include <emscripten/html5.h>
+#include <string.h>
+
+struct kon_context {
+ int initialized;
+};
+
+struct kon_window {
+ char canvasID[64];
+ bool shouldClose;
+ int exitKey;
+
+ bool hasKeyEvent;
+ kon_eventType_t keyEventType;
+ int keyEventKey;
+
+ bool hasResizeEvent;
+ int resizeWidth, resizeHeight;
+
+ bool hasMouseEvent;
+ kon_eventType_t mouseEventType;
+ int mouseEventX, mouseEventY;
+ kon_mouseButton_t mouseEventButton;
+};
+
+static kon_key_t kon_translateEmKey_(int keyCode) {
+ switch (keyCode) {
+ case 65: return KON_KEY_A;
+ case 66: return KON_KEY_B;
+ case 67: return KON_KEY_C;
+ case 68: return KON_KEY_D;
+ case 69: return KON_KEY_E;
+ case 70: return KON_KEY_F;
+ case 71: return KON_KEY_G;
+ case 72: return KON_KEY_H;
+ case 73: return KON_KEY_I;
+ case 74: return KON_KEY_J;
+ case 75: return KON_KEY_K;
+ case 76: return KON_KEY_L;
+ case 77: return KON_KEY_M;
+ case 78: return KON_KEY_N;
+ case 79: return KON_KEY_O;
+ case 80: return KON_KEY_P;
+ case 81: return KON_KEY_Q;
+ case 82: return KON_KEY_R;
+ case 83: return KON_KEY_S;
+ case 84: return KON_KEY_T;
+ case 85: return KON_KEY_U;
+ case 86: return KON_KEY_V;
+ case 87: return KON_KEY_W;
+ case 88: return KON_KEY_X;
+ case 89: return KON_KEY_Y;
+ case 90: return KON_KEY_Z;
+
+ case 48: return KON_KEY_0;
+ case 49: return KON_KEY_1;
+ case 50: return KON_KEY_2;
+ case 51: return KON_KEY_3;
+ case 52: return KON_KEY_4;
+ case 53: return KON_KEY_5;
+ case 54: return KON_KEY_6;
+ case 55: return KON_KEY_7;
+ case 56: return KON_KEY_8;
+ case 57: return KON_KEY_9;
+
+ case 112: return KON_KEY_F1;
+ case 113: return KON_KEY_F2;
+ case 114: return KON_KEY_F3;
+ case 115: return KON_KEY_F4;
+ case 116: return KON_KEY_F5;
+ case 117: return KON_KEY_F6;
+ case 118: return KON_KEY_F7;
+ case 119: return KON_KEY_F8;
+ case 120: return KON_KEY_F9;
+ case 121: return KON_KEY_F10;
+ case 122: return KON_KEY_F11;
+ case 123: return KON_KEY_F12;
+
+ case 37: return KON_KEY_LEFT;
+ case 39: return KON_KEY_RIGHT;
+ case 38: return KON_KEY_UP;
+ case 40: return KON_KEY_DOWN;
+
+ case 32: return KON_KEY_SPACE;
+ case 13: return KON_KEY_ENTER;
+ case 9: return KON_KEY_TAB;
+ case 8: return KON_KEY_BACKSPACE;
+ case 27: return KON_KEY_ESCAPE;
+ case 46: return KON_KEY_DELETE;
+
+ case 16: return KON_KEY_LEFT_SHIFT;
+ case 17: return KON_KEY_LEFT_CTRL;
+ case 18: return KON_KEY_LEFT_ALT;
+
+ case 189: return KON_KEY_MINUS;
+ case 187: return KON_KEY_EQUAL;
+ case 219: return KON_KEY_LEFT_BRACKET;
+ case 221: return KON_KEY_RIGHT_BRACKET;
+ case 186: return KON_KEY_SEMICOLON;
+ case 222: return KON_KEY_APOSTROPHE;
+ case 188: return KON_KEY_COMMA;
+ case 190: return KON_KEY_PERIOD;
+ case 191: return KON_KEY_SLASH;
+ case 220: return KON_KEY_BACKSLASH;
+ case 192: return KON_KEY_GRAVE;
+
+ default: return KON_KEY_UNKNOWN;
+ }
+}
+bool kon_init(void) {
+ kon_context_t *ctx = malloc(sizeof(kon_context_t));
+ if (!ctx) return false;
+
+ ctx->initialized = 1;
+ kon_ctx = ctx;
+ return true;
+}
+
+void kon_deinit(void) {
+ if (!kon_ctx) return;
+
+ free(kon_ctx);
+ kon_ctx = NULL;
+}
+
+static EM_BOOL kon_keyDownCallback_(int eventType, const EmscriptenKeyboardEvent *e, void *userData) {
+ (void)eventType;
+ kon_window_t *window = (kon_window_t *)userData;
+
+ window->hasKeyEvent = true;
+ window->keyEventType = KON_EVENT_KEY_DOWN;
+ window->keyEventKey = kon_translateEmKey_(e->keyCode);
+
+ if (window->exitKey != 0 && window->keyEventKey == window->exitKey) {
+ window->shouldClose = true;
+ }
+
+ return EM_TRUE;
+}
+
+static EM_BOOL kon_keyUpCallback_(int eventType, const EmscriptenKeyboardEvent *e, void *userData) {
+ (void)eventType;
+ kon_window_t *window = (kon_window_t *)userData;
+
+ window->hasKeyEvent = true;
+ window->keyEventType = KON_EVENT_KEY_UP;
+ window->keyEventKey = kon_translateEmKey_(e->keyCode);
+
+ return EM_TRUE;
+}
+
+static EM_BOOL kon_mouseDownCallback_(int eventType, const EmscriptenMouseEvent *e, void *userData) {
+ (void)eventType;
+ kon_window_t *window = (kon_window_t *)userData;
+
+ if (e->button > KON_MOUSE_RIGHT) return EM_FALSE;
+
+ window->hasMouseEvent = true;
+ window->mouseEventType = KON_EVENT_MOUSE_DOWN;
+ window->mouseEventButton = (kon_windowButton_t)e->button;
+ window->mouseEventX = e->targetX;
+ window->mouseEventY = e->targetY;
+
+ return EM_TRUE;
+}
+
+static EM_BOOL kon_mouseUpCallback_(int eventType, const EmscriptenMouseEvent *e, void *userData) {
+ (void)eventType;
+ kon_window_t *window = (kon_window_t *)userData;
+
+ if (e->button > KON_MOUSE_RIGHT) return EM_FALSE:
+
+ window->hasMouseEvent = true;
+ window->mouseEventType = KON_EVENT_MOUSE_UP;
+ window->mouseEventButton = (kon_windowButton_t)e->button;
+ window->mouseEventX = e->targetX;
+ window->mouseEventY = e->targetY;
+
+ return EM_TRUE;
+}
+
+static EM_BOOL kon_mouseMoveCallback_(int eventType, const EmscriptenMouseEvent *e, void *userData) {
+ (void)eventType;
+ kon_window_t *window = (kon_window_t *)userData;
+
+ window->hasMouseEvent = true;
+ window->mouseEventType = KON_EVENT_MOUSE_MOVE;
+ window->mouseEventX = e->targetX;
+ window->mouseEventY = e->targetY;
+
+ return EM_TRUE;
+}
+
+kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int height, kon_windowFlags_t flags) {
+ (void)x; (void)y; (void)flags;
+
+ if (!kon_ctx) {
+ fprintf(stderr, "kon_createWindow: error creating window, no ctx\n");
+ return NULL;
+ }
+
+ kon_window_t *window = malloc(sizeof(kon_window_t));
+ if (!window) return NULL;
+
+ strncpy(window->canvasID, "#canvas", sizeof(window->canvasID) - 1);
+ window->canvasID[sizeof(window->canvasID) - 1] = '\0';
+
+ window->shouldClose = false;
+ window->exitKey = 0;
+ window->hasKeyEvent = false;
+ window->hasResizeEvent = false;
+ window->hasMouseEvent = false;
+
+ emscripten_set_canvas_element_size(window->canvasID, width, height);
+
+ EM_ASM({
+ try { document.title = UTF8ToString($0); } catch (e) {}
+ }, title);
+
+ emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, window, EM_FALSE, kon_keyDownCallback_);
+ emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, window, EM_FALSE, kon_keyUpCallback_);
+ emscripten_set_mousedown_callback(window->canvasID, window, EM_FALSE, kon_mouseDownCallback_);
+ emscripten_set_mouseup_callback(window->canvasID, window, EM_FALSE, kon_mouseUpCallback_);
+ emscripten_set_mousemove_callback(window->canvasID, window, EM_FALSE, kon_mouseMoveCallback_);
+
+ return window;
+}
+
+void kon_destroyWindow(kon_window_t *window) {
+ if (!window) return;
+
+ emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, NULL);
+ emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, NULL);
+ emscripten_set_mousedown_callback(window->canvasID, NULL, EM_FALSE, NULL);
+ emscripten_set_mouseup_callback(window->canvasID, NULL, EM_FALSE, NULL);
+ emscripten_set_mousemove_callback(window->canvasID, NULL, EM_FALSE, NULL);
+
+ free(window);
+}
+
+bool kon_windowShouldClose(kon_window_t *window) {
+ if (!window) return true;
+ return window->shouldClose;
+}
+
+void kon_setExitKey(kon_window_t *window, int key) {
+ if (!window) return;
+ window->exitKey = key;
+}
+
+void kon_setWindowPos(kon_window_t *window, int x, int y) {
+ (void)window; (void)x; (void)y;
+}
+
+void kon_setWindowSize(kon_window_t *window, int width, int height) {
+ if (!window) return;
+ emscripten_set_canvas_element_size(window->canvasID, width, height);
+}
+
+void kon_getWindowPos(kon_window_t *window, int *x, int *y) {
+ (void)window;
+ if (x) *x = 0;
+ if (y) *y = 0;
+}
+
+void kon_getWindowSize(kon_window_t *window, int *width, int *height) {
+ if (!window || !width || !height) return;
+
+ double w, h;
+ emscripten_get_element_css_size(window->canvasID, &w, &h);
+ *width = (int)w;
+ *height = (int)h;
+}
+
+int kon_pollEvent(kon_window_t *window, kon_event_t *event) {
+ if (!window || !event) return 0;
+
+ event->type = KON_EVENT_NONE;
+
+ if (window->hasResizeEvent) {
+ event->type = KON_EVENT_RESIZE;
+ event->width = window->resizeWidth;
+ event->height = window->resizeHeight;
+ window->hasResizeEvent = false;
+ return 1;
+ }
+
+ if (window->hasKeyEvent) {
+ event->type = window->keyEventType;
+ event->key = window->keyEventKey;
+ window->hasKeyEvent = false;
+ return 1;
+ }
+
+ if (window->hasMouseEvent) {
+ event->type = window->mouseEventType;
+ event->mouseX = window->mouseEventX;
+ event->mouseY = window-<mouseEventY;
+ event->mouseButton = window->mouseEventButton;
+ window->hasMouseEvent = false;
+ return 1;
+ }
+
+ return 0;
+}
+
+EM_JS(void, kon_jsBlit_, (const char *canvasID, uint32_t *pixels, int width, int height), {
+ var sel = UTF8ToString(canvasID);
+ var canvas = document.querySelector(sel);
+ if (!canvas) return;
+
+ if (!canvas.__konCtx) canvas.__konCtx = canvas.getContext('2d');
+ var ctx = canvas.__konCtx;
+
+ if (!canvas.__konImgData || canvas.__konImgData.width !== width || canvas.__konImgData.height !== height) {
+ canvas.__konImgData = ctx.createImageData(width, height);
+ }
+ var imgData = canvas.__konImgData;
+
+ var dst = new Uint32Array(imgData.data.buffer);
+ var srcIdx = pixels >> 2;
+ var count = width * height;
+
+ for (var i = 0; i < count; i++) {
+ var px = HEAPU32[srcIdx + i];
+ dst[i] = (px & 0xFF00FF00) | ((px & 0x00FF0000) >>> 16) | ((px & 0x000000FF) << 16);
+ }
+
+ ctx.putImageData(imgData, 0, 0);
+});
+
+void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int height) {
+ if (!window || !pixels) return;
+ kon_jsBlit_(window->canvasID, (uint32_t *)pixels, width, height);
+}
+
+#elif defined(__linux__) || defined(__unix__)
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/keysym.h>
+#include <string.h>
+
+static kon_key_t kon_translateX11Key_(KeySym sym) {
+ switch (sym) {
+ case XK_a: return KON_KEY_A;
+ case XK_b: return KON_KEY_B;
+ case XK_c: return KON_KEY_C;
+ case XK_d: return KON_KEY_D;
+ case XK_e: return KON_KEY_E;
+ case XK_f: return KON_KEY_F;
+ case XK_g: return KON_KEY_G;
+ case XK_h: return KON_KEY_H;
+ case XK_i: return KON_KEY_I;
+ case XK_j: return KON_KEY_J;
+ case XK_k: return KON_KEY_K;
+ case XK_l: return KON_KEY_L;
+ case XK_m: return KON_KEY_M;
+ case XK_n: return KON_KEY_N;
+ case XK_o: return KON_KEY_O;
+ case XK_p: return KON_KEY_P;
+ case XK_q: return KON_KEY_Q;
+ case XK_r: return KON_KEY_R;
+ case XK_s: return KON_KEY_S;
+ case XK_t: return KON_KEY_T;
+ case XK_u: return KON_KEY_U;
+ case XK_v: return KON_KEY_V;
+ case XK_w: return KON_KEY_W;
+ case XK_x: return KON_KEY_X;
+ case XK_y: return KON_KEY_Y;
+ case XK_z: return KON_KEY_Z;
+
+ case XK_0: return KON_KEY_0;
+ case XK_1: return KON_KEY_1;
+ case XK_2: return KON_KEY_2;
+ case XK_3: return KON_KEY_3;
+ case XK_4: return KON_KEY_4;
+ case XK_5: return KON_KEY_5;
+ case XK_6: return KON_KEY_6;
+ case XK_7: return KON_KEY_7;
+ case XK_8: return KON_KEY_8;
+ case XK_9: return KON_KEY_9;
+
+ case XK_F1: return KON_KEY_F1;
+ case XK_F2: return KON_KEY_F2;
+ case XK_F3: return KON_KEY_F3;
+ case XK_F4: return KON_KEY_F4;
+ case XK_F5: return KON_KEY_F5;
+ case XK_F6: return KON_KEY_F6;
+ case XK_F7: return KON_KEY_F7;
+ case XK_F8: return KON_KEY_F8;
+ case XK_F9: return KON_KEY_F9;
+ case XK_F10: return KON_KEY_F10;
+ case XK_F11: return KON_KEY_F11;
+ case XK_F12: return KON_KEY_F12;
+
+ case XK_Left: return KON_KEY_LEFT;
+ case XK_Right: return KON_KEY_RIGHT;
+ case XK_Up: return KON_KEY_UP;
+ case XK_Down: return KON_KEY_DOWN;
+
+ case XK_space: return KON_KEY_SPACE;
+ case XK_Return: return KON_KEY_ENTER;
+ case XK_Tab: return KON_KEY_TAB;
+ case XK_BackSpace: return KON_KEY_BACKSPACE;
+ case XK_Escape: return KON_KEY_ESCAPE;
+ case XK_Delete: return KON_KEY_DELETE;
+
+ case XK_Shift_L: return KON_KEY_LEFT_SHIFT;
+ case XK_Shift_R: return KON_KEY_RIGHT_SHIFT;
+ case XK_Control_L: return KON_KEY_LEFT_CTRL;
+ case XK_Control_R: return KON_KEY_RIGHT_CTRL;
+ case XK_Alt_L: return KON_KEY_LEFT_ALT;
+ case XK_Alt_R: return KON_KEY_RIGHT_ALT;
+
+ case XK_minus: return KON_KEY_MINUS;
+ case XK_equal: return KON_KEY_EQUAL;
+ case XK_bracketleft: return KON_KEY_LEFT_BRACKET;
+ case XK_bracketright: return KON_KEY_RIGHT_BRACKET;
+ case XK_semicolon: return KON_KEY_SEMICOLON;
+ case XK_apostrophe: return KON_KEY_APOSTROPHE;
+ case XK_comma: return KON_KEY_COMMA;
+ case XK_period: return KON_KEY_PERIOD;
+ case XK_slash: return KON_KEY_SLASH;
+ case XK_backslash: return KON_KEY_BACKSLASH;
+ case XK_grave: return KON_KEY_GRAVE;
+
+ default: return KON_KEY_UNKNOWN;
+ }
+}
+
+struct kon_context {
+ Display *display;
+};
+
+bool kon_init(void) {
+ kon_context_t *ctx = malloc(sizeof(kon_context_t));
+ if (!ctx) return false;
+ ctx->display = XOpenDisplay(NULL);
+ if (!ctx->display) return false;
+ kon_ctx = ctx;
+ return true;
+}
+
+void kon_deinit(void) {
+ if (!kon_ctx) return;
+
+ XCloseDisplay(kon_ctx->display);
+ free(kon_ctx);
+ kon_ctx=NULL;
+}
+
+struct kon_window {
+ Window window;
+ GC gc;
+ Visual *visual;
+ int depth;
+ Colormap colormap;
+ bool owns_colormap;
+ Atom wm_delete;
+ bool shouldClose;
+ int exitKey;
+};
+
+kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int height, kon_windowFlags_t flags) {
+ if (!kon_ctx) {
+ fprintf(stderr, "kon_createWindow: There appears to be no kon_ctx. \nPlease, do kon_init(); before doing anything with konwinlib, thank you.\n");
+ return NULL;
+ }
+
+ kon_window_t *window = malloc(sizeof(kon_window_t));
+ if (!window) return NULL;
+
+ int screen = DefaultScreen(kon_ctx->display);
+
+ if (flags & KON_WINDOW_CENTER) {
+ Window root = RootWindow(kon_ctx->display, screen);
+
+ int mon_x = 0, mon_y = 0;
+ int mon_width = DisplayWidth(kon_ctx->display, screen);
+ int mon_height = DisplayHeight(kon_ctx->display, screen);
+
+ Window root_ret, child_ret;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask;
+ XQueryPointer(kon_ctx->display, root, &root_ret, &child_ret, &root_x, &root_y, &win_x, &win_y, &mask);
+
+ XRRScreenResources *res = XRRGetScreenResourcesCurrent(kon_ctx->display, root);
+ if (res) {
+ for (int i = 0; i < res->noutput; i++) {
+ XRROutputInfo *out_info = XRRGetOutputInfo(kon_ctx->display, res, res->outputs[i]);
+
+ if (out_info && out_info->crtc) {
+ XRRCrtcInfo *crtc = XRRGetCrtcInfo(kon_ctx->display, res, out_info->crtc);
+ if (crtc) {
+
+ if (root_x >= crtc->x && root_x < crtc->x + (int)crtc->width &&
+ root_y >= crtc->y && root_y < crtc->y + (int)crtc->height) {
+ mon_x = crtc->x;
+ mon_y = crtc->y;
+ mon_width = crtc->width;
+ mon_height = crtc->height;
+ XRRFreeCrtcInfo(crtc);
+ if (out_info) XRRFreeOutputInfo(out_info);
+ break;
+ }
+ XRRFreeCrtcInfo(crtc);
+ }
+ }
+ if (out_info) XRRFreeOutputInfo(out_info);
+ }
+ XRRFreeScreenResources(res);
+ }
+
+ x = mon_x + (mon_width - width ) / 2;
+ y = mon_y + (mon_height - height) / 2;
+ }
+
+ if (flags & KON_WINDOW_TRANSPARENT) {
+ XVisualInfo vinfo;
+ if (!XMatchVisualInfo(kon_ctx->display, screen, 32, TrueColor, &vinfo)) {
+ free(window);
+ return NULL;
+ }
+
+ window->visual = vinfo.visual;
+ window->depth = vinfo.depth;
+ window->colormap = XCreateColormap(kon_ctx->display, RootWindow(kon_ctx->display, screen), window->visual, AllocNone);
+
+ XSetWindowAttributes attrs;
+ attrs.colormap = window->colormap;
+ attrs.background_pixel = 0;
+ attrs.border_pixel = 0;
+
+ window->window = XCreateWindow(
+ kon_ctx->display, RootWindow(kon_ctx->display, screen),
+ x, y, width, height, 0, window->depth, InputOutput,
+ window->visual, CWColormap | CWBackPixel | CWBorderPixel, &attrs);
+
+ window->owns_colormap = true;
+ } else {
+ window->visual = DefaultVisual(kon_ctx->display, screen);
+ window->depth = DefaultDepth(kon_ctx->display, screen);
+ window->colormap = DefaultColormap(kon_ctx->display, screen);
+
+ window->window = XCreateSimpleWindow(
+ kon_ctx->display, RootWindow(kon_ctx->display, screen),
+ x, y, width, height, 0, 0, 0);
+
+ window->owns_colormap = false;
+ }
+
+ if (!window->window) {
+ if (window->owns_colormap) {
+ XFreeColormap(kon_ctx->display, window->colormap);
+ }
+ free(window);
+ return NULL;
+ }
+
+ XStoreName(kon_ctx->display, window->window, title);
+ XSelectInput(kon_ctx->display, window->window, ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
+
+ if (flags & KON_WINDOW_NO_DECOR) {
+ Atom motif_hints = XInternAtom(kon_ctx->display, "_MOTIF_WM_HINTS", False);
+ struct { unsigned long flags, functions, decorations; long input_mode; unsigned long status; } hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.flags = 2;
+ hints.decorations = 0;
+ XChangeProperty(kon_ctx->display, window->window, motif_hints,
+ motif_hints, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ }
+
+ if (flags & KON_WINDOW_ALWAYS_ONTOP) {
+ Atom wm_state = XInternAtom(kon_ctx->display, "_NET_WM_STATE", False);
+ Atom wm_above = XInternAtom(kon_ctx->display, "_NET_WM_STATE_ABOVE", False);
+ XChangeProperty(kon_ctx->display, window->window, wm_state, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *)&wm_above, 1);
+ }
+
+ if (!(flags & KON_WINDOW_RESIZABLE)) {
+ XSizeHints hints;
+ hints.flags = PMinSize | PMaxSize;
+ hints.min_width = hints.max_width = width;
+ hints.min_height = hints.max_height = height;
+ XSetWMNormalHints(kon_ctx->display, window->window, &hints);
+ }
+
+ window->wm_delete = XInternAtom(kon_ctx->display, "WM_DELETE_WINDOW", False);
+ XSetWMProtocols(kon_ctx->display, window->window, &window->wm_delete, 1);
+
+ window->gc = XCreateGC(kon_ctx->display, window->window, 0, NULL);
+
+ XMapWindow(kon_ctx->display, window->window);
+ XFlush(kon_ctx->display);
+
+ window->exitKey = 0;
+
+ window->shouldClose = false;
+
+ return window;
+}
+
+void kon_destroyWindow(kon_window_t *window) {
+ if (!window) return;
+ if (!kon_ctx) { free(window); return; }
+
+ if (window->gc) XFreeGC(kon_ctx->display, window->gc);
+ if (window->window) XDestroyWindow(kon_ctx->display, window->window);
+ if (window->owns_colormap && window->colormap) XFreeColormap(kon_ctx->display, window->colormap);
+
+ XFlush(kon_ctx->display);
+
+ free(window);
+}
+
+void kon_setExitKey(kon_window_t *window, int key) {
+ if (!window) return;
+ window->exitKey = key;
+}
+
+void kon_setWindowPos(kon_window_t *window, int x, int y) {
+ if (!window || !kon_ctx) return;
+
+ XMoveWindow(kon_ctx->display, window->window, x, y);
+ XFlush(kon_ctx->display);
+}
+
+void kon_setWindowSize(kon_window_t *window, int width, int height) {
+ if (!window || !kon_ctx) return;
+
+ XResizeWindow(kon_ctx->display, window->window, width, height);
+ XFlush(kon_ctx->display);
+}
+
+void kon_getWindowPos(kon_window_t *window, int *x, int *y) {
+ if (!window || !kon_ctx || !x || !y) return;
+
+ Window child;
+ XTranslateCoordinates(kon_ctx->display, window->window, RootWindow(kon_ctx->display, DefaultScreen(kon_ctx->display)),
+ 0, 0, x, y, &child);
+}
+
+void kon_getWindowSize(kon_window_t *window, int *width, int *height) {
+ if (!window || !kon_ctx || !width || !height) return;
+
+ XWindowAttributes attrs;
+ XGetWindowAttributes(kon_ctx->display, window->window, &attrs);
+ *width = attrs.width;
+ *height = attrs.height;
+}
+
+int kon_pollEvent(kon_window_t *window, kon_event_t *event) {
+ if (!window || !kon_ctx || !event) return 0;
+
+ event->type = KON_EVENT_NONE;
+
+ if (!XPending(kon_ctx->display)) return 0;
+
+ XEvent xev;
+ XNextEvent(kon_ctx->display, &xev);
+
+ if (xev.type == ClientMessage) {
+ if ((Atom)xev.xclient.data.l[0] == window->wm_delete) {
+ event->type = KON_EVENT_CLOSE;
+ window->shouldClose = true;
+ }
+ }
+
+ if (xev.type == ConfigureNotify) {
+ event->type = KON_EVENT_RESIZE;
+ event->width = xev.xconfigure.width;
+ event->height = xev.xconfigure.height;
+ return 1;
+ }
+
+ if (xev.type == KeyPress) {
+ event->type = KON_EVENT_KEY_DOWN;
+ event->key = (int)kon_translateX11Key_(XLookupKeysym(&xev.xkey, 0));
+ if (window->exitKey != 0 && event->key == window->exitKey) {
+ window->shouldClose = true;
+ }
+ return 1;
+ }
+
+ if (xev.type == KeyRelease) {
+ event->type = KON_EVENT_KEY_UP;
+ event->key = (int)kon_translateX11Key_(XLookupKeysym(&xev.xkey, 0));
+ return 1;
+ }
+
+ if (xev.type == ButtonPress || xev.type == ButtonRelease) {
+ int konButton = -1;
+ switch (xev.xbutton.button) {
+ case Button1:
+ konButton = KON_MOUSE_LEFT;
+ break;
+ case Button2:
+ konButton = KON_MOUSE_MIDDLE;
+ break;
+ case Button3:
+ konButton = KON_MOUSE_RIGHT;
+ break;
+ default:
+ break;
+ }
+
+ if (konButton != -1) {
+ event->type = (xev.type == ButtonPress) ? KON_EVENT_MOUSE_DOWN : KON_EVENT_MOUSE_UP;
+ event->mouseButton = (kon_mouseButton_t)konButton;
+ event->mouseX = xev.xbutton.x;
+ event->mouseY = xev.xbutton.y;
+ }
+ return 1;
+ }
+
+ if (xev.type == MotionNotify) {
+ event->type = KON_EVENT_MOUSE_MOVE;
+ event->mouseX = xev.xmotion.x;
+ event->mouseY = xev.xmotion.y;
+ return 1;
+ }
+
+ return 1;
+}
+
+bool kon_windowShouldClose(kon_window_t *window) {
+ if (!window || !kon_ctx) return true;
+ return window->shouldClose;
+}
+
+/* TODO: implement the rest of these */
+
+void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int height) {
+ if (!window || !kon_ctx || !pixels) return;
+
+ XImage *ximg = XCreateImage(kon_ctx->display, window->visual, window->depth, ZPixmap, 0, (char *)pixels, width, height, 32, 0);
+ if (!ximg) return;
+
+ XPutImage(kon_ctx->display, window->window, window->gc, ximg, 0, 0, 0, 0, width, height);
+
+ ximg->data = NULL;
+ XDestroyImage(ximg);
+
+ XFlush(kon_ctx->display);
+}
+
+/*** windows implementation ***/
+
+#elif defined(_WIN32)
+
+#include <windows.h>
+
+struct kon_context {
+ HINSTANCE hInstance;
+};
+
+struct kon_window {
+ HWND hwnd;
+ bool shouldClose;
+ bool hasResizeEvent;
+ int resizeWidth, resizeHeight;
+ bool isTransparent;
+ bool hasKeyEvent;
+ kon_eventType_t keyEventType;
+ int keyEventKey;
+ int exitKey;
+ bool hasMouseEvent;
+ kon_eventType_t mouseEventType;
+ int mouseEventX, mouseEventY;
+ kon_mouseButton_t mouseEventButton;
+};
+
+static kon_key_t kon_translateWin32Key_(WPARAM vk, LPARAM lParam) {
+ if (vk == VK_SHIFT) {
+ UINT scancode = (lParam >> 16) & 0xFF;
+ UINT lshift = MapVirtualKey(VK_LSHIFT, MAPVK_VK_TO_VSC);
+ return (scancode == lshift) ? KON_KEY_LEFT_SHIFT : KON_KEY_RIGHT_SHIFT;
+ }
+ if (vk == VK_CONTROL) {
+ return (lParam & 0x01000000) ? KON_KEY_RIGHT_CTRL : KON_KEY_LEFT_CTRL;
+ }
+ if (vk == VK_MENU) {
+ return (lParam & 0x01000000) ? KON_KEY_RIGHT_ALT : KON_KEY_LEFT_ALT;
+ }
+
+ switch (vk) {
+ case 'A': return KON_KEY_A;
+ case 'B': return KON_KEY_B;
+ case 'C': return KON_KEY_C;
+ case 'D': return KON_KEY_D;
+ case 'E': return KON_KEY_E;
+ case 'F': return KON_KEY_F;
+ case 'G': return KON_KEY_G;
+ case 'H': return KON_KEY_H;
+ case 'I': return KON_KEY_I;
+ case 'J': return KON_KEY_J;
+ case 'K': return KON_KEY_K;
+ case 'L': return KON_KEY_L;
+ case 'M': return KON_KEY_M;
+ case 'N': return KON_KEY_N;
+ case 'O': return KON_KEY_O;
+ case 'P': return KON_KEY_P;
+ case 'Q': return KON_KEY_Q;
+ case 'R': return KON_KEY_R;
+ case 'S': return KON_KEY_S;
+ case 'T': return KON_KEY_T;
+ case 'U': return KON_KEY_U;
+ case 'V': return KON_KEY_V;
+ case 'W': return KON_KEY_W;
+ case 'X': return KON_KEY_X;
+ case 'Y': return KON_KEY_Y;
+ case 'Z': return KON_KEY_Z;
+
+ case '0': return KON_KEY_0;
+ case '1': return KON_KEY_1;
+ case '2': return KON_KEY_2;
+ case '3': return KON_KEY_3;
+ case '4': return KON_KEY_4;
+ case '5': return KON_KEY_5;
+ case '6': return KON_KEY_6;
+ case '7': return KON_KEY_7;
+ case '8': return KON_KEY_8;
+ case '9': return KON_KEY_9;
+
+ case VK_F1: return KON_KEY_F1;
+ case VK_F2: return KON_KEY_F2;
+ case VK_F3: return KON_KEY_F3;
+ case VK_F4: return KON_KEY_F4;
+ case VK_F5: return KON_KEY_F5;
+ case VK_F6: return KON_KEY_F6;
+ case VK_F7: return KON_KEY_F7;
+ case VK_F8: return KON_KEY_F8;
+ case VK_F9: return KON_KEY_F9;
+ case VK_F10: return KON_KEY_F10;
+ case VK_F11: return KON_KEY_F11;
+ case VK_F12: return KON_KEY_F12;
+
+ case VK_LEFT: return KON_KEY_LEFT;
+ case VK_RIGHT: return KON_KEY_RIGHT;
+ case VK_UP: return KON_KEY_UP;
+ case VK_DOWN: return KON_KEY_DOWN;
+
+ case VK_SPACE: return KON_KEY_SPACE;
+ case VK_RETURN: return KON_KEY_ENTER;
+ case VK_TAB: return KON_KEY_TAB;
+ case VK_BACK: return KON_KEY_BACKSPACE;
+ case VK_ESCAPE: return KON_KEY_ESCAPE;
+ case VK_DELETE: return KON_KEY_DELETE;
+
+ case VK_OEM_MINUS: return KON_KEY_MINUS;
+ case VK_OEM_PLUS: return KON_KEY_EQUAL;
+ case VK_OEM_4: return KON_KEY_LEFT_BRACKET;
+ case VK_OEM_6: return KON_KEY_RIGHT_BRACKET;
+ case VK_OEM_1: return KON_KEY_SEMICOLON;
+ case VK_OEM_7: return KON_KEY_APOSTROPHE;
+ case VK_OEM_COMMA: return KON_KEY_COMMA;
+ case VK_OEM_PERIOD: return KON_KEY_PERIOD;
+ case VK_OEM_2: return KON_KEY_SLASH;
+ case VK_OEM_5: return KON_KEY_BACKSLASH;
+ case VK_OEM_3: return KON_KEY_GRAVE;
+
+ default: return KON_KEY_UNKNOWN;
+ }
+}
+
+LRESULT CALLBACK kon_wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
+ kon_window_t *window = (kon_window_t *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_NCCREATE: {
+ CREATESTRUCT *cs = (CREATESTRUCT *)lParam;
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams);
+ break;
+ }
+ case WM_KEYDOWN:
+ if (window) {
+ window->hasKeyEvent = true;
+ window->keyEventType = KON_EVENT_KEY_DOWN;
+ window->keyEventKey = (int)kon_translateWin32Key_(wParam, lParam);
+ if (window->exitKey != 0 && window->keyEventKey == window->exitKey) {
+ window->shouldClose = true;
+ }
+ }
+ break;
+ case WM_KEYUP:
+ if (window) {
+ window->hasKeyEvent = true;
+ window->keyEventType = KON_EVENT_KEY_UP;
+ window->keyEventKey = (int)kon_translateWin32Key_(wParam, lParam);
+ }
+ break;
+ case WM_LBUTTONDOWN: case WM_LBUTTONUP:
+ case WM_RBUTTONDOWN: case WM_RBUTTONUP:
+ case WM_MBUTTONDOWN: case WM_MBUTTONUP:
+ if (window) {
+ window->hasMouseEvent = true;
+ window->mouseEventType = (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN)
+ ? KON_EVENT_MOUSE_DOWN : KON_EVENT_MOUSE_UP;
+ window->mouseEventButton = (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP) ? KON_MOUSE_LEFT
+ : (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONUP) ? KON_MOUSE_RIGHT
+ : KON_MOUSE_MIDDLE;
+ window->mouseEventX = (int)(short)LOWORD(lParam);
+ window->mouseEventY = (int)(short)HIWORD(lParam);
+ }
+ break;
+ case WM_MOUSEMOVE:
+ if (window) {
+ window->hasMouseEvent = true;
+ window->mouseEventType = KON_EVENT_MOUSE_MOVE;
+ window->mouseEventX = (int)(short)LOWORD(lParam);
+ window->mouseEventY = (int)(short)HIWORD(lParam);
+ }
+ break;
+ case WM_CLOSE:
+ if (window) window->shouldClose = true;
+ break;
+ case WM_SIZE:
+ if (window) {
+ window->hasResizeEvent = true;
+ window->resizeWidth = LOWORD(lParam);
+ window->resizeHeight = HIWORD(lParam);
+ }
+ break;
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ return 0;
+ break;
+ case WM_ERASEBKGND:
+ return 1;
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+bool kon_init(void) {
+ kon_context_t *ctx = malloc(sizeof(kon_context_t));
+ if (!ctx) return false;
+
+ ctx->hInstance = GetModuleHandle(NULL);
+
+ WNDCLASSEX wc = {0};
+ wc.cbSize = sizeof(WNDCLASSEX);
+ wc.lpfnWndProc = kon_wndProc;
+ wc.hInstance = ctx->hInstance;
+ wc.lpszClassName = "KonWinLibClass";
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+
+ if (!RegisterClassEx(&wc)) {
+ free(ctx);
+ return false;
+ }
+
+ kon_ctx = ctx;
+ return true;
+}
+
+void kon_deinit(void) {
+ if (!kon_ctx) return;
+
+ free(kon_ctx);
+ kon_ctx = NULL;
+}
+
+
+kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int height, kon_windowFlags_t flags) {
+ if (!kon_ctx) return NULL;
+
+ kon_window_t *window = calloc(1, sizeof(kon_window_t));
+ if (!window) return NULL;
+
+ window->shouldClose = false;
+ window->hasResizeEvent = false;
+ window->isTransparent = (flags & KON_WINDOW_TRANSPARENT);
+ window->exitKey = 0;
+
+ DWORD style;
+ if (flags & KON_WINDOW_NO_DECOR) {
+ style = WS_POPUP;
+ } else {
+ style = WS_OVERLAPPEDWINDOW;
+ if (!(flags & KON_WINDOW_RESIZABLE)) {
+ style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
+ }
+ }
+
+ DWORD exStyle = 0;
+ if (flags & KON_WINDOW_ALWAYS_ONTOP) exStyle |= WS_EX_TOPMOST;
+ if (flags & KON_WINDOW_TRANSPARENT) exStyle |= WS_EX_LAYERED;
+
+ RECT adj = { 0, 0, width, height };
+ AdjustWindowRectEx(&adj, style, FALSE, exStyle);
+ int winW = adj.right - adj.left;
+ int winH = adj.bottom - adj.top;
+
+ if (flags & KON_WINDOW_CENTER) {
+ POINT cursor;
+ GetCursorPos(&cursor);
+
+ HMONITOR hmon = MonitorFromPoint(cursor, MONITOR_DEFAULTTONEAREST);
+
+ MONITORINFO mi;
+ mi.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(hmon, &mi);
+
+ int mon_x = mi.rcMonitor.left;
+ int mon_y = mi.rcMonitor.top;
+ int mon_width = mi.rcMonitor.right - mi.rcMonitor.left;
+ int mon_height = mi.rcMonitor.bottom - mi.rcMonitor.top;
+
+ x = mon_x + (mon_width - winW) / 2;
+ y = mon_y + (mon_height - winH) / 2;
+ }
+
+ window->hwnd = CreateWindowEx(exStyle, "KonWinLibClass", title, style, x, y, winW, winH, NULL, NULL, kon_ctx->hInstance, window);
+
+ if (!window->hwnd) {
+ free(window);
+ return NULL;
+ }
+
+ ShowWindow(window->hwnd, SW_SHOW);
+
+ return window;
+}
+
+void kon_destroyWindow(kon_window_t *window) {
+ if (!window) return;
+ if (window->hwnd) DestroyWindow(window->hwnd);
+ free(window);
+}
+
+bool kon_windowShouldClose(kon_window_t *window) {
+ if (!window) return true;
+ return window->shouldClose;
+}
+
+
+void kon_setExitKey(kon_window_t *window, int key) {
+ if (!window) return;
+ window->exitKey = key;
+}
+
+void kon_setWindowPos(kon_window_t *window, int x, int y) {
+ if (!window || !kon_ctx) return;
+
+ SetWindowPos(window->hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
+}
+
+void kon_setWindowSize(kon_window_t *window, int width, int height) {
+ if (!window || !kon_ctx) return;
+
+ RECT r = { 0, 0, width, height };
+ AdjustWindowRectEx(&r, (DWORD)GetWindowLongPtr(window->hwnd, GWL_STYLE), FALSE, (DWORD)GetWindowLongPtr(window->hwnd, GWL_EXSTYLE));
+ SetWindowPos(window->hwnd, NULL, 0, 0, r.right - r.left, r.bottom - r.top, SWP_NOMOVE | SWP_NOZORDER);
+}
+
+
+void kon_getWindowPos(kon_window_t *window, int *x, int *y) {
+ if (!window || !x || !y) return;
+
+ RECT rect;
+ GetWindowRect(window->hwnd, &rect);
+ *x = rect.left;
+ *y = rect.top;
+}
+
+void kon_getWindowSize(kon_window_t *window, int *width, int *height) {
+ if (!window || !width || !height) return;
+
+ RECT rect;
+ GetClientRect(window->hwnd, &rect);
+ *width = rect.right - rect.left;
+ *height = rect.bottom - rect.top;
+}
+
+int kon_pollEvent(kon_window_t *window, kon_event_t *event) {
+ if (!window || !event) return 0;
+
+ event->type = KON_EVENT_NONE;
+
+ MSG msg;
+ if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) return 0;
+
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+
+ if (window->hasResizeEvent) {
+ event->type = KON_EVENT_RESIZE;
+ event->width = window->resizeWidth;
+ event->height = window->resizeHeight;
+ window->hasResizeEvent = false;
+ return 1;
+ }
+
+ if (window->shouldClose) {
+ event->type = KON_EVENT_CLOSE;
+ return 1;
+ }
+
+ if (window->hasKeyEvent) {
+ event->type = window->keyEventType;
+ event->key = window->keyEventKey;
+ window->hasKeyEvent = false;
+ return 1;
+ }
+
+ if (window->hasMouseEvent) {
+ event->type = window->mouseEventType;
+ event->mouseX = window->mouseEventX;
+ event->mouseY = window->mouseEventY;
+ event->mouseButton = window->mouseEventButton;
+ window->hasMouseEvent = false;
+ return 1;
+ }
+
+ return 1;
+}
+
+void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int height) {
+ if (!window || !pixels) return;
+
+ BITMAPINFO bmi = {0};
+ bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bmi.bmiHeader.biWidth = width;
+ bmi.bmiHeader.biHeight = -height; /* top-down */
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = 32;
+ bmi.bmiHeader.biCompression = BI_RGB;
+
+ if (window->isTransparent) {
+ HDC screenDC = GetDC(NULL);
+ HDC memDC = CreateCompatibleDC(screenDC);
+
+ void *dibPixels;
+ HBITMAP hbm = CreateDIBSection(screenDC, &bmi, DIB_RGB_COLORS, &dibPixels, NULL, 0);
+ if (hbm) {
+ memcpy(dibPixels, pixels, (size_t)width * height * 4);
+ HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, hbm);
+
+ RECT winRect;
+ GetWindowRect(window->hwnd, &winRect);
+ POINT dstPos = { winRect.left, winRect.top };
+ POINT srcPos = { 0, 0 };
+ SIZE size = { width, height };
+ BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
+
+ UpdateLayeredWindow(window->hwnd, screenDC, &dstPos, &size, memDC, &srcPos, 0, &blend, ULW_ALPHA);
+
+ SelectObject(memDC, oldBmp);
+ DeleteObject(hbm);
+ }
+
+ DeleteDC(memDC);
+ ReleaseDC(NULL, screenDC);
+ } else {
+ HDC winDC = GetDC(window->hwnd);
+ SetDIBitsToDevice(winDC, 0, 0, width, height, 0, 0, 0, height, pixels, &bmi, DIB_RGB_COLORS);
+ ReleaseDC(window->hwnd, winDC);
+ }
+}
+
+#else
+ #error "konwinlib.h: unsupported platform"
+#endif /* end of platform switch */
+
+/*** main loop ***/
+
+#ifdef __EMSCRIPTEN__
+
+typedef struct {
+ kon_window_t *window;
+ kon_mainLoopFn callback;
+ void *userData;
+} kon_mainLoopCtx_;
+
+static void kon_mainLoopTrampoline_(void *arg) {
+ kon_mainLoopCtx_ *ctx = (kon_mainLoopCtx_ *)arg;
+
+ if (kon_windowShouldClose(ctx->window)) {
+ emscripten_cancel_main_loop();
+ free(ctx);
+ return;
+ }
+
+ ctx->callback(ctx->userData);
+}
+
+void kon_runMainLoop(kon_window_t *window, kon_mainLoopFn callback, void *userData) {
+ kon_mainLoopCtx_ *ctx = malloc(sizeof(kon_mainLoopCtx_));
+ if (!ctx) return;
+
+ ctx->window = window;
+ ctx->callback = callback;
+ ctx->userData = userData;
+
+ emscripten_set_main_loop_arg(kon_mainLoopTrampoline_, ctx, 0, 1);
+}
+
+#else
+
+void kon_runMainLoop(kon_window_t *window, kon_mainLoopFn callback, void *userData) {
+ while (!kon_windowShouldClose(window)) {
+ callback(userData);
+ }
+}
+
+#endif /* __EMSCRIPTEN__ */
+
+#endif /* end of KONWINLIB_IMPLEMENTATION */
+
+#endif