Add zoom controls to basic drawing.

This commit is contained in:
Holt59 2018-03-03 15:23:08 +01:00
parent 7827bfc63c
commit dede97bc68
5 changed files with 354 additions and 108 deletions

View File

@ -5,12 +5,16 @@ import java.awt.Color;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image; import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
@ -286,17 +290,42 @@ public class BasicDrawing extends JPanel implements Drawing {
// Mapping DrawingClickListener -> MouseEventListener // Mapping DrawingClickListener -> MouseEventListener
private Map<DrawingClickListener, MouseListener> listenerMapping = new IdentityHashMap<>(); private Map<DrawingClickListener, MouseListener> listenerMapping = new IdentityHashMap<>();
// Zoom controls
private MapZoomControls zoomControls;
/** /**
* Create a new BasicDrawing. * Create a new BasicDrawing.
* *
*/ */
public BasicDrawing() { public BasicDrawing() {
this.zoomAndPanListener = new ZoomAndPanListener(this, ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20, 1.2); this.zoomAndPanListener = new ZoomAndPanListener(this, ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20, 1.2);
// Try...
try {
this.zoomControls = new MapZoomControls(this, 0, ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20);
this.zoomControls.addZoomInListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
zoomAndPanListener.zoomIn();
}
});
this.zoomControls.addZoomOutListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
zoomAndPanListener.zoomOut();
}
});
}
catch (IOException e) {
e.printStackTrace();
}
} }
@Override @Override
public void paintComponent(Graphics g1) { public void paintComponent(Graphics g1) {
super.paintComponent(g1);
Graphics2D g = (Graphics2D) g1; Graphics2D g = (Graphics2D) g1;
AffineTransform sTransform = g.getTransform();
g.clearRect(0, 0, getWidth(), getHeight()); g.clearRect(0, 0, getWidth(), getHeight());
g.setTransform(zoomAndPanListener.getCoordTransform()); g.setTransform(zoomAndPanListener.getCoordTransform());
@ -311,6 +340,14 @@ public class BasicDrawing extends JPanel implements Drawing {
overlay.draw(g); overlay.draw(g);
} }
} }
g.setTransform(sTransform);
if (this.zoomControls != null) {
this.zoomControls.setZoomLevel(this.zoomAndPanListener.getZoomLevel());
this.zoomControls.draw(g, getWidth() - this.zoomControls.getWidth() - 20,
this.getHeight() - this.zoomControls.getHeight() - 20, this);
}
} }
/** /**
@ -513,6 +550,7 @@ public class BasicDrawing extends JPanel implements Drawing {
(this.getHeight() - this.height * scale) / 2); (this.getHeight() - this.height * scale) / 2);
this.zoomAndPanListener.getCoordTransform().scale(scale, scale); this.zoomAndPanListener.getCoordTransform().scale(scale, scale);
this.zoomAndPanListener.setZoomLevel(0); this.zoomAndPanListener.setZoomLevel(0);
this.zoomControls.setZoomLevel(0);
// Repaint // Repaint
this.repaint(); this.repaint();

View File

@ -0,0 +1,173 @@
package org.insa.graphics.drawing.components;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class MapZoomControls {
// Default ID for action events
private static final int ZOOM_IN_ACTION_ID = 0x1;
private static final String ZOOM_IN_ACTION_NAME = "ZoomIn";
private static final int ZOOM_OUT_ACTION_ID = 0x2;
private static final String ZOOM_OUT_ACTION_NAME = "ZoomOut";
// Height
private static final int DEFAULT_HEIGHT = 18;
// Default spacing between mark
private static final int DEFAULT_SPACING = 3;
// Zoom ticks ratio from height (not the current one)
private static final double ZOOM_TICK_HEIGHT_RATIO = 0.3;
// Zoom ticks color
private static final Color ZOOM_TICK_COLOR = Color.GRAY;
// Current zoom ticks ratio from height
private static final double CURRENT_ZOOM_TICK_HEIGHT_RATIO = 0.8;
// Zoom ticks color
private static final Color CURRENT_ZOOM_TICK_COLOR = new Color(25, 25, 25);
// Use half mark or not
private boolean halfMark = true;
private int currentLevel = 0;
private final int minLevel, maxLevel;
// Zoom in/out image and their rectangles.
private final Image zoomIn, zoomOut;
private final Rectangle zoomInRect = new Rectangle(0, 0, 0, 0), zoomOutRect = new Rectangle(0, 0, 0, 0);
// List of listeners
private final List<ActionListener> zoomInListeners = new ArrayList<>();
private final List<ActionListener> zoomOutListeners = new ArrayList<>();
public MapZoomControls(Component component, final int defaultZoom, final int minZoom, final int maxZoom)
throws IOException {
zoomIn = ImageIO.read(new File("zoomIn.png")).getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT,
Image.SCALE_SMOOTH);
zoomOut = ImageIO.read(new File("zoomOut.png")).getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT,
Image.SCALE_SMOOTH);
this.currentLevel = defaultZoom;
this.minLevel = minZoom;
this.maxLevel = maxZoom;
component.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (zoomInRect.contains(e.getPoint()) && currentLevel < maxLevel) {
currentLevel += 1;
for (ActionListener al: zoomInListeners) {
al.actionPerformed(new ActionEvent(this, ZOOM_IN_ACTION_ID, ZOOM_IN_ACTION_NAME));
}
}
else if (zoomOutRect.contains(e.getPoint()) && currentLevel > minLevel) {
currentLevel -= 1;
for (ActionListener al: zoomOutListeners) {
al.actionPerformed(new ActionEvent(this, ZOOM_OUT_ACTION_ID, ZOOM_OUT_ACTION_NAME));
}
}
component.repaint();
}
});
}
/**
* Add a zoom-in listener.
*
* @param listener
*/
public void addZoomInListener(ActionListener listener) {
this.zoomInListeners.add(listener);
}
/**
* Add a zoom-out listener.
*
* @param listener
*/
public void addZoomOutListener(ActionListener listener) {
this.zoomOutListeners.add(listener);
}
/**
* @return the current zoom level.
*/
public int getZoomLevel() {
return this.currentLevel;
}
/**
* Set the current zoom level.
*
* @param level
*/
public void setZoomLevel(int level) {
this.currentLevel = level;
}
/**
* @return Height of this "component" when drawn.
*/
public int getHeight() {
return DEFAULT_HEIGHT;
}
/**
* @return Width of this "component" when drawn.
*/
public int getWidth() {
return DEFAULT_HEIGHT + 2 + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2 + DEFAULT_HEIGHT;
}
protected void draw(Graphics2D g, int xoffset, int yoffset, ImageObserver observer) {
int height = getHeight();
// Draw icon
g.drawImage(zoomOut, xoffset, yoffset, observer);
zoomOutRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT);
// Draw ticks
xoffset += DEFAULT_HEIGHT + 2;
g.setColor(ZOOM_TICK_COLOR);
g.drawLine(xoffset, yoffset + height / 2, xoffset + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1,
yoffset + height / 2);
for (int i = 0; i <= (this.maxLevel - this.minLevel); i += halfMark ? 2 : 1) {
g.drawLine(xoffset + i * DEFAULT_SPACING, yoffset + (int) (height * (1 - ZOOM_TICK_HEIGHT_RATIO) / 2),
xoffset + i * DEFAULT_SPACING, yoffset + (int) (height * (1 + ZOOM_TICK_HEIGHT_RATIO) / 2));
}
// Draw current ticks
g.setColor(CURRENT_ZOOM_TICK_COLOR);
g.drawLine(xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING,
yoffset + (int) (height * (1 - CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2),
xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING,
yoffset + (int) (height * (1 + CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2));
xoffset += (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2;
g.drawImage(zoomIn, xoffset, yoffset, observer);
zoomInRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT);
}
}

View File

@ -1,141 +1,176 @@
package org.insa.graphics.drawing.components; package org.insa.graphics.drawing.components;
import java.awt.*; import java.awt.Component;
import java.awt.event.*; import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
public class ZoomAndPanListener implements MouseListener, MouseMotionListener, MouseWheelListener { public class ZoomAndPanListener implements MouseListener, MouseMotionListener, MouseWheelListener {
public static final int DEFAULT_MIN_ZOOM_LEVEL = -20; public static final int DEFAULT_MIN_ZOOM_LEVEL = -20;
public static final int DEFAULT_MAX_ZOOM_LEVEL = 10; public static final int DEFAULT_MAX_ZOOM_LEVEL = 10;
public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2; public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2;
private Component targetComponent; private Component targetComponent;
private int zoomLevel = 0; private int zoomLevel = 0;
private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL; private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL;
private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL; private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL;
private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR; private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR;
private Point dragStartScreen; private Point dragStartScreen;
private Point dragEndScreen; private Point dragEndScreen;
private AffineTransform coordTransform = new AffineTransform(); private AffineTransform coordTransform = new AffineTransform();
public ZoomAndPanListener(Component targetComponent) { public ZoomAndPanListener(Component targetComponent) {
this.targetComponent = targetComponent; this.targetComponent = targetComponent;
} }
public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel, double zoomMultiplicationFactor) { public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel,
this.targetComponent = targetComponent; double zoomMultiplicationFactor) {
this.minZoomLevel = minZoomLevel; this.targetComponent = targetComponent;
this.maxZoomLevel = maxZoomLevel; this.minZoomLevel = minZoomLevel;
this.zoomMultiplicationFactor = zoomMultiplicationFactor; this.maxZoomLevel = maxZoomLevel;
} this.zoomMultiplicationFactor = zoomMultiplicationFactor;
}
public void translate(double dx, double dy) { public void translate(double dx, double dy) {
coordTransform.translate(dx, dy); coordTransform.translate(dx, dy);
targetComponent.repaint(); targetComponent.repaint();
} }
public void mouseClicked(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) { public void mousePressed(MouseEvent e) {
} dragStartScreen = e.getPoint();
dragEndScreen = null;
}
public void mousePressed(MouseEvent e) { public void mouseReleased(MouseEvent e) {
dragStartScreen = e.getPoint(); }
dragEndScreen = null;
}
public void mouseReleased(MouseEvent e) { public void mouseEntered(MouseEvent e) {
// moveCamera(e); }
}
public void mouseEntered(MouseEvent e) { public void mouseExited(MouseEvent e) {
} }
public void mouseExited(MouseEvent e) { public void mouseMoved(MouseEvent e) {
} }
public void mouseMoved(MouseEvent e) { public void mouseDragged(MouseEvent e) {
} moveCamera(e);
}
public void mouseDragged(MouseEvent e) { public void mouseWheelMoved(MouseWheelEvent e) {
moveCamera(e); zoomCamera(e);
} }
public void mouseWheelMoved(MouseWheelEvent e) { private void moveCamera(MouseEvent e) {
zoomCamera(e); try {
} dragEndScreen = e.getPoint();
Point2D.Float dragStart = transformPoint(dragStartScreen);
Point2D.Float dragEnd = transformPoint(dragEndScreen);
double dx = dragEnd.getX() - dragStart.getX();
double dy = dragEnd.getY() - dragStart.getY();
coordTransform.translate(dx, dy);
dragStartScreen = dragEndScreen;
dragEndScreen = null;
targetComponent.repaint();
}
catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
private void moveCamera(MouseEvent e) { private void zoomCamera(MouseWheelEvent e) {
try { try {
dragEndScreen = e.getPoint(); int wheelRotation = e.getWheelRotation();
Point2D.Float dragStart = transformPoint(dragStartScreen); Point p = e.getPoint();
Point2D.Float dragEnd = transformPoint(dragEndScreen); if (wheelRotation < 0) {
double dx = dragEnd.getX() - dragStart.getX(); if (zoomLevel < maxZoomLevel) {
double dy = dragEnd.getY() - dragStart.getY(); zoomLevel++;
coordTransform.translate(dx, dy); Point2D p1 = transformPoint(p);
dragStartScreen = dragEndScreen; coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
dragEndScreen = null; Point2D p2 = transformPoint(p);
targetComponent.repaint(); coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
} catch (NoninvertibleTransformException ex) { targetComponent.repaint();
ex.printStackTrace(); }
} }
} else {
if (zoomLevel > minZoomLevel) {
zoomLevel--;
Point2D p1 = transformPoint(p);
coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor);
Point2D p2 = transformPoint(p);
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
targetComponent.repaint();
}
}
}
catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
private void zoomCamera(MouseWheelEvent e) { private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException {
try {
int wheelRotation = e.getWheelRotation();
Point p = e.getPoint();
if (wheelRotation > 0) {
if (zoomLevel < maxZoomLevel) {
zoomLevel++;
Point2D p1 = transformPoint(p);
coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor);
Point2D p2 = transformPoint(p);
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
targetComponent.repaint();
}
} else {
if (zoomLevel > minZoomLevel) {
zoomLevel--;
Point2D p1 = transformPoint(p);
coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
Point2D p2 = transformPoint(p);
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
targetComponent.repaint();
}
}
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException { AffineTransform inverse = coordTransform.createInverse();
AffineTransform inverse = coordTransform.createInverse(); Point2D.Float p2 = new Point2D.Float();
inverse.transform(p1, p2);
return p2;
}
Point2D.Float p2 = new Point2D.Float(); public int getZoomLevel() {
inverse.transform(p1, p2); return zoomLevel;
return p2; }
}
public int getZoomLevel() { public void setZoomLevel(int zoomLevel) {
return zoomLevel; this.zoomLevel = zoomLevel;
} }
public void zoomIn() {
try {
Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2);
zoomLevel++;
Point2D p1 = transformPoint(p);
coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
Point2D p2 = transformPoint(p);
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
targetComponent.repaint();
}
catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
public void setZoomLevel(int zoomLevel) { public void zoomOut() {
this.zoomLevel = zoomLevel; try {
} Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2);
zoomLevel--;
Point2D p1 = transformPoint(p);
coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor);
Point2D p2 = transformPoint(p);
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
targetComponent.repaint();
}
catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
public AffineTransform getCoordTransform() {
return coordTransform;
}
public AffineTransform getCoordTransform() { public void setCoordTransform(AffineTransform coordTransform) {
return coordTransform; this.coordTransform = coordTransform;
} }
public void setCoordTransform(AffineTransform coordTransform) {
this.coordTransform = coordTransform;
}
} }

BIN
zoomIn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
zoomOut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB