Under what circumstances will a display group that is inside another display group receive touch notifications before it's parent?

The topic says it all.

I am seeing this.  In my game there is a board made of tiles.  Each tile is a display group.  All tiles are in a display group called “allLocG” and allLocG is in a display group called CurrentMap.

CurrentMap has a touch listener called “moveMap” that moves the map around since it extends off the screen.

Each tile has a touch listener so that you can interact with them.

The map has areas where there are no tiles.  If I attempt to move the map where there are no tiles, then the map moves as expected, if I attempt to move the map in an area with tiles it fires the touchtile function.

by using self.parent.parent I have confirmed that the tile is actually inside current map.

I added “do return false end” to the top of the touch tile function and the map will move.

So, to summarize, the tiles are behaving as if they are on top of the display group “currentMap” however in the same function when I print the table that is .parent.parent they appear to be inside the currentMap.

I’m just looking for ideas here.  Thanks.

According to the event guide, it says: “Hit events propagate until they are handled. This means that if you have multiple objects overlaying each other in the display hierarchy, and a hit event listener has been applied to each, the hit event will propagate through all of these objects. However, you can stop propagation to the next underlying object by telling Corona that the event has been handled. This is as simple as returning true from the event listener — this stops the propagation cycle and prevents any underlying objects from responding to the hit event.”

So, no matter how you add organize the groups, the object visually frontmost will get the tap / touches it seems.

http://docs.coronalabs.com/guide/events/detectEvents/index.html

Yeah, I know that is how it is supposed to work.

I have confirmed that the tile display group is inside the map display group.  I have also confirmed that the tile eventListener is firing before the event listener of the map when you click on a location with a tile.

According to the documentation that is not how it is supposed to work.  In my case, if I just comment out the event listener on the tile, the map listener works as expected.  So, the engine is behaving as if the tile is on top of the map instead of inside it.

Here is an example of what might cause what I am seeing:

There is a listener on the ‘outer’ display group that points points to an inner group:

innerGroup.touch = doSomething

outerGroup:addEventListener(“touch”, innerGroup)

That is not happening in this case but that is the kind of example I mean.  I am just kind of hitting the wall here and was hoping someone might have some suggestions about what might cause it.

I’m also wondering if I used a reserved variable as a local variable and that broke it.  The super frustrating thing is that I wasn’t working on this part of the code when it happened.  I just made a build and I noticed it, I reloaded my previous build and it was happening then too but I hadn’t noticed it.  I do a build a week so I have 2 weeks of code to filter through… not fun.

If you manage to solve this please keep us updated. I have a similar problem.

I guess I don’t understand.

There are no bitmaps itself for CurrentMap (what you sometimes call “map”)?

If the the group CurrentMap has no bitmap, but just tile groups under AllLocG, then if a tile is touched, the first thing to get the hit is the tile, the top most visible object.

If a transparent area of group CurrentMap is touched, then no tile gets the event, and CurrentMap gets it. (unless you use a mask, the entire area of the object/group gets the event).

Again, the topmost visible object gets the event first. In the case where there is no object in the group at that location (it is effectively transparent for the group), then the group itself seems to pick up the event (in its “transparent” area).

Another way to put it is that groups themselves (sans display objects - the transparent areas of groups) are last in the event dispatch. If an item in a group (or subgroup) gets hit, it (or its direct parent group if that is where you put the listener) will receive the event first (before bouncing out through the groups). Groups appear to be last in the touch/tap event chain (a group itself has no visible area without objects in it, after all).

Are you trying to get input above the tiles, no matter what in some circumstances? Or just to always get it first?

Instead of adding a touch listener to a group, which is behind everything it contains (last in event chain), you could insert an invisible object into the group, make it frontmost, and put a hit detect on it (instead of a hit detect on the group itself, which is last in the event chain). This is a function I use to absorb taps. It inserts itself into a group, then eats the clicks. When I dont want it, I do a group.touchMask:remove(), and it’s gone. (the utils.throwAwayEvent() is just a function I have that does a return true, so the click is eaten, but instead it could point to your CurrentMap group - and it would be called before tile hits).

[lua]

function addTouchmask(this)

    print(" – adding touchmask")
    local touchMask = display.newRect(0,0,644,964)  – Cover the whole screen… One screens worth…  with a couple pixels to spare for good luck (in multiples of 4 of course)
    touchMask:setFillColor(144,144,144,128)        – Doesn’t really matter me thinks, isVisible will be false
    touchMask.x = 320         – Centered for my virtual screen layout / coords (640x960)
    touchMask.y = 480
    touchMask.isVisible = false             – don’t want it to mess up my prety screen (resize for a group to group.width, group.height)
    touchMask.isHitTestable = true      – This lets it accept touches even though isVisible == false
    this:insert(touchMask)                   – insert it into the group. For now, it is in front of other objects
    this.touchMask = touchMask          – Save off a pointer in the group so the caller can access this touchMask if need be
    
    this.touchMask:addEventListener( “touch” , utils.throwAwayEvent )     – In my case, I use touch masks to throw out user taps while app is busy
    this.touchMask:addEventListener( “tap” , utils.throwAwayEvent )     – Other use cases could be to move a map, ot other things

end

[/lua]

Ok, this point about the group being behind the other objects is news to me. I had assumed the display group had first chance to respond to a touch event. I had thought the other objects were inside the display group like… Well like a table in Lua where the display group is the root table and the other objects are inside the table. I put a isHitTestable= true flag on the group because I was trying to account for it being invisible.

Anyway, I can definitely adapt now that I understand the model. I think this is a key piece of information about display groups and I recommend that it should be added to the documentation.

Thank you very much for your help.

I agree, it’s not clear in the docs how groups relate in the input propagation hierarchy at al. To deal with it, these days I always use an invisible rectangle (like the touchMask thing above), and either push it toFront or toBack of the group, depending on where I want to intercept the hit events.

Note: I don’t actually just :remove() it as above actually, the listeners need to be dropped too, so if anyone wants the other half of the touchmask thing, which removes it when you’re done, here she is:

[lua]

function removeTouchmask(this)

    if( this ~= nil ) then
        if( this.touchMask ~= nil ) then
            – Remove the touchMask touch listener…
            this.touchMask:removeEventListener( “touch” , utils.throwAwayEvent ) 
            this.touchMask:removeEventListener( “tap” , utils.throwAwayEvent ) 
            
            print(" – removing touchmask")     – Now remove the rectangle
            this.touchMask:removeSelf()
            this.touchMask = nil
        end
    end

end

[/lua]

According to the event guide, it says: “Hit events propagate until they are handled. This means that if you have multiple objects overlaying each other in the display hierarchy, and a hit event listener has been applied to each, the hit event will propagate through all of these objects. However, you can stop propagation to the next underlying object by telling Corona that the event has been handled. This is as simple as returning true from the event listener — this stops the propagation cycle and prevents any underlying objects from responding to the hit event.”

So, no matter how you add organize the groups, the object visually frontmost will get the tap / touches it seems.

http://docs.coronalabs.com/guide/events/detectEvents/index.html

Yeah, I know that is how it is supposed to work.

I have confirmed that the tile display group is inside the map display group.  I have also confirmed that the tile eventListener is firing before the event listener of the map when you click on a location with a tile.

According to the documentation that is not how it is supposed to work.  In my case, if I just comment out the event listener on the tile, the map listener works as expected.  So, the engine is behaving as if the tile is on top of the map instead of inside it.

Here is an example of what might cause what I am seeing:

There is a listener on the ‘outer’ display group that points points to an inner group:

innerGroup.touch = doSomething

outerGroup:addEventListener(“touch”, innerGroup)

That is not happening in this case but that is the kind of example I mean.  I am just kind of hitting the wall here and was hoping someone might have some suggestions about what might cause it.

I’m also wondering if I used a reserved variable as a local variable and that broke it.  The super frustrating thing is that I wasn’t working on this part of the code when it happened.  I just made a build and I noticed it, I reloaded my previous build and it was happening then too but I hadn’t noticed it.  I do a build a week so I have 2 weeks of code to filter through… not fun.

If you manage to solve this please keep us updated. I have a similar problem.

I guess I don’t understand.

There are no bitmaps itself for CurrentMap (what you sometimes call “map”)?

If the the group CurrentMap has no bitmap, but just tile groups under AllLocG, then if a tile is touched, the first thing to get the hit is the tile, the top most visible object.

If a transparent area of group CurrentMap is touched, then no tile gets the event, and CurrentMap gets it. (unless you use a mask, the entire area of the object/group gets the event).

Again, the topmost visible object gets the event first. In the case where there is no object in the group at that location (it is effectively transparent for the group), then the group itself seems to pick up the event (in its “transparent” area).

Another way to put it is that groups themselves (sans display objects - the transparent areas of groups) are last in the event dispatch. If an item in a group (or subgroup) gets hit, it (or its direct parent group if that is where you put the listener) will receive the event first (before bouncing out through the groups). Groups appear to be last in the touch/tap event chain (a group itself has no visible area without objects in it, after all).

Are you trying to get input above the tiles, no matter what in some circumstances? Or just to always get it first?

Instead of adding a touch listener to a group, which is behind everything it contains (last in event chain), you could insert an invisible object into the group, make it frontmost, and put a hit detect on it (instead of a hit detect on the group itself, which is last in the event chain). This is a function I use to absorb taps. It inserts itself into a group, then eats the clicks. When I dont want it, I do a group.touchMask:remove(), and it’s gone. (the utils.throwAwayEvent() is just a function I have that does a return true, so the click is eaten, but instead it could point to your CurrentMap group - and it would be called before tile hits).

[lua]

function addTouchmask(this)

    print(" – adding touchmask")
    local touchMask = display.newRect(0,0,644,964)  – Cover the whole screen… One screens worth…  with a couple pixels to spare for good luck (in multiples of 4 of course)
    touchMask:setFillColor(144,144,144,128)        – Doesn’t really matter me thinks, isVisible will be false
    touchMask.x = 320         – Centered for my virtual screen layout / coords (640x960)
    touchMask.y = 480
    touchMask.isVisible = false             – don’t want it to mess up my prety screen (resize for a group to group.width, group.height)
    touchMask.isHitTestable = true      – This lets it accept touches even though isVisible == false
    this:insert(touchMask)                   – insert it into the group. For now, it is in front of other objects
    this.touchMask = touchMask          – Save off a pointer in the group so the caller can access this touchMask if need be
    
    this.touchMask:addEventListener( “touch” , utils.throwAwayEvent )     – In my case, I use touch masks to throw out user taps while app is busy
    this.touchMask:addEventListener( “tap” , utils.throwAwayEvent )     – Other use cases could be to move a map, ot other things

end

[/lua]

Ok, this point about the group being behind the other objects is news to me. I had assumed the display group had first chance to respond to a touch event. I had thought the other objects were inside the display group like… Well like a table in Lua where the display group is the root table and the other objects are inside the table. I put a isHitTestable= true flag on the group because I was trying to account for it being invisible.

Anyway, I can definitely adapt now that I understand the model. I think this is a key piece of information about display groups and I recommend that it should be added to the documentation.

Thank you very much for your help.

I agree, it’s not clear in the docs how groups relate in the input propagation hierarchy at al. To deal with it, these days I always use an invisible rectangle (like the touchMask thing above), and either push it toFront or toBack of the group, depending on where I want to intercept the hit events.

Note: I don’t actually just :remove() it as above actually, the listeners need to be dropped too, so if anyone wants the other half of the touchmask thing, which removes it when you’re done, here she is:

[lua]

function removeTouchmask(this)

    if( this ~= nil ) then
        if( this.touchMask ~= nil ) then
            – Remove the touchMask touch listener…
            this.touchMask:removeEventListener( “touch” , utils.throwAwayEvent ) 
            this.touchMask:removeEventListener( “tap” , utils.throwAwayEvent ) 
            
            print(" – removing touchmask")     – Now remove the rectangle
            this.touchMask:removeSelf()
            this.touchMask = nil
        end
    end

end

[/lua]