Button touch event NOT propagating through to button underneath

I have searched this forum, and all I can find related to my issue are discussions of the opposite happening - button touches propagating through to a button underneath where that behavior is not wanted.

Well, I would like the button touch to propagate through to the button underneath - but it’s not happening!

I have a button (button A) that is disabled and transparent, with no onEvent function set. When I click on it, nothing happens. Underneath it is another button (button B ), with an onEvent function, that is enabled. When I click on the portion of button B that is not hidden under button A, it works fine. But when I click on the portion of button B underneath the transparent button A, nothing happens.

I have tried to use an event function that returns false for button A, which I would expect to then pass the touch event on to the next object in the hierarchy, but this does absolutely nothing.

It seems as though this propagation happens naturally for other people (judging by the posts I found discussing how to make it stop!), so what is going on here?

We are going to need to see your code in order to assist further.

I was hoping it was pretty self explanatory, but here you are:

[lua]
local buttonA = widget.newButton(
{

  width = buttonAWidth,

  height = buttonAHeight,
  isEnabled = false,
  sheet = sheet, – a 9-slice image sheet with some transparent parts

  … 9-slice frames …

})

local function onEvent(event)

  print(“tapped”)

end

local buttonB = widget.newButton(
{
  width = buttonBWidth,
  height = buttonBHeight,
  id = buttonBId,
  label = buttonBLabel,
  labelColor = { default={0,0,0}, over={0,0,0}},
  onEvent = onEvent,
  sheet = buttonSheet, – a different sheet

… 9-slice frames …

})

sceneGroup:insert(buttonB)

sceneGroup:insert(buttonA)

[/lua]

And as I said above, I also tried including an onEvent listener for button A:

[lua]

local function onEventA(event)

 return false

end

[/lua]

… but this doesn’t seem to have done anything at all.

Hi Sarah,

To clarify, widget buttons (widget.newButton()) can not be tweaked to allow touch pass-through to underlying objects, because most users (99% I would guess) would experience UI issues/problems if the touch did pass through. So, the widget button blocks that from happening.

That being said, you can pretty easily just use the traditional method of adding a display object (image, vector rectangle, etc.) and using normal Corona touch handling that will not prevent the pass through to underlying objects.

Or, you could set up code that, under some condition related to “button A”, forcibly dispatches a pseudo-touch to “button B”. I can assist you with this code if you want to go that direction.

Best regards,

Brent

Thanks for the reply, Brent.

I’m using a button for the top layer because it is the simplest way to do a rectangle with a 9-slice image sheet. Is there a way to do a 9-slice non-button rectangle? Because that would definitely be the preferable route.

The simplest solution would be if there is a way to have buttonA simply act as though it is not a button when it is touched (pass the touch on to whatever is beneath it). Otherwise, I will have to do a calculation every time buttonA is touched to see if the touch also happened in the area of buttonB (because they only overlap slightly), which I’d rather avoid, but it sounds like that may be the next best option.

Hi Sarah,

I think the “object:dispatchEvent()” API is a better solution for you:

https://docs.coronalabs.com/api/type/EventDispatcher/dispatchEvent.html

Essentially, this allows you to dispatch a named event to some object, and one of the most useful applications is to dispatch a “pseudo touch” to an object even if the user never touched the screen or any object on it. In your case, it equates to basically:

  1. Create “buttonB” first, for Lua scope reasons.

  2. Insert “buttonB” into the display group (“sceneGroup”).

  3. Write the listener function for “buttonA”.

  4. Create “buttonA” and insert it into “sceneGroup”.

  5. Potentially set “buttonA:setEnabled( false )”. It will then be “disabled” but it should still pick up touch events.

Then, in your listener function for “buttonA” (step 3 above), add something like this:

[lua]

    buttonB:dispatchEvent( { name=“touch”, phase=“began”, target=buttonB } )

[/lua]

Brent

Yeah, I think that will work. The only caveat is that since I don’t want all touch events from buttonA to go through to buttonB (only those which are in the area of buttonA that overlaps buttonB) I will have to manually calculate whether or not the touch happened within the area of buttonB.

I definitely could have avoided this whole issue in the first place if it were possible to do a rectangle display object (non-button) with a 9-slice image sheet. There is really no way to do that?

Hi Sarah,

Well, the 9-slice concept would be pretty easy to handle in your own code, once you have the image sheet set up (which you do). It’s just a matter of laying out the corners first then spanning the sides and middle between. I think it would be around 10 lines of code and I could provide you with a starting sample if you’d like.

Otherwise, you can pretty easily detect the touch location and compare it between buttonA and buttonB.

Depending on your choice, I can help you out with a little example code.

Brent

I decided to go with the second approach - it was simpler than I expected, thanks to the handy-dandy object.contentBounds property.

If you don’t mind, though, I’d love to see an example of creating a rectangle with a 9-slice image sheet. I think it would be very helpful in the future!

Sure thing, I’m always up for a bit of code hacking!

Let’s assume you have your image sheet configured, named “imageSheet”, and your 9-slice rectangle will go inside a display group called “rectGroup”. For this example, we’ll also assume that the image frames to build it begin at the top-left corner (frame 1) and proceed across to the top-right corner (frame 3), then wrap to the middle “row” (frames 4-6) and finally the bottom “row” (frames 7-9).

[lua]

local rectCenterX = display.contentCenterX

local rectCenterY = display.contentCenterY

local rectWidth = 200

local rectHeight = 300

– Create the left portion of the rectangle

local rectTopLeft = display.newImage( rectGroup, imageSheet, 1 )

local rectMiddleLeft = display.newImage( rectGroup, imageSheet, 4 )

local rectBottomLeft = display.newImage( rectGroup, imageSheet, 7 )

– Create the right portion of the rectangle

local rectTopRight = display.newImage( rectGroup, imageSheet, 3 )

local rectMiddleRight = display.newImage( rectGroup, imageSheet, 6 )

local rectBottomRight = display.newImage( rectGroup, imageSheet, 9 )

– Create the middle portion of the rectangle

local rectTopMiddle = display.newImage( rectGroup, imageSheet, 2 )

local rectMiddle = display.newImage( rectGroup, imageSheet, 5 )

local rectBottomMiddle = display.newImage( rectGroup, imageSheet, 8 )

– Top positioning/sizing

rectTopLeft.x = rectCenterX + ( rectTopLeft.contentWidth * 0.5 )

rectTopLeft.y = rectCenterY + ( rectTopLeft.contentHeight * 0.5 )

rectTopMiddle.width = rectWidth - ( rectTopLeft.contentWidth + rectTopRight.contentWidth )

rectTopMiddle.x = rectTopLeft.x + ( rectTopLeft.contentWidth * 0.5 ) + ( rectTopMiddle.contentWidth * 0.5 )

rectTopMiddle.y = rectTopLeft.y

rectTopRight.x = rectTopMiddle.x + ( rectTopMiddle.contentWidth * 0.5 ) + ( rectTopRight.contentWidth * 0.5 )

rectTopRight.y = rectTopLeft.y

– Middle positioning/sizing

rectMiddleLeft.height = rectHeight - ( rectTopLeft.contentHeight + rectTopRight.contentHeight )

rectMiddleLeft.x = rectTopLeft.x

rectMiddleLeft.y = rectTopLeft.contentBounds.yMax + ( rectMiddleLeft.height * 0.5 )

rectMiddle.width = rectTopMiddle.width

rectMiddle.height = rectHeight - ( rectTopLeft.contentHeight + ( rectTopRight.contentHeight ) )

rectMiddle.x = rectTopMiddle.x

rectMiddle.y = rectMiddleLeft.y

rectMiddleRight.height = rectHeight - ( rectTopLeft.contentHeight + rectTopRight.contentHeight )

rectMiddleRight.x = rectTopRight.x

rectMiddleRight.y = rectMiddleLeft.y

– Bottom positioning/sizing

rectBottomLeft.x = rectTopLeft.x

rectBottomLeft.y = rectMiddle.y + ( rectMiddle.contentHeight * 0.5 ) + ( rectBottomLeft.contentHeight * 0.5 )

rectBottomMiddle.width = rectTopMiddle.width

rectBottomMiddle.x = rectTopMiddle.x

rectBottomMiddle.y = rectBottomLeft.y

rectBottomRight.x = rectTopRight.x

rectBottomRight.y = rectBottomLeft.y

[/lua]

OK, so I lied about it being about 10 lines, but it’s still not bad. :) Toss this into a function and it could potentially be used over and over throughout your code. You could even make it more elegant by passing in an image sheet reference to the function and then you’d have the ability to create different styled rectangles using different image sheets.

Hope this helps,

Brent

That’s awesome and super helpful. Thank you!!

You can, actually, tweak the widget buttons to propagate touch events.  Full example code below (as long as you have a button image file at images/button.png or change the reference to point to a button image somewhere else)

local widget = require("widget") local options = { x = display.screenOriginX + 5, y = display.screenOriginY + 5, defaultFile = "images/button.png", label = "Button!", } local button = widget.newButton(options) button.anchorX, button.anchorY = 0, 0 button.touch = function(self, event) -- Overwrite the widget button's touch field with a new function if event.phase == "began" then print("button") return true -- Don't propagate on "began" phase end -- For anything besides "began", it automatically returns nil, which equates to false, which propogates the touch event end local function globalTouch(event) print("global touch"..event.x..event.y) end Runtime:addEventListener("touch", globalTouch)

This example is just a single touch, but I had to do this for a runtime event multitouch handler in a certain app, because otherwise the runtime touch function would never receive the touch “ended” if you moved your finger over a button before releasing.

I didn’t have to worry about what would happen if the user started a touch on the button and then dragged off of it because if they started a touch on any of my buttons, it would change scenes anyway, but it’s something to keep in mind if you’re not leaving the scene or otherwise disabling your other touch events when pressing the button.

As a note, you can do something similar with some of the other widgets.  For example, I made a “breakable” slider bar widget by jailbreaking the regular slider widget.  My slider’s handle can be broken off of the slider bar by sliding it to the end too quickly, making the handle fall to the bottom of the screen and bounce/roll around.  You can then pick it back up and reattach it to the slider from either end of the slider bar, and the slider is still fully functional (only while the handle is attached)! :slight_smile:

Whenever you want to see the fields in a table, try using this:

local function printTable(table) print("=========================================") for k, v in pairs(table) do print(k, "", v) end print("=========================================") end

Edit: Changed button anchoring and position a bit…forgot to do so first time.

We are going to need to see your code in order to assist further.

I was hoping it was pretty self explanatory, but here you are:

[lua]
local buttonA = widget.newButton(
{

  width = buttonAWidth,

  height = buttonAHeight,
  isEnabled = false,
  sheet = sheet, – a 9-slice image sheet with some transparent parts

  … 9-slice frames …

})

local function onEvent(event)

  print(“tapped”)

end

local buttonB = widget.newButton(
{
  width = buttonBWidth,
  height = buttonBHeight,
  id = buttonBId,
  label = buttonBLabel,
  labelColor = { default={0,0,0}, over={0,0,0}},
  onEvent = onEvent,
  sheet = buttonSheet, – a different sheet

… 9-slice frames …

})

sceneGroup:insert(buttonB)

sceneGroup:insert(buttonA)

[/lua]

And as I said above, I also tried including an onEvent listener for button A:

[lua]

local function onEventA(event)

 return false

end

[/lua]

… but this doesn’t seem to have done anything at all.

Hi Sarah,

To clarify, widget buttons (widget.newButton()) can not be tweaked to allow touch pass-through to underlying objects, because most users (99% I would guess) would experience UI issues/problems if the touch did pass through. So, the widget button blocks that from happening.

That being said, you can pretty easily just use the traditional method of adding a display object (image, vector rectangle, etc.) and using normal Corona touch handling that will not prevent the pass through to underlying objects.

Or, you could set up code that, under some condition related to “button A”, forcibly dispatches a pseudo-touch to “button B”. I can assist you with this code if you want to go that direction.

Best regards,

Brent

Thanks for the reply, Brent.

I’m using a button for the top layer because it is the simplest way to do a rectangle with a 9-slice image sheet. Is there a way to do a 9-slice non-button rectangle? Because that would definitely be the preferable route.

The simplest solution would be if there is a way to have buttonA simply act as though it is not a button when it is touched (pass the touch on to whatever is beneath it). Otherwise, I will have to do a calculation every time buttonA is touched to see if the touch also happened in the area of buttonB (because they only overlap slightly), which I’d rather avoid, but it sounds like that may be the next best option.

Hi Sarah,

I think the “object:dispatchEvent()” API is a better solution for you:

https://docs.coronalabs.com/api/type/EventDispatcher/dispatchEvent.html

Essentially, this allows you to dispatch a named event to some object, and one of the most useful applications is to dispatch a “pseudo touch” to an object even if the user never touched the screen or any object on it. In your case, it equates to basically:

  1. Create “buttonB” first, for Lua scope reasons.

  2. Insert “buttonB” into the display group (“sceneGroup”).

  3. Write the listener function for “buttonA”.

  4. Create “buttonA” and insert it into “sceneGroup”.

  5. Potentially set “buttonA:setEnabled( false )”. It will then be “disabled” but it should still pick up touch events.

Then, in your listener function for “buttonA” (step 3 above), add something like this:

[lua]

    buttonB:dispatchEvent( { name=“touch”, phase=“began”, target=buttonB } )

[/lua]

Brent

Yeah, I think that will work. The only caveat is that since I don’t want all touch events from buttonA to go through to buttonB (only those which are in the area of buttonA that overlaps buttonB) I will have to manually calculate whether or not the touch happened within the area of buttonB.

I definitely could have avoided this whole issue in the first place if it were possible to do a rectangle display object (non-button) with a 9-slice image sheet. There is really no way to do that?

Hi Sarah,

Well, the 9-slice concept would be pretty easy to handle in your own code, once you have the image sheet set up (which you do). It’s just a matter of laying out the corners first then spanning the sides and middle between. I think it would be around 10 lines of code and I could provide you with a starting sample if you’d like.

Otherwise, you can pretty easily detect the touch location and compare it between buttonA and buttonB.

Depending on your choice, I can help you out with a little example code.

Brent

I decided to go with the second approach - it was simpler than I expected, thanks to the handy-dandy object.contentBounds property.

If you don’t mind, though, I’d love to see an example of creating a rectangle with a 9-slice image sheet. I think it would be very helpful in the future!