AtlatestRepositorysigil-app

sigil-app / tree / src / capp.c

1/*
2 * app.c - Sigil App Module
3 *
4 * Wraps sokol_app.h to provide windowing, input, and application lifecycle.
5 */
6
7#include "app-internal.h"
8#include "sigil-internal.h"
9
10/* Sokol headers (implementation is in sokol.c) */
11#include "sokol_app.h"
12#include "sokol_time.h"
13#include "sokol_log.h"
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
19/* Global app state */
20AppState *g_app = NULL;
22/*
23 * Initialize app state
24 */
25void sigil_app_state_init(SigilVM *vm)
27 if (g_app) return;
29 g_app = calloc(1, sizeof(AppState));
30 g_app->vm = vm;
31 g_app->init_callback = SIGIL_FALSE;
32 g_app->frame_callback = SIGIL_FALSE;
33 g_app->cleanup_callback = SIGIL_FALSE;
36void sigil_app_state_shutdown(void)
38 if (g_app) {
39 free(g_app);
40 g_app = NULL;
41 }
44/*
45 * Clear per-frame input state
46 */
47void sigil_app_clear_frame_input(void)
49 if (!g_app) return;
50 memset(g_app->keys_pressed, 0, sizeof(g_app->keys_pressed));
51 memset(g_app->keys_released, 0, sizeof(g_app->keys_released));
52 memset(g_app->mouse_pressed, 0, sizeof(g_app->mouse_pressed));
53 memset(g_app->mouse_released, 0, sizeof(g_app->mouse_released));
56/*
57 * Convert Scheme key symbol to Sokol keycode
58 */
59int sigil_app_key_code_from_symbol(SigilVM *vm, Value sym)
61 (void)vm;
62 if (!sigil_is_symbol(sym)) return SAPP_KEYCODE_INVALID;
64 SigilSymbol *s = (SigilSymbol *)sigil_as_ptr(sym);
65 const char *name = s->name;
67 /* Letters */
68 if (s->length == 1 && name[0] >= 'a' && name[0] <= 'z') {
69 return SAPP_KEYCODE_A + (name[0] - 'a');
70 }
72 /* Numbers */
73 if (s->length == 1 && name[0] >= '0' && name[0] <= '9') {
74 return SAPP_KEYCODE_0 + (name[0] - '0');
75 }
77 /* Special keys */
78 if (strcmp(name, "space") == 0) return SAPP_KEYCODE_SPACE;
79 if (strcmp(name, "return") == 0 || strcmp(name, "enter") == 0) return SAPP_KEYCODE_ENTER;
80 if (strcmp(name, "escape") == 0 || strcmp(name, "esc") == 0) return SAPP_KEYCODE_ESCAPE;
81 if (strcmp(name, "tab") == 0) return SAPP_KEYCODE_TAB;
82 if (strcmp(name, "backspace") == 0) return SAPP_KEYCODE_BACKSPACE;
84 /* Arrow keys */
85 if (strcmp(name, "left") == 0) return SAPP_KEYCODE_LEFT;
86 if (strcmp(name, "right") == 0) return SAPP_KEYCODE_RIGHT;
87 if (strcmp(name, "up") == 0) return SAPP_KEYCODE_UP;
88 if (strcmp(name, "down") == 0) return SAPP_KEYCODE_DOWN;
90 /* Modifiers */
91 if (strcmp(name, "shift") == 0) return SAPP_KEYCODE_LEFT_SHIFT;
92 if (strcmp(name, "ctrl") == 0 || strcmp(name, "control") == 0) return SAPP_KEYCODE_LEFT_CONTROL;
93 if (strcmp(name, "alt") == 0) return SAPP_KEYCODE_LEFT_ALT;
95 return SAPP_KEYCODE_INVALID;
98/*
99 * Convert mouse button symbol to index
100 */
101static int mouse_button_from_symbol(SigilVM *vm, Value sym)
103 (void)vm;
104 if (!sigil_is_symbol(sym)) return -1;
106 SigilSymbol *s = (SigilSymbol *)sigil_as_ptr(sym);
107 const char *name = s->name;
109 if (strcmp(name, "left") == 0) return 0;
110 if (strcmp(name, "right") == 0) return 1;
111 if (strcmp(name, "middle") == 0) return 2;
113 return -1;
116/* ============================================================
117 * SOKOL CALLBACKS
118 * ============================================================ */
120static uint64_t last_time = 0;
122/* Check for VM errors and print them */
123static void check_vm_error(const char *context)
125 if (!g_app) return;
126 const char *err = sigil_error_message(g_app->vm);
127 if (err) {
128 fprintf(stderr, "Scheme error in %s: %s\n", context, err);
129 sigil_error_clear(g_app->vm);
130 sapp_request_quit();
131 }
134static void app_init(void)
136 stm_setup();
137 last_time = stm_now();
139 if (g_app && !sigil_is_false(g_app->init_callback)) {
140 sigil_apply0(g_app->vm, g_app->init_callback);
141 check_vm_error("init");
142 }
145static void app_frame(void)
147 /* Calculate frame time */
148 uint64_t now = stm_now();
149 g_app->frame_time = stm_sec(stm_diff(now, last_time));
150 g_app->time_elapsed += g_app->frame_time;
151 last_time = now;
153 /* Call Scheme frame callback with delta time */
154 if (g_app && !sigil_is_false(g_app->frame_callback)) {
155 Value dt = sigil_flonum(g_app->frame_time);
156 sigil_apply1(g_app->vm, g_app->frame_callback, dt);
157 check_vm_error("frame");
158 }
160 /* Clear per-frame input state for next frame */
161 sigil_app_clear_frame_input();
164static void app_cleanup(void)
166 if (g_app && !sigil_is_false(g_app->cleanup_callback)) {
167 sigil_apply0(g_app->vm, g_app->cleanup_callback);
168 check_vm_error("cleanup");
169 }
172static void app_event(const sapp_event *ev)
174 if (!g_app) return;
176 switch (ev->type) {
177 case SAPP_EVENTTYPE_KEY_DOWN:
178 if (ev->key_code < 512) {
179 if (!g_app->keys_down[ev->key_code]) {
180 g_app->keys_pressed[ev->key_code] = true;
181 }
182 g_app->keys_down[ev->key_code] = true;
183 }
184 break;
186 case SAPP_EVENTTYPE_KEY_UP:
187 if (ev->key_code < 512) {
188 g_app->keys_down[ev->key_code] = false;
189 g_app->keys_released[ev->key_code] = true;
190 }
191 break;
193 case SAPP_EVENTTYPE_MOUSE_DOWN:
194 if (ev->mouse_button < 3) {
195 if (!g_app->mouse_buttons[ev->mouse_button]) {
196 g_app->mouse_pressed[ev->mouse_button] = true;
197 }
198 g_app->mouse_buttons[ev->mouse_button] = true;
199 }
200 break;
202 case SAPP_EVENTTYPE_MOUSE_UP:
203 if (ev->mouse_button < 3) {
204 g_app->mouse_buttons[ev->mouse_button] = false;
205 g_app->mouse_released[ev->mouse_button] = true;
206 }
207 break;
209 case SAPP_EVENTTYPE_MOUSE_MOVE:
210 g_app->mouse_x = ev->mouse_x;
211 g_app->mouse_y = ev->mouse_y;
212 break;
214 case SAPP_EVENTTYPE_QUIT_REQUESTED:
215 g_app->quit_requested = true;
216 break;
218 default:
219 break;
220 }
223/* ============================================================
224 * NATIVE FUNCTIONS
225 * ============================================================ */
227/*
228 * (app-run init-proc frame-proc cleanup-proc [title] [width] [height])
229 *
230 * Run the application main loop.
231 * init-proc: called once at startup
232 * frame-proc: called each frame with delta-time argument
233 * cleanup-proc: called before shutdown
234 */
235static Value native_app_run(SigilVM *vm, int argc, Value *args)
237 if (argc < 3) {
238 sigil__vm_error(vm, SIGIL_ERR_ARITY, "app-run: requires init, frame, and cleanup procedures");
239 return SIGIL_UNDEFINED;
240 }
242 /* Initialize app state */
243 sigil_app_state_init(vm);
245 /* Store callbacks */
246 g_app->init_callback = args[0];
247 g_app->frame_callback = args[1];
248 g_app->cleanup_callback = args[2];
250 /* Parse optional arguments */
251 const char *title = "Sigil App";
252 int width = 800;
253 int height = 600;
255 if (argc > 3 && sigil_is_string(args[3])) {
256 SigilString *s = (SigilString *)sigil_as_ptr(args[3]);
257 title = s->data;
258 }
259 if (argc > 4 && sigil_is_fixnum(args[4])) {
260 width = (int)sigil_as_fixnum(args[4]);
261 }
262 if (argc > 5 && sigil_is_fixnum(args[5])) {
263 height = (int)sigil_as_fixnum(args[5]);
264 }
266 /* Configure and run Sokol app */
267 sapp_desc desc = {
268 .init_cb = app_init,
269 .frame_cb = app_frame,
270 .cleanup_cb = app_cleanup,
271 .event_cb = app_event,
272 .width = width,
273 .height = height,
274 .window_title = title,
275 .icon.sokol_default = true,
276 .logger.func = slog_func,
277 };
279 sapp_run(&desc);
281 /* Cleanup */
282 sigil_app_state_shutdown();
284 return SIGIL_NIL;
287/*
288 * (frame-width) -> integer
289 */
290static Value native_frame_width(SigilVM *vm, int argc, Value *args)
292 (void)vm; (void)argc; (void)args;
293 return sigil_fixnum(sapp_width());
296/*
297 * (frame-height) -> integer
298 */
299static Value native_frame_height(SigilVM *vm, int argc, Value *args)
301 (void)vm; (void)argc; (void)args;
302 return sigil_fixnum(sapp_height());
305/*
306 * (frame-time) -> float (seconds since last frame)
307 */
308static Value native_frame_time(SigilVM *vm, int argc, Value *args)
310 (void)vm; (void)argc; (void)args;
311 if (!g_app) return sigil_flonum(0.0);
312 return sigil_flonum(g_app->frame_time);
315/*
316 * (time-elapsed) -> float (seconds since app start)
317 */
318static Value native_time_elapsed(SigilVM *vm, int argc, Value *args)
320 (void)vm; (void)argc; (void)args;
321 if (!g_app) return sigil_flonum(0.0);
322 return sigil_flonum(g_app->time_elapsed);
325/*
326 * (mouse-x) -> float
327 */
328static Value native_mouse_x(SigilVM *vm, int argc, Value *args)
330 (void)vm; (void)argc; (void)args;
331 if (!g_app) return sigil_flonum(0.0);
332 return sigil_flonum(g_app->mouse_x);
335/*
336 * (mouse-y) -> float
337 */
338static Value native_mouse_y(SigilVM *vm, int argc, Value *args)
340 (void)vm; (void)argc; (void)args;
341 if (!g_app) return sigil_flonum(0.0);
342 return sigil_flonum(g_app->mouse_y);
345/*
346 * (mouse-down? button) -> boolean
347 */
348static Value native_mouse_down(SigilVM *vm, int argc, Value *args)
350 if (argc < 1) return SIGIL_FALSE;
351 int btn = mouse_button_from_symbol(vm, args[0]);
352 if (btn < 0 || !g_app) return SIGIL_FALSE;
353 return g_app->mouse_buttons[btn] ? SIGIL_TRUE : SIGIL_FALSE;
356/*
357 * (mouse-pressed? button) -> boolean
358 */
359static Value native_mouse_pressed(SigilVM *vm, int argc, Value *args)
361 if (argc < 1) return SIGIL_FALSE;
362 int btn = mouse_button_from_symbol(vm, args[0]);
363 if (btn < 0 || !g_app) return SIGIL_FALSE;
364 return g_app->mouse_pressed[btn] ? SIGIL_TRUE : SIGIL_FALSE;
367/*
368 * (mouse-released? button) -> boolean
369 */
370static Value native_mouse_released(SigilVM *vm, int argc, Value *args)
372 if (argc < 1) return SIGIL_FALSE;
373 int btn = mouse_button_from_symbol(vm, args[0]);
374 if (btn < 0 || !g_app) return SIGIL_FALSE;
375 return g_app->mouse_released[btn] ? SIGIL_TRUE : SIGIL_FALSE;
378/*
379 * (key-down? key) -> boolean
380 */
381static Value native_key_down(SigilVM *vm, int argc, Value *args)
383 if (argc < 1) return SIGIL_FALSE;
384 int key = sigil_app_key_code_from_symbol(vm, args[0]);
385 if (key == SAPP_KEYCODE_INVALID || !g_app) return SIGIL_FALSE;
386 return g_app->keys_down[key] ? SIGIL_TRUE : SIGIL_FALSE;
389/*
390 * (key-pressed? key) -> boolean
391 */
392static Value native_key_pressed(SigilVM *vm, int argc, Value *args)
394 if (argc < 1) return SIGIL_FALSE;
395 int key = sigil_app_key_code_from_symbol(vm, args[0]);
396 if (key == SAPP_KEYCODE_INVALID || !g_app) return SIGIL_FALSE;
397 return g_app->keys_pressed[key] ? SIGIL_TRUE : SIGIL_FALSE;
400/*
401 * (key-released? key) -> boolean
402 */
403static Value native_key_released(SigilVM *vm, int argc, Value *args)
405 if (argc < 1) return SIGIL_FALSE;
406 int key = sigil_app_key_code_from_symbol(vm, args[0]);
407 if (key == SAPP_KEYCODE_INVALID || !g_app) return SIGIL_FALSE;
408 return g_app->keys_released[key] ? SIGIL_TRUE : SIGIL_FALSE;
411/*
412 * (quit-requested?) -> boolean
413 */
414static Value native_quit_requested(SigilVM *vm, int argc, Value *args)
416 (void)vm; (void)argc; (void)args;
417 if (!g_app) return SIGIL_FALSE;
418 return g_app->quit_requested ? SIGIL_TRUE : SIGIL_FALSE;
421/*
422 * (request-quit)
423 */
424static Value native_request_quit(SigilVM *vm, int argc, Value *args)
426 (void)vm; (void)argc; (void)args;
427 sapp_request_quit();
428 return SIGIL_NIL;
431/* ============================================================
432 * MODULE INITIALIZATION
433 * ============================================================ */
435void sigil__init_sigil_app_module(SigilVM *vm)
437 SigilModule *module = sigil_begin_module(vm, "(sigil app)");
438 if (!module) return;
440 /* Application lifecycle */
441 sigil_module_register_native(vm, "app-run", native_app_run,
442 SIGIL_ARITY_RANGE(3, 6),
443 "Run application with init/frame/cleanup callbacks");
445 /* Frame info */
446 sigil_module_register_native(vm, "frame-width", native_frame_width,
447 SIGIL_ARITY_EXACT(0), "Get frame buffer width");
448 sigil_module_register_native(vm, "frame-height", native_frame_height,
449 SIGIL_ARITY_EXACT(0), "Get frame buffer height");
450 sigil_module_register_native(vm, "frame-time", native_frame_time,
451 SIGIL_ARITY_EXACT(0), "Seconds since last frame");
452 sigil_module_register_native(vm, "time-elapsed", native_time_elapsed,
453 SIGIL_ARITY_EXACT(0), "Seconds since app start");
455 /* Mouse input */
456 sigil_module_register_native(vm, "mouse-x", native_mouse_x,
457 SIGIL_ARITY_EXACT(0), "Mouse X position");
458 sigil_module_register_native(vm, "mouse-y", native_mouse_y,
459 SIGIL_ARITY_EXACT(0), "Mouse Y position");
460 sigil_module_register_native(vm, "mouse-down?", native_mouse_down,
461 SIGIL_ARITY_EXACT(1), "Is mouse button held?");
462 sigil_module_register_native(vm, "mouse-pressed?", native_mouse_pressed,
463 SIGIL_ARITY_EXACT(1), "Was mouse button just pressed?");
464 sigil_module_register_native(vm, "mouse-released?", native_mouse_released,
465 SIGIL_ARITY_EXACT(1), "Was mouse button just released?");
467 /* Keyboard input */
468 sigil_module_register_native(vm, "key-down?", native_key_down,
469 SIGIL_ARITY_EXACT(1), "Is key held?");
470 sigil_module_register_native(vm, "key-pressed?", native_key_pressed,
471 SIGIL_ARITY_EXACT(1), "Was key just pressed?");
472 sigil_module_register_native(vm, "key-released?", native_key_released,
473 SIGIL_ARITY_EXACT(1), "Was key just released?");
475 /* Quit handling */
476 sigil_module_register_native(vm, "quit-requested?", native_quit_requested,
477 SIGIL_ARITY_EXACT(0), "Was quit requested?");
478 sigil_module_register_native(vm, "request-quit", native_request_quit,
479 SIGIL_ARITY_EXACT(0), "Request application quit");
481 /* Export all */
482 sigil_module_export(vm, "app-run");
483 sigil_module_export(vm, "frame-width");
484 sigil_module_export(vm, "frame-height");
485 sigil_module_export(vm, "frame-time");
486 sigil_module_export(vm, "time-elapsed");
487 sigil_module_export(vm, "mouse-x");
488 sigil_module_export(vm, "mouse-y");
489 sigil_module_export(vm, "mouse-down?");
490 sigil_module_export(vm, "mouse-pressed?");
491 sigil_module_export(vm, "mouse-released?");
492 sigil_module_export(vm, "key-down?");
493 sigil_module_export(vm, "key-pressed?");
494 sigil_module_export(vm, "key-released?");
495 sigil_module_export(vm, "quit-requested?");
496 sigil_module_export(vm, "request-quit");
498 sigil_end_module(vm);