user3075478
user3075478

Reputation: 137

Inaccurate grid's cells coordinates

Using swing and awt i've created a grid. The user can click over any empty cell of the grid, marking it as a red cell. The grid is composed by 20 x 20 cells, 32 pixels each.

When the user clicks over a cell, i get the x and y coordinates of the mouse click and i perform the following calculation to find out which is the selected cell:

int mouseX = e.getX();
int mouseY = e.getY();

int row = mouseY / Model.NODE_SIZE;
int col = mouseX / Model.NODE_SIZE;

The problem is that row and col get inaccurate (wrong) when i click near the edges of a cell (right border or bottom border), resulting in the marking of the next cell (cell to the right or cell to the bottom) and not in the marking of the correct one that i'm clicking over.

The more i get closest to the right/bottom borders of the grid, the more the accuracy is bad.

This is how i draw the grid:

public class MainPanel extends JPanel {

// reference to the model
private Model model;

public MainPanel() {

    setPreferredSize(new Dimension(660, 660));

    // retrieve the model
    model = Controller.getController().getModel();

    // draw 
    repaint();

    // listen to mouse clicks
    addMouseListener(new MouseAdapter() {

        @Override
        public void mouseClicked(MouseEvent e) {

            int mouseX = e.getX();
            int mouseY = e.getY();

            System.out.println("mouseX: " + mouseX + " mouseY: " + mouseY);

            // get the row and column clicked
            int row = mouseY / Model.NODE_SIZE;
            int col = mouseX / Model.NODE_SIZE;

            if(row > Model.BOARD_SIZE - 1 || col > Model.BOARD_SIZE - 1) { // avoid out of bounds
                return;
            }

            System.out.println("row: " + row + " col: " + col);

            Controller.getController().getModel().setTarget(col, row);

            repaint();
        }
    });
}

/** 
 * Custom painting codes on this JPanel 
 * Called by repaint()
 */
@Override
public void paintComponent(Graphics g) { 
    super.paintComponent(g);    // fills background
    setBackground(Color.WHITE); // sets background color

    // draw the tiles
    Node[][] nodes = model.getBoard();

    for(int i = 0; i < Model.BOARD_SIZE; i++) {
        for(int j = 0; j < Model.BOARD_SIZE; j++) {
            if(nodes[i][j].getNodeType() == NodeType.OBSTACLE) {
                g.setColor(Color.BLACK);
                g.fillRect(i + Model.NODE_SIZE * i, j + Model.NODE_SIZE * j, Model.NODE_SIZE, Model.NODE_SIZE);
            }
            else if(nodes[i][j].getNodeType() == NodeType.SOURCE) {
                g.setColor(Color.GREEN);
                g.fillRect(i + Model.NODE_SIZE * i, j + Model.NODE_SIZE * j, Model.NODE_SIZE, Model.NODE_SIZE);
            }
            else if(nodes[i][j].getNodeType() == NodeType.TARGET) {
                g.setColor(Color.RED);
                g.fillRect(i + Model.NODE_SIZE * i, j + Model.NODE_SIZE * j, Model.NODE_SIZE, Model.NODE_SIZE);
            }
            else {
                g.setColor(Color.BLACK);
                g.drawRect(i + Model.NODE_SIZE * i, j + Model.NODE_SIZE * j, Model.NODE_SIZE, Model.NODE_SIZE);
            }
        }
    }
}

}

Link to the runnable:

https://www.dropbox.com/s/bqoimipp7i1f39s/GridExample.jar?dl=0

Upvotes: 1

Views: 97

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347244

Your paint code is wrong, basically...

g.drawRect(i + Model.NODE_SIZE * i, j + Model.NODE_SIZE * j, Model.NODE_SIZE, Model.NODE_SIZE);

is increasing the size of each cell by a factor of the row/cell been drawn, for example...

Bad Screen

The green line is calculated by using g.drawRect(0, 0, BOARD_SIZE * NODE_SIZE, BOARD_SIZE * NODE_SIZE);, which should be the visible area of your grid, but you're drawing beyond it.

Instead, if I use...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new MainPanel());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class MainPanel extends JPanel {

        public static final int NODE_SIZE = 32;
        public static final int BOARD_SIZE = 8;

        private int row, col = -1;

        public MainPanel() {

            // listen to mouse clicks
            addMouseListener(new MouseAdapter() {

                @Override
                public void mouseClicked(MouseEvent e) {

                    int mouseX = e.getX();
                    int mouseY = e.getY();

                    System.out.println("mouseX: " + mouseX + " mouseY: " + mouseY);

                    // get the row and column clicked
                    int row = mouseY / NODE_SIZE;
                    int col = mouseX / NODE_SIZE;

                    if (row > BOARD_SIZE - 1 || col > BOARD_SIZE - 1) { // avoid out of bounds
                        return;
                    }

                    System.out.println("row: " + row + " col: " + col);

                    repaint();
                }
            });

            addMouseMotionListener(new MouseAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    int mouseX = e.getX();
                    int mouseY = e.getY();
                    row = mouseY / NODE_SIZE;
                    col = mouseX / NODE_SIZE;
                    repaint();
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(BOARD_SIZE * NODE_SIZE, BOARD_SIZE * NODE_SIZE);
        }

        /**
         * Custom painting codes on this JPanel Called by repaint()
         */
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);    // fills background
            setBackground(Color.WHITE); // sets background color

            g.setColor(Color.GREEN);

            for (int i = 0; i < BOARD_SIZE; i++) {
                for (int j = 0; j < BOARD_SIZE; j++) {
                    if (row == j && col == i) {
                        g.setColor(Color.RED);
                        g.fillRect(NODE_SIZE * i, NODE_SIZE * j, NODE_SIZE, NODE_SIZE);
                    }
                    g.setColor(Color.BLACK);
                    g.drawRect(NODE_SIZE * i, NODE_SIZE * j, NODE_SIZE, NODE_SIZE);
                }
            }
        }
    }
}

It becomes decidedly more accurate..

Grid

Another idea might be to use Rectangle to define each cell of the grid, that way you could just use Rectangle#contains to test if the mouse is within any given cell bounds...as an idea

Upvotes: 4

ug_
ug_

Reputation: 11440

Seeing this code:

g.fillRect(i + Model.NODE_SIZE * i, j + Model.NODE_SIZE * j, Model.NODE_SIZE, Model.NODE_SIZE);

In combination with this code:

int row = mouseY / Model.NODE_SIZE;
int col = mouseX / Model.NODE_SIZE;

is suspicious, your drawing your rectangles on screen with a size of i + Model.NODE_SIZE * i - so the size of rectangles on screen are offset by 1 for every preceding rectangle. This offset would compound making your underlying "detection" of the rectangle you selected slowly get worse the further bottom right on the screen you click.

I suspect if you change your code to read

g.fillRect(Model.NODE_SIZE * i, Model.NODE_SIZE * j, Model.NODE_SIZE, Model.NODE_SIZE);

It will work as intended.

Upvotes: 3

Related Questions