[Cuis-dev] [ANN] Cuis-Smalltalk-RoadTrip idle game + a question on implementing a ZoomPanMorph

Juan Vuletich juan at cuis.st
Fri Mar 1 10:59:58 PST 2024

Hi Ian,

Welcome to the Cuis community!

On 2/29/2024 3:02 PM, Ian Jeffries via Cuis-dev wrote:
> Hi all,
> I'm working on an idle game for learning geography. The first version 
> of it's up here: https://github.com/seagreen/Cuis-Smalltalk-RoadTrip
> It doesn't do much at the moment, there's just a car that noodles 
> around between US cities. However I wanted to go ahead and publish it 
> so I could get advice on the zooming and panning mechanism.
> Previously when I've implemented zoom-and-pan everything was based on 
> screen coordinates. On zoom-in I stored a new scaling factor, and then 
> when drawing the game applied this scaling factor to the game 
> coordinates to get screen coordinates. Easy enough.
> For RoadTrip though I'm taking advantage of Cuis' local coordinate 
> system to keep my coordinates in longitude and latitude all the way to 
> the user's display. It's extremely neat the users can pull up the 
> morph halo and see a grid in long/lat.
> But this makes scaling "interesting". On zoom-in when I apply 
> `scaleBy:` to the morph the US map wants to shoot up and to the left 
> off the screen. I'm currently handling this by moving the 
> `morphPosition` after each zoom to offset this, so that while scaleBy 
> makes the map jump up and to the left, the morph itself moves within 
> its parent down and to the right to make up for it, hopefully keeping 
> the map at the same place.
> Unfortunately I can't figure out how to make it exactly accurate and 
> it jerks around a lot when zoomed in. Before putting more debugging 
> work into this I wanted to ask if it seems like I'm on the right track 
> or if I've missed something obvious.
> Thanks for the help,
> Ian Jeffries

You're example is very interesting, and it sparks several ideas to me...

My first thought was "you'd use a PluggableScrollPane". You can put a 
simpler morph, that knows nothing about panning and scaling in a 
ScrollPane, and play with the halo to scale it. Most likely 
PluggableScrollPane could use some tweaks (like handling the scaling, 
and the pan / zoom gestures), but it is not far from what you need. 
Separating RoadTrip from Zoom/Pan issues, by composing them instead of 
subclassing, is better IMO. Besides, in places like City>>drawOn: you're 
hiding cities out of bounds yourself, while PluggableScrollPane already 
clips for you. At least that what I'd do.

But then I tried to understand what was going on with your code, and I 
found a bug in the Cuis image, Rectangle class >> #center:extent: This 
is the real reason for the jerking you see. I just pushed an update for 
that. Please pull & install. It will be much better.

Still, I think that the zoom behavior can be improved. The attach 
includes a tweaked version of your #zoom:mouse: that first sets the 
location to keep the point below the mouse exactly at the same place. 
Then, I adjust 'origin' to keep the morph display bounds constant too.

I'd also rewrite the pan with mouse, and I'd remove 
#adjustPositionInOwner: . But I leave that to you.

One last comment. You'd add ‘JSON’ as a prerequisite to your package, so 
it is loaded automatically if needed.

This is a nice project, and I look forward for its evolution. Thanks!


Juan Vuletich

-------------- next part --------------
'From Cuis6.3 [latest update: #6245] on 1 March 2024 at 3:24:48 pm'!

!ZoomPanMorph methodsFor: 'zoom' stamp: 'jmv 3/1/2024 15:23:04'!
zoom: amount mouse: latLongUnderMouse

	"For instance, zoom in might be:
		zoom: 1.1
	zoom out might be:
		zoom: 0.9"

	| pixelUnderMouse pixelForOrigin delta |
	pixelForOrigin := location externalizePosition: origin.
	width := width / amount.
	height := height / amount.
	pixelUnderMouse := location externalizePosition: latLongUnderMouse.
	self scaleBy: amount.
	delta := pixelUnderMouse - (location externalizePosition: latLongUnderMouse).
	location := location translatedBy: delta.
	origin := location internalizePosition: pixelForOrigin.
	self submorphsDo: [ :each |
		each scaleBy: 1 / amount
	].! !

More information about the Cuis-dev mailing list