how does setFocus work (or not work)?

(Game Edition, iPad target)

Something doesn’t make any sense to me.

I have 2 boxes (1 red, 1 blue) on screen. I set up a touch handler for these boxes.

I also have a screen touch handler.

When I touch one of the boxes, I want it to capture all future events until event.phase = “ended”. I want no other handlers to get called.

I followed the docs and:

  1. at box handler event.phase = “began”, set the focus to the touched box.
  2. at box handler event.phase = “ended”, set the focus to nil.
  3. box handler always return true.

Yet, the screen touch handler always gets invoked.

Here is my code:

local screenTouched = function( event )  
 print ("screenTouched")  
 return true  
end  
Runtime:addEventListener( "touch", screenTouched )  
  
local boxTouched = function( event )  
 print ("drag event "..event.phase.." for "..event.target.name)  
 if event.phase == "began" then  
 display.getCurrentStage():setFocus(event.target)  
 elseif event.phase == "ended" then  
 display.getCurrentStage():setFocus(nil)  
 end  
 return true  
end  
  
local box1 = display.newRect(display.contentWidth/2 - 60, display.contentHeight/2, 100, 100)  
local box2 = display.newRect(display.contentWidth/2 + 60, display.contentHeight/2, 100, 100)  
box1:setFillColor(100, 200, 250)  
box2:setFillColor(250, 100, 100)  
box1:addEventListener( "touch", boxTouched)  
box2:addEventListener( "touch", boxTouched)  
box1.name = "blue box"  
box2.name = "red box"  

If you run this, you’ll see that if I touch and drag box1, the handler for box2 never gets called. That’s good.

But, the screen touch handler always gets called, regardless of where the current touch is. Not good.

Thanks for any advice.
Alex
[import]uid: 3473 topic_id: 2314 reply_id: 302314[/import]

Worked around this problem by removing screen touch listener at box touch began phase:

local screenTouched = function( event )  
 print ("screenTouched")  
 return true  
end  
Runtime:addEventListener( "touch", screenTouched )  
  
local drag = function( event )  
 print ("drag event "..event.phase.." for "..event.target.name)  
 if event.phase == "began" then  
 Runtime:removeEventListener( "touch", screenTouched )  
 display.getCurrentStage():setFocus(event.target)  
 elseif event.phase == "ended" then  
 display.getCurrentStage():setFocus(nil)  
 Runtime:addEventListener( "touch", screenTouched )  
 end  
 return true  
end  
  
local box1 = display.newRect(display.contentWidth/2 - 60, display.contentHeight/2, 100, 100)  
local box2 = display.newRect(display.contentWidth/2 + 60, display.contentHeight/2, 100, 100)  
box1:setFillColor(100, 200, 250)  
box2:setFillColor(250, 100, 100)  
box1:addEventListener( "touch", drag)  
box2:addEventListener( "touch", drag)  
box1.name = "blue box"  
box2.name = "red box"  

But I still think that Corona is handling this wrong, or not documenting it clearly enough.

Alex
[import]uid: 3473 topic_id: 2314 reply_id: 7057[/import]

Funny, I was working on some Touch Listener test code today and found the example same problem. I think it’s a bug. I also found that if you comment out your setFocus statement in the Began phase, it works as expected. The only problem is you lose object focus once you drag off the object.

My test code had three handlers: one for an object that had setFocus; one for an object that didn’t setFocus; and the third was the Runtime listener (like yours). This only seems to be a problem when you mix object and global touch listeners.

I’m filing this as a bug.

Thanks,
-Tom [import]uid: 7559 topic_id: 2314 reply_id: 7066[/import]

Tom

I’m glad you’ll be filing a bug report for that. This problem, workable around though it may be, has complicated my touch handling code so much – object and screen touch handlers now have to each test for the special case of a touch originating in a different handler and all that this implies. It’s a mess and makes prototyping with Corona not as straightforward as I would have hoped.

Alex
[import]uid: 3473 topic_id: 2314 reply_id: 7071[/import]

Hi Alex,

It sounds like you found the same work-around I was going to suggest. At least in the simple case you provided, the solution (below) doesn’t seem that unmanageable, although I’m sure your actual project is more complex.

Tim

[code]
local boxFocus = false

local screenTouched = function( event )
if boxFocus then return end
print (“screenTouched”)
return true
end
Runtime:addEventListener( “touch”, screenTouched )

local boxTouched = function( event )
print ("drag event “…event.phase…” for "…event.target.name)
if event.phase == “began” then
boxFocus = true
display.getCurrentStage():setFocus(event.target)
elseif event.phase == “ended” then
display.getCurrentStage():setFocus(nil)
boxFocus = false
end
return true
end

local box1 = display.newRect(display.contentWidth/2 - 60, display.contentHeight/2, 100, 100)
local box2 = display.newRect(display.contentWidth/2 + 60, display.contentHeight/2, 100, 100)
box1:setFillColor(100, 200, 250)
box2:setFillColor(250, 100, 100)
box1:addEventListener( “touch”, boxTouched)
box2:addEventListener( “touch”, boxTouched)
box1.name = “blue box”
box2.name = “red box”

[/code] [import]uid: 8196 topic_id: 2314 reply_id: 7097[/import]

(Alex, sorry didn’t read far enough up the thread to see your code work-around…not sure if mine is any better. Tim) [import]uid: 8196 topic_id: 2314 reply_id: 7098[/import]

Hi Tim

The workaround works partially and that helps. But when box dragging ends, the screen handler still gets called one time (“screenTouched”), and that complicates my handler compartmentalization attempts. As a result, screenTouched() must remain aware of the different application contexts under which it operates so that it can handle event.phase == “ended” gracefully.

With one context (drag boxes around) only, things were relatively simple. Now with 3 (selection of boxes, i.e., a drag that starts on the blank canvas; and dropping of a box on top of a “drop zone”), it’s getting messy by the minute.

Flash event propagation’s bubbles property (indicating whether or not an event bubbles back up through the display list after having reached its target) would be handy here.

Alex
[import]uid: 3473 topic_id: 2314 reply_id: 7108[/import]

Here’s a a better workaround: create a background graphic (rectangle) that fills the screen. Attach the “stage” touch listener function to that graphic instead of the actual stage. With this setup, the event model works as you would expect: the stage/background does not receive any touch events that originate within one of the boxes.

Corona actually does have a similar “bubbling” mechanism for handling touch events, from http://developer.anscamobile.com/content/events-and-listeners#Touch_Events:

Hit events propagate until they are handled. You can stop propagation to the next object (all listeners of the current object still get the event) by telling the system that the event was handled. This boils down to making a listener return true. If at least one of the listeners of the current object returns true, event propagation ends; the next object will not get the event. If the event is still unhandled at the end of this traversal, it is broadcast as a global event to the global Runtime object.

The issue (bug) in this case was that, after calling setFocus(), the global Runtime touch handler was receiving events when it should not have been. As Tom pointed out, after removing setFocus() things worked as expected. Hopefully this workaround will be sufficient for your needs.

Tim

[code]
local background = display.newRect(0,0,display.contentWidth, display.contentHeight)
background.name = “stage”
background:setFillColor(255, 255, 255)

local screenTouched = function( event )
print ("drag event “…event.phase…” for "…event.target.name)
– Uncomment the following lines for “stage” to keep touch focus, which may or may not be desired
– if event.phase == “began” then
– display.getCurrentStage():setFocus(event.target)
– elseif event.phase == “ended” then
– display.getCurrentStage():setFocus(nil)
– end
return true
end
background:addEventListener( “touch”, screenTouched )

local boxTouched = function( event )
print ("drag event “…event.phase…” for "…event.target.name)
if event.phase == “began” then
display.getCurrentStage():setFocus(event.target)
elseif event.phase == “ended” then
display.getCurrentStage():setFocus(nil)
end
return true
end

local box1 = display.newRect(display.contentWidth/2 - 60, display.contentHeight/2, 100, 100)
local box2 = display.newRect(display.contentWidth/2 + 60, display.contentHeight/2, 100, 100)
box1:setFillColor(100, 200, 250)
box2:setFillColor(250, 100, 100)
box1:addEventListener( “touch”, boxTouched)
box2:addEventListener( “touch”, boxTouched)
box1.name = “blue box”
box2.name = “red box”

[/code] [import]uid: 8196 topic_id: 2314 reply_id: 7111[/import]

Tim,

thanks for the neat workaround. It’s perfect!

Alex
[import]uid: 3473 topic_id: 2314 reply_id: 7118[/import]