Dragging an image within screen bounds

So I have an image (a map) thats bigger than the screen of the device. I want to be able to drag the map around so I can see other parts of it but to stop it dragging when the edges of the image reach the same edge of the screen.

So the top hits the top - left hits the left etc

I have taken some code I found and adjusted it below but its not quite there.

I think instead of checking t.x and t.y I need to somehow check if the left hand side of the image has passed the left of the screen etc. How would I do a check for the left hand side of the image?

 xMin = display.screenOriginX; xMax = display.contentWidth-display.screenOriginX; yMin = display.screenOriginY; yMax = display.contentHeight-display.screenOriginY; local function moveMap( event ) local t = event.target local phase = event.phase if "began" == phase then t.isFocus = true -- Store initial touch position on the actual object - prevents jumping when touched t.xStart = event.x - t.x t.yStart = event.y - t.y elseif t.isFocus then if "moved" == phase then print(t.xStart) print(t.yStart) t.x = event.x - t.xStart t.y = event.y - t.yStart if (t.x \< xMin) then t.x = xMin end if (t.x \> xMax) then t.x = xMax end if (t.y \< yMin) then t.y = yMin end if (t.y \> yMax) then t.y = yMax end elseif "ended" == phase or "cancelled" == phase then t.isFocus = false end end --This tells the system that the event -- should not be propagated to listeners of any objects underneath. return true end

You just need your xMin, yMin, etc to account for half of the image width (it’s half the width because the 0,0 origin of it is right in the middle.).

So, your xMin would be 

xMin = display.screenOriginX + map.width/2

and the line to stop the map going over the left edge would be

if (t.x \> xMin) then t.x = xMin end

I think you can figure out the others yourself. Just remember to comment out the lines you haven’t got right yet, as you don’t want them interfering with the one you’re working on.

Just as I look back on that, I realise that what I call xMin is actually the the xMax, but that’s just a trivial point. :slight_smile:

Hey,

Thanks for the reply. Just trying to get my head around it.

My map is bigger than the device screen size - lets for the sake of it say its 700px wide and that im looking at an iphone (320px wide).

Does your code above not set xMax as being the left hand side of the screen+half the map (350px in this case)

So now if my x position is xMax then set it to be at xMax which would actually be outside of the screen width.

Or am I reading it wrong and misunderstanding?

Supposing display.screenOriginX is 0, your screen width is 320 and your image width is 800. xMax would then be 400, somewhere just off screen to the right. If you drag the map so that it’s centre goes to the right of this point, it gets set to this point. Since the map is now at x = 400 and half its width( 400 ) is to the left of here, its left end will then be at position x = 0, where you want it. I think. lol, you’re right it is very confusing, but I think I’ve got it right. I’ve tested what I showed you above and it does work. The other end points should be easy to do using the same principle.

Yeah that makes sense. Something not working with it for me though - must be doing something stupid!

I have an event listener on the map object so when I touch it then its event.target

so in my xMax when I do event.target.width/2 its the same thing. Main reason I did it was the map object is created after the function so it was giving me an error initially.

Any ideas whats wrong with it? It works for xMax but xMin doesnt

 local function moveMap( event ) xMax = display.screenOriginX + event.target.width/2; xMin = display.screenOriginX - event.target.width/2; local t = event.target local phase = event.phase if "began" == phase then t.isFocus = true -- Store initial touch position on the actual object - prevents jumping when touched t.xStart = event.x - t.x t.yStart = event.y - t.y elseif t.isFocus then if "moved" == phase then print(t.xStart) print(t.yStart) t.x = event.x - t.xStart t.y = event.y - t.yStart if (t.x \> xMax) then t.x = xMax end if (t.x \< xMin) then t.x = xMin end elseif "ended" == phase or "cancelled" == phase then t.isFocus = false end end --This tells the system that the event -- should not be propagated to listeners of any objects underneath. return true end

Edit - nevermind…was making a mistake with the maths…assumed needed to also subtract half the width for xMin but I dont.

Ok so I spoke to soon…

So I have the bounds working and can move the map around. But when I view it on different devices its off on some of them.

I have a header and footer section which act as buttons and then the map sits in the background behind those.

The header and footer are images - so they have a set width and height.

When I change the Max and Min around based on their height and its good on one device then its wrong on another.

Is this because of the @2x images being used on the other devices and the height I put in is based on the pixel height of the normal image?

The images are declared after the function, how can I have it so I can mention them in the function above and use their height as an offset for the min/max where needed…if I reference them at the minute it doesnt know they exist and I cant move them as it will cause the same issue with a different function.

Is that even the reason or is it something to do with the screenOrigin?

Just to add, when I print the values for xMin I can see that it moves exactly half the size of my map (the map being 1064px wide…xMin kicks in at 532px) so it is correct but for some reason thats not the exact edge of the map, there are more pixels off screen which I cant see.

Any suggestion as to whats going on there or how to fix it?

Im using letterbox in my build settings…so may have something to with scaling either?

You could put at the top:

local headerHeight, footerHeight = 50, 50 -- or whatever you like

You can then use these values when you create the header and footer, and also factor them into yMin and yMax. I wouldn’t have thought using the different images would make a difference, but I’m no expert. Is the difference on devices only occuring with the top and bottom bounds, or with the left and right bounds as well? It might be helpful to see more code.

Its actually happening independant of those images I mentioned, I thought it was due to those but its not.

Because its a map I can see where it should end but on screen the bottom of actual map is further down than where its stopping it. Even though when I print the co-ordinates they are right.

hmm. I can’t think what might be causing it. Post what you’ve got and I’ll see if anything reveals itself to me.

Everything from the file is below:

Config file settings under that.

Did a few tests there and for some reason the map stops moving when there is about 50 pixels more hidden off screen to the right even though the values for the position and xMin etc show the right values.

local leftSide = display.screenOriginX; local rightSide = display.contentWidth-display.screenOriginX; local topSide = display.screenOriginY; local bottomSide = display.contentHeight-display.screenOriginY; --------------------------------------------------------------------------------------- local composer = require "composer" local scene = composer.newScene() local widget = require "widget" local arrowRight local arrowLeft math.randomseed(os.time()) local function btnTouch( event ) if event.phase == "ended" then print ("button pressed") composer.gotoScene(event.target.btnDestination, {time=250, effect="crossFade"}) return true end end local function locationTouch( event ) if event.phase == "ended" then print (myCurrentImage.currentImageIndex) if myCurrentImage.currentImageIndex == 1 then composer.gotoScene("cork\_poi", {time=250, effect="crossFade"}) end if myCurrentImage.currentImageIndex == 2 then composer.gotoScene("cork\_poi", {time=250, effect="crossFade"}) end if myCurrentImage.currentImageIndex == 3 then composer.gotoScene("cork\_poi", {time=250, effect="crossFade"}) end if myCurrentImage.currentImageIndex == 4 then composer.gotoScene("cork\_poi", {time=250, effect="crossFade"}) end if myCurrentImage.currentImageIndex == 5 then composer.gotoScene("cork\_poi", {time=250, effect="crossFade"}) end if myCurrentImage.currentImageIndex == 6 then composer.gotoScene("cork\_poi", {time=250, effect="crossFade"}) end if myCurrentImage.currentImageIndex == 7 then composer.gotoScene("cork\_poi", {time=250, effect="crossFade"}) end return true end end -- Called when the scene's view does not exist: function scene:create( event ) local sceneGroup = self.view local footerGroup = display.newGroup() local map = display.newImageRect("images/dublinMap.jpg", 1064, 636) map.x = display.contentCenterX map.y = display.contentCenterY sceneGroup:insert( map ) local Footerbg = display.newImageRect("images/footerbg.jpg", 360, 60) Footerbg.x = display.contentCenterX Footerbg.y = bottomSide -30 Footerbg.isVisible = false; sceneGroup:insert( Footerbg ) -- code to slide through locations -- myImages = {"images/Dublin/location1.jpg", "images/Dublin/location2.jpg", "images/Dublin/location3.jpg", "images/Dublin/location4.jpg", "images/Dublin/location5.jpg", "images/Dublin/location6.jpg", "images/Dublin/location7.jpg", "images/Dublin/location8.jpg", "images/Dublin/location9.jpg", "images/Dublin/location10.jpg" } -- name of images currentImageIndex = 1 -- this is index to track your current image -- display the first image myCurrentImage = display.newImageRect( myImages[1],240,60) myCurrentImage.x = display.contentCenterX myCurrentImage.y = bottomSide -30 myCurrentImage.currentImageIndex = 1 sceneGroup:insert( myCurrentImage ) myCurrentImage:addEventListener( "touch", locationTouch ) -- Function to handle back and forward buttons local function handleButtonEvent( event ) if ( "ended" == event.phase ) then if event.target.id == "back" and currentImageIndex \> 1 then print( "Back" ) myCurrentImage:removeSelf() myCurrentImage = nil myCurrentImage = display.newImage( myImages[currentImageIndex - 1]) myCurrentImage.x = display.contentCenterX myCurrentImage.y = bottomSide -30 sceneGroup:insert( myCurrentImage ) arrowLeft:toFront() arrowRight:toFront() currentImageIndex = currentImageIndex - 1 myCurrentImage.currentImageIndex = currentImageIndex myCurrentImage:addEventListener( "touch", locationTouch ) elseif event.target.id == "fwd" and currentImageIndex \< 10 then print( "fwd" ) myCurrentImage:removeSelf() myCurrentImage = nil myCurrentImage = display.newImage( myImages[currentImageIndex + 1]) myCurrentImage.x = display.contentCenterX myCurrentImage.y = bottomSide -30 sceneGroup:insert( myCurrentImage ) arrowLeft:toFront() arrowRight:toFront() currentImageIndex = currentImageIndex + 1 myCurrentImage.currentImageIndex = currentImageIndex myCurrentImage:addEventListener( "touch", locationTouch ) end return true end end -- drag map function -- local function moveMap( event ) xMax = display.screenOriginX + map.width/2; xMin = display.screenOriginX - 260 ; yMax = display.screenOriginY + map.height/2; yMin = display.screenOriginY + map.height/2; local t = event.target local phase = event.phase if "began" == phase then t.isFocus = true -- Store initial touch position on the actual object - prevents jumping when touched t.xStart = event.x - t.x t.yStart = event.y - t.y elseif t.isFocus then if "moved" == phase then print( display.actualContentWidth ) print("posx:"..t.x) print("xMin:"..xMin) print("xMax:"..xMax) print("pos:"..t.y) print("yMin:"..yMin) print("yMax"..yMax) t.x = event.x - t.xStart t.y = event.y - t.yStart if (t.x \> xMax) then t.x = xMax end if (t.x \< xMin) then t.x = xMin end if (t.y \> yMax) then t.y = yMax end if (t.y \< yMin) then t.y = yMin end elseif "ended" == phase or "cancelled" == phase then t.isFocus = false end end --This tells the system that the event -- should not be propagated to listeners of any objects underneath. return true end -- end of drag map function -- map:addEventListener( "touch", moveMap ) arrowLeft = display.newImageRect("images/arrowLeft.png", 12, 21) arrowLeft.x = display.screenOriginX+20 arrowLeft.y = bottomSide -40 arrowLeft:toFront( ) arrowLeft.id = "back" arrowLeft:addEventListener( "touch", handleButtonEvent ) sceneGroup:insert( arrowLeft ) arrowRight = display.newImageRect("images/arrowRight.png", 12, 21) arrowRight.x = display.contentWidth-display.screenOriginX -20; arrowRight.y = bottomSide -40 arrowRight:toFront( ) arrowRight.id = "fwd" arrowRight:addEventListener( "touch", handleButtonEvent ) sceneGroup:insert( arrowRight ) local function moveToFront( ) arrowRight:toFront( ) arrowLeft:toFront( ) end local back = display.newImageRect("images/button\_dublin.jpg", 360, 40) back.x = display.contentCenterX back.y = display.screenOriginY+20; back.btnDestination = "Scene1" back:addEventListener( "touch", btnTouch ) sceneGroup:insert( back ) end function scene:show( event ) local sceneGroup = self.view local phase = event.phase if "did" == phase then print( "1: show event, phase did" ) -- remove previous scene's view --composer.removeScene( "main" ) end end function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if "will" == phase then print( "1: hide event, phase will" ) -- remove touch listener for image --audioBtn:removeEventListener( "touch", myAudio ) --back:removeEventListener( "touch", btnTouch ) -- remove audio audio.stop() audio.dispose( audioSound ) audioSound = nil -- cancel timer -- timer.cancel( memTimer ); memTimer = nil; end end function scene:destroy( event ) print( "((destroying scene 1's view))" ) local sceneGroup = self.view end --------------------------------------------------------------------------------- -- Listener setup scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) --------------------------------------------------------------------------------- return scene

Config file

application = { content = { width = 320, height = 480, scale = "letterbox", xAlign = "center", yAlign = "center", imageSuffix = { ["@2x"] = 1.5, }, } }

Cheers for having a look

The line xMin = display.screenOriginX - 260 ;  is where I was messing around with the position

If I set it to be -210 then I get the right position where the map wont pass the edge of the screen…the map is 1064 wide though so this doesnt seem to have any magic relevance

Ok, it’s getting a bit late for me, so I’ll have another look tomorrow. In the mean time, I made this, which seems to do the job:

 local map = display.newImageRect("map.png", 1100, 1100) map.x, map.y = 250, 200 local xMax = display.screenOriginX + map.width/2 ; local xMin = display.contentWidth - map.width/2 ; local yMax = display.screenOriginY + map.height/2 ; local yMin = display.contentHeight - map.height/2; local function moveMap( event ) local t = event.target local phase = event.phase if "began" == phase then t.isFocus = true -- Store initial touch position on the actual object - prevents jumping when touched t.xStart = event.x - t.x t.yStart = event.y - t.y elseif t.isFocus then if "moved" == phase then t.x = event.x - t.xStart t.y = event.y - t.yStart if (t.x \> xMax) then t.x = xMax end if (t.x \< xMin) then t.x = xMin end if (t.y \> yMax) then t.y = yMax end if (t.y \< yMin) then t.y = yMin end elseif "ended" == phase or "cancelled" == phase then t.isFocus = false end end return true end map:addEventListener("touch", moveMap)

Have you viewed it on various devices - ipad Air seems to be one of the ones where it gives issues. I replaced my xmin etc with yours and while it looks ok on iphone on ipad Air when I drag the map left I get a gap on the far right

Seem to have it working now…

I had these declared at the top and used them as part of the calculations for xmin etc

local leftSide = display.screenOriginX;
local rightSide = display.contentWidth-display.screenOriginX;
local topSide = display.screenOriginY;
local bottomSide = display.contentHeight-display.screenOriginY;

So the values for xmin etc including the offsets for the heights of my buttons becomes

local function moveMap( event )
    xMax = leftSide + map.width/2 ;
    xMin = rightSide - map.width/2 ;
    yMax = topSide + map.height/2 + backBtn.height;
    yMin = bottomSide - (map.height/2+ Footerbg.height);

All seems to work now…fingers crossed! Cheers again

Actually display.screenOriginX represents extra space added/subtracted on each side of the screen. The total width of the screen includes both of these, so to get the right side of the screen you need to multiply display.screenOriginX by 2. Similarly with the bottom side.

Glad it’s working for you. Here’s another way of doing it (too lazy to account for your button and footer :slight_smile: ):

local leftSide = display.screenOriginX local rightSide = leftSide + display.actualContentWidth local top = display.screenOriginY local bottom = top + display.actualContentHeight local xMax = leftSide + map.width/2 local xMin = rightSide - map.width/2 local yMax = top + map.width/2 local yMin = bottom - map.width/2

Also, since the xMax, yMax, etc, values aren’t changing as far as I can tell, it’s wasteful to keep calculating them in your move Map function. Just declare them once, and keep using them.

You just need your xMin, yMin, etc to account for half of the image width (it’s half the width because the 0,0 origin of it is right in the middle.).

So, your xMin would be 

xMin = display.screenOriginX + map.width/2

and the line to stop the map going over the left edge would be

if (t.x \> xMin) then t.x = xMin end

I think you can figure out the others yourself. Just remember to comment out the lines you haven’t got right yet, as you don’t want them interfering with the one you’re working on.

Just as I look back on that, I realise that what I call xMin is actually the the xMax, but that’s just a trivial point. :slight_smile:

Hey,

Thanks for the reply. Just trying to get my head around it.

My map is bigger than the device screen size - lets for the sake of it say its 700px wide and that im looking at an iphone (320px wide).

Does your code above not set xMax as being the left hand side of the screen+half the map (350px in this case)

So now if my x position is xMax then set it to be at xMax which would actually be outside of the screen width.

Or am I reading it wrong and misunderstanding?

Supposing display.screenOriginX is 0, your screen width is 320 and your image width is 800. xMax would then be 400, somewhere just off screen to the right. If you drag the map so that it’s centre goes to the right of this point, it gets set to this point. Since the map is now at x = 400 and half its width( 400 ) is to the left of here, its left end will then be at position x = 0, where you want it. I think. lol, you’re right it is very confusing, but I think I’ve got it right. I’ve tested what I showed you above and it does work. The other end points should be easy to do using the same principle.