Search code examples
javaswingrepaintcustom-painting

Custom painting code does not update correctly and I try to honor the clip area. Run it and see


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();

}

}


Solution

  • 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).