/* SPDX-License-Identifier: MIT */ /* * konprogram.c * * Copyright (c) 2026 KonAki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include /* the debug functions only exist with the debug module, without KON_DEVELOPER the header gives empty ones instead */ #if defined(KON_DEVELOPER) && !defined(KON_MODULE_DEBUG) #undef KON_DEVELOPER #endif #include "konprogram.h" #include "kontime.h" /*** definition ***/ typedef struct kon_programState { kon_program_t pub; kon_programConfig_t config; kon_time last; float shakeLeft; int shakeX, shakeY; } kon_programState_t; /*** console commands ***/ #ifdef KON_DEVELOPER static void kon_cmdOpacity_(kon_debug_t *debug, void *user, int argc, char **argv) { kon_programState_t *state = user; (void)debug; if (argc > 1) kon_setWindowOpacity(state->pub.window, (float)atof(argv[1])); } static void kon_cmdTopmost_(kon_debug_t *debug, void *user, int argc, char **argv) { kon_programState_t *state = user; (void)debug; if (argc > 1) kon_setWindowTopmost(state->pub.window, atoi(argv[1]) != 0); } static void kon_cmdBorderless_(kon_debug_t *debug, void *user, int argc, char **argv) { kon_programState_t *state = user; (void)debug; if (argc > 1) kon_setWindowDecorated(state->pub.window, atoi(argv[1]) == 0); } static void kon_cmdClickThrough_(kon_debug_t *debug, void *user, int argc, char **argv) { kon_programState_t *state = user; (void)debug; if (argc > 1) kon_setWindowClickThrough(state->pub.window, atoi(argv[1]) != 0); } static void kon_cmdMessage_(kon_debug_t *debug, void *user, int argc, char **argv) { (void)debug; (void)user; kon_messageBox("konengine", argc > 1 ? argv[1] : "hello", KON_MESSAGE_INFO); } static void kon_cmdShake_(kon_debug_t *debug, void *user, int argc, char **argv) { kon_programState_t *state = user; (void)debug; (void)argc; (void)argv; kon_getWindowPos(state->pub.window, &state->shakeX, &state->shakeY); state->shakeLeft = 0.5f; } static void kon_addWindowCommands_(kon_programState_t *state) { kon_debug_t *debug = state->pub.debug; kon_debugCommand(debug, "opacity", "window opacity 0 to 1", kon_cmdOpacity_, state); kon_debugCommand(debug, "topmost", "topmost 0 or 1", kon_cmdTopmost_, state); kon_debugCommand(debug, "borderless", "borderless 0 or 1", kon_cmdBorderless_, state); kon_debugCommand(debug, "clickthrough", "clickthrough 0 or 1", kon_cmdClickThrough_, state); kon_debugCommand(debug, "message", "message \"text\"", kon_cmdMessage_, state); kon_debugCommand(debug, "shake", "shake the window", kon_cmdShake_, state); } #endif /*** loop ***/ static void kon_frame_(void *data) { kon_programState_t *state = data; kon_program_t *program = &state->pub; const kon_programConfig_t *config = &state->config; kon_display_t *display = program->display; kon_event_t ev; kon_time now, dt, t0, t1, t2, t3, t4, cap; while (kon_pollEvent(program->window, &ev)) { if (kon_debugFeedEvent(program->debug, &ev)) continue; if (ev.type == KON_EVENT_RESIZE) kon_resizeDisplay(display, ev.width, ev.height); if (config->event) config->event(program, &ev, config->user); } now = kon_getTime(); dt = now - state->last; state->last = now; /* a long stall, like dragging the window, shouldn't make the game jump */ if (dt > 0.25) dt = 0.25; program->time += dt; if (state->shakeLeft > 0.0f) { state->shakeLeft -= (float)dt; if (state->shakeLeft > 0.0f) kon_setWindowPos(program->window, state->shakeX + rand() % 21 - 10, state->shakeY + rand() % 21 - 10); else kon_setWindowPos(program->window, state->shakeX, state->shakeY); } t0 = kon_getTime(); if (config->update) config->update(program, dt, config->user); t1 = kon_getTime(); kon_clearFramebuffer(display->game, KON_BACKGROUND_COLOR); if (config->draw) config->draw(program, display->game, config->user); t2 = kon_getTime(); kon_presentDisplay(display); t3 = kon_getTime(); if (config->drawScreen) config->drawScreen(program, display->screen, config->user); kon_debugFrame(program->debug); if (kon_debugQuitRequested(program->debug)) kon_setShouldClose(program->window, true); kon_debugDraw(program->debug, display->screen); t4 = kon_getTime(); kon_blitPixels(program->window, display->screen->data, display->screen->width, display->screen->height); kon_debugTimes(program->debug, t1 - t0, t2 - t1, t3 - t2, kon_getTime() - t4); /* only wait out what is left of the frame, a fixed sleep eats time the frame could use */ if (config->maxFps >= 0) { cap = 1.0 / (config->maxFps > 0 ? config->maxFps : 120); dt = kon_getTime() - now; if (dt < cap) kon_sleep(cap - dt); } } void kon_programQuit(kon_program_t *program) { if (program) kon_setShouldClose(program->window, true); } int kon_programRun(const kon_programConfig_t *config) { kon_programState_t *state; kon_program_t *program; int gameWidth, gameHeight, windowWidth, windowHeight; if (!config) return 1; state = calloc(1, sizeof *state); if (!state) return 1; program = &state->pub; state->config = *config; gameWidth = config->gameWidth > 0 ? config->gameWidth : 320; gameHeight = config->gameHeight > 0 ? config->gameHeight : 180; windowWidth = config->windowWidth > 0 ? config->windowWidth : gameWidth * 3; windowHeight = config->windowHeight > 0 ? config->windowHeight : gameHeight * 3; if (!kon_init()) goto fail; program->window = kon_createWindow(config->title ? config->title : "konengine", 0, 0, windowWidth, windowHeight, KON_WINDOW_CENTER | KON_WINDOW_RESIZABLE); if (!program->window) goto fail; #ifdef KON_DEVELOPER kon_setExitKey(program->window, KON_KEY_F10); #else kon_setExitKey(program->window, KON_KEY_ESCAPE); #endif program->display = kon_createDisplay(gameWidth, gameHeight, windowWidth, windowHeight); if (!program->display) goto fail; #ifdef KON_MODULE_PACK if (config->packPath) program->pack = kon_packOpen(config->packPath, NULL); #endif #ifdef KON_MODULE_ASSETS program->assets = kon_assetsCreate(program->pack, config->assetDir ? config->assetDir : "assets"); #endif #ifdef KON_MODULE_FONT { #ifdef KON_MODULE_ASSETS kon_blob_t *ttf = program->assets ? kon_assetBlob(program->assets, "font.ttf") : NULL; if (ttf) program->font = kon_loadFont(ttf->data, ttf->size, 14.0f); #endif if (!program->font) program->font = kon_loadBuiltinFont(1); } #endif #ifdef KON_MODULE_DEBUG program->debug = kon_debugCreate(program->font); kon_debugSetAssets(program->debug, program->assets); #ifdef KON_DEVELOPER kon_addWindowCommands_(state); #endif #endif state->last = kon_getTime(); if (config->init && !config->init(program, config->user)) goto fail; kon_runMainLoop(program->window, kon_frame_, state); if (config->shutdown) config->shutdown(program, config->user); kon_debugDestroy(program->debug); #ifdef KON_MODULE_FONT kon_freeFont(program->font); #endif #ifdef KON_MODULE_ASSETS kon_assetsDestroy(program->assets); #endif #ifdef KON_MODULE_PACK if (program->pack) kon_packClose(program->pack); #endif kon_freeDisplay(program->display); kon_destroyWindow(program->window); kon_deinit(); free(state); return 0; fail: if (program->display) kon_freeDisplay(program->display); if (program->window) kon_destroyWindow(program->window); free(state); return 1; }