Drawing lines between JLabel in 2d array grid - java

I'm trying to draw lines between cells which are JLabel but fail to get their coordinates.
The lines must follow a path from a startPoint to an endPoint.
In my View class, the below code creates the grid:
void createGrid(int rows, int cols) {
MyMouseListener listener = new MyMouseListener();
setRows(rows);
mainPanel.setLayout(new GridLayout(rows, cols, 1, 1));
mainPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
mainPanel.setBackground(Color.DARK_GRAY);
grid = new JLabel[rows][cols];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
grid[r][c] = new JLabel(iconMap.get(Token.VIDE));
grid[r][c].addMouseListener(listener);
grid[r][c].setOpaque(true);
grid[r][c].setBackground(Color.WHITE);
//grid[r][c].setPreferredSize(new Dimension(ICON_W, ICON_W));
mainPanel.add(grid[r][c]);
}
}
}
While this one creates the icons:
private Icon createIcon(Color color) {
BufferedImage img = new BufferedImage(ICON_W, ICON_W, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fillOval(1, 1, ICON_W - 2, ICON_W - 2); // int ICON_W = 20;
g2.dispose();
return new ImageIcon(img);
}
The whole code for the View class: https://pastebin.com/sgyn6rSR
Code for the path algo: https://pastebin.com/Gcw9YVMF
I've tried something like below, but doesn't work
Graphics2D g2d = (Graphics2D) g.create();
for (JLabel[] grid : cells) {
g2d.setColor(Color.BLUE);
Point startPoint = grid[0].getLocation();
Point endPoint = grid[1].getLocation();
startPoint.x += (grid[0].getWidth() / 2);
startPoint.y += (grid[1].getHeight()/ 2);
endPoint.x += (grid[0].getWidth() / 2);
endPoint.y += (grid[1].getHeight()/ 2);
if(startPoint != null){
g2d.draw(new Line2D.Double(startPoint, endPoint));
}
startPoint = endPoint;
}

Override the paintComponent() method of the panel you add the labels to.
The basic code would be:
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
for (int i = 0; i < getComponentCount() - 1; i++)
{
Component component1 = getComponent(i);
Point point1 = component1.getLocation();
Component component2 = getComponent(i + 1);
Point point2 = component2.getLocation();
g.drawLine(point1.x, point1.y, point2.x, point2.y);
}
}
Once you get this working you can modify the logic to draw the line from the from/to the center of each component.

That took a long time. Sorry but I have a lot of context to the original question, including the base View and Path code :P
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.StringJoiner;
import javax.swing.BorderFactory;
import javax.swing.DebugGraphics;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class View extends JPanel {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
DrawLines drawLines = new DrawLines();
JFrame frame = new JFrame();
frame.setGlassPane(drawLines);
frame.add(new View(20, 20, drawLines));
frame.getGlassPane().setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
enum Token {
VIDE, CERCLE_BLEU, CERCLE_ROUGE
}
private static final int ICON_W = 20;
public static final String CELL_SELECTION = "cell selection";
private int rows;
private Token[][] tokens;
private JLabel[][] grid;
private Map<Token, Icon> iconMap = new EnumMap<>(Token.class);
private int selectedRow;
private int selectedCol;
//a collection of cells representing a path
private ArrayList<Cell> path;
private DrawLines drawLines;
View(int rows, int cols, DrawLines drawLines) {
this.drawLines = drawLines;
iconMap.put(Token.VIDE, createIcon(new Color(0, 0, 0, 0)));
iconMap.put(Token.CERCLE_BLEU, createIcon(Color.BLUE));
iconMap.put(Token.CERCLE_ROUGE, createIcon(Color.RED));
createGrid(rows, cols);
}
private Icon createIcon(Color color) {
BufferedImage img = new BufferedImage(ICON_W, ICON_W, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fillOval(1, 1, ICON_W - 2, ICON_W - 2);
g2.dispose();
return new ImageIcon(img);
}
void createGrid(int rows, int cols) {
tokens = new Token[rows][cols];
MyMouseListener listener = new MyMouseListener();
setRows(rows);
setLayout(new GridLayout(rows, cols, 1, 1));
setBorder(BorderFactory.createLineBorder(Color.BLACK));
setBackground(Color.DARK_GRAY);
grid = new JLabel[rows][cols];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
grid[r][c] = new JLabel(iconMap.get(Token.VIDE));
grid[r][c].addMouseListener(listener);
grid[r][c].setOpaque(true);
grid[r][c].setBackground(Color.WHITE);
grid[r][c].setPreferredSize(new Dimension(ICON_W, ICON_W));
add(grid[r][c]);
}
}
set(6, 6, Token.CERCLE_ROUGE);
set(6, 7, Token.CERCLE_ROUGE);
set(6, 8, Token.CERCLE_ROUGE);
set(6, 9, Token.CERCLE_ROUGE);
set(7, 9, Token.CERCLE_ROUGE);
set(8, 9, Token.CERCLE_ROUGE);
set(9, 9, Token.CERCLE_ROUGE);
set(10, 9, Token.CERCLE_ROUGE);
set(10, 6, Token.CERCLE_ROUGE);
set(10, 7, Token.CERCLE_ROUGE);
set(10, 8, Token.CERCLE_ROUGE);
set(10, 9, Token.CERCLE_ROUGE);
set(7, 6, Token.CERCLE_ROUGE);
set(8, 6, Token.CERCLE_ROUGE);
set(9, 6, Token.CERCLE_ROUGE);
//
// set(7, 6, Token.CERCLE_ROUGE);
// set(7, 7, Token.CERCLE_BLEU);
// set(7, 8, Token.CERCLE_BLEU);
//
// set(8, 6, Token.CERCLE_ROUGE);
// set(8, 7, Token.CERCLE_ROUGE);
// set(8, 8, Token.CERCLE_ROUGE);
//
// set(5, 7, Token.CERCLE_ROUGE);
// set(5, 8, Token.CERCLE_ROUGE);
// set(5, 9, Token.CERCLE_ROUGE);
// set(6, 9, Token.CERCLE_ROUGE);
// set(7, 9, Token.CERCLE_ROUGE);
Path path = new Path(tokens);
if (path.findPath(new int[]{6, 6})) {
setPath(path.getPath());
}
}
void set(int row, int col, Token token) {
grid[row][col].setIcon(iconMap.get(token));
tokens[row][col] = token;
}
int getSelectedRow() {
return selectedRow;
}
int getSelectedCol() {
return selectedCol;
}
void setCell(Token token, int row, int col) {
grid[row][col].setIcon(iconMap.get(token));
}
int getRows() {
return rows;
}
void setRows(int rows) {
this.rows = rows;
}
//added to each cell to listen to mouse clicks
//fires property change with cell index
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel selection = (JLabel) e.getSource();
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (selection == grid[r][c]) {
selectedRow = r;
selectedCol = c;
int index = (r * grid[r].length) + c;
selection.setIcon(iconMap.get(Token.CERCLE_BLEU));
tokens[selectedRow][selectedCol] = Token.CERCLE_BLEU;
System.out.println(c + "x" + r + " = " + index);
firePropertyChange(CELL_SELECTION, -1, index);
Path path = new Path(tokens);
if (path.findPath(new int[]{selectedRow, selectedCol})) {
setPath(path.getPath());
}
}
}
}
}
}
//add listener to listen to property changes fired by MyMouseListener
public void addPropertyChangeListener(PropertyChangeListener viewListener) {
addPropertyChangeListener(viewListener);
}
void setPath(ArrayList<Cell> path) {
this.path = path;
if (path != null) {
drawPath();
}
}
//highlight path by changing background color.
//It can be changed to draw lines between cells
private void drawPath() {
if (path != null) {
List<JLabel> labels = new ArrayList<>(25);
for (Cell cell : path) {
JLabel label = grid[cell.y][cell.x];
label.setBackground(Color.YELLOW);
labels.add(label);
}
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (!path.contains(new Cell(c, r))) {
grid[r][c].setBackground(Color.WHITE);
}
}
}
drawLines.setPath(labels);
}
}
void refresh() {
repaint();
}
//This is what I added to try drawing lines
public static class DrawLines extends JLabel {
List<JLabel> path;
public void setPath(List<JLabel> path) {
this.path = new ArrayList<>(path);
this.path.add(path.get(0));
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (path != null) {
Graphics2D g2d = (Graphics2D) g.create();
Point startPoint = null;
for (JLabel label : path) {
g2d.setColor(Color.DARK_GRAY);
Point endPoint = label.getLocation();
endPoint.x += (label.getWidth() / 2);
endPoint.y += (label.getHeight() / 2);
if (startPoint != null) {
g2d.draw(new Line2D.Float(startPoint, endPoint));
}
startPoint = endPoint;
}
g2d.dispose();
}
}
}
class Cell {
private int x, y;
public Cell(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
#Override
public int hashCode() {
int hash = 7;
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Cell other = (Cell) obj;
if (this.x != other.x) {
return false;
}
if (this.y != other.y) {
return false;
}
return true;
}
#Override
public String toString() {
return x + "x" + y;
}
}
class Path extends Stack<Cell> {
private Token[][] grid;
//a path shorter than min can not surround any cell
private static final int MIN_PATH_LEGTH = 3;
//a collection of cells that has been tested
private ArrayList<Cell> checked;
//represents the cell where the search starts from
Cell origin;
//represents the token of the origin
Token originToken;
private int rows;
private int cols;
Path(Token[][] grid) {
this.grid = grid;
rows = grid.length;
cols = grid[0].length;
}
//search for a path
boolean findPath(int[] origin) {
int row = origin[0], col = origin[1];
this.origin = new Cell(col, row);
//represents the token of the origin
originToken = grid[row][col];
//initialize list of checked items
checked = new ArrayList<>();
boolean found = findPath(row, col);
if (found) {
printPath();
} else {
System.out.println("No path found");
}
return found;
}
//recursive method to find path. a cell is represented by its row, col
//returns true when path was found
private boolean findPath(int row, int col) {
//check if cell has the same token as origin
if (grid[row][col] != originToken) {
return false;
}
Cell cell = new Cell(col, row);
//check if this cell was tested before to avoid checking again
if (checked.contains(cell)) {
return false;
}
//get cells neighbors
ArrayList<Cell> neighbors = getNeighbors(row, col);
//check if solution found. If path size > min and cell
//neighbors contain the origin it means that path was found
if ((size() >= MIN_PATH_LEGTH) && neighbors.contains(origin)) {
add(cell);
return true;
}
//add cell to checked list
checked.add(cell);
//add cell to path
add(cell);
//if path was not found check cell neighbors
for (Cell neighbor : neighbors) {
boolean found = findPath(neighbor.getY(), neighbor.getX());
if (found) {
return true;
}
}
//path not found
pop(); //remove last element from stack
return false;
}
//return a list of all neighbors of cell row, col
private ArrayList<Cell> getNeighbors(int row, int col) {
ArrayList<Cell> neighbors = new ArrayList<Cell>();
for (int colNum = col - 1; colNum <= (col + 1); colNum += 1) {
for (int rowNum = row - 1; rowNum <= (row + 1); rowNum += 1) {
if (!((colNum == col) && (rowNum == row))) {
if (withinGrid(rowNum, colNum)) {
neighbors.add(new Cell(colNum, rowNum));
}
}
}
}
return neighbors;
}
private boolean withinGrid(int colNum, int rowNum) {
if ((colNum < 0) || (rowNum < 0)) {
return false;
}
if ((colNum >= cols) || (rowNum >= rows)) {
return false;
}
return true;
}
//use for testing
private void printPath() {
StringJoiner sj = new StringJoiner(",", "Path: ", "");
for (Cell cell : this) {
sj.add(cell.toString());
}
System.out.println(sj);
}
public ArrayList<Cell> getPath() {
ArrayList<Cell> cl = new ArrayList<>();
cl.addAll(this);
return cl;
}
}
}
But The lines are not repainting. Is there as way to save them?
The DrawLines will draw the path you pass it. To provide a "history", you need to maintain another List of all the paths you pass, for example...
public static class DrawLines extends JLabel {
private List<List<JLabel>> history;
public DrawLines() {
history = new ArrayList<>(25);
}
public void setPath(List<JLabel> path) {
List<JLabel> copy = new ArrayList<>(path);
copy.add(path.get(0));
history.add(copy);
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Point startPoint = null;
for (List<JLabel> path : history) {
for (JLabel label : path) {
g2d.setColor(Color.DARK_GRAY);
Point endPoint = label.getLocation();
endPoint.x += (label.getWidth() / 2);
endPoint.y += (label.getHeight() / 2);
if (startPoint != null) {
g2d.draw(new Line2D.Float(startPoint, endPoint));
}
startPoint = endPoint;
}
}
g2d.dispose();
}
}

Here is the View class modified to add lines as requested.
Please review and see comments :
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.util.EnumMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class View {
public static final String CELL_SELECTION = "cell selection";
private final static int GAP = 2;
private static int iconWidth;
private int rows;
private JPanel mainPanel;
private JLabel[][] grid;
private Map<Token, Icon> iconMap = new EnumMap<>(Token.class);
private int selectedRow;
private int selectedCol;
//a collection of cells representing a path
private CellsList path;
//a pane to hold two panels one on top of the other
JPanel layeredPanel;
View() {
mainPanel = new JPanel();
//add layerd pane to hold two panels one on top of the other
layeredPanel = new JPanel();
//set GridBagLayout to allow placing two components one on top of the other
layeredPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
//add main panel to layered panel
layeredPanel.add(mainPanel, gbc);
//create and add a drawing panel
DrawJPanel drawPanel = new DrawJPanel();
drawPanel.setOpaque(false); //set if to transparent so it does not
//hide mainPanel under it
layeredPanel.add(drawPanel, gbc);
//set z-order (depth) of two panels
layeredPanel.setComponentZOrder(mainPanel,1);
layeredPanel.setComponentZOrder(drawPanel,0);
}
void createGrid(int rows, int cols) {
MyMouseListener listener = new MyMouseListener();
setRows(rows);
setIcons();
mainPanel.setLayout(new GridLayout(rows, cols, 1, 1));
mainPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
mainPanel.setBackground(Color.BLACK);
grid = new JLabel[rows][cols];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
grid[r][c] = new JLabel(iconMap.get(Token.VIDE));
grid[r][c].addMouseListener(listener);
grid[r][c].setOpaque(true);
grid[r][c].setBackground(Color.WHITE);
mainPanel.add(grid[r][c]);
}
}
layeredPanel.setPreferredSize(mainPanel.getPreferredSize());
}
int getSelectedRow() {
return selectedRow;
}
int getSelectedCol() {
return selectedCol;
}
void setCell(Token token, int row, int col) {
grid[row][col].setIcon(iconMap.get(token));
}
int getRows() {
return rows;
}
void setRows(int rows) {
this.rows = rows;
}
//added to each cell to listen to mouse clicks
//fires property change with cell index
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel selection = (JLabel) e.getSource();
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (selection == grid[r][c]) {
selectedRow = r;
selectedCol = c;
int index = (r * grid[r].length) + c;
mainPanel.firePropertyChange(CELL_SELECTION, -1, index);
}
}
}
}
}
void start() {
JFrame frame = new JFrame("MVC Pha");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(layeredPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
//add listener to listen to property changes fired by MyMouseListener
void addPropertyChangeListener(PropertyChangeListener viewListener) {
mainPanel.addPropertyChangeListener(viewListener);
}
void setPath(CellsList path) {
this.path = path;
if(path != null) {
drawPath();
}
}
//highlight path by changing background color.
//It can be changed to draw lines between cells
private void drawPath() {
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[row].length; col++) {
if((path != null) && path.contains(new int[] {row,col})) {
grid[row][col].setBackground(Color.YELLOW);
} else {
grid[row][col].setBackground(Color.WHITE);
}
}
}
}
//receives rectangle and returns its center point
private Point getComponentCenter(Rectangle bounds) {
int xCenter = (int) ((bounds.getX()+ bounds.getMaxX())/2);
int yCenter = (int) ((bounds.getMinY()+ bounds.getMaxY())/2);
return new Point(xCenter, yCenter);
}
void refresh() {
layeredPanel.repaint();
}
private void setIcons() {
if(rows <= 20) {
iconWidth = 24;
} else if( rows <= 40 ) {
iconWidth = 20;
} else if( rows <= 60 ) {
iconWidth = 15;
} else if( rows <= 80 ) {
iconWidth = 12;
} else {
iconWidth = 8;
}
iconMap.put(Token.VIDE, createIcon(new Color(0, 0, 0, 0)));
iconMap.put(Token.CERCLE_BLEU, createIcon(Color.BLUE));
iconMap.put(Token.CERCLE_ROUGE, createIcon(Color.RED));
}
private Icon createIcon(Color color) {
BufferedImage img = new BufferedImage(iconWidth, iconWidth, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fillOval(1, 1, iconWidth - GAP, iconWidth - GAP);
g2.dispose();
return new ImageIcon(img);
}
//a panel to draw line on
class DrawJPanel extends JPanel {
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
if((path == null) || path.isEmpty() ) {
return;
}
//calculate points to construct line along the path
Point[] polygon = new Point[path.size()];
int i=0;
for(int[] address : path) { //iterate over path
//get cell location (row / col)
int row = address[0]; int col = address[1];
//get label in that location
JLabel lable = grid[row][col];
//add center of label as a point in polygon
polygon[i++] = getComponentCenter(lable.getBounds());
}
//draw points in polygon
g.setColor(Color.CYAN); //set line color and width
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(2));
int j;
for (j = 0; j < (polygon.length -1); j++) {
Point point1 = polygon[j];
Point point2 = polygon[j+1];
g2.draw(new Line2D.Float(point1, point2));
}
//add line to connect last point to first
g2.draw(new Line2D.Float(polygon[j], polygon[0]));
}
}
}

The following answer present the same solution as in my previous answer.
For the benefit of all (other users, those who try to answer, as well as for the poster), I think the question should have been asked and answer as a mcve :
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class View {
private static final String WHITE = "white";
private static final String BLUE = "blue";
private final static int GAP = 2;
private static final int iconWidth = 24;
private Map<String, Icon> iconMap = new HashMap<>();
private JPanel mainPanel;
private JLabel[][] grid;
//a collection of cells representing a path
private ArrayList<int[]> path;
//a pane to hold two panels one on top of the other
JPanel layeredPanel;
View(int rows) {
JFrame frame = new JFrame("Line between JLabels");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainPanel = new JPanel();
//add layered pane to hold two panels one on top of the other
layeredPanel = new JPanel();
//set GridBagLayout to allow placing two components one on top of the other
layeredPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
//add main panel to layered panel
layeredPanel.add(mainPanel, gbc);
//create and add a drawing panel
DrawJPanel drawPanel = new DrawJPanel();
drawPanel.setOpaque(false); //set if to transparent so it does not
//hide mainPanel under it
layeredPanel.add(drawPanel, gbc);
//set z-order (depth) of two panels
layeredPanel.setComponentZOrder(mainPanel,1);
layeredPanel.setComponentZOrder(drawPanel,0);
path = new ArrayList<>();
setTestData();
createGrid(rows,rows);
frame.getContentPane().add(layeredPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
void createGrid(int rows, int cols) {
setIcons();
mainPanel.setLayout(new GridLayout(rows, cols, 1, 1));
mainPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
mainPanel.setBackground(Color.BLACK);
grid = new JLabel[rows][cols];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if(isInPath(new int[] {r,c})) {
grid[r][c] = new JLabel(iconMap.get(BLUE));
} else {
grid[r][c] = new JLabel(iconMap.get(WHITE));
}
grid[r][c].setOpaque(true);
grid[r][c].setBackground(Color.WHITE);
mainPanel.add(grid[r][c]);
}
}
layeredPanel.setPreferredSize(mainPanel.getPreferredSize());
}
//receives rectangle and returns its center point
private Point getComponentCenter(Rectangle bounds) {
int xCenter = (int) ((bounds.getX()+ bounds.getMaxX())/2);
int yCenter = (int) ((bounds.getMinY()+ bounds.getMaxY())/2);
return new Point(xCenter, yCenter);
}
private void setIcons() {
iconMap.put(WHITE, createIcon(new Color(0, 0, 0, 0)));
iconMap.put(BLUE, createIcon(Color.BLUE));
}
private Icon createIcon(Color color) {
BufferedImage img = new BufferedImage(iconWidth, iconWidth, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fillOval(1, 1, iconWidth - GAP, iconWidth - GAP);
g2.dispose();
return new ImageIcon(img);
}
private boolean isInPath(int[] cell) {
if((path == null) || path.isEmpty() ) {
return false;
}
for(int[] p : path) {
if((cell[0]== p[0]) && (cell[1] == p[1])) {
return true;
}
}
return false;
}
//a panel to draw line on
class DrawJPanel extends JPanel {
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
if((path == null) || path.isEmpty() ) {
return;
}
//calculate points to construct line along the path
Point[] polygon = new Point[path.size()];
int i=0;
for(int[] address : path) { //iterate over path
//get cell location (row / col)
int row = address[0]; int col = address[1];
//get label in that location
JLabel lable = grid[row][col];
//add center of label as a point in polygon
polygon[i++] = getComponentCenter(lable.getBounds());
}
//draw points in polygon
g.setColor(Color.CYAN); //set line color and width
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(2));
int j;
for (j = 0; j < (polygon.length -1); j++) {
Point point1 = polygon[j];
Point point2 = polygon[j+1];
g2.draw(new Line2D.Float(point1, point2));
}
//add line to connect last point to first
g2.draw(new Line2D.Float(polygon[j], polygon[0]));
}
}
public static void main(String[] args) {
new View(10);
}
private void setTestData() {
path.add(new int[] {3,3});
path.add(new int[] {3,4});
path.add(new int[] {3,5});
path.add(new int[] {4,6});
path.add(new int[] {5,5});
path.add(new int[] {5,4});
path.add(new int[] {5,3});
path.add(new int[] {4,4});
}
}

Related

Java.awt Affine Transform doesn't seem to update image location

I am trying to make a chess game in java, by having a class of pieces and a subclass for each piece. However, When I try to draw the pieces, The position doesn't seem to register.
Here is my Piece class:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.net.URL;
public class Piece {
public int Id;
public int color;
public int state;
public Image Sprite;
public AffineTransform tx;
public boolean dragged;
public int x;
public int y;
public Piece(int Id, int color, int position){
dragged = false;
this.Id = Id;
this.color = color;
int x = 100*(position % 8);
int y = 100*(position / 8);
System.out.println(x);
tx = AffineTransform.getTranslateInstance(x, y);
init(x, y);
}
private void init (double a, double b) {
tx.setToTranslation(a, b);
tx.scale(0.1, 0.1);
}
private void update(){
tx.setToTranslation(x*1000, y*1000);
tx.scale(0.1, 0.1);
}
protected Image getImage(String path) {
Image tempImage = null;
try {
URL imageURL = Piece.class.getResource(path);
tempImage = Toolkit.getDefaultToolkit().getImage(imageURL);
} catch (Exception e) {e.printStackTrace();}
return tempImage;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
update();
g2.drawImage(Sprite, tx, null);
}
}
my pawn class:
public class Pawn extends Piece {
public Pawn(int Id, int color, int position) {
super(Id, color, position);
this.state = 0;
String path = "/imgs/Pieces/";
if(color == 0){
path += "W";
} else{
path += "B";
}
path += "_Pawn.png";
Sprite = getImage(path);
}
}
my Board class:
Piece[][] board;
public Board(){
board = new Piece[8][8];
for(int i = 0; i < 8; i++){
board[1][i] = new Pawn(i, 1, 8+i);
}
for(int i = 0; i < 8; i++){
board[6][i] = new Pawn(i, 0, 8+i);
}
}
}
and my main class:
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main extends JPanel implements ActionListener, MouseListener, KeyListener{
Color GREEN = new Color( 41, 176, 59);
Color WHITE = new Color(254, 255, 228);
Board board = new Board();
public static void main(String[] args) {
new Main();
}
public void paint(Graphics g){
super.paintComponent(g);
boolean flag = true;
for(int i = 0; i < 8; i++){
for(int j = 0; j < 8; j++){
if(flag){
g.setColor(WHITE);
} else{
g.setColor(GREEN);
}
g.fillRect((j*100), (i*100), ((j+1)*100), ((i+1)*100));
flag = !flag;
}
flag = !flag;
}
for(int i = 0; i < 8; i++){
for(int j = 0; j < 8; j++){
if(board.board[i][j] != null){
board.board[i][j].paint(g);
}
}
}
}
public Main() {
JFrame f = new JFrame("Chess");
f.setSize(new Dimension(800, 800));
f.setBackground(Color.blue);
f.add(this);
f.setResizable(false);
f.setLayout(new GridLayout(1,2));
f.addMouseListener(this);
f.addKeyListener(this);
Timer t = new Timer(16, this);
t.start();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
I had previously written a game that implemented this techqnique, so I'm not sure what could have gone wrong with this one
It's really important to read the documentation, especially for something that is (to my simple brain), complicated.
If you have a read of the documentation for AffineTransform#scale
Concatenates this transform with a scaling transformation
(emphis added by me)
This is important, as it seems to apply that each time you call it, it will apply ANOTHER scaling operation.
Based on your avaliable code, this means that when the Piece is created, a scale is applied and the each time it's painted, a new scale is applied, until you're basically scaled out of existence.
Sooo. I took out your init (applied the scale within the constructor directly instead) and update methods and was able to get a basic result
Scaling from 1.0, 0.75, 0.5, 0.25and0.1` (it's there but I had to reduce the size of the output)
Runnable example...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new Main());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class Main extends JPanel implements ActionListener, MouseListener, KeyListener {
Color GREEN = new Color(41, 176, 59);
Color WHITE = new Color(254, 255, 228);
Board board;
public Main() throws IOException {
board = new Board();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
super.paintComponent(g);
boolean flag = true;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
if (flag) {
g.setColor(WHITE);
} else {
g.setColor(GREEN);
}
g.fillRect((j * 100), (i * 100), ((j + 1) * 100), ((i + 1) * 100));
flag = !flag;
}
flag = !flag;
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
if (board.board[i][j] != null) {
board.board[i][j].paint(g);
}
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
}
public class Board {
Piece[][] board;
public Board() throws IOException {
board = new Piece[8][8];
for (int i = 0; i < 8; i++) {
board[1][i] = new Pawn(i, 1, 8 + i);
}
for (int i = 0; i < 8; i++) {
board[6][i] = new Pawn(i, 0, 16 + i);
}
}
}
public class Pawn extends Piece {
public Pawn(int Id, int color, int position) throws IOException {
super(Id, color, position);
this.state = 0;
String path = "/imgs/Pieces/";
if (color == 0) {
path += "W";
} else {
path += "B";
}
path += "_Pawn.png";
Sprite = getImage(path);
}
}
public class Piece {
public int Id;
public int color;
public int state;
public Image Sprite;
public AffineTransform tx;
public boolean dragged;
public int x;
public int y;
public Piece(int Id, int color, int position) {
dragged = false;
this.Id = Id;
this.color = color;
x = 100 * (position % 8);
y = 100 * (position / 8);
System.out.println(x + "x" + y);
tx = AffineTransform.getTranslateInstance(x, y);
tx.scale(0.1, 0.1);
}
protected Image getImage(String path) throws IOException {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setFont(new JLabel().getFont().deriveFont(Font.PLAIN, 16));
g2d.setColor(Color.BLACK);
FontMetrics fm = g2d.getFontMetrics();
int cellX = x;
int cellY = y;
String text = x + "x" + y;
int x = (100 - fm.stringWidth(text)) / 2;
int y = ((100 - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(text, x, y);
g2d.setStroke(new BasicStroke(16));
g2d.drawRect(0, 0, 99, 99);
g2d.dispose();
return img;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(Sprite, tx, null);
}
}
}

Why are the paintComponents being called but not seen?

I have a GridLayout of Cells that each extends JComponent.
The GridLayout manages a Board class that extends JPanel and the Board object/panel is added to the main panel with two other panels.
Here's the Cell Class:
public class Cell extends JComponent{
private int row;
private int col;
private int rowHeight;
private int colWidth;
private boolean active = false;
private Color color;
public Cell(int row, int col, Color color) {
this.row = row;
this.col = col;
this.color = color;
}
public Cell(int row, int col, int rowHeight, int colWidth, Color color) {
this.row = row;
this.col = col;
this.rowHeight = rowHeight;
this.colWidth = colWidth;
this.color = color;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
paintSquare(g);
}
private void paintSquare(Graphics g) {
g.setColor(color);
g.fillRoundRect(
(int) (col * colWidth),
(int) (row * rowHeight),
(int) (rowHeight),
(int) (colWidth),
10,
10);
}
public int getCol()
{
return col;
}
public int getRow()
{
return row;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
Here's the Board Class:
public class Board extends JPanel {
public Cell[][] gameBoard;
public final int GAME_ROWS;
public final int GAME_COLUMNS;
public int rowHeight;
public int columnWidth;
public Color selectedColor;
public Board(int GAME_ROWS, int GAME_COLUMNS) {
this.GAME_COLUMNS = GAME_COLUMNS;
this.GAME_ROWS = GAME_ROWS;
calculateDimensions();
createGameBoard();
setUpBoardPanel();
}
private void calculateDimensions() {
rowHeight = (int) this.getHeight() / GAME_ROWS;
columnWidth = (int) this.getWidth() / GAME_COLUMNS;
}
private void createGameBoard() {
Random random = new Random();
int cellColor;
gameBoard = new Cell[GAME_ROWS][GAME_COLUMNS];
for (int row = 0; row < GAME_ROWS; row++) {
for (int col = 0; col < GAME_COLUMNS; col++) {
cellColor = random.nextInt(Properties.COLORS.length);
Cell newCell = new Cell(row, col, rowHeight,
columnWidth,Properties.COLORS[cellColor]);
gameBoard[row][col] = newCell;
}
}
}
private void setUpBoardPanel() {
setLayout(new GridLayout(GAME_ROWS, GAME_COLUMNS));
setPreferredSize(Properties.BOARD_TABLE_SIZE);
setBorder(new EmptyBorder(20, 10, 0, 0));
setBackground(Properties.BACKGROUND_COLOR);
addBoardPanelComponents();
}
private void addBoardPanelComponents() {
for(int r = 0; r < GAME_ROWS; r++) {
for(int c = 0; c < GAME_COLUMNS; c++) {
add(gameBoard[r][c]);
}
}
}
}
Everything on the main panel is showing up perfectly, and I can see that the Board panel was added because I when I change its background it shows up as it's set.
I've looked through a bunch of tutorials and am calling super right so I'm not sure how the components could be added and called correctly and just not show up.
To see the full program code you can go to my github, but the relevant code is above. TIA!
The "core" issue is, you don't understand how the coordinate space of a component works.
The x/y position of component is relative to its parent. The top/left corner of any component/container is always 0x0.
So when you do something like this...
g.fillRoundRect(
(int) (col * colWidth),
(int) (row * rowHeight),
(int) (rowHeight),
(int) (colWidth),
10,
10);
You're saying, fill a rect which starts at col * width x row * rowHeight relative to the top left corner of the component itself (which is always 0x0)
What you should be doing is something more like this...
g.fillRoundRect(
0,
0,
getWidth() - 1,
getHeight() - 1,
10,
10);
This will fill the entire visible area of the component.
But why use getWidth and getHeight. Well, in this context, this ensures that the entire visible area of the component is filled, but how do you affect the size of the component?
The preferred way is to override the getPreferredSize method of the component and return the "preferred" size (all things been equal).
#Override
public Dimension getPreferredSize() {
return new Dimension(colWidth, rowHeight);
}
This provides a hint to the parent layout manager about how the component would "like" to be laid out.
Another issue is...
private void calculateDimensions() {
rowHeight = (int) this.getHeight() / GAME_ROWS;
columnWidth = (int) this.getWidth() / GAME_COLUMNS;
}
This is pointless, because until the component has undergone a layout pass, it's size is 0x0, so, basically you're saying the rowHeight and columnWidth should be 0x0 :/
Honestly, best to just rid of it. If you need to, seed a known value to the Cell directly
Runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
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 Board(10, 10));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Board extends JPanel {
public Cell[][] gameBoard;
public final int GAME_ROWS;
public final int GAME_COLUMNS;
public int rowHeight = 50;
public int columnWidth = 50;
public Color selectedColor;
public Board(int GAME_ROWS, int GAME_COLUMNS) {
this.GAME_COLUMNS = GAME_COLUMNS;
this.GAME_ROWS = GAME_ROWS;
createGameBoard();
setUpBoardPanel();
}
private void createGameBoard() {
Random random = new Random();
int cellColor;
Color[] colors = new Color[]{Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};
gameBoard = new Cell[GAME_ROWS][GAME_COLUMNS];
for (int row = 0; row < GAME_ROWS; row++) {
for (int col = 0; col < GAME_COLUMNS; col++) {
cellColor = random.nextInt(colors.length);
Cell newCell = new Cell(row, col, rowHeight,
columnWidth, colors[cellColor]);
gameBoard[row][col] = newCell;
}
}
}
private void setUpBoardPanel() {
setLayout(new GridLayout(GAME_ROWS, GAME_COLUMNS));
setBorder(new EmptyBorder(20, 10, 0, 0));
setBackground(Color.RED);
addBoardPanelComponents();
}
private void addBoardPanelComponents() {
for (int r = 0; r < GAME_ROWS; r++) {
for (int c = 0; c < GAME_COLUMNS; c++) {
add(gameBoard[r][c]);
}
}
}
}
public class Cell extends JComponent {
private int row;
private int col;
private int rowHeight;
private int colWidth;
private boolean active = false;
private Color color;
public Cell(int row, int col, int rowHeight, int colWidth, Color color) {
this.row = row;
this.col = col;
this.rowHeight = rowHeight;
this.colWidth = colWidth;
this.color = color;
setBorder(new LineBorder(Color.BLACK));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(colWidth, rowHeight);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
paintSquare(g);
}
private void paintSquare(Graphics g) {
g.setColor(color);
g.fillRoundRect(
0,
0,
getWidth() - 1,
getHeight() - 1,
10,
10);
}
public int getCol() {
return col;
}
public int getRow() {
return row;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
}

Choosing a Layout Manager - Java

I'm having trouble to find the right Layout Manager. I have some images inside a JPanel, they are all different size so I wanted to use a Flow Layout to let the manager handle them. The manager fills all the first row as possible and then warps to another line. This is all ok, but what I want is to stop at second "warp". I just want to show 2 rows of images and if then you want to see more you must click a JButton to load the others. Actually the FlowLayout keeps inserting in a third line and the images are cutted by half because the panel is not "tall" enough. Any tips?
I've already tried with Flow Layout and Mig Layout without success.
Combine a FlowLayout(outerPanel) with a GridBagLayout(innerPannel)
u can define the rows by yourself, and then u put a JScrollPane over it so it wont cut your pictures and u will still be able to see them in full size(my suggestion)
I couldn't resist trying to solve this. I had hoped to make use of FlowLayouts, but in the end, I ended up using only GridBagLayouts: One for each row of images, plus one for the entire container. I may have over-engineered in terms of flexibility, but I imagine something like this may be useful to myself or others in the future.
In essence, every time the container changes size, or images are added/removed, or any of its visual properties change, updateLayout() is called, which rebuilds all of the GridBagLayout panels from scratch. I'm sure there are ways to make this more efficient, and I'm sure it's possible to write a LayoutManager2 implementation from scatch that does the job, but this performed reasonably well for me.
import java.util.List;
import java.util.ArrayList;
import java.util.Objects;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class ImagePanel
extends JPanel {
private static final long serialVersionUID = 1;
/** #serial */
private final List<Icon> images = new ArrayList<>();
/** #serial */
private final List<Icon> scaledImages = new ArrayList<>();
/** #serial */
private int maxRowCount = 2;
/** #serial */
private int hgap = 6;
/** #serial */
private int vgap = 6;
/** #serial */
private int maxImageWidth = 200;
/** #serial */
private int maxImageHeight = 200;
public ImagePanel() {
setLayout(new GridBagLayout());
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent event) {
updateLayout();
}
});
}
#Override
public void addNotify() {
super.addNotify();
updateLayout();
}
#Override
public Dimension getPreferredSize() {
Rectangle screen = findGraphicsConfiguration().getBounds();
Dimension size = new Dimension();
Dimension row = new Dimension();
int rowsComputed = 0;
int gap = 0;
for (Icon image : scaledImages) {
if (row.width > 0 &&
row.width + gap + image.getIconWidth() > screen.width) {
if (++rowsComputed >= maxRowCount) {
break;
}
size.width = Math.max(size.width, row.width);
size.height += (size.height > 0 ? vgap : 0) + row.height;
row.setSize(0, 0);
gap = 0;
}
row.width += gap + image.getIconWidth();
row.height = Math.max(row.height, image.getIconHeight());
gap = hgap;
}
size.width = Math.max(size.width, row.width);
size.height += (size.height > 0 ? vgap : 0) + row.height;
return size;
}
private void updateLayout() {
int width = getWidth();
if (width == 0) {
return;
}
for (Component rowContainer : getComponents()) {
((JComponent) rowContainer).removeAll();
}
GridBagConstraints rowConstraints = new GridBagConstraints();
rowConstraints.gridwidth = GridBagConstraints.REMAINDER;
rowConstraints.weightx = 1;
rowConstraints.anchor = GridBagConstraints.FIRST_LINE_START;
int row = -1;
int rowWidth = 0;
GridBagConstraints gbc = new GridBagConstraints();
for (Icon image : scaledImages) {
JComponent rowContainer = (row >= 0 && row < getComponentCount() ?
(JComponent) getComponent(row) : null);
int gap = (rowWidth > 0 ? hgap : 0);
if (rowContainer == null ||
rowWidth + gap + image.getIconWidth() > width) {
if (++row >= maxRowCount) {
break;
}
gap = 0;
rowWidth = 0;
if (row < getComponentCount()) {
rowContainer = (JComponent) getComponent(row);
} else {
rowContainer = new JPanel(new GridBagLayout());
add(rowContainer, rowConstraints);
}
rowConstraints.insets.top = vgap;
}
gbc.insets.left = gap;
JComponent imageContainer = new JLabel(image);
rowContainer.add(imageContainer, gbc);
rowWidth += gap + image.getIconWidth();
}
for (int i = getComponentCount() - 1; i >= maxRowCount; i--) {
remove(i);
}
}
private GraphicsConfiguration findGraphicsConfiguration() {
GraphicsConfiguration config = getGraphicsConfiguration();
if (config == null) {
GraphicsEnvironment env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
config = env.getDefaultScreenDevice().getDefaultConfiguration();
}
return config;
}
private Icon scale(Icon image) {
int imageWidth = image.getIconWidth();
int imageHeight = image.getIconHeight();
if (imageWidth > maxImageWidth || imageHeight > maxImageHeight) {
float scale = Math.min((float) maxImageWidth / imageWidth,
(float) maxImageHeight / imageHeight);
if (scale < 1) {
GraphicsConfiguration config = findGraphicsConfiguration();
BufferedImage scaledImage = config.createCompatibleImage(
(int) (imageWidth * scale),
(int) (imageHeight * scale));
Graphics2D g = scaledImage.createGraphics();
g.scale(scale, scale);
image.paintIcon(this, g, 0, 0);
g.dispose();
image = new ImageIcon(scaledImage);
}
}
return image;
}
public List<Icon> getImages() {
return new ArrayList<>(images);
}
public void clearImages() {
images.clear();
updateLayout();
revalidate();
}
public void addImage(Icon image) {
Objects.requireNonNull(image, "Image cannot be null");
images.add(image);
scaledImages.add(scale(image));
updateLayout();
revalidate();
}
public void removeImage(Icon image) {
int index = images.indexOf(image);
if (index >= 0) {
removeImage(index);
}
}
public void removeImage(int index) {
images.remove(index);
scaledImages.remove(index);
updateLayout();
revalidate();
}
public int getHgap() {
return hgap;
}
public void setHgap(int gap) {
if (gap < 0) {
throw new IllegalArgumentException("Gap must be at least zero");
}
int old = this.hgap;
this.hgap = gap;
if (old != gap) {
updateLayout();
revalidate();
}
firePropertyChange("hgap", old, gap);
}
public int getVgap() {
return vgap;
}
public void setVgap(int gap) {
if (gap < 0) {
throw new IllegalArgumentException("Gap must be at least zero");
}
int old = this.vgap;
this.vgap = gap;
if (old != gap) {
updateLayout();
revalidate();
}
firePropertyChange("vgap", old, gap);
}
public int getMaxRowCount() {
return maxRowCount;
}
public void setMaxRowCount(int count) {
if (count < 0) {
throw new IllegalArgumentException("Count must be at least zero");
}
int old = this.maxRowCount;
this.maxRowCount = count;
if (old != count) {
updateLayout();
revalidate();
}
firePropertyChange("maxRowCount", old, count);
}
public int getMaxImageWidth() {
return maxImageWidth;
}
private void recomputeScaledImages() {
scaledImages.clear();
for (Icon image : images) {
scaledImages.add(scale(image));
}
}
public void setMaxImageWidth(int width) {
if (width <= 0) {
throw new IllegalArgumentException("Width must be positive");
}
int old = this.maxImageWidth;
this.maxImageWidth = width;
if (old != width) {
recomputeScaledImages();
updateLayout();
revalidate();
}
firePropertyChange("maxImageWidth", old, width);
}
public int getMaxImageHeight() {
return maxImageHeight;
}
public void setMaxImageHeight(int height) {
if (height <= 0) {
throw new IllegalArgumentException("Height must be positive");
}
int old = this.maxImageHeight;
this.maxImageHeight = height;
if (old != height) {
recomputeScaledImages();
updateLayout();
revalidate();
}
firePropertyChange("maxImageHeight", old, height);
}
public static void main(final String[] args)
throws java.io.IOException {
if (args.length == 0) {
System.err.println("Usage: java " + ImagePanel.class.getName()
+ " <directory> | <url1> <url2> ...");
System.exit(2);
}
final List<java.net.URL> urls;
if (args.length == 1 && !args[0].contains(":")) {
urls = new ArrayList<>();
try (java.nio.file.DirectoryStream<java.nio.file.Path> dir =
java.nio.file.Files.newDirectoryStream(
java.nio.file.Paths.get(args[0]))) {
for (java.nio.file.Path file : dir) {
urls.add(file.toUri().toURL());
}
}
} else {
urls = new ArrayList<>(args.length);
for (String url : args) {
urls.add(new java.net.URL(url));
}
}
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ImagePanel imagePanel = new ImagePanel();
for (java.net.URL url : urls) {
imagePanel.addImage(new ImageIcon(url));
}
javax.swing.JFrame frame =
new javax.swing.JFrame("ImagePanel");
frame.setDefaultCloseOperation(
javax.swing.JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.FIRST_LINE_START;
gbc.weightx = 1;
gbc.weighty = 1;
panel.add(imagePanel, gbc);
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
});
}
}

SnakeGame how to make the tail follow the head?

I am making a snake game, and I am stuck at where making the tails follow the head. And I heard using an add and remove on the head and tails could make that happen, but I have no idea where to start with that.
Here's my code so far:
Screen.java
public class Screen extends JPanel implements ActionListener, KeyListener {
public static final JLabel statusbar = new JLabel("Default");
public static final int WIDTH = 800, HEIGHT = 800;
Timer t = new Timer(100, this);
int x = 400;
int y = 400;
int size = 5; //increase size if eat
private boolean right = false, left = false, up = false, down = false;
int head = 0;
private LinkedList<BodyPart> snake = new LinkedList<BodyPart>();
private BodyPart b;
public Screen(){
initSnake();
t.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void update(){
}
public void direction(){
if(right) x+=10;
if(left) x-=10;
if(up) y-=10;
if(down) y+=10;
}
public void trackOutBound(){
if(x < 0 || x > 800 || y < 0 || y > 800) {
x = 400;
y = 400;
}
}
public void initSnake(){
if(snake.size() == 0){
b = new BodyPart(x, y);
for(int i = 0; i < size; i++) {
snake.add(b);
}
System.out.println(snake);
}
}
public static void main(String[] args) {
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(new Color(10, 50, 0));
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.BLACK);
for(int i = 0; i < WIDTH / 10; i++) {
g.drawLine(i * 10, 0, i * 10, HEIGHT);
}
for(int i = 0; i < HEIGHT / 10; i++) {
g.drawLine(0, i * 10, WIDTH, i * 10);
}
int tempx = 0, tempy = 0;
int temp = 0;
for(int i = 0; i < size; i++){
if(i == head) {
snake.get(i).x = x;
snake.get(i).y = y;
snake.get(i).draw(g);
g.setColor(Color.blue);
g.fillRect(x, y, 10, 10);
g.setColor(Color.white);
g.drawRect(x, y, 10, 10);
} else if(i > 0 && up) {
snake.get(i).x = x;
snake.get(i).y = y + temp;
snake.get(i).draw(g);
} else if(i > 0 && down) {
snake.get(i).x = x;
snake.get(i).y = y - temp;
snake.get(i).draw(g);
} else if(i > 0 && left) {
snake.get(i).x = x + temp;
snake.get(i).y = y;
snake.get(i).draw(g);
} else if(i > 0 && right) {
snake.get(i).x = x - temp;
snake.get(i).y = y;
snake.get(i).draw(g);
}
temp += 10;
}
/*
if(snake.size() == 5){
snake.add(b);
size += 1;
}
*/
}
#Override
public void actionPerformed(ActionEvent e) {
direction();
trackOutBound();
repaint();
// System.out.println(snake);
statusbar.setText("(" + x + " , " + y + ")");
}
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if(key == KeyEvent.VK_RIGHT && !left) {
up = false;
down = false;
right = true;
}
if(key == KeyEvent.VK_LEFT && !right) {
up = false;
down = false;
left = true;
}
if(key == KeyEvent.VK_UP && !down) {
left = false;
right = false;
up = true;
}
if(key == KeyEvent.VK_DOWN && !up) {
left = false;
right = false;
down = true;
}
}
#Override
public void keyReleased(KeyEvent e) {}
}
BodyPart.java
public class BodyPart {
int x;
int y;
public BodyPart(int x, int y) {
this.x = x;
this.y = y;
}
public void draw(Graphics g) {
this.x = x;
this.y = y;
g.setColor(Color.red);
g.fillRect(x, y, 10, 10);
g.setColor(Color.white);
g.drawRect(x, y, 10, 10);
}
}
Frame.java
public class Frame extends JPanel {
private static JLabel statusbar = new JLabel("Default");
public void statusbar(){
statusbar = Screen.statusbar;
}
public static void main(String[] args) {
JFrame f = new JFrame();
Screen s = new Screen();
f.add(s);
f.add(statusbar, BorderLayout.SOUTH);
f.setSize(800, 800);
f.setVisible(true);
f.setLocationRelativeTo(null);
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Now this code would only make the tails flip to the horizontal or vertical, is it possible to make the tails follow the head by using this code? or I need to change my code?
Thank you
The basic idea is, you need some kind of List which contains ALL the points of the snake. Conceptually, the List would contain virtual coordinates, that is 1x1 would represent a coordinate in virtual space, which presented a place on a virtual board (which would have some wide and height).
You could then translate that to the screen, so this would allow each part of the snake to be larger then a single pixel. So, if each part was 5x5 pixels, then 1x1 would actually be 5x5 in the screen.
Each time the snake moves, you add a new value to the head and remove the last value from tail (assuming it's not growing). When you needed to paint the snake, you would simply iterate over the List, painting each point of the snake.
The following is a simple example, which uses a LinkedList, which pushes a new Point onto the List, making a new head, and removing the last element (the tail) on each cycle.
Which basically boils down to...
snakeBody.removeLast();
snakeBody.push(new Point(xPos, yPos));
As a runnable concept
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Snake {
public static void main(String[] args) {
new Snake();
}
public Snake() {
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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
public enum Direction {
UP, DOWN, LEFT, RIGHT
}
private int xPos, yPos;
private Direction direction = Direction.UP;
private LinkedList<Point> snakeBody = new LinkedList<>();
public TestPane() {
xPos = 100;
yPos = 100;
for (int index = 0; index < 50; index++) {
snakeBody.add(new Point(xPos, yPos));
}
bindKeyStrokeTo("up.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), new MoveAction(Direction.UP));
bindKeyStrokeTo("down.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), new MoveAction(Direction.DOWN));
bindKeyStrokeTo("left.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), new MoveAction(Direction.LEFT));
bindKeyStrokeTo("right.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), new MoveAction(Direction.RIGHT));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (direction) {
case UP:
yPos--;
break;
case DOWN:
yPos++;
break;
case LEFT:
xPos--;
break;
case RIGHT:
xPos++;
break;
}
if (yPos < 0) {
yPos--;
} else if (yPos > getHeight() - 1) {
yPos = getHeight() - 1;
}
if (xPos < 0) {
xPos--;
} else if (xPos > getWidth() - 1) {
xPos = getWidth() - 1;
}
snakeBody.removeLast();
snakeBody.push(new Point(xPos, yPos));
repaint();
}
});
timer.start();
}
public void bindKeyStrokeTo(String name, KeyStroke keyStroke, Action action) {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
for (Point p : snakeBody) {
g2d.drawLine(p.x, p.y, p.x, p.y);
}
g2d.dispose();
}
public class MoveAction extends AbstractAction {
private Direction moveIn;
public MoveAction(Direction direction) {
this.moveIn = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
direction = this.moveIn;
}
}
}
}
Now, this has no collision detection or other functionality, but you can move the snake around and it will follow itself
For snake style movement, you can, from the tail to the head, move each BodyPart position to the position of the BodyPart ahead of it. For the head there is no part ahead so you have to write decision code whether to simply move the same direction as the part before it or a new direction based on input. Then update the screen.

How to draw grid using swing class Java and detect mouse position when click and drag

I am trying to create a grid UI (5*5) using Swing classes. I tried a nested loop and adding a jPanel dynamically to the jFrame. And I also tried to change the background colour of each jPanel when user clicks and drops over it.
But with my code there are huge gaps between each cell and I can't get the drag event to work.
public class clsCanvasPanel extends JPanel {
private static final int intRows = 5;
private static final int intCols = 5;
private List<JPanel> jpllist = new ArrayList<JPanel>();
public clsCanvasPanel() {
/*
*
* Add eventListener to individual JPanel within CanvasPanel
*
*
* TODO :
* 1) mousePressed --> update Temperature and HeatConstant of clsElement Class
* 2) start a new thread and
* 3) call clsElement.run() method
*
*
* Right Now : it updates the colours of the JPanel
* */
MouseListener mouseListener = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
JPanel panel = (JPanel) e.getSource();
Component[] components = panel.getComponents();
for (Component component : components) {
component.setVisible(!component.isVisible());
component.setBackground(new Color(255,255,0));
}
panel.revalidate();
panel.repaint();
}
};
//TODO : refactoring
GridLayout gdlyPlates = new GridLayout();
gdlyPlates.setColumns(intCols);
gdlyPlates.setRows(intRows);
gdlyPlates.setHgap(0);
gdlyPlates.setVgap(0);
setLayout(gdlyPlates);
//TODO : refactoring
for (int row = 0; row < intRows; row++) {
for (int col = 0; col < intCols; col++) {
JPanel panel = new JPanel(new GridBagLayout());
panel.setOpaque(false);
JPanel jl = new JPanel();
jl.setVisible(true);
panel.add(jl);
panel.addMouseListener(mouseListener);
jpllist.add(panel);
add(panel);
}
}
}
}
So now I am trying to create one panel and draw grids on it, then detects the mouse position on the grid, further change the colour of each cell.
Could someone give me some advices on how to implement this grid on JPanel, and change the colour of a chosen cell.
There are any number of ways to get this to work, depending on what it is you want to achieve.
This first example simply uses the 2D Graphics API to render the cells and a MouseMotionListener to monitor which cell is highlighted.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestGrid01 {
public static void main(String[] args) {
new TestGrid01();
}
public TestGrid01() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int columnCount = 5;
private int rowCount = 5;
private List<Rectangle> cells;
private Point selectedCell;
public TestPane() {
cells = new ArrayList<>(columnCount * rowCount);
MouseAdapter mouseHandler;
mouseHandler = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
Point point = e.getPoint();
int width = getWidth();
int height = getHeight();
int cellWidth = width / columnCount;
int cellHeight = height / rowCount;
selectedCell = null;
if (e.getX() >= xOffset && e.getY() >= yOffset) {
int column = (e.getX() - xOffset) / cellWidth;
int row = (e.getY() - yOffset) / cellHeight;
if (column >= 0 && row >= 0 && column < columnCount && row < rowCount) {
selectedCell = new Point(column, row);
}
}
repaint();
}
};
addMouseMotionListener(mouseHandler);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void invalidate() {
cells.clear();
selectedCell = null;
super.invalidate();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth();
int height = getHeight();
int cellWidth = width / columnCount;
int cellHeight = height / rowCount;
int xOffset = (width - (columnCount * cellWidth)) / 2;
int yOffset = (height - (rowCount * cellHeight)) / 2;
if (cells.isEmpty()) {
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < columnCount; col++) {
Rectangle cell = new Rectangle(
xOffset + (col * cellWidth),
yOffset + (row * cellHeight),
cellWidth,
cellHeight);
cells.add(cell);
}
}
}
if (selectedCell != null) {
int index = selectedCell.x + (selectedCell.y * columnCount);
Rectangle cell = cells.get(index);
g2d.setColor(Color.BLUE);
g2d.fill(cell);
}
g2d.setColor(Color.GRAY);
for (Rectangle cell : cells) {
g2d.draw(cell);
}
g2d.dispose();
}
}
}
This example does resize the grid with the window, but it would be a trivial change to make the cells fixed size.
Check out 2D Graphics for more details
Update with component example
This example uses a series of JPanels to represent each cell.
Each cell is defined with a fixed width and height and do not resize with the main window.
In this example, each cell panel has it's own mouse listener. It wouldn't be overly difficult to re-code it so that the main panel had a single mouse listener and managed the work load itself instead.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
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;
import javax.swing.border.Border;
import javax.swing.border.MatteBorder;
public class TestGrid02 {
public static void main(String[] args) {
new TestGrid02();
}
public TestGrid02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
for (int row = 0; row < 5; row++) {
for (int col = 0; col < 5; col++) {
gbc.gridx = col;
gbc.gridy = row;
CellPane cellPane = new CellPane();
Border border = null;
if (row < 4) {
if (col < 4) {
border = new MatteBorder(1, 1, 0, 0, Color.GRAY);
} else {
border = new MatteBorder(1, 1, 0, 1, Color.GRAY);
}
} else {
if (col < 4) {
border = new MatteBorder(1, 1, 1, 0, Color.GRAY);
} else {
border = new MatteBorder(1, 1, 1, 1, Color.GRAY);
}
}
cellPane.setBorder(border);
add(cellPane, gbc);
}
}
}
}
public class CellPane extends JPanel {
private Color defaultBackground;
public CellPane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
defaultBackground = getBackground();
setBackground(Color.BLUE);
}
#Override
public void mouseExited(MouseEvent e) {
setBackground(defaultBackground);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(50, 50);
}
}
}
I did not like rendering of borders since inside the grid some were duplicated if there were more than the example. I think that this solution is better:
private int width;
private int height;
// ...
for (int row = 0; row <= this.height; row++) {
for (int col = 0; col <= this.width; col++) {
gbc.gridx = col;
gbc.gridy = row;
CellPane cellPane = new CellPane();
Border border = new MatteBorder(1, 1, (row == this.height ? 1 : 0), (col == this.width ? 1 : 0), Color.GRAY);
cellPane.setBorder(border);
this.add(cellPane, gbc);
}
}
Edit:
My solution is better, because if the original code will be 5x5 cells, but more, such as 10x10 ... inner edges of some cells will be in contact and to create some places thick grid. It's nice to see on the screenshot
thick grid
In the MouseListener example in the mouseMoved method, you might want to consider the xOffset/yOffset though for a smoother cell recognition.
int column = (x - xOffset) / cellWidth;
int row = (y - yOffset) / cellHeight;

Categories

Resources