Results 1 to 3 of 3
  1. #1
    koyotter is offline Member
    Join Date
    Jan 2016
    Posts
    1
    Rep Power
    0

    Default Java2D zoom and scroll relative to mouse position

    I am trying to create a simple drawing application using swing and java2D. The aim is to achieve a smooth zoom, always relative to the mouse cursor point. The application consists of two classes: CanvasPane and Canvas.
    • CanvasPane class is a simple container with BorderLayout and JScrollPane in the center.
    • Canvas class is a drawing component added to a JScrollPane in CanvasPane. Canvas draws a simple rectangle[800x600], and dispatches it's mouse events (wheel and drag).

    When rectangle is smaller then visibleRect, canvas size is equal to visibleRect and I call AffineTransform.translate to follow mouse (thanks to this question)

    When rectangle grows bigger then canvas, canvas size grows too and became scrollable. Then I call scrollRectToVisible on it to follow mouse.

    The question is: How to use translate and scrollRectToVisible together, to smooth scale without graphics jumps. May be there is some known decision?

    What I want is perfectly realized in Yed graph editor, but it's code is closed. I have tried with many examples but there were only zoom or scroll without complex usage of them.

    Full code follows.

    Class CanvasPane:
    Java Code:
    import javax.swing.*;
    import java.awt.*;
    
    
    public class CanvasPane extends JPanel {
    
        private Canvas canvas;
    
        public CanvasPane(boolean isDoubleBuffered) {
            super(isDoubleBuffered);
            setLayout(new BorderLayout());
            canvas = new Canvas(1.0);
            JScrollPane pane = new JScrollPane(canvas);
            pane.getViewport().setBackground(Color.DARK_GRAY);
            add(pane, BorderLayout.CENTER);
        }
    
        public static void main(String[] args) {
            JFrame frame = new JFrame("Test Graphics");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLayout(new BorderLayout());
            frame.add(new CanvasPane(true), BorderLayout.CENTER);
            frame.setSize(new Dimension(1000, 800));
            frame.setVisible(true);
        }
    }
    Class Canvas:
    Java Code:
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.NoninvertibleTransformException;
    import java.awt.geom.Point2D;
    import java.awt.geom.Rectangle2D;
    
    
    public class Canvas extends JComponent implements MouseWheelListener, MouseMotionListener, MouseListener {
        private double zoom = 1.0;
        public static final double SCALE_STEP = 0.1d;
        private Dimension initialSize;
        private Point origin;
        private double previousZoom = zoom;
        AffineTransform tx = new AffineTransform();
        private double scrollX = 0d;
        private double scrollY = 0d;
        private Rectangle2D rect = new Rectangle2D.Double(0,0, 800, 600);
    
        public Canvas(double zoom) {
            this.zoom = zoom;
            addMouseWheelListener(this);
            addMouseMotionListener(this);
            addMouseListener(this);
            setAutoscrolls(true);
        }
    
        public Dimension getInitialSize() {
            return initialSize;
        }
    
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.clearRect(0, 0, getWidth(), getHeight());
            g2d.transform(tx);
            g2d.setColor(Color.DARK_GRAY);
            g2d.fill(rect);
            g2d.setColor(Color.GRAY);
            g2d.setStroke(new BasicStroke(5.0f));
            g2d.draw(rect);
            g2d.dispose();
        }
    
        @Override
        public void setSize(Dimension size) {
            super.setSize(size);
            if (initialSize == null) {
                this.initialSize = size;
            }
        }
    
        @Override
        public void setPreferredSize(Dimension preferredSize) {
            super.setPreferredSize(preferredSize);
            if (initialSize == null) {
                this.initialSize = preferredSize;
            }
        }
    
        public void mouseWheelMoved(MouseWheelEvent e) {
            double zoomFactor = - SCALE_STEP*e.getPreciseWheelRotation()*zoom;
            zoom = Math.abs(zoom + zoomFactor);
            //Here we calculate new size of canvas relative to zoom.
            Rectangle realView = getVisibleRect();
            Dimension d = new Dimension(
                    (int)(initialSize.width*zoom),
                    (int)(initialSize.height*zoom));
    //        if (d.getWidth() >= realView.getWidth() && d.getHeight() >= realView.getHeight()) {
                setPreferredSize(d);
                setSize(d);
                validate();
                followMouseOrCenter(e);
    //        }
    
            //Here we calculate transform for the canvas graphics to scale relative to mouse
                translate(e);
                repaint();
            previousZoom = zoom;
        }
    
        private void translate(MouseWheelEvent e) {
            Rectangle realView = getVisibleRect();
            Point2D p1 = e.getPoint();
            Point2D p2 = null;
            try {
                p2 = tx.inverseTransform(p1, null);
            } catch (NoninvertibleTransformException ex) {
                ex.printStackTrace();
                return;
            }
            Dimension d = getSize();
            if (d.getWidth() <= realView.getWidth() && d.getHeight() <= realView.getHeight()) {
                //Zooming and translating relative to the mouse position
                tx.setToIdentity();
                tx.translate(p1.getX(), p1.getY());
                tx.scale(zoom, zoom);
                tx.translate(-p2.getX(), -p2.getY());
            } else {
                //Only zooming, translate is not needed because scrollRectToVisible works;
                tx.setToIdentity();
                tx.scale(zoom, zoom);
            }
    //        What to do next?
    //        The only translation works when rect is smaller then canvas size.
    //        Rect bigger then canvas must be scrollable, but relative to mouse position as before.
            // But when the rect gets bigger than canvas, there is a terrible jump of a graphics.
            //So there must be some combination of translation ans scroll to achieve a smooth scale.
            //... brain explosion(((
        }
    
    
        public void followMouseOrCenter(MouseWheelEvent e) {
            Point2D point = e.getPoint();
            Rectangle visibleRect = getVisibleRect();
    
            scrollX = point.getX()/previousZoom*zoom - (point.getX()-visibleRect.getX());
            scrollY = point.getY()/previousZoom*zoom - (point.getY()-visibleRect.getY());
    
            visibleRect.setRect(scrollX, scrollY, visibleRect.getWidth(), visibleRect.getHeight());
            scrollRectToVisible(visibleRect);
        }
    
        public void mouseDragged(MouseEvent e) {
            if (origin != null) {
                int deltaX = origin.x - e.getX();
                int deltaY = origin.y - e.getY();
                Rectangle view = getVisibleRect();
                Dimension size = getSize();
                view.x += deltaX;
                view.y += deltaY;
                scrollRectToVisible(view);
            }
        }
    
        public void mouseMoved(MouseEvent e) {
        }
    
        public void mouseClicked(MouseEvent e) {
        }
    
        public void mousePressed(MouseEvent e) {
            origin = new Point(e.getPoint());
        }
    
        public void mouseReleased(MouseEvent e) {
    
        }
    
        public void mouseEntered(MouseEvent e) {
    
        }
    
        public void mouseExited(MouseEvent e) {
    
        }
    
    }

  2. #2
    jim829 is offline Senior Member
    Join Date
    Jan 2013
    Location
    Northern Virginia, United States
    Posts
    6,226
    Rep Power
    13

    Default Re: Java2D zoom and scroll relative to mouse position

    It is not advisable to mix Swing with AWT. I suggest you get away from Canvas and just use a JPanel.

    Regards,
    Jim
    The JavaTM Tutorials | SSCCE | Java Naming Conventions
    Poor planning on your part does not constitute an emergency on my part

  3. #3
    Norm's Avatar
    Norm is offline Moderator
    Join Date
    Jun 2008
    Location
    Eastern Florida
    Posts
    20,001
    Rep Power
    33

    Default Re: Java2D zoom and scroll relative to mouse position

    Note: It's better not to use the same name as the name of a Java SE class. Canvas is a Java SE class. Change your class's name to make it different.
    If you don't understand my response, don't ignore it, ask a question.

Similar Threads

  1. Replies: 5
    Last Post: 02-10-2014, 01:34 PM
  2. Retrieving mouse location inside a thread relative to an external JPanel.
    By Zambash in forum Threads and Synchronization
    Replies: 7
    Last Post: 08-25-2011, 04:33 PM
  3. Mouse scroll capture (GUI independent under linux)
    By krimen_sp in forum Advanced Java
    Replies: 1
    Last Post: 04-22-2011, 10:51 PM
  4. Replies: 1
    Last Post: 10-13-2008, 12:42 PM
  5. Controlling Page Scroll Position in Jsp....f there are fewer than 4 pages only that n
    By 82rathi.angara in forum JavaServer Pages (JSP) and JSTL
    Replies: 0
    Last Post: 07-05-2008, 02:11 PM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •