diff --git a/src/main/org/insa/graphics/drawing/components/BasicDrawing.java b/src/main/org/insa/graphics/drawing/components/BasicDrawing.java index 75a3ee8..cd0230f 100644 --- a/src/main/org/insa/graphics/drawing/components/BasicDrawing.java +++ b/src/main/org/insa/graphics/drawing/components/BasicDrawing.java @@ -5,12 +5,16 @@ import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; @@ -286,17 +290,42 @@ public class BasicDrawing extends JPanel implements Drawing { // Mapping DrawingClickListener -> MouseEventListener private Map listenerMapping = new IdentityHashMap<>(); + // Zoom controls + private MapZoomControls zoomControls; + /** * Create a new BasicDrawing. * */ public BasicDrawing() { 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 public void paintComponent(Graphics g1) { + super.paintComponent(g1); Graphics2D g = (Graphics2D) g1; + AffineTransform sTransform = g.getTransform(); g.clearRect(0, 0, getWidth(), getHeight()); g.setTransform(zoomAndPanListener.getCoordTransform()); @@ -311,6 +340,14 @@ public class BasicDrawing extends JPanel implements Drawing { 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.zoomAndPanListener.getCoordTransform().scale(scale, scale); this.zoomAndPanListener.setZoomLevel(0); + this.zoomControls.setZoomLevel(0); // Repaint this.repaint(); diff --git a/src/main/org/insa/graphics/drawing/components/MapZoomControls.java b/src/main/org/insa/graphics/drawing/components/MapZoomControls.java new file mode 100644 index 0000000..0e18a33 --- /dev/null +++ b/src/main/org/insa/graphics/drawing/components/MapZoomControls.java @@ -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 zoomInListeners = new ArrayList<>(); + private final List 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); + + } + +} diff --git a/src/main/org/insa/graphics/drawing/components/ZoomAndPanListener.java b/src/main/org/insa/graphics/drawing/components/ZoomAndPanListener.java index c52f06c..764861d 100644 --- a/src/main/org/insa/graphics/drawing/components/ZoomAndPanListener.java +++ b/src/main/org/insa/graphics/drawing/components/ZoomAndPanListener.java @@ -1,141 +1,176 @@ package org.insa.graphics.drawing.components; -import java.awt.*; -import java.awt.event.*; +import java.awt.Component; +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.NoninvertibleTransformException; import java.awt.geom.Point2D; public class ZoomAndPanListener implements MouseListener, MouseMotionListener, MouseWheelListener { - public static final int DEFAULT_MIN_ZOOM_LEVEL = -20; - public static final int DEFAULT_MAX_ZOOM_LEVEL = 10; - public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2; + public static final int DEFAULT_MIN_ZOOM_LEVEL = -20; + public static final int DEFAULT_MAX_ZOOM_LEVEL = 10; + public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2; - private Component targetComponent; + private Component targetComponent; - private int zoomLevel = 0; - private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL; - private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL; - private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR; + private int zoomLevel = 0; + private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL; + private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL; + private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR; - private Point dragStartScreen; - private Point dragEndScreen; - private AffineTransform coordTransform = new AffineTransform(); + private Point dragStartScreen; + private Point dragEndScreen; + private AffineTransform coordTransform = new AffineTransform(); - public ZoomAndPanListener(Component targetComponent) { - this.targetComponent = targetComponent; - } + public ZoomAndPanListener(Component targetComponent) { + this.targetComponent = targetComponent; + } - public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel, double zoomMultiplicationFactor) { - this.targetComponent = targetComponent; - this.minZoomLevel = minZoomLevel; - this.maxZoomLevel = maxZoomLevel; - this.zoomMultiplicationFactor = zoomMultiplicationFactor; - } - - public void translate(double dx, double dy) { - coordTransform.translate(dx, dy); - targetComponent.repaint(); - } + public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel, + double zoomMultiplicationFactor) { + this.targetComponent = targetComponent; + this.minZoomLevel = minZoomLevel; + this.maxZoomLevel = maxZoomLevel; + this.zoomMultiplicationFactor = zoomMultiplicationFactor; + } + public void translate(double dx, double dy) { + coordTransform.translate(dx, dy); + 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) { + dragStartScreen = e.getPoint(); + dragEndScreen = null; + } - public void mouseReleased(MouseEvent e) { -// moveCamera(e); - } + public void mouseReleased(MouseEvent e) { + } - public void mouseEntered(MouseEvent 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) { + moveCamera(e); + } - public void mouseWheelMoved(MouseWheelEvent e) { - zoomCamera(e); - } + public void mouseWheelMoved(MouseWheelEvent e) { + zoomCamera(e); + } - private void moveCamera(MouseEvent 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) { + 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 zoomCamera(MouseWheelEvent e) { - 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 void zoomCamera(MouseWheelEvent e) { + try { + int wheelRotation = e.getWheelRotation(); + Point p = e.getPoint(); + if (wheelRotation < 0) { + if (zoomLevel < maxZoomLevel) { + 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(); + } + } + 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 Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException { + 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(); + inverse.transform(p1, p2); + return p2; + } - public int getZoomLevel() { - return zoomLevel; - } + public int getZoomLevel() { + return zoomLevel; + } + public void setZoomLevel(int zoomLevel) { + this.zoomLevel = zoomLevel; + } - public void setZoomLevel(int 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 zoomOut() { + 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() { + return coordTransform; + } - public void setCoordTransform(AffineTransform coordTransform) { - this.coordTransform = coordTransform; - } + public void setCoordTransform(AffineTransform coordTransform) { + this.coordTransform = coordTransform; + } } \ No newline at end of file diff --git a/zoomIn.png b/zoomIn.png new file mode 100644 index 0000000..52e80bd Binary files /dev/null and b/zoomIn.png differ diff --git a/zoomOut.png b/zoomOut.png new file mode 100644 index 0000000..1d01d90 Binary files /dev/null and b/zoomOut.png differ