0

This is runnable java code. If you want to test it, copy the two code excerpts below. compile and run Triangleframe.java

I am drawing two triangles(may be I add more later) on a JPanel. I click on one of the triangles and drag it. This used to work (sort of) before I decided to honor the clip area as recommended by Oracle in this lesson: Custom painting

The reason I switched from repaint() to repaint(x,y,with,height) was that when I tried to drag one of the triangles, it was very sluggish to repaint, and it was not very good at following the mousepointer either(lag?). I reasoneded that staying within bounds and repainting only the portion of the screen I am using would fix the problem. It did fix the lag, but now the boundingbox I am repainting does not seem to move as long as the mouse button is pressed. The triangle is only moving withing the bounding box. Not until I release the mousebutton that is(at which point a new triangle is created). Preferrably I should only be redrawing only the triangle and not the bounding box, but for convenience sake I try to tackle this problem first. It is desireable that the triangles are able to overlap.

see comments in code for more in depth explanation.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle; 
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JPanel;

//********************************************************************
//*** This is a stripped down version of the code which shows      ***
//*** the problem. Just create an instance of the class by running *** 
//*** TriangleFrame.java. Then try to drag either triangle with    ***
//*** the mouse.                                                   ***
//********************************************************************
public class TrianglePanel extends JPanel implements MouseListener,
    MouseMotionListener {

    Triangle triangle1 = null;
    Triangle triangle2 = null;
    Rectangle boundingBox = null;

    int lastXPos = 0;
    int lastYPos = 0;
    boolean draggable = false;

    public TrianglePanel() {

        triangle1 = new Triangle(new Point(100, 10), new Point(50, 100),
                new Point(150, 100));
        triangle2 = new Triangle(new Point(250, 10), new Point(150, 100),
                new Point(350, 100));
        lastXPos = this.getX();
        lastYPos = this.getY();

        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    public void mouseReleased(MouseEvent e) {

        triangle1.createNewTriangle();
        triangle2.createNewTriangle();
    }

    @Override
    public void mousePressed(MouseEvent e) {
        draggable = false;
        if (triangle1.getPos().contains(e.getPoint())
                || triangle2.getPos().contains(e.getPoint())) {
            draggable = true;
            lastXPos = e.getX();
            lastYPos = e.getY();
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (draggable) {
            isInside(triangle1, e);
            isInside(triangle2, e);
        }
    }

    private void isInside(Triangle t, MouseEvent e) {

        if (t.getPos().contains(e.getPoint()))
            updatePos(t, e);
    }

    //*****************************************************************
    //*** Next I try to do the right thing by only repainting the   ***   
    //*** portion of the panel that I use.                          ***
    //*** Well, almost. To make it as simple as possible for now    ***
    //*** I use a boundingbox rectangle and repaint within those    ***
    //*** bounds. The problem seem to be that the rest of the panel ***
    //*** does not want to repaint anything outside the bounding    ***
    //*** box, until I release the mousebutton(after click+dragging)***
    //*** When the mousebutton is released, a new triangle is created**
    //*** in the same spot. Se mousereleased method. Otherwise      ***
    //*** I would only be able to drag the triangle once            ***
    //*****************************************************************
    private void updatePos(Triangle t, MouseEvent event) {

        boundingBox = t.getPos().getBounds();

        // stored as final variables to avoid repeat invocations to methods.
        // is this a problem? Anybody care to explain?
        final int CURR_X = boundingBox.x;
        final int CURR_Y = boundingBox.y;
        final int CURR_W = boundingBox.width;
        final int CURR_H = boundingBox.height;
        final int OFFSET = 1;

        if ((CURR_X != event.getX()) || (CURR_Y != event.getY())) {

            // paint over the bounding-box of the old triangle
            repaint(CURR_X, CURR_Y, CURR_W + OFFSET, CURR_H + OFFSET);

            // update x-coordinates
            int xPos = event.getX();
            int[] xPoints = t.getPos().xpoints; // get old x coordinates
            for (int i = 0; i < xPoints.length; i++) {
                xPoints[i] = xPoints[i] - (lastXPos - xPos); // create new x
                                                             // coordinates
            }
            lastXPos = xPos;

            // update y-coordinates
            int yPos = event.getY();
            int[] yPoints = t.getPos().ypoints; // get old y coordinates
            for (int i = 0; i < yPoints.length; i++) {
                yPoints[i] = yPoints[i] - (lastYPos - yPos); // create new y
                                                             // coordinates
            }
            lastYPos = yPos;

            // paint inside bounding box of the new triangle
            repaint(boundingBox.x, boundingBox.y, boundingBox.width + OFFSET,
                    boundingBox.height + OFFSET);

            // repaint the whole panel (not recommended).
            // repaint(); //-> makes dragging the triangle sluggish.
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.red);
        triangle1.draw(g);
        triangle2.draw(g);
    }

    // not used
    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    /**
     * 
     * Inner Triangle Class.
     * A polygon object is used for convenience to
     * create the Triangle. Otherwise I would
     * have to deal with Points all through the program. Which means
     * 3 coordinate pairs = 6 coordinates, which
     * means more code.
     * 
     */
    private class Triangle {

        private Polygon polygon;

        private Triangle(Point p1, Point p2, Point p3) {

            polygon = new Polygon();
            polygon.addPoint(p1.x, p1.y);
            polygon.addPoint(p2.x, p2.y);
            polygon.addPoint(p3.x, p3.y);
        }

        public Polygon getPos() {
            return polygon;
        }

        public void createNewTriangle() {
            polygon = new Polygon(polygon.xpoints, polygon.ypoints,
                    polygon.npoints);
        }

        public void draw(Graphics g) {
            g.fillPolygon(polygon);
        }

    } // end inner class Triangle
} // end outer class TrianglePanel

For your convenience I have provided the class containing the main-method(runnable from here):

import java.awt.Dimension;
import javax.swing.JFrame;

public class TriangleFrame extends JFrame {

public TriangleFrame() {
    this.setTitle("Draggable triangles. Click one and drag it with the   mouse.");
    TrianglePanel panel = new TrianglePanel();
    panel.setPreferredSize(new Dimension(500, 500));
    this.add(panel);
    pack();
    setVisible(true);
}
public static void main(String[] args) {
    new TriangleFrame();

}

}

  • `For your convenience I have provided the class containing the main-method` - actually for our convenience the main() method to should be part of the Triangle class that you want us to test so we only have to copy/paste one source file. – camickr Jul 15 '15 at 19:21
  • Right you are sir. I will remember that for future posts. – Tore Bjerkan Jul 15 '15 at 22:30

3 Answers3

2

Polygons cache the bounding box. If you modify the coordinates directly, you must call Polygon.invalidate():

// paint inside bounding box of the new triangle
t.getPos().invalidate();
boundingBox = t.getPos().getBounds();
repaint(boundingBox);

However, it would be easier to use Polygon.translate(int deltaX, int deltaY), which does all the work for you. (Modifies the coordinates and ensures the bounding box is correct at the next call). I also used repaint(Rectangle).

kiheru
  • 6,588
  • 25
  • 31
  • 2
    (1+), the Polygon.translate(...) would have been my suggestion. No need to manage the Array of Points manually. – camickr Jul 15 '15 at 19:37
  • Hey Polygon.invalidate() worked. Learned something new today. Splendid.However, I will test Polygon.translate as suggeted. – Tore Bjerkan Jul 16 '15 at 13:34
2

The triangle is only moving withing the bounding box.

Because as you state in your question you only create a new Triange on mouseReleased so the bounding box never changes.

I personally never like to drag "painted" objects. I would rather deal with real components and drag them around the screen.

If you are interested in this approach you can check out Playing With Shapes. It shows how you can create a ShapeComponent using a ShapeIcon. You can easily create the ShapeIcon using the Polygon class.

You can then drag the component around the screen using the Component Mover. The ComponentMover class just uses the setLocation() method to move the component.

When you use the approach you don't need to worry about new Polygon coordinates because the Polygon is painted as an Icon which is always painted at the same offset to the label.

camickr
  • 321,443
  • 19
  • 166
  • 288
1

I tried to use a repaint bounding box, but when I moved the triangle quickly, the redraw left artifacts of the triangles in the previous positions.

Here's the GUI I created.

Draggable Triangles

Here's the TrianglePanel class.

package com.ggl.triangle;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;

//********************************************************************
//*** This is a stripped down version of the code which shows      ***
//*** the problem. Just create an instance of the class by running *** 
//*** TriangleFrame.java. Then try to drag either triangle with    ***
//*** the mouse.                                                   ***
//********************************************************************
public class TrianglePanel extends JPanel implements MouseListener,
        MouseMotionListener {

    private static final long serialVersionUID = 5615435125201426466L;

    private static final boolean DEBUG = false;

    private List<Triangle> triangles;

    private Triangle dragTriangle;

    private int originalXPos = 0;
    private int originalYPos = 0;

    private boolean draggable = false;

    public TrianglePanel() {
        setPreferredSize(new Dimension(600, 400));

        triangles = new ArrayList<>();
        triangles.add(new Triangle(Color.RED, new Point(100, 10), new Point(50,
                100), new Point(150, 100)));
        triangles.add(new Triangle(Color.BLUE, new Point(350, 10), new Point(
                250, 100), new Point(450, 100)));

        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (draggable) {
            repaint();
        }

        this.originalXPos = 0;
        this.originalYPos = 0;

        this.draggable = false;
    }

    @Override
    public void mousePressed(MouseEvent e) {
        this.draggable = false;

        Point p = e.getPoint();
        if (DEBUG) {
            System.out.println("mousePressed: x: " + p.x + ", y: " + p.y);
        }

        for (Triangle triangle : triangles) {
            if (DEBUG) {
                System.out.println(triangle);
            }

            if (triangle.contains(p.x, p.y)) {
                if (DEBUG) {
                    System.out.println("Mouse pressed point is contained "
                            + "in the triangle");
                }

                originalXPos = p.x;
                originalYPos = p.y;
                dragTriangle = triangle;
                draggable = true;
                break;
            } else {
                if (DEBUG) {
                    System.out.println("Mouse pressed point is not "
                            + "contained in the triangle");
                }
            }
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (draggable) {
            dragTriangle.moveTriangle(e.getX() - originalXPos, e.getY()
                    - originalYPos);
            originalXPos = e.getX();
            originalYPos = e.getY();

            repaint();
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        for (Triangle triangle : triangles) {
            triangle.draw(g);
        }
    }

    // not used
    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    /**
     * 
     * Inner Triangle Class. A polygon object is used for convenience to create
     * the Triangle. Otherwise I would have to deal with Points all through the
     * program. Which means 3 coordinate pairs = 6 coordinates, which means more
     * code.
     * 
     */
    private class Triangle {

        private Color color;

        private Polygon polygon;

        private Triangle(Color color, Point p1, Point p2, Point p3) {
            this.color = color;

            polygon = new Polygon();
            polygon.addPoint(p1.x, p1.y);
            polygon.addPoint(p2.x, p2.y);
            polygon.addPoint(p3.x, p3.y);
        }

        public boolean contains(int x, int y) {
            return polygon.contains(x, y);
        }

        public void moveTriangle(int x, int y) {
            polygon.translate(x, y);
        }

        public void draw(Graphics g) {
            g.setColor(color);
            g.fillPolygon(polygon);
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("Triangle [polygon=");
            polygonToString(builder, polygon);
            builder.append("]");

            return builder.toString();
        }

        private void polygonToString(StringBuilder builder, Polygon polygon) {
            for (int i = 0; i < polygon.npoints; i++) {
                Point p = new Point(polygon.xpoints[i], polygon.ypoints[i]);
                builder.append(p);
                if (i < (polygon.npoints - 1)) {
                    builder.append(",");
                } else {
                    builder.append("]");
                }
            }
        }

    } // end inner class Triangle

} // end outer class TrianglePanel

Here's the TriangleFrame class.

package com.ggl.triangle;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TriangleFrame extends JFrame {

    private static final long serialVersionUID = -4599398094173430071L;

    public TriangleFrame() {
        init();
    }

    private void init() {
        this.setTitle("Draggable triangles. Click one and drag "
                + "it with the mouse.");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        TrianglePanel panel = new TrianglePanel();
        this.add(panel);
        pack();
    }

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                TriangleFrame frame = new TriangleFrame();
                frame.setVisible(true);
            }

        };

        SwingUtilities.invokeLater(runnable);
    }
}

Let me know if this is fast enough on your computer.

Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111
  • I was experiencing artifacts as well when using bounding box. I have run your code and it is indeed fast enough. It is quite elegant. Thank you. – Tore Bjerkan Jul 16 '15 at 18:00
  • @Tore Bjerkan: You're welcome. I guess render bounding boxes have a use with static drawings, like a mini-map superimposed on the corner of a game map. – Gilbert Le Blanc Jul 16 '15 at 18:51