diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-07 04:11:02 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-07 04:11:02 +0800 |
| commit | f4a0c7936a57bc63381556188fa3de2903b2dc99 (patch) | |
| tree | 5e0d072504e0cb675ff76ebfcbe30b2f19c72a06 /src | |
initial commit
Diffstat (limited to 'src')
| -rw-r--r-- | src/konfont.h | 197 | ||||
| -rw-r--r-- | src/koninput.h | 142 | ||||
| -rw-r--r-- | src/konmath.h | 295 | ||||
| -rw-r--r-- | src/konsofren.h | 402 | ||||
| -rw-r--r-- | src/kontime.h | 85 | ||||
| -rw-r--r-- | src/kontoml.h | 649 | ||||
| -rw-r--r-- | src/konwinlib.h | 1349 |
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, ¤tSection); + 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 |
