Dr.John.B.Matthews

ScaledView

Here is an example of using a scaled Graphics environment to create an arbitrarily resizable JPanel. Note that all drawing is done in arbitrary coordinates based on maxX and maxY and scaled to the current Component size. Because mouse coordinates are unscaled, they must be converted before use. The checker can be moved by clicking, dragging or using the arrow keys. Note that all relationships are preserved when resizing the frame changes the aspect ratio.

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;

/** @author John B. Matthews; distribution per GNU Public License */
public class ScaledPanel extends JPanel
    implements KeyListener, MouseListener, MouseMotionListener {

    private static final int SIZE = 8; // 8x8 board
    private static final int DIAM = SIZE * 10; // checker size
    private static final int maxX = SIZE * DIAM;
    private static final int maxY = SIZE * DIAM;
    private static final Color white = new Color(0xF0F0C0); 
    private static final Color light = new Color(0x40C040); 
    private static final Color dark  = new Color(0x404040);
    private static final Cursor hand = new Cursor(Cursor.HAND_CURSOR);
    private static final Cursor norm = new Cursor(Cursor.DEFAULT_CURSOR);
    private static final Font font = new Font("Serif", Font.BOLD, 48);
    private final Ellipse2D.Double checker = new Ellipse2D.Double();
    private final Rectangle boundary = new Rectangle();
    private int posX = 3 * DIAM; // column three
    private int posY = 3 * DIAM; // row three
    private int pressedX, pressedY;
    private int frameX, frameY;
    private boolean checkerVisible = true;
    private boolean mouseDown = false;
    
    public static void main(String args[]) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new ScaledPanel());
                frame.pack();
                frame.setVisible(true);
            }
        });
    }

    public ScaledPanel() {
        this.setPreferredSize(new Dimension(maxX, maxY));
        this.addKeyListener(this);
        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.setFocusable(true);
    }

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

        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2D.scale(
            (double) this.getWidth() / maxX,
            (double) this.getHeight() / maxY);

        for (int row = 0; row < SIZE; row++) {
             for (int col = 0; col < SIZE; col++) {
                 g.setColor((row + col) % 2 == 0 ? light : dark);
                 g.fillRect(col * DIAM, row * DIAM, DIAM, DIAM);
             }
        }

        if (checkerVisible) {
            checker.setFrame(posX, posY, DIAM, DIAM);
            g.setColor(white);
            g2D.fill(checker);
            g.setFont(font);
            int a = g.getFontMetrics().getAscent();
            int w = g.getFontMetrics().stringWidth("\u2748");
            int bx = posX + DIAM / 2 - w / 2 + 1;
            int by = posY + DIAM / 2 + a / 2 - 1;
            g.setColor(dark);
            g.drawString("\u2748", bx, by);
        }
    }

    public void keyTyped(KeyEvent e) {}
    public void keyReleased(KeyEvent e) {}
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_UP) moveChecker(0, -1);
        if (e.getKeyCode() == KeyEvent.VK_DOWN) moveChecker(0, 1);
        if (e.getKeyCode() == KeyEvent.VK_LEFT) moveChecker(-1, 0);
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) moveChecker(1, 0);
        if (e.getKeyCode() == KeyEvent.VK_HOME) {
            eraseChecker();
            posX = 3 * DIAM;
            posY = 3 * DIAM;
            repaintChecker();
        }
    }

    private void moveChecker(int dx, int dy) {
        eraseChecker();
        posX = posX + DIAM * dx;
        posY = posY + DIAM * dy;
        repaintChecker();
    }

    public void mouseClicked(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}

    public void mousePressed(MouseEvent e) {
        if (checker.contains(scaleX(e.getX()), scaleY(e.getY()))) {
            this.setCursor(hand);
            mouseDown = true;
            Rectangle2D r = checker.getFrame();
            frameX = (int) r.getX();
            frameY = (int) r.getY();
            pressedX = e.getX(); 
            pressedY = e.getY(); 
        } else {
            mouseDown = false;
        }
    }
    
    public void mouseReleased(MouseEvent e) {
        posX = ((int) scaleX(e.getX())) / DIAM * DIAM;
        posY = ((int) scaleY(e.getY())) / DIAM * DIAM;
        this.repaint();
        this.setCursor(norm);
        mouseDown = false;
    }
    
    public void mouseMoved(MouseEvent e) {}
    public void mouseDragged(MouseEvent e) {
        if (mouseDown) {
            updateChecker(e);
        }
    }
    
    public void updateChecker(MouseEvent e) {
        eraseChecker();
        int deltaX = (int) (scaleX(e.getX() - pressedX));
        int deltaY = (int) (scaleY(e.getY() - pressedY));
        posX = frameX + deltaX;
        posY = frameY + deltaY;
        repaintChecker();
    }

    private void eraseChecker () {
        checkerVisible = false;
        repaintChecker();
    }

    private void repaintChecker() {
        checkerVisible = true;
        int rawX = (int) (unScaleX(posX));
        int rawY = (int) (unScaleY(posY));
        int rawW = (int) (unScaleX(DIAM));
        int rawH = (int) (unScaleY(DIAM));
        boundary.setRect(rawX, rawY, rawW, rawH);
        boundary.grow(2, 2);
        this.repaint(boundary);
    }

    private double scaleX(int x) {
        return ((double) x / getWidth()) * maxX;
    }

    private double scaleY(int y) {
        return ((double) y / getHeight()) * maxY;
    }

    private double unScaleX(int x) {
        return ((double) x / maxX) * getWidth();
    }

    private double unScaleY(int y) {
        return ((double) y / maxY) * getHeight();
    }
}