From 639580cbd165b35fb049af40eadbabe1afa576fb Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Wed, 25 Dec 2024 13:15:17 -0400 Subject: [PATCH] Add opt-in support for trackpad touch events. Add touch device types. - Add new touch device type enum ("touchscreen", "touchpad", "touchpadrelative"). Coordinates for touchscreens are window-relative DPI-scaled pixels, and coordinates for touchpads are normalized [0, 1] values. - Add new devicetype enum and ismouse boolean params to touch callbacks. - Add love.touch.getDeviceType(touchid), love.touch.isMouse(touchid), and optional device type filter param for love.touch.getTouches. - Add t.trackpadtouch boolean (defaults to false) to love.conf. Trackpad touch events will not be generated unless it's enabled. Currently the trackpadtouch setting only works on macOS. Resolves #1633. Resolves #2093. --- CMakeLists.txt | 1 + .../xcode/liblove.xcodeproj/project.pbxproj | 8 +- src/modules/event/sdl/Event.cpp | 76 ++++++++++--------- src/modules/love/boot.lua | 5 ++ src/modules/love/callbacks.lua | 16 ++-- src/modules/love/love.cpp | 16 ++++ src/modules/touch/Touch.cpp | 46 +++++++++++ src/modules/touch/Touch.h | 23 +++++- src/modules/touch/sdl/Touch.cpp | 20 +++++ src/modules/touch/sdl/Touch.h | 2 + src/modules/touch/wrap_Touch.cpp | 59 ++++++++++++-- 11 files changed, 216 insertions(+), 56 deletions(-) create mode 100644 src/modules/touch/Touch.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 87033fb4c..386dcab64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1182,6 +1182,7 @@ target_link_libraries(love_timer PUBLIC # add_library(love_touch_root STATIC + src/modules/touch/Touch.cpp src/modules/touch/Touch.h src/modules/touch/wrap_Touch.cpp src/modules/touch/wrap_Touch.h diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index ff962e571..6ae9e56f9 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -51,6 +51,8 @@ 217DFC111D9F6D490055D849 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; }; 217DFC121D9F6D490055D849 /* usocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD61D9F6D490055D849 /* usocket.h */; }; D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */; }; + D93660F82D1C727C00C0EC4B /* Touch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D93660F72D1C727C00C0EC4B /* Touch.cpp */; }; + D93660F92D1C727C00C0EC4B /* Touch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D93660F72D1C727C00C0EC4B /* Touch.cpp */; }; D943E58E2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; }; D943E58F2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; }; D943E5902A24D56000D80361 /* PhysfsIo.h in Headers */ = {isa = PBXBuildFile; fileRef = D943E58D2A24D56000D80361 /* PhysfsIo.h */; }; @@ -1413,6 +1415,7 @@ 217DFBD51D9F6D490055D849 /* usocket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = usocket.c; sourceTree = ""; }; 217DFBD61D9F6D490055D849 /* usocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usocket.h; sourceTree = ""; }; D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = harfbuzz.xcframework; path = ios/libraries/harfbuzz.xcframework; sourceTree = ""; }; + D93660F72D1C727C00C0EC4B /* Touch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Touch.cpp; sourceTree = ""; }; D943E58C2A24D56000D80361 /* PhysfsIo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PhysfsIo.cpp; sourceTree = ""; }; D943E58D2A24D56000D80361 /* PhysfsIo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhysfsIo.h; sourceTree = ""; }; D9596F602CBAC93800BE58C1 /* SDL3.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL3.xcframework; path = shared/Frameworks/SDL3.xcframework; sourceTree = ""; }; @@ -3386,8 +3389,9 @@ children = ( FA0B7CBF1A95902C000E1D17 /* sdl */, FA0B7CC21A95902C000E1D17 /* Touch.h */, - FA0B7CC31A95902C000E1D17 /* wrap_Touch.cpp */, + D93660F72D1C727C00C0EC4B /* Touch.cpp */, FA0B7CC41A95902C000E1D17 /* wrap_Touch.h */, + FA0B7CC31A95902C000E1D17 /* wrap_Touch.cpp */, ); path = touch; sourceTree = ""; @@ -4797,6 +4801,7 @@ FA0B7DBC1A95902C000E1D17 /* Joystick.cpp in Sources */, FA0B7DAF1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */, FA0B7AD51A958EA3000E1D17 /* unix.c in Sources */, + D93660F92D1C727C00C0EC4B /* Touch.cpp in Sources */, FAB17BE71ABFAA9000F9BA27 /* lz4.c in Sources */, FAE64A902071364800BC7981 /* physfs_unicode.c in Sources */, FACA06B1293EE5CD001A2557 /* Sensor.cpp in Sources */, @@ -5229,6 +5234,7 @@ FA0B7DC41A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */, FA0B7E6F1A95902C000E1D17 /* wrap_RopeJoint.cpp in Sources */, FA24348721D401CB00B8918A /* attribute.cpp in Sources */, + D93660F82D1C727C00C0EC4B /* Touch.cpp in Sources */, FA0B7AB51A958EA3000E1D17 /* ddsparse.cpp in Sources */, FACA02F41F5E396B0084B28F /* wrap_CompressedData.cpp in Sources */, FAF140AC1E20934C00F898D2 /* Versions.cpp in Sources */, diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index 50afa32cd..74e69398d 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/src/modules/event/sdl/Event.cpp @@ -317,45 +317,51 @@ Message *Event::convert(const SDL_Event &e) case SDL_EVENT_FINGER_DOWN: case SDL_EVENT_FINGER_UP: case SDL_EVENT_FINGER_MOTION: - // TODO: Expose APIs to enable different touch device types. - if (SDL_GetTouchDeviceType(e.tfinger.touchID) == SDL_TOUCH_DEVICE_DIRECT) - { - touchinfo.id = (int64) e.tfinger.fingerID; - touchinfo.x = e.tfinger.x; - touchinfo.y = e.tfinger.y; - touchinfo.dx = e.tfinger.dx; - touchinfo.dy = e.tfinger.dy; - touchinfo.pressure = e.tfinger.pressure; + touchinfo.id = (int64)e.tfinger.fingerID; + touchinfo.x = e.tfinger.x; + touchinfo.y = e.tfinger.y; + touchinfo.dx = e.tfinger.dx; + touchinfo.dy = e.tfinger.dy; + touchinfo.pressure = e.tfinger.pressure; + touchinfo.deviceType = love::touch::sdl::Touch::getDeviceType(SDL_GetTouchDeviceType(e.tfinger.touchID)); + touchinfo.mouse = e.tfinger.touchID == SDL_MOUSE_TOUCHID; - // SDL's coords are normalized to [0, 1], but we want screen coords. + // SDL's coords are normalized to [0, 1], but we want screen coords for direct touches. + if (touchinfo.deviceType == love::touch::Touch::DEVICE_TOUCHSCREEN) + { normalizedToDPICoords(&touchinfo.x, &touchinfo.y); normalizedToDPICoords(&touchinfo.dx, &touchinfo.dy); - - // We need to update the love.touch.sdl internal state from here. - touchmodule = (touch::sdl::Touch *) Module::getInstance("love.touch.sdl"); - if (touchmodule) - touchmodule->onEvent(e.type, touchinfo); - - // This is a bit hackish and we lose the higher 32 bits of the id on - // 32-bit systems, but SDL only ever gives id's that at most use as many - // bits as can fit in a pointer (for now.) - // We use lightuserdata instead of a lua_Number (double) because doubles - // can't represent all possible id values on 64-bit systems. - vargs.emplace_back((void *) (intptr_t) touchinfo.id); - vargs.emplace_back(touchinfo.x); - vargs.emplace_back(touchinfo.y); - vargs.emplace_back(touchinfo.dx); - vargs.emplace_back(touchinfo.dy); - vargs.emplace_back(touchinfo.pressure); - - if (e.type == SDL_EVENT_FINGER_DOWN) - txt = "touchpressed"; - else if (e.type == SDL_EVENT_FINGER_UP) - txt = "touchreleased"; - else - txt = "touchmoved"; - msg = new Message(txt, vargs); } + + // We need to update the love.touch.sdl internal state from here. + touchmodule = (touch::sdl::Touch *) Module::getInstance("love.touch.sdl"); + if (touchmodule) + touchmodule->onEvent(e.type, touchinfo); + + if (!love::touch::Touch::getConstant(touchinfo.deviceType, txt)) + txt = "unknown"; + + // This is a bit hackish and we lose the higher 32 bits of the id on + // 32-bit systems, but SDL only ever gives id's that at most use as many + // bits as can fit in a pointer (for now.) + // We use lightuserdata instead of a lua_Number (double) because doubles + // can't represent all possible id values on 64-bit systems. + vargs.emplace_back((void *)(intptr_t)touchinfo.id); + vargs.emplace_back(touchinfo.x); + vargs.emplace_back(touchinfo.y); + vargs.emplace_back(touchinfo.dx); + vargs.emplace_back(touchinfo.dy); + vargs.emplace_back(touchinfo.pressure); + vargs.emplace_back(txt, strlen(txt)); + vargs.emplace_back(touchinfo.mouse); + + if (e.type == SDL_EVENT_FINGER_DOWN) + txt = "touchpressed"; + else if (e.type == SDL_EVENT_FINGER_UP) + txt = "touchreleased"; + else + txt = "touchmoved"; + msg = new Message(txt, vargs); break; case SDL_EVENT_JOYSTICK_BUTTON_DOWN: case SDL_EVENT_JOYSTICK_BUTTON_UP: diff --git a/src/modules/love/boot.lua b/src/modules/love/boot.lua index 487d3d23b..f9b08cfbb 100644 --- a/src/modules/love/boot.lua +++ b/src/modules/love/boot.lua @@ -220,6 +220,7 @@ function love.init() highdpi = false, renderers = nil, -- Moved to t.graphics. excluderenderers = nil, -- Moved to t.graphics. + trackpadtouch = false, } -- Console hack, part 1. @@ -315,6 +316,10 @@ function love.init() love._setHighDPIAllowed(c.highdpi) end + if love._setTrackpadTouch then + love._setTrackpadTouch(c.trackpadtouch) + end + if love._setAudioMixWithSystem then if c.audio and c.audio.mixwithsystem ~= nil then love._setAudioMixWithSystem(c.audio.mixwithsystem) diff --git a/src/modules/love/callbacks.lua b/src/modules/love/callbacks.lua index cc5197378..1c0072717 100644 --- a/src/modules/love/callbacks.lua +++ b/src/modules/love/callbacks.lua @@ -52,14 +52,14 @@ function love.createhandlers() wheelmoved = function (x,y,px,py,dir) if love.wheelmoved then return love.wheelmoved(x,y,px,py,dir) end end, - touchpressed = function (id,x,y,dx,dy,p) - if love.touchpressed then return love.touchpressed(id,x,y,dx,dy,p) end + touchpressed = function (id,x,y,dx,dy,p,t,m) + if love.touchpressed then return love.touchpressed(id,x,y,dx,dy,p,t,m) end end, - touchreleased = function (id,x,y,dx,dy,p) - if love.touchreleased then return love.touchreleased(id,x,y,dx,dy,p) end + touchreleased = function (id,x,y,dx,dy,p,t,m) + if love.touchreleased then return love.touchreleased(id,x,y,dx,dy,p,t,m) end end, - touchmoved = function (id,x,y,dx,dy,p) - if love.touchmoved then return love.touchmoved(id,x,y,dx,dy,p) end + touchmoved = function (id,x,y,dx,dy,p,t,m) + if love.touchmoved then return love.touchmoved(id,x,y,dx,dy,p,t,m) end end, joystickpressed = function (j,b) if love.joystickpressed then return love.joystickpressed(j,b) end @@ -163,13 +163,13 @@ function love.run() -- Process events. if love.event then love.event.pump() - for name, a,b,c,d,e,f in love.event.poll() do + for name, a,b,c,d,e,f,g,h in love.event.poll() do if name == "quit" then if not love.quit or not love.quit() then return a or 0, b end end - love.handlers[name](a,b,c,d,e,f) + love.handlers[name](a,b,c,d,e,f,g,h) end end diff --git a/src/modules/love/love.cpp b/src/modules/love/love.cpp index 235dea72d..b2908140c 100644 --- a/src/modules/love/love.cpp +++ b/src/modules/love/love.cpp @@ -78,6 +78,11 @@ # include "system/System.h" #endif +// For love::touch::setTrackpadTouch. +#ifdef LOVE_ENABLE_TOUCH +# include "touch/Touch.h" +#endif + // Scripts. #include "scripts/nogame.lua.h" @@ -439,6 +444,14 @@ static int w__setHighDPIAllowed(lua_State *L) return 0; } +static int w__setTrackpadTouch(lua_State *L) +{ +#ifdef LOVE_ENABLE_TOUCH + love::touch::setTrackpadTouch((bool) lua_toboolean(L, 1)); +#endif + return 0; +} + static int w__setAudioMixWithSystem(lua_State *L) { bool success = false; @@ -570,6 +583,9 @@ int luaopen_love(lua_State *L) lua_pushcfunction(L, w__setHighDPIAllowed); lua_setfield(L, -2, "_setHighDPIAllowed"); + lua_pushcfunction(L, w__setTrackpadTouch); + lua_setfield(L, -2, "_setTrackpadTouch"); + // Exposed here because we need to be able to call it before the audio // module is initialized. lua_pushcfunction(L, w__setAudioMixWithSystem); diff --git a/src/modules/touch/Touch.cpp b/src/modules/touch/Touch.cpp new file mode 100644 index 000000000..09b76a2d0 --- /dev/null +++ b/src/modules/touch/Touch.cpp @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2006-2024 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#include "Touch.h" + +namespace love +{ +namespace touch +{ + +// TODO: find a cleaner way to do this... +// The touch backend (e.g. love.touch.sdl) is expected to implement this. +void setTrackpadTouchImplementation(bool enable); + +void setTrackpadTouch(bool enable) +{ + setTrackpadTouchImplementation(enable); +} + +STRINGMAP_CLASS_BEGIN(Touch, Touch::DeviceType, Touch::DEVICE_MAX_ENUM, deviceType) +{ + { "touchscreen", Touch::DEVICE_TOUCHSCREEN }, + { "touchpad", Touch::DEVICE_TOUCHPAD }, + { "touchpadrelative", Touch::DEVICE_TOUCHPAD_RELATIVE }, +} +STRINGMAP_CLASS_END(Touch, Touch::DeviceType, Touch::DEVICE_MAX_ENUM, deviceType) + +} // touch +} // love diff --git a/src/modules/touch/Touch.h b/src/modules/touch/Touch.h index 08b9de60a..db8e1564e 100644 --- a/src/modules/touch/Touch.h +++ b/src/modules/touch/Touch.h @@ -25,6 +25,7 @@ #include "common/int.h" #include "common/Object.h" #include "common/Module.h" +#include "common/StringMap.h" // C++ #include @@ -35,18 +36,30 @@ namespace love namespace touch { +void setTrackpadTouch(bool enable); + class Touch : public Module { public: + enum DeviceType + { + DEVICE_TOUCHSCREEN, + DEVICE_TOUCHPAD, + DEVICE_TOUCHPAD_RELATIVE, + DEVICE_MAX_ENUM + }; + struct TouchInfo { int64 id; // Identifier. Only unique for the duration of the touch-press. - double x; // Position in pixels along the x-axis. - double y; // Position in pixels along the y-axis. - double dx; // Amount in pixels moved along the x-axis. - double dy; // Amount in pixels moved along the y-axis. + double x; // Position in pixels (for touchscreens) or normalized [0, 1] position (for touchpads) along the x-axis. + double y; // Position in pixels (for touchscreens) or normalized [0, 1] position (for touchpads) along the y-axis. + double dx; // Amount moved along the x-axis. + double dy; // Amount moved along the y-axis. double pressure; + DeviceType deviceType; + bool mouse; }; virtual ~Touch() {} @@ -61,6 +74,8 @@ public: **/ virtual const TouchInfo &getTouch(int64 id) const = 0; + STRINGMAP_CLASS_DECLARE(DeviceType); + protected: Touch(const char *name) diff --git a/src/modules/touch/sdl/Touch.cpp b/src/modules/touch/sdl/Touch.cpp index 78aa63bc8..a5206fe42 100644 --- a/src/modules/touch/sdl/Touch.cpp +++ b/src/modules/touch/sdl/Touch.cpp @@ -26,10 +26,19 @@ // C++ #include +#include + namespace love { namespace touch { + +// See src/modules/touch/Touch.cpp. +void setTrackpadTouchImplementation(bool enable) +{ + SDL_SetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY, enable ? "1" : "0"); +} + namespace sdl { @@ -84,6 +93,17 @@ void Touch::onEvent(Uint32 eventtype, const TouchInfo &info) } } +Touch::DeviceType Touch::getDeviceType(SDL_TouchDeviceType sdltype) +{ + switch (sdltype) + { + case SDL_TOUCH_DEVICE_DIRECT: return DEVICE_TOUCHSCREEN; + case SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE: return DEVICE_TOUCHPAD; + case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE: return DEVICE_TOUCHPAD_RELATIVE; + default: return DEVICE_TOUCHSCREEN; + } +} + } // sdl } // touch } // love diff --git a/src/modules/touch/sdl/Touch.h b/src/modules/touch/sdl/Touch.h index c19c25dad..0ba4ae795 100644 --- a/src/modules/touch/sdl/Touch.h +++ b/src/modules/touch/sdl/Touch.h @@ -51,6 +51,8 @@ public: // love::event::sdl::Event::convert. void onEvent(Uint32 eventtype, const TouchInfo &info); + static DeviceType getDeviceType(SDL_TouchDeviceType sdltype); + private: // All current touches. diff --git a/src/modules/touch/wrap_Touch.cpp b/src/modules/touch/wrap_Touch.cpp index 69b72cae3..fc2835abd 100644 --- a/src/modules/touch/wrap_Touch.cpp +++ b/src/modules/touch/wrap_Touch.cpp @@ -24,6 +24,7 @@ #include "wrap_Touch.h" #include "sdl/Touch.h" +#include "common/Optional.h" namespace love { @@ -42,19 +43,33 @@ int64 luax_checktouchid(lua_State *L, int idx) int w_getTouches(lua_State *L) { + Optional typefilter; + if (!lua_isnoneornil(L, 1)) + { + const char *typestr = luaL_checkstring(L, 1); + if (!Touch::getConstant(typestr, typefilter.value)) + return luax_enumerror(L, "touch device type", Touch::getConstants(typefilter.value), typestr); + typefilter.hasValue = true; + } + const std::vector &touches = instance()->getTouches(); lua_createtable(L, (int) touches.size(), 0); - for (size_t i = 0; i < touches.size(); i++) + int filteredindex = 1; + for (const Touch::TouchInfo &touch : touches) { - // This is a bit hackish and we lose the higher 32 bits of the id on - // 32-bit systems, but SDL only ever gives id's that at most use as many - // bits as can fit in a pointer (for now.) - // We use lightuserdata instead of a lua_Number (double) because doubles - // can't represent all possible id values on 64-bit systems. - lua_pushlightuserdata(L, (void *) (intptr_t) touches[i].id); - lua_rawseti(L, -2, (int) i + 1); + if (!typefilter.hasValue || typefilter.value == touch.deviceType) + { + // This is a bit hackish and we lose the higher 32 bits of the id on + // 32-bit systems, but SDL only ever gives id's that at most use as many + // bits as can fit in a pointer (for now.) + // We use lightuserdata instead of a lua_Number (double) because doubles + // can't represent all possible id values on 64-bit systems. + lua_pushlightuserdata(L, (void *)(intptr_t)touch.id); + lua_rawseti(L, -2, filteredindex); + filteredindex++; + } } return 1; @@ -84,11 +99,39 @@ int w_getPressure(lua_State *L) return 1; } +int w_getDeviceType(lua_State *L) +{ + int64 id = luax_checktouchid(L, 1); + + Touch::TouchInfo touch = {}; + luax_catchexcept(L, [&]() { touch = instance()->getTouch(id); }); + + const char *typestr = nullptr; + if (!Touch::getConstant(touch.deviceType, typestr)) + return luaL_error(L, "Unknown touch device type."); + + lua_pushstring(L, typestr); + return 1; +} + +int w_isMouse(lua_State *L) +{ + int64 id = luax_checktouchid(L, 1); + + Touch::TouchInfo touch = {}; + luax_catchexcept(L, [&]() { touch = instance()->getTouch(id); }); + + luax_pushboolean(L, touch.mouse); + return 1; +} + static const luaL_Reg functions[] = { { "getTouches", w_getTouches }, { "getPosition", w_getPosition }, { "getPressure", w_getPressure }, + { "getDeviceType", w_getDeviceType }, + { "isMouse", w_isMouse }, { 0, 0 } };