isModal overlays with transitions prevent touches after they are hidden

I am seeing a new problem with build 3135 with modal overlays and overlay transition effects.

Scenario:

scene1 has an image with touch listener.  when touched, it shows overlay1 (isModal=true, effect=“crossFade”, time=500)

overlay 1 has two buttons:

  • Green button calls hideOverlay (effect=“crossFade”, time=500) and then shows overlay2 (isModal=true, effect=“crossFade”, time=500)

  • Red button calls hideOverlay (effect=“crossFade”, time=500)

overlay 2 has one Blue button, which calls hideOverlay(effect=“crossFade”, time=500) and then shows overlay1 (isModal=true, effect=“crossFade”, time=500)

The problem that is occurring is that when all the modal overlays are shown and then eventually hidden, the original parent scene is not receiving touch events.

Steps to reproduce:

  1. tap image
  • overlay1 is shown
  1. tap green button
  • overlay2 is shown
  1. tap blue button
  • overlay1 is shown
  1. tap red button
  • all overlays are hidden
  1. tap image
  • touch events do not occur

The problem appears to be a regression caused by the fix for the issue described here:

https://forums.coronalabs.com/topic/68849-composer-hideoverlay-ismodal-no-touch-blocker-during-transition/?hl=%2Boverlay+%2Btouch

The problem does not occur on build 3118, but does occur on 3119 and 3135.

Project files are below.  “world.png” image was taken from the HelloWorld sample.

main.lua

local composer = require("composer") composer.gotoScene( "scene1" )

scene1.lua

local composer = require( "composer" ) local scene = composer.newScene() -- ----------------------------------------------------------------------------------- -- Code outside of the scene event functions below will only be executed ONCE unless -- the scene is removed entirely (not recycled) via "composer.removeScene()" -- ----------------------------------------------------------------------------------- local helpText = nil function worldTouched( self, event ) if ( event.phase == "began" ) then print( "Touch event began on: " .. self.id ) -- Set touch focus display.getCurrentStage():setFocus( self ) self.isFocus = true elseif ( self.isFocus ) then if ( event.phase == "moved" ) then --print( "Moved phase of touch event detected." ) elseif ( event.phase == "ended" or event.phase == "cancelled" ) then print("Touch event ended on: " .. self.id) scene:showOverlay1() -- Reset touch focus display.getCurrentStage():setFocus( nil ) self.isFocus = nil end end return true end function scene:showOverlay1() helpText.isVisible = false composer.hideOverlay( "crossFade", 500) local options = { parent = sceneGroup, isModal = true, effect = "crossFade", time = 500 } composer.showOverlay( "overlay1", options ) end function scene:showHelpText() helpText.isVisible = true end -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- -- create() function scene:create( event ) local sceneGroup = self.view -- Code here runs when the scene is first created but has not yet appeared on screen local world = display.newImageRect(sceneGroup, "world.png", system.ResourceDirectory, 250, 250) world.x = display.contentCenterX world.y = display.contentCenterY world.id = "world" world.touch = worldTouched world:addEventListener("touch") local textOpts = { parent = sceneGroup, x = display.contentCenterX, y = 30, text = "Tap the world to show overlay 1", font = native.systemFont, fontSize = 18 } helpText = display.newText( textOpts ) end -- show() function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) scene:showOverlay1() elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen end end -- hide() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is on screen (but is about to go off screen) elseif ( phase == "did" ) then -- Code here runs immediately after the scene goes entirely off screen end end -- destroy() function scene:destroy( event ) local sceneGroup = self.view -- Code here runs prior to the removal of scene's view world:removeEventListener("touch") o1Button:removeEventListener("touch") end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene

overlay1.lua

local composer = require( "composer" ) local scene = composer.newScene() -- ----------------------------------------------------------------------------------- -- Code outside of the scene event functions below will only be executed ONCE unless -- the scene is removed entirely (not recycled) via "composer.removeScene()" -- ----------------------------------------------------------------------------------- local overlayParent = nil function ButtonTouched( self, event ) if ( event.phase == "began" ) then print( "Touch event began on: " .. self.id ) -- Set touch focus display.getCurrentStage():setFocus( self ) self.isFocus = true elseif ( self.isFocus ) then if ( event.phase == "moved" ) then --print( "Moved phase of touch event detected." ) elseif ( event.phase == "ended" or event.phase == "cancelled" ) then print( "Touch event ended on: " .. self.id ) if ( self.action ) then self.action() end -- Reset touch focus display.getCurrentStage():setFocus( nil ) self.isFocus = nil end end return true end function Button1Action() composer.hideOverlay("crossFade", 500) local options = { parent = sceneGroup, isModal = true, effect = "crossFade", time = 500 } composer.showOverlay("overlay2", options) end function Button2Action() composer.hideOverlay("crossFade", 500) overlayParent:showHelpText() end -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- -- create() function scene:create( event ) local sceneGroup = self.view -- Code here runs when the scene is first created but has not yet appeared on screen local textOpts = { parent = sceneGroup, text = "This is overlay 1", x = display.contentCenterX, y = 30, font = native.systemFont, fontSize = 18 } local o1Text = display.newText(textOpts) local o1Button = display.newCircle(sceneGroup, 30, 30, 20) o1Button:setFillColor(0, 1, 0, 1) o1Button.id = "ShowOverlay2Button" o1Button.touch = ButtonTouched o1Button.action = Button1Action o1Button:addEventListener("touch") local o2Button = display.newCircle(sceneGroup, display.contentWidth - 30, 30, 20) o2Button:setFillColor(1, 0, 0, 1) o2Button.id = "HideOverlayButton" o2Button.touch = ButtonTouched o2Button.action = Button2Action o2Button:addEventListener("touch") end -- show() function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) overlayParent = event.parent elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen end end -- hide() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is on screen (but is about to go off screen) elseif ( phase == "did" ) then -- Code here runs immediately after the scene goes entirely off screen end end -- destroy() function scene:destroy( event ) local sceneGroup = self.view -- Code here runs prior to the removal of scene's view end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene

overlay2.lua

local composer = require( "composer" ) local scene = composer.newScene() -- ----------------------------------------------------------------------------------- -- Code outside of the scene event functions below will only be executed ONCE unless -- the scene is removed entirely (not recycled) via "composer.removeScene()" -- ----------------------------------------------------------------------------------- local overlayParent = nil function ButtonTouched( self, event ) if ( event.phase == "began" ) then print( "Touch event began on: " .. self.id ) -- Set touch focus display.getCurrentStage():setFocus( self ) self.isFocus = true elseif ( self.isFocus ) then if ( event.phase == "moved" ) then --print( "Moved phase of touch event detected." ) elseif ( event.phase == "ended" or event.phase == "cancelled" ) then print( "Touch event ended on: " .. self.id ) if ( self.action ) then self.action() end -- Reset touch focus display.getCurrentStage():setFocus( nil ) self.isFocus = nil end end return true end function Button1Action() composer.hideOverlay("crossFade", 500) overlayParent:showOverlay1() end -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- -- create() function scene:create( event ) local sceneGroup = self.view -- Code here runs when the scene is first created but has not yet appeared on screen local textOpts = { parent = sceneGroup, text = "This is overlay 2", x = display.contentCenterX, y = 30, font = native.systemFont, fontSize = 18 } local o1Text = display.newText(textOpts) local o1Button = display.newCircle(sceneGroup, 30, 30, 20) o1Button:setFillColor(0, 0, 1, 1) o1Button.id = "HideOverlay2Button" o1Button.touch = ButtonTouched o1Button.action = Button1Action o1Button:addEventListener("touch") end -- show() function scene:show( event ) local sceneGroup = self.view local phase = event.phase overlayParent = event.parent if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen end end -- hide() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is on screen (but is about to go off screen) elseif ( phase == "did" ) then -- Code here runs immediately after the scene goes entirely off screen end end -- destroy() function scene:destroy( event ) local sceneGroup = self.view -- Code here runs prior to the removal of scene's view end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ----------------------------------------------------------------------------------- return scene

I submitted this bug today.  Unfortunately, it submitted twice, so there are two IDs: 10788285 and 10788290.

composer behaves badly in general whenever you ask it to do two simultaneous overlapping transitions - one or the other is going to get clobbered and the effect of which might leave something not performed (like not hiding a touch blocker on-transition-complete, or firing event in an unexpected order).  it’d be nice if it self-fixed all these potentially problematic use cases, but until then two workarounds suggest themselves:

  1. the call to composer.hideOverlay() in scene1.showOverlay1() isn’t strictly needed, so just comment it out

    function scene:showOverlay1() helpText.isVisible = false – composer.hideOverlay( “crossFade”, 500) local options = { parent = sceneGroup, isModal = true, effect = “crossFade”, time = 500 } composer.showOverlay( “overlay1”, options ) – this will implicitly hide overlay2 end

  2. if you really do want two separate crossfades, then wrap composer.showOverlay() in a timer delay until the first hide completes:

    function scene:showOverlay1() helpText.isVisible = false composer.hideOverlay( “crossFade”, 500) local options = { parent = sceneGroup, isModal = true, effect = “crossFade”, time = 500 } timer.performWithDelay(600, function() – wait “more than 500” until hide is done composer.showOverlay( “overlay1”, options ) end) end

Hello Dave,

Thanks for the response.  In fact, as I was trying to narrow down the cause of this issue, I tried commenting out all of the hideOverlay calls, but the same problem occurs even without any use of hideOverlay , so workaround #1 is not viable.

I did not try the second workaround (using a timer to prevent showOverlay and hideOverlay doing effects at the same time), because I believe it will still not work around the problem since not using hideOverlay at all doesn’t work around the problem.

For well over a year, transitions for modal overlays did not have this problem at all.  Build 3119 is the first build to exhibit this behavior, so it is definitely a regression.

Best regards,

Tony

i’d agree it’s a bug, but it’s in the domain of things that if it ever worked right then imo you were just “lucky” (iow it probably didn’t work that way by design) – simultaneous scene transitions is just asking for obscure bugs to surface, best to avoid them.

re workaround #1, it ought to work.  (post an as-is runnable zip with world.png and config.lua and build.settings then I or others here might experiment for you)

here’s the test:  take your original code and comment out only two things

1)  the initial showOverlay in scene1.show-will - since this is yet another simultaneous transition that may (?) start out your demo in a weird state already, and isn’t needed to duplicate your button-press sequence (in fact, would seem to get in the way of it, requiring first to close the initial overlay to get at the image button, right?), so anyway let’s just remove that in order to rule it out

  1. the hideOverlay() in scene1.showOverlay1()

now run and try your image-green-blue-red sequence repeatedly.  if it still experiences the bug once you’re sure you haven’t set off any simultaneous transitions anywhere, then it’s a much bigger deal.

The sample I built is easily re-created by using the HelloWorld sample and adding the main.lua, scene1.lua, overlay1.lua and overlay2.lua with the ones provided above.  Here is a zip of the project I submitted in the bug report.

I appreciate that you think this scenario shouldn’t work, but as I stated in my previous post, it HAS worked for a very long time and as I already explained above, it is now broken  even without hideOverlay , so simultaneous scene transitions are not the issue.  The only way I have found to work around the issue is to remove the transitions altogether, in both showOverlay and hideOverlay.

I don’t understand your statement about it being “lucky” that it ever worked and probably wasn’t working by design.  There is nothing in the documentation for showOverlay/hideOverlay that states using transitions is a “Gotcha.”  Please qualify your statement with some documentation.

Best regards,

Tony

there are two other places (in the overlay scenes) that need same fix to prevent simultaneous hide/show, here are just the modified files for workaround #1, search for “–DB” comment:  https://www.dropbox.com/s/z3d31vihtpzg4k9/ModalOverlayTest_Modified.zip?dl=0

>> so simultaneous scene transitions are not the issue

yes they are

TBH, I didn’t even know you could do multiple scene transitions at once. I’ve never done so, because it seems very “unnatural” that I would ever want to. Conceptually, an overlay (to me) is something that comes on screen, I do something with, then I exit/close it and go back to the parent scene. I never envision overlays as like a separate full “layer” of Composer over another instance of Composer with multiple overlays sliding in and out while the parent scene remains untouched.

Also, it has always been documented that you can only have one overlay at a time… Composer was never designed to utilize multiple overlays transitioning in and out at the same time.

Regardless, we’ll look into the issue further, because some changes were made in a recent build…

Best regards,

Brent

Yes, I was incorrect earlier when I said removing the hideOverlay transitions did not work around the problem.  It does appear to, even if I don’t comment out the initial showOverlay call in scene1’s will show event handler (because there is no transition to scene1, I assume).

That allows at least half of the effect to work, which is better, but still not desired, so I think your second workaround is probably the best fit.   Thanks for the suggestions.

Thanks, Brent.  For now, it looks like there are workarounds.  It is too bad the point about multiple transitions is missing from the showOverlay/hideOverlay documentation.  I admit I wasn’t thinking much about the internal design of the overlays when I was applying the transitions to hide and show.

I submitted this bug today.  Unfortunately, it submitted twice, so there are two IDs: 10788285 and 10788290.

composer behaves badly in general whenever you ask it to do two simultaneous overlapping transitions - one or the other is going to get clobbered and the effect of which might leave something not performed (like not hiding a touch blocker on-transition-complete, or firing event in an unexpected order).  it’d be nice if it self-fixed all these potentially problematic use cases, but until then two workarounds suggest themselves:

  1. the call to composer.hideOverlay() in scene1.showOverlay1() isn’t strictly needed, so just comment it out

    function scene:showOverlay1() helpText.isVisible = false – composer.hideOverlay( “crossFade”, 500) local options = { parent = sceneGroup, isModal = true, effect = “crossFade”, time = 500 } composer.showOverlay( “overlay1”, options ) – this will implicitly hide overlay2 end

  2. if you really do want two separate crossfades, then wrap composer.showOverlay() in a timer delay until the first hide completes:

    function scene:showOverlay1() helpText.isVisible = false composer.hideOverlay( “crossFade”, 500) local options = { parent = sceneGroup, isModal = true, effect = “crossFade”, time = 500 } timer.performWithDelay(600, function() – wait “more than 500” until hide is done composer.showOverlay( “overlay1”, options ) end) end

Hello Dave,

Thanks for the response.  In fact, as I was trying to narrow down the cause of this issue, I tried commenting out all of the hideOverlay calls, but the same problem occurs even without any use of hideOverlay , so workaround #1 is not viable.

I did not try the second workaround (using a timer to prevent showOverlay and hideOverlay doing effects at the same time), because I believe it will still not work around the problem since not using hideOverlay at all doesn’t work around the problem.

For well over a year, transitions for modal overlays did not have this problem at all.  Build 3119 is the first build to exhibit this behavior, so it is definitely a regression.

Best regards,

Tony

i’d agree it’s a bug, but it’s in the domain of things that if it ever worked right then imo you were just “lucky” (iow it probably didn’t work that way by design) – simultaneous scene transitions is just asking for obscure bugs to surface, best to avoid them.

re workaround #1, it ought to work.  (post an as-is runnable zip with world.png and config.lua and build.settings then I or others here might experiment for you)

here’s the test:  take your original code and comment out only two things

1)  the initial showOverlay in scene1.show-will - since this is yet another simultaneous transition that may (?) start out your demo in a weird state already, and isn’t needed to duplicate your button-press sequence (in fact, would seem to get in the way of it, requiring first to close the initial overlay to get at the image button, right?), so anyway let’s just remove that in order to rule it out

  1. the hideOverlay() in scene1.showOverlay1()

now run and try your image-green-blue-red sequence repeatedly.  if it still experiences the bug once you’re sure you haven’t set off any simultaneous transitions anywhere, then it’s a much bigger deal.

The sample I built is easily re-created by using the HelloWorld sample and adding the main.lua, scene1.lua, overlay1.lua and overlay2.lua with the ones provided above.  Here is a zip of the project I submitted in the bug report.

I appreciate that you think this scenario shouldn’t work, but as I stated in my previous post, it HAS worked for a very long time and as I already explained above, it is now broken  even without hideOverlay , so simultaneous scene transitions are not the issue.  The only way I have found to work around the issue is to remove the transitions altogether, in both showOverlay and hideOverlay.

I don’t understand your statement about it being “lucky” that it ever worked and probably wasn’t working by design.  There is nothing in the documentation for showOverlay/hideOverlay that states using transitions is a “Gotcha.”  Please qualify your statement with some documentation.

Best regards,

Tony

there are two other places (in the overlay scenes) that need same fix to prevent simultaneous hide/show, here are just the modified files for workaround #1, search for “–DB” comment:  https://www.dropbox.com/s/z3d31vihtpzg4k9/ModalOverlayTest_Modified.zip?dl=0

>> so simultaneous scene transitions are not the issue

yes they are

TBH, I didn’t even know you could do multiple scene transitions at once. I’ve never done so, because it seems very “unnatural” that I would ever want to. Conceptually, an overlay (to me) is something that comes on screen, I do something with, then I exit/close it and go back to the parent scene. I never envision overlays as like a separate full “layer” of Composer over another instance of Composer with multiple overlays sliding in and out while the parent scene remains untouched.

Also, it has always been documented that you can only have one overlay at a time… Composer was never designed to utilize multiple overlays transitioning in and out at the same time.

Regardless, we’ll look into the issue further, because some changes were made in a recent build…

Best regards,

Brent

Yes, I was incorrect earlier when I said removing the hideOverlay transitions did not work around the problem.  It does appear to, even if I don’t comment out the initial showOverlay call in scene1’s will show event handler (because there is no transition to scene1, I assume).

That allows at least half of the effect to work, which is better, but still not desired, so I think your second workaround is probably the best fit.   Thanks for the suggestions.

Thanks, Brent.  For now, it looks like there are workarounds.  It is too bad the point about multiple transitions is missing from the showOverlay/hideOverlay documentation.  I admit I wasn’t thinking much about the internal design of the overlays when I was applying the transitions to hide and show.