Merge pull request #9 from fourbites/master
Add support for zooming in VirtualMediaPanel
This commit is contained in:
@@ -19,27 +19,25 @@
|
|||||||
|
|
||||||
package com.threerings.media;
|
package com.threerings.media;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Point;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.event.MouseWheelEvent;
|
import java.awt.event.MouseWheelEvent;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import com.samskivert.util.RunAnywhere;
|
import com.samskivert.util.RunAnywhere;
|
||||||
|
import static com.threerings.media.Log.log;
|
||||||
import com.threerings.media.image.ImageUtil;
|
import com.threerings.media.image.ImageUtil;
|
||||||
import com.threerings.media.image.Mirage;
|
import com.threerings.media.image.Mirage;
|
||||||
import com.threerings.media.util.MathUtil;
|
import com.threerings.media.util.MathUtil;
|
||||||
import com.threerings.media.util.Pathable;
|
import com.threerings.media.util.Pathable;
|
||||||
|
|
||||||
import static com.threerings.media.Log.log;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends the base media panel with the notion of a virtual coordinate system. All entities in
|
* Extends the base media panel with the notion of a virtual coordinate system. All entities in
|
||||||
* the virtual media panel have virtual coordinates and the virtual media panel displays a window
|
* the virtual media panel have virtual coordinates and the virtual media panel displays a window
|
||||||
@@ -67,6 +65,11 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
public VirtualMediaPanel (FrameManager framemgr)
|
public VirtualMediaPanel (FrameManager framemgr)
|
||||||
{
|
{
|
||||||
super(framemgr);
|
super(framemgr);
|
||||||
|
|
||||||
|
addZoomListener((oldZoom, newZoom) -> {
|
||||||
|
_vbounds = _zoomManager.rescaleBounds(_vbounds, getWidth(), getHeight());
|
||||||
|
_metamgr.getRegionManager().addDirtyRegion(_vbounds);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,7 +152,8 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
@Override
|
@Override
|
||||||
protected void processMouseEvent (MouseEvent event)
|
protected void processMouseEvent (MouseEvent event)
|
||||||
{
|
{
|
||||||
event.translatePoint(_vbounds.x, _vbounds.y);
|
event.translatePoint(_nx, _ny);
|
||||||
|
_zoomManager.adjustMouseEvent(event, _vbounds);
|
||||||
super.processMouseEvent(event);
|
super.processMouseEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +164,8 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
@Override
|
@Override
|
||||||
protected void processMouseMotionEvent (MouseEvent event)
|
protected void processMouseMotionEvent (MouseEvent event)
|
||||||
{
|
{
|
||||||
event.translatePoint(_vbounds.x, _vbounds.y);
|
event.translatePoint(_nx, _ny);
|
||||||
|
_zoomManager.adjustMouseEvent(event, _vbounds);
|
||||||
super.processMouseMotionEvent(event);
|
super.processMouseMotionEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +176,8 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
@Override
|
@Override
|
||||||
protected void processMouseWheelEvent (MouseWheelEvent event)
|
protected void processMouseWheelEvent (MouseWheelEvent event)
|
||||||
{
|
{
|
||||||
event.translatePoint(_vbounds.x, _vbounds.y);
|
event.translatePoint(_nx, _ny);
|
||||||
|
_zoomManager.adjustMouseEvent(event, _vbounds);
|
||||||
super.processMouseWheelEvent(event);
|
super.processMouseWheelEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +185,8 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
protected void dirtyScreenRect (Rectangle rect)
|
protected void dirtyScreenRect (Rectangle rect)
|
||||||
{
|
{
|
||||||
// translate the screen rect into happy coordinates
|
// translate the screen rect into happy coordinates
|
||||||
rect.translate(_vbounds.x, _vbounds.y);
|
rect.translate(_nx, _ny);
|
||||||
|
rect = _zoomManager.scaleOnCenter(rect);
|
||||||
_metamgr.getRegionManager().addDirtyRegion(rect);
|
_metamgr.getRegionManager().addDirtyRegion(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +208,7 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
// keep track of the size of the viewport
|
// keep track of the size of the viewport
|
||||||
_vbounds.width = getWidth();
|
_vbounds.width = getWidth();
|
||||||
_vbounds.height = getHeight();
|
_vbounds.height = getHeight();
|
||||||
|
_vbounds = _zoomManager.scaleOnCenter(_vbounds);
|
||||||
|
|
||||||
// we need to obtain our absolute screen coordinates to work
|
// we need to obtain our absolute screen coordinates to work
|
||||||
// around the Windows copyArea() bug
|
// around the Windows copyArea() bug
|
||||||
@@ -212,7 +220,7 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
{
|
{
|
||||||
// Adjust for any scrolling we're currently doing.
|
// Adjust for any scrolling we're currently doing.
|
||||||
super.addObscurerDirtyRegion(
|
super.addObscurerDirtyRegion(
|
||||||
new Rectangle(region.x - _dx, region.y - _dy, region.width, region.height));
|
new Rectangle(region.x - (int) _dx, region.y - (int) _dy, region.width, region.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -235,47 +243,60 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
|
|
||||||
protected void adjustBoundsCenter ()
|
protected void adjustBoundsCenter ()
|
||||||
{
|
{
|
||||||
int width = getWidth(), height = getHeight();
|
int width = _vbounds.width, height = _vbounds.height;
|
||||||
|
|
||||||
// adjusts our view location to track any pathable we might be tracking
|
// adjusts our view location to track any pathable we might be tracking
|
||||||
trackPathable();
|
trackPathable();
|
||||||
|
|
||||||
|
// our offset is from the center of the view panel, so we need to adjust
|
||||||
|
// coordinates to account for zoom.
|
||||||
|
Point viewCenter = ZoomManager.center(_vbounds);
|
||||||
|
Point panelCenter = new Point(getWidth() / 2, getHeight() / 2);
|
||||||
|
int offsetX = viewCenter.x - panelCenter.x;
|
||||||
|
int offsetY = viewCenter.y - panelCenter.y;
|
||||||
|
|
||||||
// if we have a new target location, we'll need to generate dirty
|
// if we have a new target location, we'll need to generate dirty
|
||||||
// regions for the area exposed by the scrolling
|
// regions for the area exposed by the scrolling
|
||||||
if (_nx != _vbounds.x || _ny != _vbounds.y) {
|
if (_nx != offsetX || _ny != offsetY) {
|
||||||
// determine how far we'll be moving on this tick
|
// determine how far we'll be moving on this tick
|
||||||
int dx = _nx - _vbounds.x, dy = _ny - _vbounds.y;
|
int dx = _nx - offsetX, dy = _ny - offsetY;
|
||||||
|
|
||||||
// log.info("Scrolling into place [n=(" + _nx + ", " + _ny +
|
// log.info("Scrolling into place [n=(" + _nx + ", " + _ny +
|
||||||
// "), t=(" + _vbounds.x + ", " + _vbounds.y +
|
// "), t=(" + _vbounds.x + ", " + _vbounds.y +
|
||||||
// "), d=(" + dx + ", " + dy +
|
// "), d=(" + dx + ", " + dy +
|
||||||
// "), width=" + width + ", height=" + height + "].");
|
// "), width=" + width + ", height=" + height + "].");
|
||||||
|
|
||||||
_dx = dx;
|
_dx += dx * getZoomLevel();
|
||||||
_dy = dy;
|
_dy += dy * getZoomLevel();
|
||||||
|
|
||||||
// these are used to prevent the vertical strip from
|
|
||||||
// overlapping the horizontal strip
|
|
||||||
int sy = _ny, shei = height;
|
|
||||||
|
|
||||||
// and add invalid rectangles for the exposed areas
|
|
||||||
if (dy > 0) {
|
|
||||||
shei = Math.max(shei - dy, 0);
|
|
||||||
_metamgr.getRegionManager().invalidateRegion(_nx, _ny + height - dy, width, dy);
|
|
||||||
} else if (dy < 0) {
|
|
||||||
sy -= dy;
|
|
||||||
_metamgr.getRegionManager().invalidateRegion(_nx, _ny, width, -dy);
|
|
||||||
}
|
|
||||||
if (dx > 0) {
|
|
||||||
_metamgr.getRegionManager().invalidateRegion(_nx + width - dx, sy, dx, shei);
|
|
||||||
} else if (dx < 0) {
|
|
||||||
_metamgr.getRegionManager().invalidateRegion(_nx, sy, -dx, shei);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// now go ahead and update our location so that changes in between here and the call
|
// now go ahead and update our location so that changes in between here and the call
|
||||||
// to paint() for this tick don't booch everything
|
// to paint() for this tick don't booch everything
|
||||||
_vbounds.x = _nx; _vbounds.y = _ny;
|
_vbounds.x += dx;
|
||||||
|
_vbounds.y += dy;
|
||||||
|
|
||||||
|
// these are used to prevent the vertical strip from
|
||||||
|
// overlapping the horizontal strip
|
||||||
|
int sy = _vbounds.y, shei = height;
|
||||||
|
|
||||||
|
// and add invalid rectangles for the exposed areas
|
||||||
|
if (dy > 0) {
|
||||||
|
// zoom rounding makes it so copyArea can't always get the full region
|
||||||
|
// so we extend each invalidation region by 1 pixel to make sure we don't leave artifacts
|
||||||
|
int dy2 = dy + 1;
|
||||||
|
shei = Math.max(shei - dy2, 0);
|
||||||
|
_metamgr.getRegionManager().invalidateRegion(_vbounds.x, _vbounds.y + height - dy2, width, dy2);
|
||||||
|
} else if (dy < 0) {
|
||||||
|
int dy2 = dy - 1;
|
||||||
|
sy -= dy2;
|
||||||
|
_metamgr.getRegionManager().invalidateRegion(_vbounds.x, _vbounds.y, width, -dy2);
|
||||||
|
}
|
||||||
|
if (dx > 0) {
|
||||||
|
int dx2 = dx + 1;
|
||||||
|
_metamgr.getRegionManager().invalidateRegion(_vbounds.x + width - dx2, sy, dx2, shei);
|
||||||
|
} else if (dx < 0) {
|
||||||
|
int dx2 = dx - 1;
|
||||||
|
_metamgr.getRegionManager().invalidateRegion(_vbounds.x, sy, -dx2, shei);
|
||||||
|
}
|
||||||
|
|
||||||
addObscurerDirtyRegions(false);
|
addObscurerDirtyRegions(false);
|
||||||
|
|
||||||
@@ -313,8 +334,8 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int width = getWidth(), height = getHeight();
|
int width = _vbounds.width, height = _vbounds.height;
|
||||||
int nx = _vbounds.x, ny = _vbounds.y;
|
int nx = _nx, ny = _ny;
|
||||||
|
|
||||||
// figure out where to move
|
// figure out where to move
|
||||||
switch (_fmode) {
|
switch (_fmode) {
|
||||||
@@ -356,11 +377,12 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
@Override
|
@Override
|
||||||
protected void paint (Graphics2D gfx, Rectangle[] dirty)
|
protected void paint (Graphics2D gfx, Rectangle[] dirty)
|
||||||
{
|
{
|
||||||
// if we're scrolling, go ahead and do the business
|
int dx = (int) Math.round(_dx);
|
||||||
if (_dx != 0 || _dy != 0) {
|
int dy = (int) Math.round(_dy);
|
||||||
|
if (dx != 0 || dy != 0) {
|
||||||
int width = getWidth(), height = getHeight();
|
int width = getWidth(), height = getHeight();
|
||||||
int cx = (_dx > 0) ? _dx : 0;
|
int cx = (dx > 0) ? dx : 0;
|
||||||
int cy = (_dy > 0) ? _dy : 0;
|
int cy = (dy > 0) ? dy : 0;
|
||||||
|
|
||||||
// set the clip to the bounds of the component (we can't assume the clip is anything
|
// set the clip to the bounds of the component (we can't assume the clip is anything
|
||||||
// sensible upon entry to paint() because the frame manager expects us to set our own
|
// sensible upon entry to paint() because the frame manager expects us to set our own
|
||||||
@@ -373,14 +395,14 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
if (RunAnywhere.isWindows()) {
|
if (RunAnywhere.isWindows()) {
|
||||||
gfx.translate(-_abounds.x, -_abounds.y);
|
gfx.translate(-_abounds.x, -_abounds.y);
|
||||||
gfx.copyArea(_abounds.x + cx, _abounds.y + cy,
|
gfx.copyArea(_abounds.x + cx, _abounds.y + cy,
|
||||||
width - Math.abs(_dx),
|
width - Math.abs(dx),
|
||||||
height - Math.abs(_dy), -_dx, -_dy);
|
height - Math.abs(dy), -dx, -dy);
|
||||||
gfx.translate(_abounds.x, _abounds.y);
|
gfx.translate(_abounds.x, _abounds.y);
|
||||||
} else if (RunAnywhere.isMacOS()) {
|
} else if (RunAnywhere.isMacOS()) {
|
||||||
try {
|
try {
|
||||||
gfx.copyArea(cx, cy,
|
gfx.copyArea(cx, cy,
|
||||||
width - Math.abs(_dx),
|
width - Math.abs(dx),
|
||||||
height - Math.abs(_dy), -_dx, -_dy);
|
height - Math.abs(dy), -dx, -dy);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// HACK when it throws an exception trying to do the copy area, just repaint
|
// HACK when it throws an exception trying to do the copy area, just repaint
|
||||||
// everything
|
// everything
|
||||||
@@ -388,29 +410,41 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gfx.copyArea(cx, cy,
|
gfx.copyArea(cx, cy,
|
||||||
width - Math.abs(_dx),
|
width - Math.abs(dx),
|
||||||
height - Math.abs(_dy), -_dx, -_dy);
|
height - Math.abs(dy), -dx, -dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// and clear out our scroll deltas
|
// and clear out our scroll deltas
|
||||||
_dx = 0; _dy = 0;
|
_dx -= dx;
|
||||||
|
_dy -= dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AffineTransform originalTransform = gfx.getTransform();
|
||||||
|
|
||||||
|
int centerX = _vbounds.x + _vbounds.width / 2;
|
||||||
|
int centerY = _vbounds.y + _vbounds.height / 2;
|
||||||
|
|
||||||
|
// if we zoom too quickly, the zoomManager scale may not be in sync with the paint tick
|
||||||
|
// so we check based on the actual view bounds to be safe
|
||||||
|
double scale = getWidth() / (double) _vbounds.width;
|
||||||
|
|
||||||
// translate into happy space
|
// translate into happy space
|
||||||
gfx.translate(-_vbounds.x, -_vbounds.y);
|
gfx.translate(getWidth() / 2, getHeight() / 2);
|
||||||
|
gfx.scale(scale, scale);
|
||||||
|
gfx.translate(-centerX, -centerY);
|
||||||
|
|
||||||
// now do the actual painting
|
// now do the actual painting
|
||||||
super.paint(gfx, dirty);
|
super.paint(gfx, dirty);
|
||||||
|
|
||||||
// translate back out of happy space
|
// translate back out of happy space
|
||||||
gfx.translate(_vbounds.x, _vbounds.y);
|
gfx.setTransform(originalTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void constrainToBounds (Rectangle dirty)
|
protected void constrainToBounds (Rectangle dirty)
|
||||||
{
|
{
|
||||||
SwingUtilities.computeIntersection(
|
SwingUtilities.computeIntersection(
|
||||||
_vbounds.x, _vbounds.y, getWidth(), getHeight(), dirty);
|
_vbounds.x, _vbounds.y, _vbounds.width, _vbounds.height, dirty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -429,14 +463,48 @@ public class VirtualMediaPanel extends MediaPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void enableZoom() {
|
||||||
|
addMouseWheelListener(_zoomManager.createMouseWheelListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void enableZoom(double min, double max) {
|
||||||
|
enableZoom();
|
||||||
|
setZoomConstraints(min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setZoomConstraints(double min, double max) {
|
||||||
|
_zoomManager.setMinZoomLevel(min);
|
||||||
|
_zoomManager.setMaxZoomLevel(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setZoomLevel(double zoom) {
|
||||||
|
_zoomManager.setZoomLevel(zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getZoomLevel() {
|
||||||
|
return _zoomManager.getZoomLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addZoomListener(ZoomManager.ZoomListener listener) {
|
||||||
|
_zoomManager.addZoomListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeZoomListener(ZoomManager.ZoomListener listener) {
|
||||||
|
_zoomManager.removeZoomListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ZoomManager _zoomManager = new ZoomManager();
|
||||||
|
|
||||||
/** Our viewport bounds in virtual coordinates. */
|
/** Our viewport bounds in virtual coordinates. */
|
||||||
protected Rectangle _vbounds = new Rectangle();
|
protected Rectangle _vbounds = new Rectangle();
|
||||||
|
|
||||||
/** Our target offsets to be effected on the next tick. */
|
/** Our target offsets to be effected on the next tick. */
|
||||||
protected int _nx, _ny;
|
protected int _nx, _ny;
|
||||||
|
|
||||||
/** Our scroll offsets. */
|
/**
|
||||||
protected int _dx, _dy;
|
* Our scroll offsets in real pixels.
|
||||||
|
*/
|
||||||
|
protected double _dx, _dy;
|
||||||
|
|
||||||
/** Our tiling background image. */
|
/** Our tiling background image. */
|
||||||
protected Mirage _background;
|
protected Mirage _background;
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package com.threerings.media;
|
||||||
|
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseWheelListener;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
public class ZoomManager {
|
||||||
|
|
||||||
|
protected static final double ZOOM_SNAP_THRESHOLD = 0.05;
|
||||||
|
protected static final double[] DEFAULT_ZOOM_STEPS = { 0.25, 0.3333, 0.5, 1.0, 2.0 };
|
||||||
|
|
||||||
|
protected double _zoomLevel = 1.0;
|
||||||
|
protected double _minZoomLevel = 0.25;
|
||||||
|
protected double _maxZoomLevel = 2.0;
|
||||||
|
protected double _zoomSteps[] = DEFAULT_ZOOM_STEPS;
|
||||||
|
|
||||||
|
public interface ZoomListener {
|
||||||
|
|
||||||
|
void zoomChanged(double oldZoom, double newZoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<ZoomListener> _listeners = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public ZoomManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoomManager(double zoomLevel) {
|
||||||
|
_zoomLevel = zoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoomManager(double zoomLevel, double minZoomLevel, double maxZoomLevel) {
|
||||||
|
_zoomLevel = zoomLevel;
|
||||||
|
_minZoomLevel = minZoomLevel;
|
||||||
|
_maxZoomLevel = maxZoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the center point of the given rectangle.
|
||||||
|
*
|
||||||
|
* @param rect the rectangle to find the center of
|
||||||
|
* @return the center point of the rectangle
|
||||||
|
*/
|
||||||
|
public static Point center(Rectangle rect) {
|
||||||
|
int centerX = rect.x + rect.width / 2;
|
||||||
|
int centerY = rect.y + rect.height / 2;
|
||||||
|
return new Point(centerX, centerY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rescales the given bounds rectangle based on the current zoom level.
|
||||||
|
* Useful when the zoom level changes to update the viewport.
|
||||||
|
*
|
||||||
|
* @param viewport the rectangle to rescale
|
||||||
|
* @param width the new width
|
||||||
|
* @param height the new height
|
||||||
|
* @return a new rectangle representing the rescaled bounds
|
||||||
|
*/
|
||||||
|
public Rectangle rescaleBounds(Rectangle viewPort, int width, int height) {
|
||||||
|
Point center = center(viewPort);
|
||||||
|
int newWidth = (int) (width / _zoomLevel);
|
||||||
|
int newHeight = (int) (height / _zoomLevel);
|
||||||
|
return new Rectangle(center.x - newWidth / 2, center.y - newHeight / 2, newWidth, newHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scales the given viewport rectangle based on the current zoom level,
|
||||||
|
* centering the scaling on the viewport's center.
|
||||||
|
*
|
||||||
|
* @param viewPort the viewport rectangle to scale
|
||||||
|
* @return a new rectangle representing the scaled viewport
|
||||||
|
*/
|
||||||
|
public Rectangle scaleOnCenter(Rectangle viewPort) {
|
||||||
|
Point center = center(viewPort);
|
||||||
|
int newWidth = (int) (viewPort.width / _zoomLevel);
|
||||||
|
int newHeight = (int) (viewPort.height / _zoomLevel);
|
||||||
|
return new Rectangle(center.x - newWidth / 2, center.y - newHeight / 2, newWidth, newHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the given mouse event for the current zoom level. Uses scaled
|
||||||
|
* distance from the center of the panel
|
||||||
|
*
|
||||||
|
* @param event the mouse event to adjust
|
||||||
|
*/
|
||||||
|
public void adjustMouseEvent(MouseEvent event, Rectangle viewPort) {
|
||||||
|
Point adjustedPoint = screenToVirtual(viewPort, event.getPoint());
|
||||||
|
event.translatePoint(
|
||||||
|
adjustedPoint.x - event.getX(),
|
||||||
|
adjustedPoint.y - event.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the given point for the current zoom level. Zoom happens from the
|
||||||
|
* center of the viewport.
|
||||||
|
*
|
||||||
|
* @param viewPort the viewport rectangle
|
||||||
|
* @param toAdjust the point to adjust
|
||||||
|
* @return a new point representing the adjusted coordinates
|
||||||
|
*/
|
||||||
|
public Point screenToVirtual(Rectangle viewPort, Point screenPoint) {
|
||||||
|
Point center = center(viewPort);
|
||||||
|
double scale = _zoomLevel;
|
||||||
|
int virtX = (int) ((screenPoint.x - center.x) / scale + center.x);
|
||||||
|
int virtY = (int) ((screenPoint.y - center.y) / scale + center.y);
|
||||||
|
return new Point(virtX, virtY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the zoom steps to use when zooming in and out.
|
||||||
|
*
|
||||||
|
* @param zoomSteps the zoom steps to set
|
||||||
|
*/
|
||||||
|
public void setZoomSteps(double[] zoomSteps) {
|
||||||
|
_zoomSteps = zoomSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mouse wheel listener that adjusts the zoom level.
|
||||||
|
*
|
||||||
|
* @return a mouse wheel listener
|
||||||
|
*/
|
||||||
|
public MouseWheelListener createMouseWheelListener() {
|
||||||
|
return e -> {
|
||||||
|
int closestStepIndex = 0;
|
||||||
|
double smallestDiff = Math.abs(_zoomLevel - _zoomSteps[0]);
|
||||||
|
for (int i = 0; i < _zoomSteps.length; i++) {
|
||||||
|
double diff = Math.abs(_zoomLevel - _zoomSteps[i]);
|
||||||
|
if (diff < smallestDiff) {
|
||||||
|
smallestDiff = diff;
|
||||||
|
closestStepIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int notches = e.getWheelRotation();
|
||||||
|
int newIndex = closestStepIndex + (notches > 0 ? -1 : 1);
|
||||||
|
if (newIndex < 0) {
|
||||||
|
newIndex = 0;
|
||||||
|
} else if (newIndex >= _zoomSteps.length) {
|
||||||
|
newIndex = _zoomSteps.length - 1;
|
||||||
|
}
|
||||||
|
setZoomLevel(_zoomSteps[newIndex]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getZoomLevel() {
|
||||||
|
return _zoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scales the current zoom level by the given factor.
|
||||||
|
*
|
||||||
|
* @param scale the scale factor
|
||||||
|
* @return the actual scale factor applied after clamping to min and max
|
||||||
|
*/
|
||||||
|
public double scaleZoomLevel(double scale) {
|
||||||
|
double oldZoom = _zoomLevel;
|
||||||
|
setZoomLevel(_zoomLevel * scale);
|
||||||
|
return _zoomLevel / oldZoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current zoom level, clamping it to the min and max zoom levels.
|
||||||
|
*
|
||||||
|
* @param zoomLevel the new zoom level
|
||||||
|
*/
|
||||||
|
public void setZoomLevel(double zoomLevel) {
|
||||||
|
double oldZoom = _zoomLevel;
|
||||||
|
if (zoomLevel < _minZoomLevel) {
|
||||||
|
_zoomLevel = _minZoomLevel;
|
||||||
|
} else if (zoomLevel > _maxZoomLevel) {
|
||||||
|
_zoomLevel = _maxZoomLevel;
|
||||||
|
} else {
|
||||||
|
_zoomLevel = zoomLevel;
|
||||||
|
// if we're close to 1.0, we might as well snap to it
|
||||||
|
if (Math.abs(_zoomLevel - 1.0) < ZOOM_SNAP_THRESHOLD) {
|
||||||
|
_zoomLevel = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldZoom != _zoomLevel) {
|
||||||
|
fireZoomChanged(oldZoom, _zoomLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMaxZoomLevel() {
|
||||||
|
return _maxZoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxZoomLevel(double maxZoomLevel) {
|
||||||
|
_maxZoomLevel = maxZoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMinZoomLevel() {
|
||||||
|
return _minZoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinZoomLevel(double minZoomLevel) {
|
||||||
|
_minZoomLevel = minZoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addZoomListener(ZoomListener listener) {
|
||||||
|
_listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeZoomListener(ZoomListener listener) {
|
||||||
|
_listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireZoomChanged(double oldZoom, double newZoom) {
|
||||||
|
for (ZoomListener listener : _listeners) {
|
||||||
|
listener.zoomChanged(oldZoom, newZoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,6 +118,10 @@ public class MisoScenePanel extends VirtualMediaPanel
|
|||||||
_resolver.start();
|
_resolver.start();
|
||||||
_resolvers.put(_ctx, _resolver);
|
_resolvers.put(_ctx, _resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addZoomListener((oldZoom, newZoom) -> {
|
||||||
|
rethink();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -716,7 +720,7 @@ public class MisoScenePanel extends VirtualMediaPanel
|
|||||||
|
|
||||||
// compute the tile coordinates of our upper left screen coordinate and request a rethink
|
// compute the tile coordinates of our upper left screen coordinate and request a rethink
|
||||||
// if they've changed
|
// if they've changed
|
||||||
MisoUtil.screenToTile(_metrics, _vbounds.x, _vbounds.y, _tcoords);
|
MisoUtil.screenToTile(_metrics, _nx, _ny, _tcoords);
|
||||||
if (_ulpos == null || !_tcoords.equals(_ulpos)) {
|
if (_ulpos == null || !_tcoords.equals(_ulpos)) {
|
||||||
// if this is a forced rethink (_ulpos is null), we might delay paint as a result of
|
// if this is a forced rethink (_ulpos is null), we might delay paint as a result of
|
||||||
// it, but only if we queue up blocks for resolution in our rethink
|
// it, but only if we queue up blocks for resolution in our rethink
|
||||||
|
|||||||
Reference in New Issue
Block a user