Absolute Positioning Graphic JPanel Inside JFrame Blocked by Blank Sections

I was really intrigued by this idea, so I made another example, using a custom layout manager.

public class MyPuzzelBoard extends JPanel {

    public static final int GRID_X = 4;
    public static final int GRID_Y = 4;
    private BufferedImage image;

    public MyPuzzelBoard(BufferedImage image) {
        setLayout(new VirtualLayoutManager());
        setImage(image);

        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    removeAll();
                    generatePuzzel();
                } else {
                    Component comp = getComponentAt(e.getPoint());
                    if (comp != null && comp != MyPuzzelBoard.this) {
                        setComponentZOrder(comp, 0);
                        invalidate();
                        revalidate();
                        repaint();
                    }
                }
            }
        });
    }

    public void setImage(BufferedImage value) {
        if (value != image) {
            image = value;
            removeAll();
            generatePuzzel();
        }
    }

    public BufferedImage getImage() {
        return image;
    }

    protected float generateRandomNumber() {
        return (float) Math.random();
    }

    protected void generatePuzzel() {
        BufferedImage image = getImage();

        if (image != null) {
            int imageWidth = image.getWidth();
            int imageHeight = image.getHeight();

            int clipWidth = imageWidth / GRID_X;
            int clipHeight = imageHeight / GRID_Y;
            for (int x = 0; x < GRID_X; x++) {
                for (int y = 0; y < GRID_Y; y++) {

                    float xPos = generateRandomNumber();
                    float yPos = generateRandomNumber();
                    Rectangle bounds = new Rectangle((x * clipWidth), (y * clipHeight), clipWidth, clipHeight);
                    MyPiece piece = new MyPiece(image, bounds);
                    add(piece, new VirtualPoint(xPos, yPos));

                }
            }
        }

        invalidate();
        revalidate();
        repaint();
    }

    public class VirtualPoint {

        private float x;
        private float y;

        public VirtualPoint(float x, float y) {
            this.x = x;
            this.y = y;
        }

        public float getX() {
            return x;
        }

        public float getY() {
            return y;
        }

        public void setX(float x) {
            this.x = x;
        }

        public void setY(float y) {
            this.y = y;
        }
    }

    public class VirtualLayoutManager implements LayoutManager2 {

        private Map<Component, VirtualPoint> mapConstraints;

        public VirtualLayoutManager() {
            mapConstraints = new WeakHashMap<>(25);
        }

        @Override
        public void addLayoutComponent(Component comp, Object constraints) {
            if (constraints instanceof VirtualPoint) {
                mapConstraints.put(comp, (VirtualPoint) constraints);
            }
        }

        @Override
        public Dimension maximumLayoutSize(Container target) {
            return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
        }

        @Override
        public float getLayoutAlignmentX(Container target) {
            return 0.5f;
        }

        @Override
        public float getLayoutAlignmentY(Container target) {
            return 0.5f;
        }

        @Override
        public void invalidateLayout(Container target) {
        }

        @Override
        public void addLayoutComponent(String name, Component comp) {
        }

        @Override
        public void removeLayoutComponent(Component comp) {
            mapConstraints.remove(comp);
        }

        @Override
        public Dimension preferredLayoutSize(Container parent) {
            return new Dimension(400, 400);
        }

        @Override
        public Dimension minimumLayoutSize(Container parent) {
            return preferredLayoutSize(parent);
        }

        @Override
        public void layoutContainer(Container parent) {
            int width = parent.getWidth();
            int height = parent.getHeight();

            for (Component comp : parent.getComponents()) {

                VirtualPoint p = mapConstraints.get(comp);
                if (p != null) {

                    int x = Math.round(width * p.getX());
                    int y = Math.round(height * p.getY());

                    Dimension size = comp.getPreferredSize();

                    x = Math.min(x, width - size.width);
                    y = Math.min(y, height - size.height);

                    comp.setBounds(x, y, size.width, size.height);

                }
            }
        }
    }
}

Basically, this uses a “virtual” coordinate system, where by rather then supply absolute x/y positions in pixels, you provide them as percentage of the parent container. Now, to be honest, it wouldn’t take much to convert back to absolute positioning, just this way, you also get layout scaling.

The example also demonstrates Z-reording (just in case) and the double click simple re-randomizes the puzzel

Oh, I also made the piece transparent (opaque = false)

Randomized layout

Oh, one thing I should mention, while going through this example, I found that it was possible to have pieces placed off screen (completely and partially).

You may want to check your positioning code to make sure that the images when they are laid out aren’t been moved off screen 😉

Leave a Comment