I am trying to make a zoomable map with Swing. The map is a JPanel in a JScrollPane. When zoomed in, the map changes size and paint() paints the elements in a different position. This all works great.
However, the ScrollPane didn't change the viewport while increasing the image size, so that zooming in always moved the elements I was looking at out of the screen. I tried to solve this with scrollRectToVisible()
, but I don't manage to get the right coordinates for the rectangle, either because I fail at doing the geometry or because I don't understand Swing all that well.
Here is what I have:
public class MapPanel extends JPanel {
[...]
public void setZoom(double zoom) {
// get the current viewport rectangle and its center in the scaled coordinate system
JViewport vp = (JViewport) this.getParent();
Rectangle rect = vp.getViewRect();
Point middle = getMiddle(rect);
Dimension dim = rect.getSize();
// zoom in
scaler.setZoom(zoom);
setPreferredSize(scaler.transform(dim));
this.revalidate();
// calculate the full size of the scaled coordinate system
Dimension fullDim = scaler.transform(dim);
// calculate the non-scaled center of the viewport
Point nMiddle = new Point((int) ((double) (middle.x)/fullDim.width*dim.width),(int) ((double) (middle.y)/fullDim.height*dim.height));
// this should do the trick, but is always a bit off towards the origin
scrollRectToVisible(getRectangleAroundPoint(nMiddle));
// the below alternative always zooms in perfectly to the center of the map
// scrollRectToVisible(getRectangleAroundPoint(new Point(400,300)));
}
private Rectangle getRectangleAroundPoint(Point p){
Point newP = scaler.transform(p);
Dimension d = railMap.getDimension();
Point corner = new Point(newP.x-d.width/2,newP.y-d.height/2);
return new Rectangle(corner,d);
}
private Point getMiddle(Rectangle r){
return new Point(r.x+r.width/2,r.y+r.height/2);
}
}
And here's the Scaler class (which does nothing very surprising, I think):
public class Scaler {
private double zoom = 1;
public void setZoom(double zoom) {
this.zoom = zoom;
}
public Point transform(Point2D p){
return new Point((int) (p.getX()*zoom), (int) (p.getY()*zoom));
}
public Dimension transform(Dimension d){
return new Dimension((int) (d.width*zoom), (int) (d.height*zoom));
}
}
Who can tell me where things are going wrong? It seems to me I did a valid calculation of the current center of the map, and with a fixed zoom point it does work...
Edit: so the hard thing here is to create the new viewport rectangle based on the old viewport rectangle.
I just did this really quick example, which basically tries to keep the scroll pane center around the middle of the supplied image
As to why yours doesn't work, I can't say, I can't run the example, but maybe this will at least give you some ideas...
Solved it. Yay. Still not sure where it actually went wrong, but moving the original rectangle (thanks @MadProgrammer) rather than creating a new one and correct rounding in the scaler may have done the trick.