Problem dragging object with display scaled

Hello,

In the code below I load a background and add a tile image and a button. For some reason I am having two problems with this.

I use the OnTouch event I wrote to drag an object on the screen and works well except when I drag the tile around without scaling first, the object is not release when I let go of the mouse. If I move the mouse and then click down anywhere else and move the mouse the tile still moves.

The other issue I have is when I click the button to scale the display it works great until I try to move the tile. It does move but does not stay with the mouse. It is probably because of the distance from the original x/y to the moved x/y is different due to scaling. I have to put my thinking hat on and calculate. Since I scaled to .8 should I get .2 difference and subtract that from t.x0?

Thanks,

Warren

local widget = require("widget") local composer = require( "composer" ) local scene = composer.newScene() local bgroup=display.newGroup() --------------------------------------------------------------------------------- local function onTouch( event ) local t = event.target local phase = event.phase if "began" == phase then -- Make target the top-most object local parent = t.parent parent:insert( t ) display.getCurrentStage():setFocus( t ) -- Spurious events can be sent to the target, e.g. the user presses -- elsewhere on the screen and then moves the finger over the target. -- To prevent this, we add this flag. Only when it's true will "move" -- events be sent to the target. t.isFocus = true -- Store initial position t.x0 = event.x - t.x t.y0 = event.y - t.y elseif t.isFocus then if "moved" == phase then -- Make object move (we subtract t.x0,t.y0 so that moves are -- relative to initial grab point, rather than object "snapping"). t.x = event.x - t.x0 t.y = event.y - t.y0 elseif "ended" == phase or "cancelled" == phase then t.isFocus = false end end -- Important to return true. This tells the system that the event -- should not be propagated to listeners of any objects underneath. return true end local function btnZoomButtonEvent( event ) if ("ended" == event.phase ) then --bgroup:scale( .8, .8 ) transition.scaleTo( bgroup, { xScale=.7, yScale=.7, time=500 } ) end end local nextSceneButton function scene:create( event ) local sceneGroup = self.view end function scene:show( event ) local sceneGroup = self.view local phase = event.phase if phase == "will" then -- Called when the scene is still off screen and is about to move on screen imgBack = display.newImageRect( bgroup, "images/back1.png", 3000, 3000 ) imgBack.anchorX = 0 imgBack.anchorY = 0 imgTile = display.newImageRect( bgroup, "images/tile1.png", 60, 120 ) imgTile.anchorX = 0 imgTile.anchorY = 0 imgTile.x = 680 imgTile.y = 320 imgTile:addEventListener( "touch", onTouch ) btnZoom = widget.newButton { width = 100, height = 100, label = "Zoom", onEvent = btnZoomButtonEvent } btnZoom.anchorX = 0 btnZoom.anchorY = 0 btnZoom.y = 200 btnZoom.x = 200 elseif phase == "did" then -- Called when the scene is now on screen end end

Hi Warren:

Your first issue is the easier of the two. Just add this line of code to your onTouch function just after the line “t.isFocus = false” in the block of code that fires when the event phase is “cancelled” or “ended”:

display.getCurrentStage():setFocus( nil )

As for the second bit of code, you should check out the object:contentToLocal() API, which will convert the global X and Y coordinates of the user’s finger to local coordinates for the tile’s parent group, taking into account scaling and the like. I don’t have time at this moment to write and test actual code, but that API will be key in solving this problem. Or, if you prefer to roll your own system, you can recalculate the “delta X” and “delta Y” of the touch input, multiply those values by the group’s xScale and yScale, and simply add that scaled delta value to the tile’s initial X and Y coordinate (when the touch event began). That would also work.

Good luck!

Thanks for the help. Unfortunately adding that line for the first issue did not help. When I finish dragging the tile and let go I can then click anywhere else and still drag the tile without the mouse on it.

Any thoughts?

Make sure that the line you enter is this:

display.getCurrentStage():setFocus( nil )

In my initial reply I copied and pasted and accidentally called setFocus(t) instead of setFocus(nil) - I edited my original reply but the email you got would have included the original (wrong) version. Sorry 'bout that!

That worked! Thanks.

Now I am going to read up on the info you sent for the second issue.

Warren

If you’re dragging in a scaled group, you’ll need to update the object’s position by the inverse of the scale.

See my dragging code in editor.lua for today’s Corona Geek Hangout.

http://bit.ly/icmt_ichi_mechanics

Thanks. I tried using your editDrag function but failed for me saying event was nil. Seems like it the function isn’t getting both self and event to it. I see what you are doing though in the code. Just trying to make it work now.

I see a lot of folks use function listeners like this:

-- Pseudo-code -- local obj = display.newCircle( ... ) local function onTouch( event ) end obj:addEventListener( "touch", onTouch )

I do not like this style.  I prefer table listeners:

-- Pseudo-code -- local obj = display.newCircle( ... ) local function onTouch( self, event ) -- different signature from 'function' listener end obj.touch = onTouch -- assign the function handle 'onTouch' (or whatever you name it) to the field 'touch' obj:addEventListener( "touch" ) -- Only pass in the name of the listener. It will look in field 'touch' for the function

Compare these two and I think you’ll see why your implementation of my sample is getting nil and not working.

@roaminggamer

Can you explain the advantages of using table listeners over the regular style of function listeners, I’m intrigued.

Well… some will disagree, but I find them to be cleaner, clearer, and logically more closely coupled to the object. 

Also, they enable a very interesting feature.

Compare this:

local onTouchA local onTouchB onTouchA = function( event ) local target = event.target local phase = event.phase print(" onTouchA - Print touched object ", phase, target.x, target.y ) if( phase == "ended" ) then target:removeEventListener( "touch" ) target:addEventListener( "touch", onTouchB ) end return true end onTouchB = function( event ) local target = event.target local phase = event.phase print(" onTouchB - Print touched object ", phase, target.x, target.y ) if( phase == "ended" ) then target:removeEventListener( "touch" ) target:addEventListener( "touch", onTouchA ) end return true end local obj = display.newCircle( ... ) obj:addEventListener( "touch", onTouchA ) 

to this:

local onTouchA local onTouchB onTouchA = function( self, event ) local phase = event.phase print(" onTouchA - Print touched object ", phase, self.x, self.y ) if( phase == "ended" ) then self.touch = onTouchB end return true end onTouchB = function( self, event ) local phase = event.phase print(" onTouchB - Print touched object ", phase, self.x, self.y ) if( phase == "ended" ) then self.touch = onTouchA end return true end local obj = display.newCircle( ... ) obj.touch = touchA obj:addEventListener( "touch" )

I use this ‘touch’ listener switching trick for making editors but you can also use it to add complex touch behaviors to objects while keeping the individual touch listeners simple.

@roaminggamer

Nice, I like it. I’ve copied your code and I’m going to study it.

Thanks for the explanation.

Hi Warren:

Your first issue is the easier of the two. Just add this line of code to your onTouch function just after the line “t.isFocus = false” in the block of code that fires when the event phase is “cancelled” or “ended”:

display.getCurrentStage():setFocus( nil )

As for the second bit of code, you should check out the object:contentToLocal() API, which will convert the global X and Y coordinates of the user’s finger to local coordinates for the tile’s parent group, taking into account scaling and the like. I don’t have time at this moment to write and test actual code, but that API will be key in solving this problem. Or, if you prefer to roll your own system, you can recalculate the “delta X” and “delta Y” of the touch input, multiply those values by the group’s xScale and yScale, and simply add that scaled delta value to the tile’s initial X and Y coordinate (when the touch event began). That would also work.

Good luck!

Thanks for the help. Unfortunately adding that line for the first issue did not help. When I finish dragging the tile and let go I can then click anywhere else and still drag the tile without the mouse on it.

Any thoughts?

Make sure that the line you enter is this:

display.getCurrentStage():setFocus( nil )

In my initial reply I copied and pasted and accidentally called setFocus(t) instead of setFocus(nil) - I edited my original reply but the email you got would have included the original (wrong) version. Sorry 'bout that!

That worked! Thanks.

Now I am going to read up on the info you sent for the second issue.

Warren

If you’re dragging in a scaled group, you’ll need to update the object’s position by the inverse of the scale.

See my dragging code in editor.lua for today’s Corona Geek Hangout.

http://bit.ly/icmt_ichi_mechanics

Thanks. I tried using your editDrag function but failed for me saying event was nil. Seems like it the function isn’t getting both self and event to it. I see what you are doing though in the code. Just trying to make it work now.

I see a lot of folks use function listeners like this:

-- Pseudo-code -- local obj = display.newCircle( ... ) local function onTouch( event ) end obj:addEventListener( "touch", onTouch )

I do not like this style.  I prefer table listeners:

-- Pseudo-code -- local obj = display.newCircle( ... ) local function onTouch( self, event ) -- different signature from 'function' listener end obj.touch = onTouch -- assign the function handle 'onTouch' (or whatever you name it) to the field 'touch' obj:addEventListener( "touch" ) -- Only pass in the name of the listener. It will look in field 'touch' for the function

Compare these two and I think you’ll see why your implementation of my sample is getting nil and not working.

@roaminggamer

Can you explain the advantages of using table listeners over the regular style of function listeners, I’m intrigued.

Well… some will disagree, but I find them to be cleaner, clearer, and logically more closely coupled to the object. 

Also, they enable a very interesting feature.

Compare this:

local onTouchA local onTouchB onTouchA = function( event ) local target = event.target local phase = event.phase print(" onTouchA - Print touched object ", phase, target.x, target.y ) if( phase == "ended" ) then target:removeEventListener( "touch" ) target:addEventListener( "touch", onTouchB ) end return true end onTouchB = function( event ) local target = event.target local phase = event.phase print(" onTouchB - Print touched object ", phase, target.x, target.y ) if( phase == "ended" ) then target:removeEventListener( "touch" ) target:addEventListener( "touch", onTouchA ) end return true end local obj = display.newCircle( ... ) obj:addEventListener( "touch", onTouchA ) 

to this:

local onTouchA local onTouchB onTouchA = function( self, event ) local phase = event.phase print(" onTouchA - Print touched object ", phase, self.x, self.y ) if( phase == "ended" ) then self.touch = onTouchB end return true end onTouchB = function( self, event ) local phase = event.phase print(" onTouchB - Print touched object ", phase, self.x, self.y ) if( phase == "ended" ) then self.touch = onTouchA end return true end local obj = display.newCircle( ... ) obj.touch = touchA obj:addEventListener( "touch" )

I use this ‘touch’ listener switching trick for making editors but you can also use it to add complex touch behaviors to objects while keeping the individual touch listeners simple.