2012-08-13 17:50:28 +02:00
|
|
|
/*
|
2019-08-03 19:43:48 +02:00
|
|
|
* This software is licensed under the terms of the MIT License.
|
2017-02-11 04:52:08 +01:00
|
|
|
* See COPYING for further information.
|
2012-08-13 17:50:28 +02:00
|
|
|
* ---
|
2024-05-16 23:30:41 +02:00
|
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
2012-08-13 17:50:28 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "events.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
|
2012-08-13 17:50:28 +02:00
|
|
|
#include "config.h"
|
|
|
|
#include "global.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "transition.h"
|
2012-08-13 17:50:28 +02:00
|
|
|
#include "video.h"
|
|
|
|
|
2017-11-11 19:47:15 +01:00
|
|
|
static hrtime_t keyrepeat_paused_until;
|
2018-11-10 01:58:37 +01:00
|
|
|
static int global_handlers_lock = 0;
|
2024-04-27 15:04:06 +02:00
|
|
|
static DYNAMIC_ARRAY(EventHandler) global_handlers_pending;
|
2021-05-05 19:32:56 +02:00
|
|
|
static DYNAMIC_ARRAY(EventHandler) global_handlers;
|
2020-06-09 02:01:53 +02:00
|
|
|
static DYNAMIC_ARRAY(SDL_Event) deferred_events;
|
2017-09-29 21:03:49 +02:00
|
|
|
|
|
|
|
uint32_t sdl_first_user_event;
|
|
|
|
|
|
|
|
static void events_register_default_handlers(void);
|
|
|
|
static void events_unregister_default_handlers(void);
|
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
#define MAX_ACTIVE_HANDLERS 32
|
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
/*
|
2018-01-12 19:26:07 +01:00
|
|
|
* Public API
|
2017-09-29 21:03:49 +02:00
|
|
|
*/
|
2017-09-18 13:19:50 +02:00
|
|
|
|
|
|
|
void events_init(void) {
|
2017-09-29 21:03:49 +02:00
|
|
|
sdl_first_user_event = SDL_RegisterEvents(NUM_TAISEI_EVENTS);
|
|
|
|
|
|
|
|
if(sdl_first_user_event == (uint32_t)-1) {
|
|
|
|
char *s =
|
|
|
|
"You have exhausted the SDL userevent pool. "
|
2019-03-11 16:33:43 +01:00
|
|
|
"How you managed that is beyond me, but congratulations.";
|
2017-09-29 21:03:49 +02:00
|
|
|
log_fatal("%s", s);
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_EventState(SDL_MOUSEMOTION, SDL_DISABLE);
|
|
|
|
|
|
|
|
events_register_default_handlers();
|
|
|
|
}
|
|
|
|
|
|
|
|
void events_shutdown(void) {
|
|
|
|
events_unregister_default_handlers();
|
2020-06-09 02:01:53 +02:00
|
|
|
dynarray_free_data(&deferred_events);
|
2017-09-18 13:19:50 +02:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
#ifdef DEBUG
|
2021-05-05 19:32:56 +02:00
|
|
|
dynarray_foreach_elem(&global_handlers, EventHandler *h, {
|
|
|
|
log_warn("Global event handler was not unregistered: %p", UNION_CAST(EventHandlerProc, void*, h->proc));
|
|
|
|
});
|
2017-09-29 21:03:49 +02:00
|
|
|
#endif
|
2021-05-05 19:32:56 +02:00
|
|
|
|
|
|
|
dynarray_free_data(&global_handlers);
|
2024-04-27 15:04:06 +02:00
|
|
|
dynarray_free_data(&global_handlers_pending);
|
2017-09-18 13:19:50 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
static bool events_invoke_handler(SDL_Event *event, EventHandler *handler) {
|
|
|
|
assert(handler->proc != NULL);
|
2018-11-10 01:58:37 +01:00
|
|
|
assert(global_handlers_lock > 0);
|
2017-09-29 21:03:49 +02:00
|
|
|
|
|
|
|
if(!handler->event_type || handler->event_type == event->type) {
|
|
|
|
bool result = handler->proc(event, handler->arg);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2017-09-19 14:13:04 +02:00
|
|
|
}
|
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
static inline int prio_index(EventPriority prio) {
|
|
|
|
return prio - EPRIO_FIRST;
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
void events_register_handler(EventHandler *handler) {
|
2022-01-24 09:58:29 +01:00
|
|
|
assert(sdl_first_user_event > 0);
|
2021-05-05 19:32:56 +02:00
|
|
|
assert(handler->proc != NULL);
|
|
|
|
assert(handler->priority >= EPRIO_FIRST);
|
|
|
|
assert(handler->priority <= EPRIO_LAST);
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2024-04-27 15:04:06 +02:00
|
|
|
if(global_handlers_lock) {
|
|
|
|
dynarray_append(&global_handlers_pending, *handler);
|
|
|
|
} else {
|
|
|
|
dynarray_append(&global_handlers, *handler);
|
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
// don't bother sorting, since most of the time we will need to re-sort it
|
|
|
|
// together with local handlers when polling
|
2018-06-01 20:40:18 +02:00
|
|
|
}
|
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
static bool hfilter_remove_pending(const void *pelem, void *ignored) {
|
|
|
|
const EventHandler *h = pelem;
|
|
|
|
return !h->_private.removal_pending;
|
2019-03-11 16:33:43 +01:00
|
|
|
}
|
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
void events_unregister_handler(EventHandlerProc proc) {
|
|
|
|
dynarray_foreach_elem(&global_handlers, EventHandler *h, {
|
2023-04-07 05:18:27 +02:00
|
|
|
if(h->proc == proc && !h->_private.removal_pending) {
|
2021-05-05 19:32:56 +02:00
|
|
|
h->_private.removal_pending = true;
|
|
|
|
break;
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2021-05-05 19:32:56 +02:00
|
|
|
});
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
if(global_handlers_lock) {
|
|
|
|
assert(global_handlers_lock > 0);
|
|
|
|
} else {
|
|
|
|
dynarray_filter(&global_handlers, hfilter_remove_pending, NULL);
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2021-05-05 19:32:56 +02:00
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
static void events_apply_flags(EventFlags flags) {
|
|
|
|
if(flags & EFLAG_TEXT) {
|
|
|
|
if(!SDL_IsTextInputActive()) {
|
|
|
|
SDL_StartTextInput();
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2021-05-05 19:32:56 +02:00
|
|
|
} else {
|
|
|
|
if(SDL_IsTextInputActive()) {
|
|
|
|
SDL_StopTextInput();
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2021-05-05 19:32:56 +02:00
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
TaiseiEvent type;
|
2019-03-11 16:33:43 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
for(type = TE_MENU_FIRST; type <= TE_MENU_LAST; ++type) {
|
|
|
|
SDL_EventState(MAKE_TAISEI_EVENT(type), (bool)(flags & EFLAG_MENU));
|
|
|
|
}
|
2019-03-11 16:33:43 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
for(type = TE_GAME_FIRST; type <= TE_GAME_LAST; ++type) {
|
|
|
|
SDL_EventState(MAKE_TAISEI_EVENT(type), (bool)(flags & EFLAG_GAME));
|
|
|
|
}
|
|
|
|
}
|
2019-03-11 16:33:43 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
static int enqueue_event_handlers(int max, EventHandler *queue[max], EventHandler local_handlers[]) {
|
|
|
|
// counting sort!
|
2019-03-11 16:33:43 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
int cnt = 0;
|
|
|
|
int pcount[NUM_EPRIOS] = { 0 };
|
|
|
|
EventHandler *temp[max];
|
2019-03-11 16:33:43 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
assert(global_handlers.num_elements <= max);
|
2018-11-10 01:58:37 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
dynarray_foreach_elem(&global_handlers, EventHandler *h, {
|
|
|
|
++pcount[prio_index(h->priority)];
|
|
|
|
temp[cnt++] = h;
|
|
|
|
});
|
|
|
|
|
|
|
|
if(local_handlers) {
|
|
|
|
for(EventHandler *h = local_handlers; h->proc; ++h) {
|
|
|
|
int pidx = prio_index(h->priority);
|
|
|
|
assert((uint)pidx < ARRAY_SIZE(pcount));
|
|
|
|
++pcount[pidx];
|
|
|
|
temp[cnt++] = h;
|
|
|
|
assert(cnt < max);
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
for(int i = 0, total = 0; i < NUM_EPRIOS; ++i) {
|
|
|
|
int t = total;
|
|
|
|
total += pcount[i];
|
|
|
|
pcount[i] = t;
|
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
for(int i = 0; i < cnt; ++i) {
|
|
|
|
EventHandler *h = temp[i];
|
|
|
|
int p = prio_index(h->priority);
|
|
|
|
queue[pcount[p]] = h;
|
|
|
|
++pcount[p];
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
return cnt;
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
static void push_event(SDL_Event *e) {
|
|
|
|
/*
|
|
|
|
* NOTE: The SDL_PushEvent() function is a wrapper around SDL_PeepEvents that also sets the
|
|
|
|
* timestamp field and calls the event filter function and event watchers. We don't use any of
|
|
|
|
* that, and setting the timestamp involves an expensive system call, so avoid it.
|
|
|
|
*/
|
|
|
|
if(UNLIKELY(SDL_PeepEvents(e, 1, SDL_ADDEVENT, 0, 0) <= 0)) {
|
|
|
|
log_sdl_error(LOG_ERROR, "SDL_PeepEvents");
|
2018-11-10 01:58:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
void events_poll(EventHandler *handlers, EventFlags flags) {
|
|
|
|
events_apply_flags(flags);
|
|
|
|
events_emit(TE_FRAME, 0, NULL, NULL);
|
2017-02-04 03:56:40 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
++global_handlers_lock;
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
EventHandler *hqueue[MAX_ACTIVE_HANDLERS];
|
|
|
|
int hqueue_size = enqueue_event_handlers(ARRAY_SIZE(hqueue), hqueue, handlers);
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
for(;;) {
|
|
|
|
if(!(flags & EFLAG_NOPUMP)) {
|
|
|
|
SDL_PumpEvents();
|
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
SDL_Event events[8];
|
|
|
|
int nevents = SDL_PeepEvents(events, ARRAY_SIZE(events), SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
|
2018-11-10 01:58:37 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
if(UNLIKELY(nevents < 0)) {
|
|
|
|
log_sdl_error(LOG_ERROR, "SDL_PeepEvents");
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2017-02-04 05:51:00 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
if(nevents == 0) {
|
|
|
|
break;
|
2017-02-04 05:51:00 +01:00
|
|
|
}
|
2017-02-04 03:56:40 +01:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
for(SDL_Event *e = events, *end = events + nevents; e < end; ++e) {
|
|
|
|
for(int i = 0; i < hqueue_size; ++i) {
|
|
|
|
EventHandler *h = NOT_NULL(hqueue[i]);
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
if(h->_private.removal_pending) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
if(events_invoke_handler(e, h)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
if(--global_handlers_lock == 0) {
|
|
|
|
dynarray_filter(&global_handlers, hfilter_remove_pending, NULL);
|
2024-04-27 15:04:06 +02:00
|
|
|
dynarray_ensure_capacity(&global_handlers, global_handlers.num_elements + global_handlers_pending.num_elements);
|
|
|
|
dynarray_foreach_elem(&global_handlers_pending, EventHandler *h, {
|
|
|
|
*dynarray_append(&global_handlers) = *h;
|
|
|
|
});
|
|
|
|
global_handlers_pending.num_elements = 0;
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2020-06-09 02:01:53 +02:00
|
|
|
|
|
|
|
dynarray_foreach_elem(&deferred_events, SDL_Event *evt, {
|
2021-05-05 19:32:56 +02:00
|
|
|
push_event(evt);
|
2020-06-09 02:01:53 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
deferred_events.num_elements = 0;
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2017-09-18 13:19:50 +02:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
void events_emit(TaiseiEvent type, int32_t code, void *data1, void *data2) {
|
|
|
|
assert(TAISEI_EVENT_VALID(type));
|
|
|
|
uint32_t sdltype = MAKE_TAISEI_EVENT(type);
|
|
|
|
assert(IS_TAISEI_EVENT(sdltype));
|
2017-09-18 13:19:50 +02:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
if(!SDL_EventState(sdltype, SDL_QUERY)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-03-13 06:44:39 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
SDL_Event event = { 0 };
|
|
|
|
event.type = sdltype;
|
|
|
|
event.user.code = code;
|
|
|
|
event.user.data1 = data1;
|
|
|
|
event.user.data2 = data2;
|
2017-09-19 14:13:04 +02:00
|
|
|
|
2021-05-05 19:32:56 +02:00
|
|
|
push_event(&event);
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2020-06-09 02:01:53 +02:00
|
|
|
void events_defer(SDL_Event *evt) {
|
2023-09-23 13:49:26 +02:00
|
|
|
dynarray_append(&deferred_events, *evt);
|
2020-06-09 02:01:53 +02:00
|
|
|
}
|
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
void events_pause_keyrepeat(void) {
|
2017-11-11 19:47:15 +01:00
|
|
|
// workaround for SDL bug
|
|
|
|
// https://bugzilla.libsdl.org/show_bug.cgi?id=3287
|
2019-01-09 04:25:10 +01:00
|
|
|
keyrepeat_paused_until = time_get() + HRTIME_RESOLUTION / 4;
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
/*
|
2018-01-12 19:26:07 +01:00
|
|
|
* Default handlers
|
2017-09-29 21:03:49 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
static bool events_handler_quit(SDL_Event *event, void *arg);
|
|
|
|
static bool events_handler_keyrepeat_workaround(SDL_Event *event, void *arg);
|
2017-09-30 04:38:16 +02:00
|
|
|
static bool events_handler_clipboard(SDL_Event *event, void *arg);
|
2017-09-29 21:03:49 +02:00
|
|
|
static bool events_handler_hotkeys(SDL_Event *event, void *arg);
|
|
|
|
static bool events_handler_key_down(SDL_Event *event, void *arg);
|
|
|
|
static bool events_handler_key_up(SDL_Event *event, void *arg);
|
2017-09-30 04:38:16 +02:00
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
attr_unused
|
|
|
|
static bool events_handler_debug_winevt(SDL_Event *event, void *arg) {
|
|
|
|
// copied from SDL wiki almost verbatim
|
|
|
|
|
|
|
|
if(event->type == SDL_WINDOWEVENT) {
|
|
|
|
switch(event->window.event) {
|
|
|
|
case SDL_WINDOWEVENT_SHOWN:
|
|
|
|
log_info("Window %d shown", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_HIDDEN:
|
|
|
|
log_info("Window %d hidden", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_EXPOSED:
|
|
|
|
log_info("Window %d exposed", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_MOVED:
|
|
|
|
log_info("Window %d moved to %d,%d", event->window.windowID, event->window.data1, event->window.data2);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_RESIZED:
|
|
|
|
log_info("Window %d resized to %dx%d", event->window.windowID, event->window.data1, event->window.data2);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
|
|
log_info("Window %d size changed to %dx%d", event->window.windowID, event->window.data1, event->window.data2);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_MINIMIZED:
|
|
|
|
log_info("Window %d minimized", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_MAXIMIZED:
|
|
|
|
log_info("Window %d maximized", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_RESTORED:
|
|
|
|
log_info("Window %d restored", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_ENTER:
|
|
|
|
log_info("Mouse entered window %d", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_LEAVE:
|
|
|
|
log_info("Mouse left window %d", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
|
|
|
log_info("Window %d gained keyboard focus", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_FOCUS_LOST:
|
|
|
|
log_info("Window %d lost keyboard focus", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_CLOSE:
|
|
|
|
log_info("Window %d closed", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_TAKE_FOCUS:
|
|
|
|
log_info("Window %d is offered a focus", event->window.windowID);
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_HIT_TEST:
|
|
|
|
log_info("Window %d has a special hit test", event->window.windowID);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
log_warn("Window %d got unknown event %d", event->window.windowID, event->window.event);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
|
|
|
|
static EventHandler default_handlers[] = {
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
#ifdef DEBUG_WINDOW_EVENTS
|
|
|
|
{ .proc = events_handler_debug_winevt, .priority = EPRIO_SYSTEM, .event_type = SDL_WINDOWEVENT },
|
|
|
|
#endif
|
2018-01-12 19:26:07 +01:00
|
|
|
{ .proc = events_handler_quit, .priority = EPRIO_SYSTEM, .event_type = SDL_QUIT },
|
|
|
|
{ .proc = events_handler_keyrepeat_workaround, .priority = EPRIO_CAPTURE, .event_type = 0 },
|
|
|
|
{ .proc = events_handler_clipboard, .priority = EPRIO_CAPTURE, .event_type = SDL_KEYDOWN },
|
|
|
|
{ .proc = events_handler_hotkeys, .priority = EPRIO_HOTKEYS, .event_type = SDL_KEYDOWN },
|
|
|
|
{ .proc = events_handler_key_down, .priority = EPRIO_TRANSLATION, .event_type = SDL_KEYDOWN },
|
|
|
|
{ .proc = events_handler_key_up, .priority = EPRIO_TRANSLATION, .event_type = SDL_KEYUP },
|
2017-09-29 21:03:49 +02:00
|
|
|
|
|
|
|
{NULL}
|
|
|
|
};
|
|
|
|
|
|
|
|
static void events_register_default_handlers(void) {
|
|
|
|
for(EventHandler *h = default_handlers; h->proc; ++h) {
|
|
|
|
events_register_handler(h);
|
|
|
|
}
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
static void events_unregister_default_handlers(void) {
|
|
|
|
for(EventHandler *h = default_handlers; h->proc; ++h) {
|
|
|
|
events_unregister_handler(h->proc);
|
|
|
|
}
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
static bool events_handler_quit(SDL_Event *event, void *arg) {
|
2018-05-19 04:01:16 +02:00
|
|
|
taisei_quit();
|
|
|
|
return true;
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
static bool events_handler_keyrepeat_workaround(SDL_Event *event, void *arg) {
|
2017-11-11 19:47:15 +01:00
|
|
|
hrtime_t timenow = time_get();
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-11-11 19:47:15 +01:00
|
|
|
if(event->type != SDL_KEYDOWN) {
|
|
|
|
uint32_t te = TAISEI_EVENT(event->type);
|
|
|
|
|
|
|
|
if(te < TE_MENU_FIRST || te > TE_MENU_LAST) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(timenow < keyrepeat_paused_until) {
|
|
|
|
log_debug(
|
2019-02-14 22:11:27 +01:00
|
|
|
"Prevented a potentially bogus key repeat (%"PRIuTIME" remaining). "
|
2017-11-11 19:47:15 +01:00
|
|
|
"This is an SDL bug. See https://bugzilla.libsdl.org/show_bug.cgi?id=3287",
|
2019-02-14 22:11:27 +01:00
|
|
|
keyrepeat_paused_until - timenow
|
2017-11-11 19:47:15 +01:00
|
|
|
);
|
2017-09-29 21:03:49 +02:00
|
|
|
return true;
|
|
|
|
}
|
2017-02-04 05:51:00 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
return false;
|
|
|
|
}
|
2017-02-04 05:51:00 +01:00
|
|
|
|
2017-09-30 04:38:16 +02:00
|
|
|
static bool events_handler_clipboard(SDL_Event *event, void *arg) {
|
|
|
|
if(!SDL_IsTextInputActive()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(event->key.keysym.mod & KMOD_CTRL && event->key.keysym.scancode == SDL_SCANCODE_V) {
|
|
|
|
if(SDL_HasClipboardText()) {
|
|
|
|
memset(event, 0, sizeof(SDL_Event));
|
|
|
|
event->type = MAKE_TAISEI_EVENT(TE_CLIPBOARD_PASTE);
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
static bool events_handler_key_down(SDL_Event *event, void *arg) {
|
|
|
|
SDL_Scancode scan = event->key.keysym.scancode;
|
|
|
|
bool repeat = event->key.repeat;
|
|
|
|
|
2019-09-11 17:42:13 +02:00
|
|
|
if(video_get_backend() == VIDEO_BACKEND_EMSCRIPTEN && scan == SDL_SCANCODE_TAB) {
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
scan = SDL_SCANCODE_ESCAPE;
|
|
|
|
}
|
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
/*
|
2018-01-12 19:26:07 +01:00
|
|
|
* Emit menu events
|
2017-09-29 21:03:49 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
struct eventmap_s { int scancode; TaiseiEvent event; } menu_event_map[] = {
|
|
|
|
// order matters
|
|
|
|
// handle all the hardcoded controls first to prevent accidentally overriding them with unusable ones
|
|
|
|
|
|
|
|
{ SDL_SCANCODE_DOWN, TE_MENU_CURSOR_DOWN },
|
|
|
|
{ SDL_SCANCODE_UP, TE_MENU_CURSOR_UP },
|
|
|
|
{ SDL_SCANCODE_RIGHT, TE_MENU_CURSOR_RIGHT },
|
|
|
|
{ SDL_SCANCODE_LEFT, TE_MENU_CURSOR_LEFT },
|
|
|
|
{ SDL_SCANCODE_RETURN, TE_MENU_ACCEPT },
|
|
|
|
{ SDL_SCANCODE_ESCAPE, TE_MENU_ABORT },
|
|
|
|
|
|
|
|
{ config_get_int(CONFIG_KEY_DOWN), TE_MENU_CURSOR_DOWN },
|
|
|
|
{ config_get_int(CONFIG_KEY_UP), TE_MENU_CURSOR_UP },
|
|
|
|
{ config_get_int(CONFIG_KEY_RIGHT), TE_MENU_CURSOR_RIGHT },
|
|
|
|
{ config_get_int(CONFIG_KEY_LEFT), TE_MENU_CURSOR_LEFT },
|
|
|
|
{ config_get_int(CONFIG_KEY_SHOT), TE_MENU_ACCEPT },
|
|
|
|
{ config_get_int(CONFIG_KEY_BOMB), TE_MENU_ABORT },
|
|
|
|
|
|
|
|
{SDL_SCANCODE_UNKNOWN, -1}
|
|
|
|
};
|
|
|
|
|
|
|
|
if(!repeat || transition.state == TRANS_IDLE) {
|
|
|
|
for(struct eventmap_s *m = menu_event_map; m->scancode != SDL_SCANCODE_UNKNOWN; ++m) {
|
2018-10-02 03:44:33 +02:00
|
|
|
if(scan == m->scancode && (!repeat || m->event != TE_MENU_ACCEPT)) {
|
2017-10-22 01:47:02 +02:00
|
|
|
events_emit(m->event, 0, (void*)(intptr_t)INDEV_KEYBOARD, NULL);
|
2017-02-04 05:51:00 +01:00
|
|
|
break;
|
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
|
|
|
}
|
2017-02-04 05:51:00 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
/*
|
2018-01-12 19:26:07 +01:00
|
|
|
* Emit game events
|
2017-09-29 21:03:49 +02:00
|
|
|
*/
|
2017-02-04 18:17:08 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
if(!repeat) {
|
|
|
|
if(scan == config_get_int(CONFIG_KEY_PAUSE) || scan == SDL_SCANCODE_ESCAPE) {
|
|
|
|
events_emit(TE_GAME_PAUSE, 0, NULL, NULL);
|
|
|
|
} else {
|
|
|
|
int key = config_key_from_scancode(scan);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
if(key >= 0) {
|
2017-10-22 01:47:02 +02:00
|
|
|
events_emit(TE_GAME_KEY_DOWN, key, (void*)(intptr_t)INDEV_KEYBOARD, NULL);
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
2012-08-13 17:50:28 +02:00
|
|
|
}
|
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool events_handler_key_up(SDL_Event *event, void *arg) {
|
|
|
|
SDL_Scancode scan = event->key.keysym.scancode;
|
|
|
|
|
|
|
|
/*
|
2018-01-12 19:26:07 +01:00
|
|
|
* Emit game events
|
2017-09-29 21:03:49 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
int key = config_key_from_scancode(scan);
|
|
|
|
|
|
|
|
if(key >= 0) {
|
2017-10-22 01:47:02 +02:00
|
|
|
events_emit(TE_GAME_KEY_UP, key, (void*)(intptr_t)INDEV_KEYBOARD, NULL);
|
2017-09-29 21:03:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2012-08-13 17:50:28 +02:00
|
|
|
}
|
2017-03-06 01:25:59 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
static bool events_handler_hotkeys(SDL_Event *event, void *arg) {
|
|
|
|
if(event->key.repeat) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_Scancode scan = event->key.keysym.scancode;
|
|
|
|
SDL_Keymod mod = event->key.keysym.mod;
|
|
|
|
|
|
|
|
if(scan == config_get_int(CONFIG_KEY_SCREENSHOT)) {
|
2024-04-29 00:57:20 +02:00
|
|
|
bool viewport_only = (mod & KMOD_ALT);
|
|
|
|
video_take_screenshot(viewport_only);
|
2017-09-29 21:03:49 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((scan == SDL_SCANCODE_RETURN && (mod & KMOD_ALT)) || scan == config_get_int(CONFIG_KEY_FULLSCREEN)) {
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
video_set_fullscreen(!video_is_fullscreen());
|
2017-09-29 21:03:49 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-09-15 17:48:03 +02:00
|
|
|
if(scan == config_get_int(CONFIG_KEY_TOGGLE_AUDIO)) {
|
|
|
|
config_set_int(CONFIG_MUTE_AUDIO, !config_get_int(CONFIG_MUTE_AUDIO));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-11-24 08:31:40 +01:00
|
|
|
if(scan == config_get_int(CONFIG_KEY_RELOAD_RESOURCES)) {
|
2023-03-22 23:14:10 +01:00
|
|
|
res_reload_all();
|
2021-11-24 08:31:40 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
return false;
|
2017-03-06 01:25:59 +01:00
|
|
|
}
|