Any comments on my touchEventHelper?

Hello,

I am refactoring a lot of my code right now, and I came up with an idea to simplify the loads of touch listeners I use in the game. Most of em look pretty much the same, so I made a helper class that creates a default touch listener for me. It handles the setFocus and disables the touch when touches in my game should be.

The helper looks like this:

local t = {} function t:getDefaultTouchListener(\_options) local \_onPress = \_options.onPress local \_onMoved = \_options.onMoved local \_onRelease = \_options.onRelease local \_shouldEndWithinBounds = \_options.shouldEndWithinBounds local touchEvent = function(event) if inputHelper:isTouchesBlocked() then return true end if event.phase == "began" then display.getCurrentStage():setFocus(event.target) event.target.isFocus = true if \_onPress then \_onPress(event) end elseif event.phase == "moved" and event.target.isFocus then if \_onMoved then \_onMoved(event) end elseif (event.phase == "ended" or event.phase == "cancelled") and event.target.isFocus then display.getCurrentStage():setFocus(nil) event.target.isFocus = nil if \_onRelease and (not \_shouldEndWithinBounds or widget.\_isWithinBounds(event.target, event)) then \_onRelease(event) end end end return touchEvent end return t 

And usage looks like this

local onTouch = touchEventHelper:getDefaultTouchListener{ onMoved = function() print "onMoved" end, onPress = function() print "onPress" end, onRelease = function() print "onRelease" end } parentGroup:addEventListener("touch", onTouch) 

This works perfectly!

but I am curious if this is LUA correct, if I don’t create memory leaking or make stuff slower. As I read passing anonymous functions could slow down code a bit? (or did I solve this by localizing the parameters?)

the “problem” (if it is one) with anonymous function as you’re using is that they’re created at run-time (as opposed to at parse-time with static functions).  sometimes that behavior is desired and useful, other times it’s unnecessary - it’s not necessarily bad but should be used judiciously.

the bigger “problem” (of the same sort) is that you’re creating unique instances of your “touchEvent” function for each such object, and that shouldn’t be necessary.  (i also see a curious reference to an undeclared “inputHelper” in there)  so if you had 12 such “buttons” then you’d have 12 copies of that touch function, along with 12*3 anonymous _onWhatever functions, stored as 3 variables inside each of 12 closure environments.   so yes, that would start getting a bit heavy in terms of run-time function definition.

as written, since you’re storing the references to “_onMoved” (et al) in the closure environment,  you do actually need all those unique closure instances of “touchEvent”.  but if you instead stored “_onMoved” (et al) directly on the object (fe, you’d have to pass the object in, then object._onMoved=_options.onMoved et al, and reference them later as event.target._onMoved et al) then you’d avoid needing unique closure environments and could have a single instance of your handler.

hth

Well if I had 12 copies of this objects my code would look more like below instead of calling the getDefaultTouchListener() function:

local onTouch = touchEventHelper:getDefaultTouchListener{ onMoved = function() print "onMoved" end, onPress = function() print "onPress" end, onRelease = function() print "onRelease" end } object1:addEventListener("touch", onTouch) object2:addEventListener("touch", onTouch) object3:addEventListener("touch", onTouch) object4:addEventListener("touch", onTouch) object5:addEventListener("touch", onTouch) object6:addEventListener("touch", onTouch) ...

Doesn’t this create only 1 instance of my “touchEvent” function? Together with the 3 _onWhatevers functions?

And btw for clarification, the inputHelper reference is a separate module:

local inputHelper = require("classes.modules.inputHelper")

yes, if that’s how you’d be calling it

you’ll get one “complete set” of everything each time you call getDefaultTouchListener

so if your 12 buttons can share the same listener then just one set needed - exactly as you show

(but if only one is needed then there are simpler solutions, which is why i assumed you’d want each button to have its own)

Thank you for the clarification!

The reason for creating this solution is because I have like 20 different screens/popups, each with like 1-4 different kind of touchEvents.

If there are a lot of buttons in the same screen, I handle them by using event.target.id, so only 1 touchEvent is needed.

Using this touchEventHelper is helping me from repeating the same code again and again.

I regularly like to create a touch event listener for moving display or physics objects around. This comes with quite a few things to think about, so my utility function is this one and is added with a normal object:addEventListener( “touch”, touch )…

local function touch(e) e.target = e.target or display.currentStage if (e.phase == "began") then e.target.hasFocus = true display.currentStage:setFocus( e.target ) if (e.target.isBodyActive and e.target.bodyType ~= "static") then e.target.touchjoint = physics.newJoint( "touch", e.target, e.x, e.y ) e.target.touchjoint.dampingRatio = 1 e.target.touchjoint.frequency = 10 e.target.angularDamping = 1 else e.target.prev = e end if (e.target.began) then e.target:began(e) end return true elseif (e.target.hasFocus) then if (e.target.isBodyActive and e.target.bodyType ~= "static") then e.target.touchjoint:setTarget( e.x, e.y ) else local x, y = e.x-e.target.prev.x, e.y-e.target.prev.y e.target.x, e.target.y = e.target.x+x, e.target.y+y e.target.prev = e end if (e.phase == "moved") then if (e.target.moved) then e.target:moved(e) end else e.target.hasFocus = nil display.currentStage:setFocus( nil ) if (e.target.isBodyActive and e.target.bodyType ~= "static") then e.target.touchjoint:removeSelf() e.target.touchjoint = nil else e.target.prev = nil end if (e.target.ended) then e.target:ended(e) end end return true end return false end

horacybury’s is one of the “simpler solutions” i alluded to - just a single static method somewhere that all buttons would share.  because if all buttons share the same listener, and your only real goal is to modularize the code to facilitate sharing it across other modules without globals, then something like the following would do it (presented in “skeleton” form only, just to illustrate)

-- DefaultTouchHandler.lua local function DefaultTouchHandler(event) -- put in whatever default behavior you want -- this just dumps the event to the console for k,v in pairs(event) do print(k,v) end end return DefaultTouchHandler -- SomeOtherModule.lua local DefaultTouchHandler = require("DefaultTouchHandler") local object1 = display.newRect( 50, 50, 50, 50) object1:addEventListener("touch", DefaultTouchHandler) local object2 = display.newRect(150, 50, 50, 50) object2:addEventListener("touch", DefaultTouchHandler) local object3 = display.newRect(250, 50, 50, 50) object3:addEventListener("touch", DefaultTouchHandler) -- YetAnotherModule.lua local DefaultTouchHandler = require("DefaultTouchHandler") local object4 = display.newRect( 50,150, 50, 50) object4:addEventListener("touch", DefaultTouchHandler) local object5 = display.newRect(150,150, 50, 50) object5:addEventListener("touch", DefaultTouchHandler) local object6 = display.newRect(250,150, 50, 50) object6:addEventListener("touch", DefaultTouchHandler)

This is indeed a very good solution and probably the most memory efficient one.

I chose a more customizable solution though, as I need the listeners to react different according to the parameters I use to create it. For example I need some some listeners to not return because they need to pass the touch, some listeners dont have to take focus.

My current solution: (the event.simulateTouch is used by the gamecontroller/gamepad/joystick)

-- touchEventHelper.lua -- local t = {} local widget = require("widget") local inputHelper = require("classes.modules.inputHelper") function t:getDefaultTouchListener(\_options) local \_onPress = \_options.onPress local \_onMoved = \_options.onMoved local \_onRelease = \_options.onRelease local \_shouldEndWithinBounds = \_options.shouldEndWithinBounds local \_shouldObtainFocus = \_options.shouldObtainFocus local \_shouldReturnTrue = \_options.shouldReturnTrue local touchEvent = function(event) if inputHelper:isTouchesBlocked() then return true end if event.phase == "began" then if \_shouldObtainFocus and event.target then display.getCurrentStage():setFocus(event.target, event.id) event.target.isFocus = true end if \_onPress then \_onPress(event) end elseif (not \_shouldObtainFocus or event.target.isFocus) or event.simulateTouch then if event.phase == "moved" then if \_onMoved then \_onMoved(event) end elseif event.phase == "ended" or event.phase == "cancelled" then if \_shouldObtainFocus then display.getCurrentStage():setFocus(nil, event.id) event.target.isFocus = nil end if \_onRelease and (event.simulateTouch or not \_shouldEndWithinBounds or widget.\_isWithinBounds(event.target, event)) then \_onRelease(event) end end end if \_shouldReturnTrue then return true end end return touchEvent end return t -- worldInterface.lua -- local touchEventHelper = require("touchEventHelper") local fullScreenRect = display.newRect(PARAMS FOR FULL SCREEN RECT) fullScreenRect.touch = inputEventHelper:getDefaultTouchListener { shouldObtainFocus = false, shouldEndWithinBounds = false, shouldReturnTrue = false, onBegan = function(event) --Spawn analog stick end, onMoved = function(event) --Move analog stick end, onRelease = function(event) --Remove analog stick end } fullScreenRect:addEventListener("touch", fullScreenRect.touch) local buttonHandler = inputEventHelper:getDefaultTouchListener { shouldObtainFocus = true, shouldEndWithinBounds = true, shouldReturnTrue = true, onRelease = function(event) if event.target.id == button1.id then elseif event.target.id == button2.id then elseif event.target.id == button3.id then end end } local button1 = display.newRect(PARAMS FOR BUTTON) button1:addEventListener("touch", buttonHandler) local button2 = display.newRect(PARAMS FOR BUTTON) button2:addEventListener("touch", buttonHandler) local button3 = display.newRect(PARAMS FOR BUTTON) button3:addEventListener("touch", buttonHandler)

your solution is fine and elegant, and i’m not actually suggesting changing it, because you do have some “hybrid” usage, but i’ll offer one further thought - just for fun to consider.  here’s how you might amend previous example to keep the listener itself static, while allowing your “response” functions (and all other options) to remain dynamic:

-- GenericTouchHandler.lua local GenericTouchHandler = {} local function privateTouchHandler(self,event) -- here are your original options local options = self.options -- or, since self==event.target... -- local options = event.target.options -- a mock up a demo response: if (event.phase=="began") then if (options.onPress) then options.onPress() end end end function GenericTouchHandler:addListener(object,options) object.options = options object.touch = privateTouchHandler object:addEventListener("touch",object) end function GenericTouchHandler:removeListener(object) object:removeEventListener("touch",object) object.touch = nil object.options = nil end return GenericTouchHandler -- SomeOtherModule.lua local GenericTouchHandler = require("GenericTouchHandler") local object1 = display.newRect( 50, 50, 50, 50) local object2 = display.newRect(150, 50, 50, 50) GenericTouchHandler:addListener(object1, { onPress = function() print("object1's onPress") end, onMove = function() print("object1's onMove") end, anotherProperty = 1234, yetAnotherProperty = true }) GenericTouchHandler:addListener(object2, { onPress = function() print("object2's onPress") end, onMove = function() print("object2's onMove") end, anotherProperty = 2345, yetAnotherProperty = false })

Also a nice and dynamic approach! I also considered giving the objects itself the “dynamic options”, but in my situation it is preferable to have the “dynamic options” in the touchEventObject itself.

1 of the reasons is that in the GenericTouchHandler approach I need to pass the parameters to every object that has a touchEventListener, in the touchEventHelper approach I only need to pass it to the touchEventObject and can re-use the touchEventObject for multiple objects

2nd reason is that im also using the touchEventObject to simulate touches with my gamepad.

onTypeIconClick({ phase = "ended", simulateTouch = true, target = cursorTarget })

Altogether, I think this topic is a good source to find inspiration to create a touch event helper for different situations :slight_smile:

fwiw re 1) - the option table passed could be “shared” by all objects sharing same values, (that is, the options only need be unique when they ARE unique, much as per your original code), fe:

local sharedOptions = { onMove=function() end, etc... } GenericTouchHandler:addListener( object1, sharedOptions ) GenericTouchHandler:addListener( object2, sharedOptions ) GenericTouchHandler:addListener( object3, sharedOptions ) local justObject4Options = { onMove=function() end, etc...} GenericTouchHandler:addListener( object4, justObject4Options )  

Going to chime in: My quite-simple-but-does-the-job approach is that I have a single library, “touch.lua”, whose only function is makeObjectTouchSensitive(). It’s a robust touch listener with inside bounds, outside bounds, multitouch, etc., which calls the callbacks according to each event. So in the end, my code looks like this:

touch.makeObjectTouchSensitive(obj) obj.touchCallbacks.endedOutsideBounds = function(event) print("Cancelled.") end obj.touchCallbacks.endedInsideBounds = function(event) print("Do something!") end obj:setTouchEnabled(true) -- Allows/disallows touch, calling the cancel events if need be

Just something else to think about. It’s simple but it works really well.

  • Caleb

the “problem” (if it is one) with anonymous function as you’re using is that they’re created at run-time (as opposed to at parse-time with static functions).  sometimes that behavior is desired and useful, other times it’s unnecessary - it’s not necessarily bad but should be used judiciously.

the bigger “problem” (of the same sort) is that you’re creating unique instances of your “touchEvent” function for each such object, and that shouldn’t be necessary.  (i also see a curious reference to an undeclared “inputHelper” in there)  so if you had 12 such “buttons” then you’d have 12 copies of that touch function, along with 12*3 anonymous _onWhatever functions, stored as 3 variables inside each of 12 closure environments.   so yes, that would start getting a bit heavy in terms of run-time function definition.

as written, since you’re storing the references to “_onMoved” (et al) in the closure environment,  you do actually need all those unique closure instances of “touchEvent”.  but if you instead stored “_onMoved” (et al) directly on the object (fe, you’d have to pass the object in, then object._onMoved=_options.onMoved et al, and reference them later as event.target._onMoved et al) then you’d avoid needing unique closure environments and could have a single instance of your handler.

hth

Well if I had 12 copies of this objects my code would look more like below instead of calling the getDefaultTouchListener() function:

local onTouch = touchEventHelper:getDefaultTouchListener{ onMoved = function() print "onMoved" end, onPress = function() print "onPress" end, onRelease = function() print "onRelease" end } object1:addEventListener("touch", onTouch) object2:addEventListener("touch", onTouch) object3:addEventListener("touch", onTouch) object4:addEventListener("touch", onTouch) object5:addEventListener("touch", onTouch) object6:addEventListener("touch", onTouch) ...

Doesn’t this create only 1 instance of my “touchEvent” function? Together with the 3 _onWhatevers functions?

And btw for clarification, the inputHelper reference is a separate module:

local inputHelper = require("classes.modules.inputHelper")

yes, if that’s how you’d be calling it

you’ll get one “complete set” of everything each time you call getDefaultTouchListener

so if your 12 buttons can share the same listener then just one set needed - exactly as you show

(but if only one is needed then there are simpler solutions, which is why i assumed you’d want each button to have its own)

Thank you for the clarification!

The reason for creating this solution is because I have like 20 different screens/popups, each with like 1-4 different kind of touchEvents.

If there are a lot of buttons in the same screen, I handle them by using event.target.id, so only 1 touchEvent is needed.

Using this touchEventHelper is helping me from repeating the same code again and again.

I regularly like to create a touch event listener for moving display or physics objects around. This comes with quite a few things to think about, so my utility function is this one and is added with a normal object:addEventListener( “touch”, touch )…

local function touch(e) e.target = e.target or display.currentStage if (e.phase == "began") then e.target.hasFocus = true display.currentStage:setFocus( e.target ) if (e.target.isBodyActive and e.target.bodyType ~= "static") then e.target.touchjoint = physics.newJoint( "touch", e.target, e.x, e.y ) e.target.touchjoint.dampingRatio = 1 e.target.touchjoint.frequency = 10 e.target.angularDamping = 1 else e.target.prev = e end if (e.target.began) then e.target:began(e) end return true elseif (e.target.hasFocus) then if (e.target.isBodyActive and e.target.bodyType ~= "static") then e.target.touchjoint:setTarget( e.x, e.y ) else local x, y = e.x-e.target.prev.x, e.y-e.target.prev.y e.target.x, e.target.y = e.target.x+x, e.target.y+y e.target.prev = e end if (e.phase == "moved") then if (e.target.moved) then e.target:moved(e) end else e.target.hasFocus = nil display.currentStage:setFocus( nil ) if (e.target.isBodyActive and e.target.bodyType ~= "static") then e.target.touchjoint:removeSelf() e.target.touchjoint = nil else e.target.prev = nil end if (e.target.ended) then e.target:ended(e) end end return true end return false end

horacybury’s is one of the “simpler solutions” i alluded to - just a single static method somewhere that all buttons would share.  because if all buttons share the same listener, and your only real goal is to modularize the code to facilitate sharing it across other modules without globals, then something like the following would do it (presented in “skeleton” form only, just to illustrate)

-- DefaultTouchHandler.lua local function DefaultTouchHandler(event) -- put in whatever default behavior you want -- this just dumps the event to the console for k,v in pairs(event) do print(k,v) end end return DefaultTouchHandler -- SomeOtherModule.lua local DefaultTouchHandler = require("DefaultTouchHandler") local object1 = display.newRect( 50, 50, 50, 50) object1:addEventListener("touch", DefaultTouchHandler) local object2 = display.newRect(150, 50, 50, 50) object2:addEventListener("touch", DefaultTouchHandler) local object3 = display.newRect(250, 50, 50, 50) object3:addEventListener("touch", DefaultTouchHandler) -- YetAnotherModule.lua local DefaultTouchHandler = require("DefaultTouchHandler") local object4 = display.newRect( 50,150, 50, 50) object4:addEventListener("touch", DefaultTouchHandler) local object5 = display.newRect(150,150, 50, 50) object5:addEventListener("touch", DefaultTouchHandler) local object6 = display.newRect(250,150, 50, 50) object6:addEventListener("touch", DefaultTouchHandler)

This is indeed a very good solution and probably the most memory efficient one.

I chose a more customizable solution though, as I need the listeners to react different according to the parameters I use to create it. For example I need some some listeners to not return because they need to pass the touch, some listeners dont have to take focus.

My current solution: (the event.simulateTouch is used by the gamecontroller/gamepad/joystick)

-- touchEventHelper.lua -- local t = {} local widget = require("widget") local inputHelper = require("classes.modules.inputHelper") function t:getDefaultTouchListener(\_options) local \_onPress = \_options.onPress local \_onMoved = \_options.onMoved local \_onRelease = \_options.onRelease local \_shouldEndWithinBounds = \_options.shouldEndWithinBounds local \_shouldObtainFocus = \_options.shouldObtainFocus local \_shouldReturnTrue = \_options.shouldReturnTrue local touchEvent = function(event) if inputHelper:isTouchesBlocked() then return true end if event.phase == "began" then if \_shouldObtainFocus and event.target then display.getCurrentStage():setFocus(event.target, event.id) event.target.isFocus = true end if \_onPress then \_onPress(event) end elseif (not \_shouldObtainFocus or event.target.isFocus) or event.simulateTouch then if event.phase == "moved" then if \_onMoved then \_onMoved(event) end elseif event.phase == "ended" or event.phase == "cancelled" then if \_shouldObtainFocus then display.getCurrentStage():setFocus(nil, event.id) event.target.isFocus = nil end if \_onRelease and (event.simulateTouch or not \_shouldEndWithinBounds or widget.\_isWithinBounds(event.target, event)) then \_onRelease(event) end end end if \_shouldReturnTrue then return true end end return touchEvent end return t -- worldInterface.lua -- local touchEventHelper = require("touchEventHelper") local fullScreenRect = display.newRect(PARAMS FOR FULL SCREEN RECT) fullScreenRect.touch = inputEventHelper:getDefaultTouchListener { shouldObtainFocus = false, shouldEndWithinBounds = false, shouldReturnTrue = false, onBegan = function(event) --Spawn analog stick end, onMoved = function(event) --Move analog stick end, onRelease = function(event) --Remove analog stick end } fullScreenRect:addEventListener("touch", fullScreenRect.touch) local buttonHandler = inputEventHelper:getDefaultTouchListener { shouldObtainFocus = true, shouldEndWithinBounds = true, shouldReturnTrue = true, onRelease = function(event) if event.target.id == button1.id then elseif event.target.id == button2.id then elseif event.target.id == button3.id then end end } local button1 = display.newRect(PARAMS FOR BUTTON) button1:addEventListener("touch", buttonHandler) local button2 = display.newRect(PARAMS FOR BUTTON) button2:addEventListener("touch", buttonHandler) local button3 = display.newRect(PARAMS FOR BUTTON) button3:addEventListener("touch", buttonHandler)

your solution is fine and elegant, and i’m not actually suggesting changing it, because you do have some “hybrid” usage, but i’ll offer one further thought - just for fun to consider.  here’s how you might amend previous example to keep the listener itself static, while allowing your “response” functions (and all other options) to remain dynamic:

-- GenericTouchHandler.lua local GenericTouchHandler = {} local function privateTouchHandler(self,event) -- here are your original options local options = self.options -- or, since self==event.target... -- local options = event.target.options -- a mock up a demo response: if (event.phase=="began") then if (options.onPress) then options.onPress() end end end function GenericTouchHandler:addListener(object,options) object.options = options object.touch = privateTouchHandler object:addEventListener("touch",object) end function GenericTouchHandler:removeListener(object) object:removeEventListener("touch",object) object.touch = nil object.options = nil end return GenericTouchHandler -- SomeOtherModule.lua local GenericTouchHandler = require("GenericTouchHandler") local object1 = display.newRect( 50, 50, 50, 50) local object2 = display.newRect(150, 50, 50, 50) GenericTouchHandler:addListener(object1, { onPress = function() print("object1's onPress") end, onMove = function() print("object1's onMove") end, anotherProperty = 1234, yetAnotherProperty = true }) GenericTouchHandler:addListener(object2, { onPress = function() print("object2's onPress") end, onMove = function() print("object2's onMove") end, anotherProperty = 2345, yetAnotherProperty = false })