diff options
-rw-r--r-- | README.asciidoc | 1 | ||||
-rw-r--r-- | c_src/esdl2.h | 12 | ||||
-rw-r--r-- | c_src/internal.c | 2 | ||||
-rw-r--r-- | c_src/sdl_rect.c | 16 | ||||
-rw-r--r-- | c_src/sdl_window.c | 223 | ||||
-rw-r--r-- | src/esdl2.erl | 12 | ||||
-rw-r--r-- | src/esdl2_callbacks.erl | 14 | ||||
-rw-r--r-- | src/sdl_window.erl | 12 |
8 files changed, 287 insertions, 5 deletions
diff --git a/README.asciidoc b/README.asciidoc index 64a9289..1ab6055 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -92,7 +92,6 @@ corresponding to the public headers. ** `SDL_GetWindowSurface` (window) ** `SDL_UpdateWindowSurface` (window) ** `SDL_UpdateWindowSurfaceRects` (window) -** `SDL_SetWindowHitTest` and the related callback `SDL_HitTestResult` (unclear if we need it and whether we can make it efficient enough) ** `SDL_IsScreenSaverEnabled` ** `SDL_EnableScreenSaver` ** `SDL_DisableScreenSaver` diff --git a/c_src/esdl2.h b/c_src/esdl2.h index 8ab4e32..8c107f8 100644 --- a/c_src/esdl2.h +++ b/c_src/esdl2.h @@ -55,6 +55,8 @@ A(blend) \ A(borderless) \ A(bottom) \ + A(bottom_left) \ + A(bottom_right) \ A(button) \ A(callback) \ A(caps) \ @@ -77,6 +79,7 @@ A(direction) \ A(dollar_gesture) \ A(dollar_record) \ + A(draggable) \ A(drop_begin) \ A(drop_complete) \ A(drop_file) \ @@ -202,6 +205,7 @@ A(right_gui) \ A(right_shift) \ A(scancode) \ + A(set_window_hit_test_result) \ A(shown) \ A(size_all) \ A(size_changed) \ @@ -226,6 +230,8 @@ A(timestamp) \ A(tooltip) \ A(top) \ + A(top_left) \ + A(top_right) \ A(touch) \ A(true) \ A(type) \ @@ -450,6 +456,9 @@ F(set_window_fullscreen, 2) \ F(set_window_gamma_ramp, 4) \ F(set_window_grab, 2) \ + F(set_window_hit_test, 3) \ + F(set_window_hit_test_remove, 1) \ + F(set_window_hit_test_result, 2) \ F(set_window_icon, 2) \ F(set_window_input_focus, 1) \ F(set_window_maximum_size, 3) \ @@ -485,8 +494,9 @@ NIF_LIST_TO_FLAGS_FUNCTION_DECL(keymod_list_to_flags, Uint16) NIF_FLAGS_TO_LIST_FUNCTION_DECL(keymod_flags_to_list, Uint16) int map_to_point(ErlNifEnv*, ERL_NIF_TERM, SDL_Point*); +ERL_NIF_TERM point_to_map(ErlNifEnv*, const SDL_Point*); int map_to_rect(ErlNifEnv*, ERL_NIF_TERM, SDL_Rect*); -ERL_NIF_TERM rect_to_map(ErlNifEnv*, SDL_Rect*); +ERL_NIF_TERM rect_to_map(ErlNifEnv*, const SDL_Rect*); ERL_NIF_TERM display_mode_to_map(ErlNifEnv*, SDL_DisplayMode*); ERL_NIF_TERM mouse_state_to_list(ErlNifEnv*, Uint32); diff --git a/c_src/internal.c b/c_src/internal.c index 462484f..10ca5a1 100644 --- a/c_src/internal.c +++ b/c_src/internal.c @@ -18,7 +18,7 @@ // register_callback_process -ErlNifPid callback_process; +static ErlNifPid callback_process; NIF_FUNCTION(register_callback_process) { diff --git a/c_src/sdl_rect.c b/c_src/sdl_rect.c index 737c920..472cf01 100644 --- a/c_src/sdl_rect.c +++ b/c_src/sdl_rect.c @@ -31,6 +31,20 @@ int map_to_point(ErlNifEnv* env, ERL_NIF_TERM map, SDL_Point* point) return 1; } +ERL_NIF_TERM point_to_map(ErlNifEnv* env, const SDL_Point* point) +{ + ERL_NIF_TERM map; + + map = enif_make_new_map(env); + + enif_make_map_put(env, map, atom_x, + enif_make_int(env, point->x), &map); + enif_make_map_put(env, map, atom_y, + enif_make_int(env, point->y), &map); + + return map; +} + int map_to_rect(ErlNifEnv* env, ERL_NIF_TERM map, SDL_Rect* rect) { ERL_NIF_TERM x, y, w, h; @@ -56,7 +70,7 @@ int map_to_rect(ErlNifEnv* env, ERL_NIF_TERM map, SDL_Rect* rect) return 1; } -ERL_NIF_TERM rect_to_map(ErlNifEnv* env, SDL_Rect* rect) +ERL_NIF_TERM rect_to_map(ErlNifEnv* env, const SDL_Rect* rect) { ERL_NIF_TERM map; diff --git a/c_src/sdl_window.c b/c_src/sdl_window.c index 8122479..6f910ed 100644 --- a/c_src/sdl_window.c +++ b/c_src/sdl_window.c @@ -82,6 +82,20 @@ NIF_ENUM_TO_ATOM_FUNCTION(window_event_to_atom, Uint8, WINDOW_EVENT_ENUM) static NIF_ATOM_TO_ENUM_FUNCTION(atom_to_window_fullscreen, Uint32, WINDOW_FULLSCREEN_ENUM) +#define HIT_TEST_ENUM(E) \ + E(normal, SDL_HITTEST_NORMAL) \ + E(draggable, SDL_HITTEST_DRAGGABLE) \ + E(top_left, SDL_HITTEST_RESIZE_TOPLEFT) \ + E(top, SDL_HITTEST_RESIZE_TOP) \ + E(top_right, SDL_HITTEST_RESIZE_TOPRIGHT) \ + E(right, SDL_HITTEST_RESIZE_RIGHT) \ + E(bottom_right, SDL_HITTEST_RESIZE_BOTTOMRIGHT) \ + E(bottom, SDL_HITTEST_RESIZE_BOTTOM) \ + E(bottom_left, SDL_HITTEST_RESIZE_BOTTOMLEFT) \ + E(left, SDL_HITTEST_RESIZE_LEFT) + +static NIF_ATOM_TO_ENUM_FUNCTION(atom_to_hit_test_result, SDL_HitTestResult, HIT_TEST_ENUM) + // create_window NIF_CALL_HANDLER(thread_create_window) @@ -835,6 +849,215 @@ NIF_FUNCTION(set_window_grab) NIF_RES_GET(Window, window_res), b); } +// set_window_hit_test + +#define WAIT_FOR_RESULT -1 +#define NO_RESULT -2 + +typedef struct hit_test_callback_data { + char module[256]; + char function[256]; + ErlNifMutex* lock; + ErlNifCond* cond; + int result; /* SDL_HitTestResult | WAIT_FOR_RESULT | NO_RESULT */ +} hit_test_callback_data; + +static SDL_HitTestResult hit_test_callback(SDL_Window* window, const SDL_Point* area, void* data) +{ + ERL_NIF_TERM module, function, window_term; + int result; + ErlNifEnv* env = enif_alloc_env(); + hit_test_callback_data* callback = SDL_GetWindowData(window, "hit_test_callback"); + + enif_mutex_lock(callback->lock); + + module = enif_make_atom(env, callback->module); + function = enif_make_atom(env, callback->function); + + callback->result = WAIT_FOR_RESULT; + + window_term = esdl2_windows_find(env, window); + + // Tell our Erlang process to execute the callback. + + enif_send(NULL, get_callback_process(), env, + enif_make_tuple6(env, atom_callback, + module, + function, + enif_make_list2(env, window_term, point_to_map(env, area)), + atom_set_window_hit_test_result, + enif_make_list1(env, window_term) + )); + + enif_free_env(env); + + // Then wait for the result. + + while (callback->result == WAIT_FOR_RESULT) + enif_cond_wait(callback->cond, callback->lock); + + result = callback->result; + callback->result = NO_RESULT; + + enif_mutex_unlock(callback->lock); + + return result; +} + +NIF_CALL_HANDLER(thread_set_window_hit_test) +{ + hit_test_callback_data* callback = SDL_GetWindowData(args[0], "hit_test_callback"); + + // We need to copy the module/function because atoms are theoretically + // dependent on an environment. This is not the case today but might change. + + if (callback) { + // We already have an SDL2 window hit test callback. + // Just update the Erlang module/function we need to call. + + enif_mutex_lock(callback->lock); + + strcpy(callback->module, args[1]), + strcpy(callback->function, args[2]), + + enif_mutex_unlock(callback->lock); + } else { + // This is the first time this function is called. We need + // to create the initial callback along with its lock/cond. + + callback = (hit_test_callback_data*)SDL_malloc(sizeof(hit_test_callback_data)); + + strcpy(callback->module, args[1]), + strcpy(callback->function, args[2]), + + callback->lock = enif_mutex_create("hit_test_callback_lock"); + callback->cond = enif_cond_create("hit_test_callback_cond"); + callback->result = NO_RESULT; + + SDL_SetWindowData(args[0], "hit_test_callback", callback); + } + + enif_free(args[1]); + enif_free(args[2]); + + if (SDL_SetWindowHitTest(args[0], &hit_test_callback, NULL)) { + SDL_SetWindowData(args[0], "hit_test_callback", NULL); + SDL_free(callback); + + return sdl_error_tuple(env); + } + + return atom_ok; +} + +NIF_FUNCTION(set_window_hit_test) +{ + void* window_res; + unsigned int module_len, function_len; + char *module = NULL, *function = NULL; + + BADARG_IF(!enif_get_resource(env, argv[0], res_Window, &window_res)); + BADARG_IF(!enif_get_atom_length(env, argv[1], &module_len, ERL_NIF_LATIN1)); + BADARG_IF(!enif_get_atom_length(env, argv[2], &function_len, ERL_NIF_LATIN1)); + + module = (char*)enif_alloc(module_len + 1); + if (!enif_get_atom(env, argv[1], module, module_len + 1, ERL_NIF_LATIN1)) + goto set_window_hit_test_badarg; + + function = (char*)enif_alloc(function_len + 1); + if (!enif_get_atom(env, argv[2], function, function_len + 1, ERL_NIF_LATIN1)) + goto set_window_hit_test_badarg; + + return nif_thread_call(env, thread_set_window_hit_test, 3, + NIF_RES_GET(Window, window_res), module, function); + +set_window_hit_test_badarg: + enif_free(module); + enif_free(function); + + return enif_make_badarg(env); +} + +// set_window_hit_test_remove + +NIF_CALL_HANDLER(thread_set_window_hit_test_remove) +{ + hit_test_callback_data* callback; + + // We execute the function unconditionally even if we know + // there is no callback set in order to best reproduce the + // behavior when the feature is unsupported. + + if (SDL_SetWindowHitTest(args[0], NULL, NULL)) + return sdl_error_tuple(env); + + callback = SDL_GetWindowData(args[0], "hit_test_callback"); + + if (!callback) + return atom_ok; + + // The callback we removed but just in case we check that + // there is no ongoing callback running before freeing memory. + + enif_mutex_lock(callback->lock); + + while (callback->result != NO_RESULT) + enif_cond_wait(callback->cond, callback->lock); + + enif_mutex_unlock(callback->lock); + + SDL_SetWindowData(args[0], "hit_test_callback", NULL); + SDL_free(callback); + + return atom_ok; +} + +NIF_FUNCTION(set_window_hit_test_remove) +{ + void* window_res; + + BADARG_IF(!enif_get_resource(env, argv[0], res_Window, &window_res)); + + return nif_thread_call(env, thread_set_window_hit_test_remove, 1, + NIF_RES_GET(Window, window_res)); +} + +// set_window_hit_test_result + +NIF_FUNCTION(set_window_hit_test_result) +{ + void* window_res; + hit_test_callback_data* callback; + SDL_HitTestResult result; + + BADARG_IF(!enif_get_resource(env, argv[0], res_Window, &window_res)); + + callback = SDL_GetWindowData(NIF_RES_GET(Window, window_res), "hit_test_callback"); + + enif_mutex_lock(callback->lock); + + if (!atom_to_hit_test_result(env, argv[1], &result)) { + // An exception occurred inside the callback function or + // the user returned an invalid value. Not much we can do. + // Act as if the callback was disabled and make the callback + // process crash loudly. + + callback->result = SDL_HITTEST_NORMAL; + + enif_cond_signal(callback->cond); + enif_mutex_unlock(callback->lock); + + return enif_make_badarg(env); + } + + callback->result = result; + + enif_cond_signal(callback->cond); + enif_mutex_unlock(callback->lock); + + return atom_ok; +} + // set_window_icon // // We use a call here because we need the surface to exist until this call diff --git a/src/esdl2.erl b/src/esdl2.erl index 69885c6..edbb3b1 100644 --- a/src/esdl2.erl +++ b/src/esdl2.erl @@ -213,6 +213,9 @@ -export([set_window_fullscreen/2]). -export([set_window_gamma_ramp/4]). -export([set_window_grab/2]). +-export([set_window_hit_test/3]). +-export([set_window_hit_test_remove/1]). +-export([set_window_hit_test_result/2]). -export([set_window_icon/2]). -export([set_window_input_focus/1]). -export([set_window_maximum_size/3]). @@ -757,6 +760,15 @@ set_window_gamma_ramp(_, _, _, _) -> set_window_grab(_, _) -> erlang:nif_error({not_loaded, ?MODULE}). +set_window_hit_test(_, _, _) -> + erlang:nif_error({not_loaded, ?MODULE}). + +set_window_hit_test_remove(_) -> + erlang:nif_error({not_loaded, ?MODULE}). + +set_window_hit_test_result(_, _) -> + erlang:nif_error({not_loaded, ?MODULE}). + set_window_icon(_, _) -> erlang:nif_error({not_loaded, ?MODULE}). diff --git a/src/esdl2_callbacks.erl b/src/esdl2_callbacks.erl index ccbe858..ecbc4a2 100644 --- a/src/esdl2_callbacks.erl +++ b/src/esdl2_callbacks.erl @@ -51,7 +51,19 @@ handle_info({callback, M, F, A}, State) -> try apply(M, F, A) catch Class:Reason -> - error_logger:error_msg("Exception ~p:~p with callback:~n{~p,~p,~p}~n", [Class, Reason, M, F, A]) + error_logger:error_msg("Exception ~p:~p with callback:~n{~p,~p,~p}~n", + [Class, Reason, M, F, A]) + end, + {noreply, State}; +handle_info({callback, M, F, A, ResF, ResA}, State) -> + try apply(M, F, A) of + Res -> + apply(esdl2, ResF, ResA ++ [Res]) + catch Class:Reason -> + %% We need to inform the NIF that an error occurred. + apply(esdl2, ResF, ResA ++ [error]), + error_logger:error_msg("Exception ~p:~p with callback:~n{~p,~p,~p}~n", + [Class, Reason, M, F, A]) end, {noreply, State}; handle_info(_Info, State) -> diff --git a/src/sdl_window.erl b/src/sdl_window.erl index 802e20c..24a4622 100644 --- a/src/sdl_window.erl +++ b/src/sdl_window.erl @@ -45,6 +45,7 @@ -export([set_display_mode/2]). -export([set_fullscreen/2]). -export([set_gamma_ramp/4]). +-export([set_hit_test_callback/3]). -export([set_icon/2]). -export([set_max_size/3]). -export([set_min_size/3]). @@ -55,6 +56,7 @@ -export([set_size/3]). -export([set_title/2]). -export([show/1]). +-export([unset_hit_test_callback/1]). -opaque window() :: <<>>. -export_type([window/0]). @@ -237,6 +239,11 @@ set_gamma_ramp(Window, Red, Green, Blue) -> esdl2:set_window_gamma_ramp(Window, Red, Green, Blue), receive {'_nif_thread_ret_', Ret} -> Ret end. +-spec set_hit_test_callback(window(), module(), atom()) -> ok | sdl:error(). +set_hit_test_callback(Window, Module, Function) -> + esdl2:set_window_hit_test(Window, Module, Function), + receive {'_nif_thread_ret_', Ret} -> Ret end. + -spec set_icon(window(), sdl_surface:surface()) -> ok. set_icon(Window, Surface) -> esdl2:set_window_icon(Window, Surface), @@ -279,3 +286,8 @@ set_title(Window, Title) -> -spec show(window()) -> ok. show(Window) -> esdl2:show_window(Window). + +-spec unset_hit_test_callback(window()) -> ok | sdl:error(). +unset_hit_test_callback(Window) -> + esdl2:set_window_hit_test_remove(Window), + receive {'_nif_thread_ret_', Ret} -> Ret end. |