From 3d501ef57ef769be56a7e6e92da70ebcbd401544 Mon Sep 17 00:00:00 2001 From: Marius Petcu Date: Tue, 25 Dec 2018 17:34:23 +0200 Subject: [PATCH] Implement reusable cursors on macOS --- defos/src/defos.cpp | 126 ++++++++++++++++++++++++------------- defos/src/defos_mac.mm | 44 ++++++++----- defos/src/defos_private.h | 10 +-- example/example.gui_script | 10 +-- 4 files changed, 123 insertions(+), 67 deletions(-) diff --git a/defos/src/defos.cpp b/defos/src/defos.cpp index 74c80dc..2f557af 100644 --- a/defos/src/defos.cpp +++ b/defos/src/defos.cpp @@ -291,6 +291,75 @@ static int is_cursor_locked(lua_State *L) return 1; } +static int cursor_metatable; + +static void *load_custom_cursor(lua_State *L, int index) +{ + #ifdef DM_PLATFORM_WINDOWS + const char *cursor_path_lua = luaL_checkstring(L, index); + return defos_load_cursor_win(cursor_path_lua); + #endif + + #ifdef DM_PLATFORM_LINUX + const char *cursor_path_lua = luaL_checkstring(L, index); + return defos_load_cursor_linux(cursor_path_lua); + return 0; + + // TODO: X11 support animated cursor by XRender + #endif + + #ifdef DM_PLATFORM_OSX + luaL_checktype(L, index, LUA_TTABLE); + + lua_getfield(L, index, "hot_spot_x"); + float hotSpotX = 0.0f; + if (!lua_isnil(L, -1)) + { + hotSpotX = luaL_checknumber(L, -1); + } + + lua_getfield(L, index, "hot_spot_y"); + float hotSpotY = 0.0f; + if (!lua_isnil(L, -1)) + { + hotSpotY = luaL_checknumber(L, -1); + } + + lua_getfield(L, index, "image"); + dmBuffer::HBuffer image = dmScript::CheckBuffer(L, -1)->m_Buffer; + + void *cursor = defos_load_cursor_mac(image, hotSpotX, hotSpotY); + lua_pop(L, 3); + return cursor; + #endif + + #ifdef DM_PLATFORM_HTML5 + const char * cursor_url = luaL_checkstring(L, 1); + return defos_load_cursor_html5(cursor_url); + #endif + + lua_pushstring(L, "Invalid argument"); + lua_error(L); + return NULL; +} + +static int load_cursor(lua_State *L) +{ + void ** userdata = (void**)lua_newuserdata(L, sizeof(void*)); + *userdata = load_custom_cursor(L, 1); + lua_rawgeti(L, LUA_REGISTRYINDEX, cursor_metatable); + lua_setmetatable(L, -2); + return 1; +} + +static int gc_cursor(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + void **cursor = (void**)lua_touserdata(L, 1); + defos_gc_custom_cursor(*cursor); + return 0; +} + static int set_cursor(lua_State *L) { if (lua_isnil(L, 1)) @@ -306,53 +375,16 @@ static int set_cursor(lua_State *L) return 0; } - #ifdef DM_PLATFORM_WINDOWS - const char *cursor_path_lua = luaL_checkstring(L, 1); - defos_set_custom_cursor_win(cursor_path_lua); - return 0; - #endif - - #ifdef DM_PLATFORM_LINUX - const char *cursor_path_lua = luaL_checkstring(L,1); - defos_set_custom_cursor_linux(cursor_path_lua); - return 0; - - // TODO: X11 support animated cursor by XRender - #endif - - #ifdef DM_PLATFORM_OSX - luaL_checktype(L, 1, LUA_TTABLE); - - lua_getfield(L, 1, "hot_spot_x"); - float hotSpotX = 0.0f; - if (!lua_isnil(L, -1)) + if (lua_isuserdata(L, 1)) { - hotSpotX = luaL_checknumber(L, -1); + void **cursor = (void**)lua_touserdata(L, 1); + defos_set_custom_cursor(*cursor); + return 0; } - lua_getfield(L, 1, "hot_spot_y"); - float hotSpotY = 0.0f; - if (!lua_isnil(L, -1)) - { - hotSpotY = luaL_checknumber(L, -1); - } - - lua_getfield(L, 1, "image"); - dmBuffer::HBuffer image = dmScript::CheckBuffer(L, -1)->m_Buffer; - - defos_set_custom_cursor_mac(image, hotSpotX, hotSpotY); - lua_pop(L, 3); - return 0; - #endif - - #ifdef DM_PLATFORM_HTML5 - const char * cursor_url = luaL_checkstring(L, 1); - defos_set_custom_cursor_html5(cursor_url); - return 0; - #endif - - lua_pushstring(L, "Invalid argument"); - lua_error(L); + void *custom_cursor = load_custom_cursor(L, 1); + defos_set_custom_cursor(custom_cursor); + defos_gc_custom_cursor(custom_cursor); return 0; } @@ -604,6 +636,7 @@ static const luaL_reg Module_methods[] = {"get_view_size", get_view_size}, {"set_cursor", set_cursor}, {"reset_cursor", reset_cursor}, + {"load_cursor", load_cursor}, {"get_displays", get_displays}, {"get_display_modes", get_display_modes}, {"get_current_display_id", get_current_display_id}, @@ -635,6 +668,11 @@ static void LuaInit(lua_State *L) lua_setfield(L, -2, "PATH_SEP"); #endif + lua_newtable(L); + lua_pushcfunction(L, gc_cursor); + lua_setfield(L, -2, "__gc"); + cursor_metatable = dmScript::Ref(L, LUA_REGISTRYINDEX); + lua_pop(L, 1); assert(top == lua_gettop(L)); } diff --git a/defos/src/defos_mac.mm b/defos/src/defos_mac.mm index c6b5c61..cc8b9d0 100644 --- a/defos/src/defos_mac.mm +++ b/defos/src/defos_mac.mm @@ -53,6 +53,7 @@ void defos_init() { // [window disableCursorRects]; // [window resetCursorRects]; default_cursor = NSCursor.arrowCursor; + [default_cursor retain]; current_cursor = default_cursor; [current_cursor retain]; enable_mouse_tracking(); @@ -60,8 +61,10 @@ void defos_init() { void defos_final() { disable_mouse_tracking(); + [default_cursor release]; [current_cursor release]; current_cursor = nil; + default_cursor = nil; } void defos_update() { @@ -338,24 +341,37 @@ bool defos_is_cursor_locked() { return is_cursor_locked; } -void defos_set_custom_cursor_mac(dmBuffer::HBuffer buffer, float hotSpotX, float hotSpotY) { - uint8_t* bytes = NULL; - uint32_t size = 0; - if (dmBuffer::GetBytes(buffer, (void**)&bytes, &size) != dmBuffer::RESULT_OK) { - dmLogError("defos_set_custom_cursor_mac: dmBuffer::GetBytes failed"); - return; +void *defos_load_cursor_mac(dmBuffer::HBuffer buffer, float hotSpotX, float hotSpotY) { + @autoreleasepool { + uint8_t* bytes = NULL; + uint32_t size = 0; + if (dmBuffer::GetBytes(buffer, (void**)&bytes, &size) != dmBuffer::RESULT_OK) { + dmLogError("defos_set_custom_cursor_mac: dmBuffer::GetBytes failed"); + return NULL; + } + + uint8_t* copy = (uint8_t*)malloc(size); + memcpy(copy, bytes, size); + NSData * data = [[NSData alloc] initWithBytesNoCopy:copy length:size freeWhenDone:YES]; + NSImage * image = [[NSImage alloc] initWithData:data]; + NSCursor * cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(hotSpotX, hotSpotY)]; + + [image release]; + [data release]; + return cursor; } +} - uint8_t* copy = (uint8_t*)malloc(size); - memcpy(copy, bytes, size); - NSData * data = [[NSData alloc] initWithBytesNoCopy:copy length:size freeWhenDone:YES]; +void defos_gc_custom_cursor(void * _cursor) { + NSCursor * cursor = (NSCursor*)_cursor; + [cursor release]; +} - NSImage * image = [[NSImage alloc] initWithData:data]; - NSCursor * cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(hotSpotX, hotSpotY)]; +void defos_set_custom_cursor(void * _cursor) { + NSCursor * cursor = (NSCursor*)_cursor; + [cursor retain]; [current_cursor release]; current_cursor = cursor; - [image release]; - [data release]; [current_cursor set]; } @@ -376,10 +392,10 @@ static NSCursor * get_cursor(DefosCursor cursor) { void defos_set_cursor(DefosCursor cur) { NSCursor * cursor = get_cursor(cur); - [cursor set]; [cursor retain]; [current_cursor release]; current_cursor = cursor; + [current_cursor set]; } void defos_reset_cursor() { diff --git a/defos/src/defos_private.h b/defos/src/defos_private.h index e2544a4..b511f92 100644 --- a/defos/src/defos_private.h +++ b/defos/src/defos_private.h @@ -107,10 +107,12 @@ extern bool defos_is_cursor_clipped(); extern void defos_set_cursor_locked(bool locked); extern bool defos_is_cursor_locked(); -extern void defos_set_custom_cursor_html5(const char *url); -extern void defos_set_custom_cursor_win(const char *filename); -extern void defos_set_custom_cursor_mac(dmBuffer::HBuffer buffer, float hotSpotX, float hotSpotY); -extern void defos_set_custom_cursor_linux(const char *filename); +extern void *defos_load_cursor_html5(const char *url); +extern void *defos_load_cursor_win(const char *filename); +extern void *defos_load_cursor_mac(dmBuffer::HBuffer buffer, float hotSpotX, float hotSpotY); +extern void *defos_load_cursor_linux(const char *filename); +extern void defos_gc_custom_cursor(void *cursor); +extern void defos_set_custom_cursor(void *cursor); extern void defos_set_cursor(DefosCursor cursor); extern void defos_reset_cursor(); diff --git a/example/example.gui_script b/example/example.gui_script index 4c0614b..9843ad0 100644 --- a/example/example.gui_script +++ b/example/example.gui_script @@ -47,26 +47,26 @@ function init(self) if system_name == "Linux" then -- NOTE: since Defold cannot load resource without extension, we added .xcur here. -- NOTE: cursor should be normal x11 cursor file that type is: image/x-xcursor - table.insert(self.cursors, extract_to_savefolder("cursor.xcur")) + table.insert(self.cursors, defos.load_cursor(extract_to_savefolder("cursor.xcur"))) end if system_name == "Windows" then for i, v in ipairs({"cursor_01.ani", "cursor_02.ani" }) do -- load source file and write them to save folder, so that we can access them with fullpath, or you can use bundle_resource - table.insert(self.cursors, extract_to_savefolder(v)) + table.insert(self.cursors, defos.load_cursor(extract_to_savefolder(v))) end end if system_name == "Darwin" then - table.insert(self.cursors, { + table.insert(self.cursors, defos.load_cursor({ image = resource.load("/resources/cursor_mac.tiff"), hot_spot_x = 18, hot_spot_y = 2, - }) + })) end if system_name == "HTML5" then - table.insert(self.cursors, cursor_url) + table.insert(self.cursors, defos.load_cursor(cursor_url)) end defos.on_mouse_enter(function ()