I'm creating a Tic Tac Toe game where I'm using MouseListener to add functionality to my game panel. When a user clicks one of the cells its suppose to generate an X or O graphic depending on whose turn it is. I've tried adding the MouseListener to the pane, but when I run it nothing happens when I click. Any ideas on how I can fix this?
Here's my game panel:
public GameMain() {
Handler handler = new Handler();
this.addMouseListener(handler);
// setup JLabel
label = new JLabel(" ");
label.setBorder(BorderFactory.createEmptyBorder(2, 5, 4, 5));
label.setOpaque(true);
label.setBackground(Color.LIGHT_GRAY);
setLayout(new BorderLayout());
add(label, BorderLayout.SOUTH);
setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT + 30));
board = new Board();
initGame();
}
}
Here's my Handler class with the mouseClick() method that's suppose to run:
public class Handler extends MouseAdapter {
public void mouseClick(MouseEvent e) {
int mouseX = e.getX();
int mouseY = e.getY();
// Get the row and column clicked
int rowSelected = mouseY / CELL_SIZE;
int colSelected = mouseX / CELL_SIZE;
if (currentState == GameState.PLAYING) {
if (rowSelected >= 0 && rowSelected < board.cells.length && colSelected >= 0 && colSelected < board.cells.length &&
board.cells[rowSelected][colSelected].content == Seed.EMPTY) {
board.cells[rowSelected][colSelected].content = currentPlayer; // move
updateGame(currentPlayer, rowSelected, colSelected); // update currentState
currentPlayer = (currentPlayer == Seed.X) ? Seed.O : Seed.X;
}
} else {
initGame();
}
repaint();
}
public void handleButtonPress(Object o) {
if (o == singlePlayer) {
singlePlayerGame();
}
if (o == multiPlayer) {
multiPlayerGame();
}
}
}
Your question inspired me to take up the challenge of writing a tic-tac-toe game, since I don't have much experience in handling custom painting. The below code is copiously commented, so I hope that those comments will serve as a good explanation of the code.
I'm presuming that people with more experience than me will find flaws in the below code, which is to be expected, since, as I already mentioned, I don't have a lot of experience in this kind of programming. Nonetheless, I hope it is good enough in order to be of help you.
Note that the below code makes use of a new java feature that was introduced with JDK 14, namely Records. Hence it may also be helpful to people as a simple example of how to integrate java records into their code. If you aren't using JDK 14 or if you are but you haven't enabled preview features, you can simply replace the record definition with a simple class. Just replace the word record with class and add a constructor and "getter" methods to the code. By default, the name of a "getter" method in a record is simply the name of the field, e.g. for member minX in record Square (in below code), the "getter" method is minX().
import java.awt.BasicStroke;
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.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
/**
* A simple tic-tac-toe (a.k.a. noughts and crosses) game. User clicks mouse on an empty square of
* the game board and either a nought or a cross is drawn in that square, depending on whose turn
* it is. Each square has an index as shown below.
* <pre>
* 0 1 2
*
* 3 4 5
*
* 6 7 8
* </pre>
* See Tic-tac-toe on <i>Wikipedia</i>.
*/
public class GameMain extends JPanel implements ActionListener, MouseListener, Runnable {
/** For serializing instances of this class. */
private static final long serialVersionUID = 7014608855599083001L;
/** A cross. */
private static final char CROSS = 'X';
/** A nought. */
private static final char NOUGHT = 'O';
/** Dimension of each square in tic-tac-toe board. */
private static final int SQUARE_DIMENSION = 80;
/** Number of consecutive squares required for a win. */
private static final int REQUIRED_SQUARES = 3;
/** Total number of squares in tic-tac-toe board. */
private static final int TOTAL_SQUARES = 9;
/** Each of the 9 squares in tic-tac-toe board. */
private final Square[] SQUARES = new Square[]{new Square(1, 1, 99, 99),
new Square(101, 1, 199, 99),
new Square(201, 1, 299, 99),
new Square(1, 101, 99, 199),
new Square(101, 101, 199, 199),
new Square(201, 101, 299, 199),
new Square(1, 201, 99, 299),
new Square(101, 201, 199, 299),
new Square(201, 201, 299, 299)};
/** Text for {#link #turnLabel} at start of a new tic-tac-toe game. */
private static final String FIRST_MOVE = "X goes first";
/** Text for <i>new game</i>. button. */
private static final String NEW_GAME = "New Game";
/** Indicates start of a new game. */
private boolean newGame;
/** <tt>true</tt> means O turn and <tt>false</tt> means X turn. */
private boolean oTurn;
/** Records occupied squares, either 'O' or 'X' */
private char[] occupied = new char[TOTAL_SQUARES];
/** Number of unoccupied squares in tic-tac-toe board. */
private int freeCount;
/** Displays whose turn it currently is. */
private JLabel turnLabel;
/** Location of last mouse click. */
private Point clickPoint;
/**
* Creates and returns instance of this class.
*/
public GameMain() {
setPreferredSize(new Dimension(300, 300));
addMouseListener(this);
freeCount = TOTAL_SQUARES;
}
private static boolean isValidRequirement(int index) {
return index >= 0 && index < REQUIRED_SQUARES;
}
/**
* Determines whether <var>square</var> is a valid index of a square in tic-tac-toe board.
*
* #param square - will be validated.
*
* #return <tt>true</tt> if <var>square</var> is valid.
*/
private static boolean isValidSquare(int square) {
return square >= 0 && square < TOTAL_SQUARES;
}
#Override // java.awt.event.ActionEvent
public void actionPerformed(ActionEvent actnEvnt) {
String actionCommand = actnEvnt.getActionCommand();
switch (actionCommand) {
case NEW_GAME:
startNewGame();
break;
default:
JOptionPane.showMessageDialog(this,
actionCommand,
"Unhandled",
JOptionPane.WARNING_MESSAGE);
}
}
#Override // java.awt.event.MouseListener
public void mouseClicked(MouseEvent mousEvnt) {
// Do nothing.
}
#Override // java.awt.event.MouseListener
public void mouseEntered(MouseEvent mousEvnt) {
// Do nothing.
}
#Override // java.awt.event.MouseListener
public void mouseExited(MouseEvent mousEvnt) {
// Do nothing.
}
#Override // java.awt.event.MouseListener
public void mousePressed(MouseEvent mousEvnt) {
// Do nothing.
}
#Override // java.awt.event.MouseListener
public void mouseReleased(MouseEvent mousEvnt) {
System.out.println("mouse clicked");
clickPoint = mousEvnt.getPoint();
repaint();
EventQueue.invokeLater(() -> {
String text;
if (isWinner()) {
text = oTurn ? "O has won!" : "X has won!";
removeMouseListener(this);
JOptionPane.showMessageDialog(this,
text,
"Winner",
JOptionPane.PLAIN_MESSAGE);
}
else if (freeCount <= 0) {
text = "Drawn game.";
removeMouseListener(this);
JOptionPane.showMessageDialog(this,
text,
"Draw",
JOptionPane.PLAIN_MESSAGE);
}
else {
oTurn = !oTurn;
text = oTurn ? "O's turn" : "X's turn";
}
turnLabel.setText(text);
});
}
#Override // java.lang.Runnable
public void run() {
createAndShowGui();
}
#Override // javax.swing.JComponent
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawLine(100, 0, 100, 300);
g2d.drawLine(200, 0, 200, 300);
g2d.drawLine(0, 100, 300, 100);
g2d.drawLine(0, 200, 300, 200);
if (!newGame) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(3));
for (int i = 0; i < TOTAL_SQUARES; i++) {
if (occupied[i] == NOUGHT) {
drawNought(i, g2d);
}
else if (occupied[i] == CROSS) {
drawCross(i, g2d);
}
}
int square = getSquare(clickPoint);
if (isFreeSquare(square)) {
if (oTurn) {
drawNought(square, g2d);
occupied[square] = NOUGHT;
}
else {
drawCross(square, g2d);
occupied[square] = CROSS;
}
freeCount--;
}
}
else {
newGame = false;
}
}
private void createAndShowGui() {
JFrame frame = new JFrame("O & X");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(createTurnPanel(), BorderLayout.PAGE_START);
frame.add(this, BorderLayout.CENTER);
frame.add(createButtonsPanel(), BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JButton createButton(String text, int mnemonic, String tooltip) {
JButton button = new JButton(text);
if (mnemonic > 0) {
button.setMnemonic(mnemonic);
}
if (tooltip != null && !tooltip.isBlank()) {
button.setToolTipText(tooltip);
}
button.addActionListener(this);
return button;
}
private JPanel createButtonsPanel() {
JPanel buttonsPanel = new JPanel();
buttonsPanel.setBorder(BorderFactory.createMatteBorder(2, 0, 0, 0, Color.GRAY));
buttonsPanel.add(createButton(NEW_GAME, KeyEvent.VK_N, "Start a new game."));
return buttonsPanel;
}
private JPanel createTurnPanel() {
JPanel turnPanel = new JPanel();
turnPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 2, 0, Color.GRAY));
turnLabel = new JLabel(FIRST_MOVE);
turnPanel.add(turnLabel);
return turnPanel;
}
/**
* Draws a {#link #CROSS} in <var>square</var> of tic-tac-toe board.
*
* #param square - index of square in tic-tac-toe board.
* #param g2d - facilitates drawing.
*/
private void drawCross(int square, Graphics2D g2d) {
if (isValidSquare(square) && g2d != null) {
g2d.setColor(Color.BLUE);
g2d.drawLine(SQUARES[square].minX() + 10,
SQUARES[square].minY() + 10,
SQUARES[square].maxX() - 10,
SQUARES[square].maxY() - 10);
g2d.drawLine(SQUARES[square].maxX() - 10,
SQUARES[square].minY() + 10,
SQUARES[square].minX() + 10,
SQUARES[square].maxY() - 10);
}
}
/**
* Draws a {#link #NOUGHT} in <var>square</var> of tic-tac-toe board.
*
* #param square - index of square in tic-tac-toe board.
* #param g2d - facilitates drawing.
*/
private void drawNought(int square, Graphics2D g2d) {
if (isValidSquare(square) && g2d != null) {
g2d.setColor(Color.RED);
g2d.drawOval(SQUARES[square].minX() + 10,
SQUARES[square].minY() + 10,
SQUARE_DIMENSION,
SQUARE_DIMENSION);
}
}
/**
* Returns the square of the tic-tac-toe board that contains <var>pt</var>.
*
* #param pt - point on tic-tac-toe board.
*
* #return index of square in tic-tac-toe board containing <var>pt</var> or -1 (negative one)
* if <var>pt</var> not in tic-tac-toe board.
*
* #see Square#contains(int, int)
*/
private int getSquare(Point pt) {
int ndx = -1;
if (pt != null) {
for (int i = 0; i < 9; i++) {
if (SQUARES[i].contains(pt.x, pt.y)) {
ndx = i;
break;
}
}
}
return ndx;
}
/**
* Determines whether <var>column</var> of tic-tac-toe board contains all {#link #CROSS} or all
* {#link #NOUGHT}.
*
* #param column - index of column in tic-tac-toe board.
*
* #return <tt>true</tt> if <var>column</var> contains all {#link #CROSS} or all {#link #NOUGHT}.
*
* #see #isValidRequirement(int)
*/
private boolean isColumnWin(int column) {
boolean isWin = false;
if (isValidRequirement(column)) {
isWin = isOccupied(column) &&
occupied[column] == occupied[column + REQUIRED_SQUARES] &&
occupied[column] == occupied[column + (REQUIRED_SQUARES * 2)];
}
return isWin;
}
/**
* Determines whether diagonal of tic-tac-toe board contains all {#link #CROSS} or all
* {#link #NOUGHT}. The board contains precisely two diagonals where each one includes the
* board's center square, i.e. the square with index 4. The other squares that constitute
* diagonals are the corner squares, including the indexes 0 (zero), 2, 6 and 8.
*
* #return <tt>true</tt> if one of the tic-tac-toe board diagonals contains all {#link #CROSS}
* or all {#link #NOUGHT}.
*
* #see #isValidRequirement(int)
*/
private boolean isDiagonalWin() {
boolean isWin = false;
isWin = (isOccupied(0) &&
occupied[0] == occupied[4] &&
occupied[0] == occupied[8])
||
(isOccupied(2) &&
occupied[2] == occupied[4] &&
occupied[2] == occupied[6]);
return isWin;
}
/**
* Determines whether <var>square</var> in tic-tac-toe board does not contain a {#link #CROSS}
* nor a {#link #NOUGHT}.
*
* #param square - index of square in tic-tac-toe board.
*
* #return <tt>true</tt> if <var>square</var> does not contain a {#link #CROSS} nor a {#link
* #NOUGHT}.
*/
private boolean isFreeSquare(int square) {
boolean freeSquare = false;
if (isValidSquare(square)) {
freeSquare = occupied[square] != CROSS && occupied[square] != NOUGHT;
}
return freeSquare;
}
/**
* Determines whether <var>row</var> of tic-tac-toe board contains all {#link #CROSS} or all
* {#link #NOUGHT}.
*
* #param row - index of row in tic-tac-toe board.
*
* #return <tt>true</tt> if <var>row</var> contains all {#link #CROSS} or all {#link #NOUGHT}.
*
* #see #isValidRequirement(int)
*/
private boolean isLineWin(int row) {
boolean isWin = false;
if (isValidRequirement(row)) {
int index = row * REQUIRED_SQUARES;
isWin = isOccupied(index) &&
occupied[index] == occupied[index + 1] &&
occupied[index] == occupied[index + 2];
}
return isWin;
}
/**
* Determines whether square at <var>index</var> in tic-tac-toe board contains either a {#link
* #CROSS} or a {#link #NOUGHT}.
*
* #param index - index of square in tic-tac-toe board.
*
* #return <tt>true</tt> if square at <var>index</var> in tic-tac-toe board contains either a
* {#link #CROSS} or a {#link #NOUGHT}.
*
* #see #isValidSquare(int)
*/
private boolean isOccupied(int index) {
boolean occupied = false;
if (isValidSquare(index)) {
occupied = this.occupied[index] == CROSS || this.occupied[index] == NOUGHT;
}
return occupied;
}
/**
* Determines if there is a winner.
*
* #return <tt>true</tt> if someone has won the game.
*
* #see #isColumnWin(int)
* #see #isDiagonalWin()
* #see #isLineWin(int)
*/
private boolean isWinner() {
return isLineWin(0) ||
isLineWin(1) ||
isLineWin(2) ||
isColumnWin(0) ||
isColumnWin(1) ||
isColumnWin(2) ||
isDiagonalWin();
}
/**
* Initializes the GUI in order to start a new game.
*/
private void startNewGame() {
freeCount = TOTAL_SQUARES;
newGame = true;
oTurn = false;
occupied = new char[TOTAL_SQUARES];
repaint();
EventQueue.invokeLater(() -> {
removeMouseListener(this);
addMouseListener(this);
turnLabel.setText(FIRST_MOVE);
});
}
/**
* This method is the first one called when this class is launched via the <tt>java</tt>
* command. It ignores the method parameter <var>args</var>.
*
* #param args - <tt>java</tt> command arguments.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new GameMain());
}
}
/**
* Represents the geometrical shape known as a square. For the purposes of this {#code record}, a
* square is defined by two points that indicate its top, left corner and its bottom, right corner.
*/
record Square (int minX, int minY, int maxX, int maxY) {
/**
* Determines whether the supplied point lies within this square.
*
* #param x - x coordinate of a point.
* #param y - y coordinate of same point (as x).
*
* #return <tt>true</tt> if supplied point lies within this square.
*/
public boolean contains(int x, int y) {
return minX <= x && x <= maxX && minY <= y && y <= maxY;
}
}
Related
So I working on adding sound to an Android Snake game. What I am required to do is to add sound in for 3 events...
When the snake moves
When the snake eats
When the snake collides w/ itself or the wall
In order to do this I am using the SoundPool class and to sort of clean things up I just made separate class could SoundPlayer that sets up everything and has 3 methods to address the events. However, I am am trying not to restructure the code we were given too much but the primary code regarding movement and events is not in onCreate(). Where all examples I have practiced with say to generate the instantiate and add call the sound. Instead it is in its own .java file that is called in onCreate() so I am at my wits end on how and where I can instantiate the SoundPlayer class.
Can anyone out there help with how I can achieve the requirements and understand where I am going wrong with trying to use this class and its methods?
Snake.java
package com.example.android.snake;
import android.app.Activity;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.widget.TextView;
/**
* Snake: a simple game that everyone can enjoy.
*
* This is an implementation of the classic Game "Snake", in which you control a
* serpent roaming around the garden looking for apples. Be careful, though,
* because when you catch one, not only will you become longer, but you'll move
* faster. Running into yourself or the walls will end the game.
*
*/
public class Snake extends Activity {
private SnakeView mSnakeView;
private static String ICICLE_KEY = "snake-view";
//U01A1: Initialize Sound class
SoundPlayer sound;
public SoundPlayer getSound() {
return sound;
}
/**
* Called when Activity is first created. Turns off the title bar, sets up
* the content views, and fires up the SnakeView.
*
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.snake_layout);
mSnakeView = (SnakeView) findViewById(R.id.snake);
mSnakeView.setTextView((TextView) findViewById(R.id.text));
//sound
sound = new SoundPlayer(this); //sound
if (savedInstanceState == null) {
// We were just launched -- set up a new game
mSnakeView.setMode(SnakeView.READY);
} else {
// We are being restored
Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
if (map != null) {
mSnakeView.restoreState(map);
} else {
mSnakeView.setMode(SnakeView.PAUSE);
}
}
}
#Override
protected void onPause() {
super.onPause();
// Pause the game along with the activity
mSnakeView.setMode(SnakeView.PAUSE);
}
#Override
public void onSaveInstanceState(Bundle outState) {
//Store the game state
outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
}
}
SnakeView.java
package com.example.android.snake;
import java.util.ArrayList;
import java.util.Random;
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
/**
* SnakeView: implementation of a simple game of Snake
*
*
*/
public class SnakeView extends TileView {
private static final String TAG = "SnakeView";
//private SoundPlayer sound;
/**
* Current mode of application: READY to run, RUNNING, or you have already
* lost. static final ints are used instead of an enum for performance
* reasons.
*/
private int mMode = READY;
public static final int PAUSE = 0;
public static final int READY = 1;
public static final int RUNNING = 2;
public static final int LOSE = 3;
/**
* Current direction the snake is headed.
*/
private int mDirection = NORTH;
private int mNextDirection = NORTH;
private static final int NORTH = 1;
private static final int SOUTH = 2;
private static final int EAST = 3;
private static final int WEST = 4;
/**
* Labels for the drawables that will be loaded into the TileView class
*/
private static final int RED_STAR = 1;
private static final int YELLOW_STAR = 2;
private static final int GREEN_STAR = 3;
/**
* mScore: used to track the number of apples captured mMoveDelay: number of
* milliseconds between snake movements. This will decrease as apples are
* captured.
*/
private long mScore = 0;
private long mMoveDelay = 600;
/**
* mLastMove: tracks the absolute time when the snake last moved, and is used
* to determine if a move should be made based on mMoveDelay.
*/
private long mLastMove;
/**
* mStatusText: text shows to the user in some run states
*/
private TextView mStatusText;
/**
* mSnakeTrail: a list of Coordinates that make up the snake's body
* mAppleList: the secret location of the juicy apples the snake craves.
*/
private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();
/**
* Everyone needs a little randomness in their life
*/
private static final Random RNG = new Random();
/**
* Create a simple handler that we can use to cause animation to happen. We
* set ourselves as a target and we can use the sleep()
* function to cause an update/invalidate to occur at a later date.
*/
private RefreshHandler mRedrawHandler = new RefreshHandler();
class RefreshHandler extends Handler {
#Override
public void handleMessage(Message msg) {
SnakeView.this.update();
SnakeView.this.invalidate();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
};
/**
* Constructs a SnakeView based on inflation from XML
*
* #param context
* #param attrs
*/
public SnakeView(Context context, AttributeSet attrs) {
super(context, attrs);
initSnakeView();
}
public SnakeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initSnakeView();
}
private void initSnakeView() {
setFocusable(true);
Resources r = this.getContext().getResources();
resetTiles(4);
loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));
}
private void initNewGame() {
mSnakeTrail.clear();
mAppleList.clear();
// For now we're just going to load up a short default eastbound snake
// that's just turned north
mSnakeTrail.add(new Coordinate(7, 7));
mSnakeTrail.add(new Coordinate(6, 7));
mSnakeTrail.add(new Coordinate(5, 7));
mSnakeTrail.add(new Coordinate(4, 7));
mSnakeTrail.add(new Coordinate(3, 7));
mSnakeTrail.add(new Coordinate(2, 7));
mNextDirection = NORTH;
// Two apples to start with
addRandomApple();
addRandomApple();
mMoveDelay = 600;
mScore = 0;
}
/**
* Given a ArrayList of coordinates, we need to flatten them into an array of
* ints before we can stuff them into a map for flattening and storage.
*
* #param cvec : a ArrayList of Coordinate objects
* #return : a simple array containing the x/y values of the coordinates
* as [x1,y1,x2,y2,x3,y3...]
*/
private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {
int count = cvec.size();
int[] rawArray = new int[count * 2];
for (int index = 0; index < count; index++) {
Coordinate c = cvec.get(index);
rawArray[2 * index] = c.x;
rawArray[2 * index + 1] = c.y;
}
return rawArray;
}
/**
* Save game state so that the user does not lose anything
* if the game process is killed while we are in the
* background.
*
* #return a Bundle with this view's state
*/
public Bundle saveState() {
Bundle map = new Bundle();
map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
map.putInt("mDirection", Integer.valueOf(mDirection));
map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));
map.putLong("mScore", Long.valueOf(mScore));
map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));
return map;
}
/**
* Given a flattened array of ordinate pairs, we reconstitute them into a
* ArrayList of Coordinate objects
*
* #param rawArray : [x1,y1,x2,y2,...]
* #return a ArrayList of Coordinates
*/
private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {
ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();
int coordCount = rawArray.length;
for (int index = 0; index < coordCount; index += 2) {
Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
coordArrayList.add(c);
}
return coordArrayList;
}
/**
* Restore game state if our process is being relaunched
*
* #param icicle a Bundle containing the game state
*/
public void restoreState(Bundle icicle) {
setMode(PAUSE);
mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
mDirection = icicle.getInt("mDirection");
mNextDirection = icicle.getInt("mNextDirection");
mMoveDelay = icicle.getLong("mMoveDelay");
mScore = icicle.getLong("mScore");
mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
}
/*
* handles key events in the game. Update the direction our snake is traveling
* based on the DPAD. Ignore events that would cause the snake to immediately
* turn back on itself.
*
* (non-Javadoc)
*
* #see android.view.View#onKeyDown(int, android.os.KeyEvent)
*/
#Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
if (mMode == READY | mMode == LOSE) {
/*
* At the beginning of the game, or the end of a previous one,
* we should start a new game.
*/
initNewGame();
setMode(RUNNING);
update();
return (true);
}
if (mMode == PAUSE) {
/*
* If the game is merely paused, we should just continue where
* we left off.
*/
setMode(RUNNING);
update();
return (true);
}
if (mDirection != SOUTH) {
mNextDirection = NORTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if (mDirection != NORTH) {
mNextDirection = SOUTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
if (mDirection != EAST) {
mNextDirection = WEST;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
if (mDirection != WEST) {
mNextDirection = EAST;
}
return (true);
}
return super.onKeyDown(keyCode, msg);
}
/**
* Sets the TextView that will be used to give information (such as "Game
* Over" to the user.
*
* #param newView
*/
public void setTextView(TextView newView) {
mStatusText = newView;
}
/**
* Updates the current mode of the application (RUNNING or PAUSED or the like)
* as well as sets the visibility of textview for notification
*
* #param newMode
*/
public void setMode(int newMode) {
int oldMode = mMode;
mMode = newMode;
if (newMode == RUNNING & oldMode != RUNNING) {
mStatusText.setVisibility(View.INVISIBLE);
update();
return;
}
Resources res = getContext().getResources();
CharSequence str = "";
if (newMode == PAUSE) {
str = res.getText(R.string.mode_pause);
}
if (newMode == READY) {
str = res.getText(R.string.mode_ready);
}
if (newMode == LOSE) {
str = res.getString(R.string.mode_lose_prefix) + mScore
+ res.getString(R.string.mode_lose_suffix);
}
mStatusText.setText(str);
mStatusText.setVisibility(View.VISIBLE);
}
/**
* Selects a random location within the garden that is not currently covered
* by the snake. Currently _could_ go into an infinite loop if the snake
* currently fills the garden, but we'll leave discovery of this prize to a
* truly excellent snake-player.
*
*/
private void addRandomApple() {
Coordinate newCoord = null;
boolean found = false;
while (!found) {
// Choose a new location for our apple
int newX = 1 + RNG.nextInt(mXTileCount - 2);
int newY = 1 + RNG.nextInt(mYTileCount - 2);
newCoord = new Coordinate(newX, newY);
// Make sure it's not already under the snake
boolean collision = false;
int snakelength = mSnakeTrail.size();
for (int index = 0; index < snakelength; index++) {
if (mSnakeTrail.get(index).equals(newCoord)) {
collision = true;
}
}
// if we're here and there's been no collision, then we have
// a good location for an apple. Otherwise, we'll circle back
// and try again
found = !collision;
}
if (newCoord == null) {
Log.e(TAG, "Somehow ended up with a null newCoord!");
}
mAppleList.add(newCoord);
}
/**
* Handles the basic update loop, checking to see if we are in the running
* state, determining if a move should be made, updating the snake's location.
*/
public void update() {
if (mMode == RUNNING) {
long now = System.currentTimeMillis();
if (now - mLastMove > mMoveDelay) {
clearTiles();
updateWalls();
updateSnake();
updateApples();
mLastMove = now;
}
mRedrawHandler.sleep(mMoveDelay);
}
}
/**
* Draws some walls.
*
*/
private void updateWalls() {
for (int x = 0; x < mXTileCount; x++) {
setTile(GREEN_STAR, x, 0);
setTile(GREEN_STAR, x, mYTileCount - 1);
}
for (int y = 1; y < mYTileCount - 1; y++) {
setTile(GREEN_STAR, 0, y);
setTile(GREEN_STAR, mXTileCount - 1, y);
}
}
/**
* Draws some apples.
*
*/
private void updateApples() {
for (Coordinate c : mAppleList) {
setTile(YELLOW_STAR, c.x, c.y);
}
}
/**
* Figure out which way the snake is going, see if he's run into anything (the
* walls, himself, or an apple). If he's not going to die, we then add to the
* front and subtract from the rear in order to simulate motion. If we want to
* grow him, we don't subtract from the rear.
*
*/
private void updateSnake() {
boolean growSnake = false;
// grab the snake by the head
Coordinate head = mSnakeTrail.get(0);
Coordinate newHead = new Coordinate(1, 1);
mDirection = mNextDirection;
switch (mDirection) {
case EAST: {
newHead = new Coordinate(head.x + 1, head.y);
break;
}
case WEST: {
newHead = new Coordinate(head.x - 1, head.y);
break;
}
case NORTH: {
newHead = new Coordinate(head.x, head.y - 1);
break;
}
case SOUTH: {
newHead = new Coordinate(head.x, head.y + 1);
break;
}
}
// Collision detection
// For now we have a 1-square wall around the entire arena
if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)
|| (newHead.y > mYTileCount - 2)) {
setMode(LOSE);
return;
}
// Look for collisions with itself
int snakelength = mSnakeTrail.size();
for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
Coordinate c = mSnakeTrail.get(snakeindex);
if (c.equals(newHead)) {
setMode(LOSE);
return;
}
}
// Look for apples
int applecount = mAppleList.size();
for (int appleindex = 0; appleindex < applecount; appleindex++) {
Coordinate c = mAppleList.get(appleindex);
if (c.equals(newHead)) {
mAppleList.remove(c);
addRandomApple();
mScore++;
mMoveDelay *= 0.9;
growSnake = true;
}
}
// push a new head onto the ArrayList and pull off the tail
mSnakeTrail.add(0, newHead);
// except if we want the snake to grow
if (!growSnake) {
mSnakeTrail.remove(mSnakeTrail.size() - 1);
}
int index = 0;
for (Coordinate c : mSnakeTrail) {
if (index == 0) {
setTile(YELLOW_STAR, c.x, c.y);
} else {
setTile(RED_STAR, c.x, c.y);
}
index++;
}
}
/**
* Simple class containing two integer values and a comparison function.
* There's probably something I should use instead, but this was quick and
* easy to build.
*
*/
private class Coordinate {
public int x;
public int y;
public Coordinate(int newX, int newY) {
x = newX;
y = newY;
}
public boolean equals(Coordinate other) {
if (x == other.x && y == other.y) {
return true;
}
return false;
}
#Override
public String toString() {
return "Coordinate: [" + x + "," + y + "]";
}
}
}
SoundPlayer.java
package com.example.android.snake;
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
public class SoundPlayer {
// U01A1: variables for SoundPool
private SoundPool soundPool; //var for soundPool
private int sound1, sound2, sound3; //var for sound files, are int because ids are ints
public SoundPlayer(Context context) {
//SoundPool (int maxStreams, int streamType, int srcQuality)
soundPool = new SoundPool (3, AudioManager.STREAM_MUSIC, 0);
//loading the sounds; priority set to 1
sound1 = soundPool.load(context, R.raw.sound1, 1);
sound2 = soundPool.load(context, R.raw.sound2, 1);
sound3 = soundPool.load(context, R.raw.sound3, 1);
}
//method to play sound1
public void playMoveSound() {
// play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
soundPool.play(sound1, 1,1,0,0,1);
}
//method to play sound2
public void playEatSound() {
soundPool.play(sound2, 1,1,0,0,1);
}
//method to play sound3
public void playHitsound() {
soundPool.play(sound3, 1,1,0,0,1);
}
}
Problem: An undecorated JFrame with a transparent background flickers when using a ComponentResizer to resize it. As seen in the below video and MCVE, the problem does not occur with an opaque background.
ComponentResizer (A MouseAdapter) works by calculating the drag distance and direction when the mouse is dragged and changes the size of its component accordingly.
The answer to What causes the Jframe to flicker while resizing? links to How to stop the auto-repaint() when I resize the Jframe, which says to turn of dynamic layout with Toolkit.getDefaultToolkit().setDynamicLayout(false), however, this does not solve the problem as it has no effect, possibly because macOS is not a platform that allows it to be disabled.
Question: How can I allow the user to resize an undecorated JFrame with a transparent background without it flickering? Is ComponentResizer the problem?
MCVE: (Length due to ComponentResizer class)
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
public class JFrameFlickerMCVE {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
Toolkit.getDefaultToolkit().setDynamicLayout(false);
final JFrame frame = new JFrame();
frame.setUndecorated(true);
final JToggleButton backgroundButton = new JToggleButton("Break me!");
backgroundButton.setSelected(true);
backgroundButton.addActionListener(e -> {
if(!backgroundButton.isSelected()) {
frame.setBackground(new Color(0, 0, 0, 0));
backgroundButton.setText("Fix me!");
} else {
frame.setBackground(UIManager.getColor("control"));
backgroundButton.setText("Break me!");
}
});
final JLabel label = new JLabel("Resize Here");
label.setBorder(BorderFactory.createLineBorder(Color.RED));
frame.getContentPane().add(backgroundButton);
frame.getContentPane().add(label, BorderLayout.SOUTH);
new ComponentResizer(frame);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
/**
* The ComponentResizer allows you to resize a component by dragging a border
* of the component.
*/
public static class ComponentResizer extends MouseAdapter
{
private final static Dimension MINIMUM_SIZE = new Dimension(10, 10);
private final static Dimension MAXIMUM_SIZE =
new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
private static Map<Integer, Integer> cursors = new HashMap<Integer, Integer>();
{
cursors.put(1, Cursor.N_RESIZE_CURSOR);
cursors.put(2, Cursor.W_RESIZE_CURSOR);
cursors.put(4, Cursor.S_RESIZE_CURSOR);
cursors.put(8, Cursor.E_RESIZE_CURSOR);
cursors.put(3, Cursor.NW_RESIZE_CURSOR);
cursors.put(9, Cursor.NE_RESIZE_CURSOR);
cursors.put(6, Cursor.SW_RESIZE_CURSOR);
cursors.put(12, Cursor.SE_RESIZE_CURSOR);
}
private Insets dragInsets;
private Dimension snapSize;
private int direction;
protected static final int NORTH = 1;
protected static final int WEST = 2;
protected static final int SOUTH = 4;
protected static final int EAST = 8;
private Cursor sourceCursor;
private boolean resizing;
private Rectangle bounds;
private Point pressed;
private boolean autoscrolls;
private Dimension minimumSize = MINIMUM_SIZE;
private Dimension maximumSize = MAXIMUM_SIZE;
/**
* Convenience contructor. All borders are resizable in increments of
* a single pixel. Components must be registered separately.
*/
public ComponentResizer()
{
this(new Insets(5, 5, 5, 5), new Dimension(1, 1));
}
/**
* Convenience contructor. All borders are resizable in increments of
* a single pixel. Components can be registered when the class is created
* or they can be registered separately afterwards.
*
* #param components components to be automatically registered
*/
public ComponentResizer(Component... components)
{
this(new Insets(5, 5, 5, 5), new Dimension(1, 1), components);
}
/**
* Convenience contructor. Eligible borders are resisable in increments of
* a single pixel. Components can be registered when the class is created
* or they can be registered separately afterwards.
*
* #param dragInsets Insets specifying which borders are eligible to be
* resized.
* #param components components to be automatically registered
*/
public ComponentResizer(Insets dragInsets, Component... components)
{
this(dragInsets, new Dimension(1, 1), components);
}
/**
* Create a ComponentResizer.
*
* #param dragInsets Insets specifying which borders are eligible to be
* resized.
* #param snapSize Specify the dimension to which the border will snap to
* when being dragged. Snapping occurs at the halfway mark.
* #param components components to be automatically registered
*/
public ComponentResizer(Insets dragInsets, Dimension snapSize, Component... components)
{
setDragInsets( dragInsets );
setSnapSize( snapSize );
registerComponent( components );
}
/**
* Get the drag insets
*
* #return the drag insets
*/
public Insets getDragInsets()
{
return dragInsets;
}
/**
* Set the drag dragInsets. The insets specify an area where mouseDragged
* events are recognized from the edge of the border inwards. A value of
* 0 for any size will imply that the border is not resizable. Otherwise
* the appropriate drag cursor will appear when the mouse is inside the
* resizable border area.
*
* #param dragInsets Insets to control which borders are resizeable.
*/
public void setDragInsets(Insets dragInsets)
{
validateMinimumAndInsets(minimumSize, dragInsets);
this.dragInsets = dragInsets;
}
/**
* Get the components maximum size.
*
* #return the maximum size
*/
public Dimension getMaximumSize()
{
return maximumSize;
}
/**
* Specify the maximum size for the component. The component will still
* be constrained by the size of its parent.
*
* #param maximumSize the maximum size for a component.
*/
public void setMaximumSize(Dimension maximumSize)
{
this.maximumSize = maximumSize;
}
/**
* Get the components minimum size.
*
* #return the minimum size
*/
public Dimension getMinimumSize()
{
return minimumSize;
}
/**
* Specify the minimum size for the component. The minimum size is
* constrained by the drag insets.
*
* #param minimumSize the minimum size for a component.
*/
public void setMinimumSize(Dimension minimumSize)
{
validateMinimumAndInsets(minimumSize, dragInsets);
this.minimumSize = minimumSize;
}
/**
* Remove listeners from the specified component
*
* #param component the component the listeners are removed from
*/
public void deregisterComponent(Component... components)
{
for (Component component : components)
{
component.removeMouseListener( this );
component.removeMouseMotionListener( this );
}
}
/**
* Add the required listeners to the specified component
*
* #param component the component the listeners are added to
*/
public void registerComponent(Component... components)
{
for (Component component : components)
{
component.addMouseListener( this );
component.addMouseMotionListener( this );
}
}
/**
* Get the snap size.
*
* #return the snap size.
*/
public Dimension getSnapSize()
{
return snapSize;
}
/**
* Control how many pixels a border must be dragged before the size of
* the component is changed. The border will snap to the size once
* dragging has passed the halfway mark.
*
* #param snapSize Dimension object allows you to separately spcify a
* horizontal and vertical snap size.
*/
public void setSnapSize(Dimension snapSize)
{
this.snapSize = snapSize;
}
/**
* When the components minimum size is less than the drag insets then
* we can't determine which border should be resized so we need to
* prevent this from happening.
*/
private void validateMinimumAndInsets(Dimension minimum, Insets drag)
{
int minimumWidth = drag.left + drag.right;
int minimumHeight = drag.top + drag.bottom;
if (minimum.width < minimumWidth
|| minimum.height < minimumHeight)
{
String message = "Minimum size cannot be less than drag insets";
throw new IllegalArgumentException( message );
}
}
/**
*/
#Override
public void mouseMoved(MouseEvent e)
{
Component source = e.getComponent();
Point location = e.getPoint();
direction = 0;
if (location.x < dragInsets.left)
direction += WEST;
if (location.x > source.getWidth() - dragInsets.right - 1)
direction += EAST;
if (location.y < dragInsets.top)
direction += NORTH;
if (location.y > source.getHeight() - dragInsets.bottom - 1)
direction += SOUTH;
// Mouse is no longer over a resizable border
if (direction == 0)
{
source.setCursor( sourceCursor );
}
else // use the appropriate resizable cursor
{
int cursorType = cursors.get( direction );
Cursor cursor = Cursor.getPredefinedCursor( cursorType );
source.setCursor( cursor );
}
}
#Override
public void mouseEntered(MouseEvent e)
{
if (! resizing)
{
Component source = e.getComponent();
sourceCursor = source.getCursor();
}
}
#Override
public void mouseExited(MouseEvent e)
{
if (! resizing)
{
Component source = e.getComponent();
source.setCursor( sourceCursor );
}
}
#Override
public void mousePressed(MouseEvent e)
{
// The mouseMoved event continually updates this variable
if (direction == 0) return;
// Setup for resizing. All future dragging calculations are done based
// on the original bounds of the component and mouse pressed location.
resizing = true;
Component source = e.getComponent();
pressed = e.getPoint();
SwingUtilities.convertPointToScreen(pressed, source);
bounds = source.getBounds();
// Making sure autoscrolls is false will allow for smoother resizing
// of components
if (source instanceof JComponent)
{
JComponent jc = (JComponent)source;
autoscrolls = jc.getAutoscrolls();
jc.setAutoscrolls( false );
}
}
/**
* Restore the original state of the Component
*/
#Override
public void mouseReleased(MouseEvent e)
{
resizing = false;
Component source = e.getComponent();
source.setCursor( sourceCursor );
if (source instanceof JComponent)
{
((JComponent)source).setAutoscrolls( autoscrolls );
}
}
/**
* Resize the component ensuring location and size is within the bounds
* of the parent container and that the size is within the minimum and
* maximum constraints.
*
* All calculations are done using the bounds of the component when the
* resizing started.
*/
#Override
public void mouseDragged(MouseEvent e)
{
if (resizing == false) return;
Component source = e.getComponent();
Point dragged = e.getPoint();
SwingUtilities.convertPointToScreen(dragged, source);
changeBounds(source, direction, bounds, pressed, dragged);
}
protected void changeBounds(Component source, int direction, Rectangle bounds, Point pressed, Point current)
{
// Start with original locaton and size
int x = bounds.x;
int y = bounds.y;
int width = bounds.width;
int height = bounds.height;
// Resizing the West or North border affects the size and location
if (WEST == (direction & WEST))
{
int drag = getDragDistance(pressed.x, current.x, snapSize.width);
int maximum = Math.min(width + x, maximumSize.width);
drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum);
x -= drag;
width += drag;
}
if (NORTH == (direction & NORTH))
{
int drag = getDragDistance(pressed.y, current.y, snapSize.height);
int maximum = Math.min(height + y, maximumSize.height);
drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum);
y -= drag;
height += drag;
}
// Resizing the East or South border only affects the size
if (EAST == (direction & EAST))
{
int drag = getDragDistance(current.x, pressed.x, snapSize.width);
Dimension boundingSize = getBoundingSize( source );
int maximum = Math.min(boundingSize.width - x, maximumSize.width);
drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum);
width += drag;
}
if (SOUTH == (direction & SOUTH))
{
int drag = getDragDistance(current.y, pressed.y, snapSize.height);
Dimension boundingSize = getBoundingSize( source );
int maximum = Math.min(boundingSize.height - y, maximumSize.height);
drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum);
height += drag;
}
source.setBounds(x, y, width, height);
source.validate();
}
/*
* Determine how far the mouse has moved from where dragging started
*/
private int getDragDistance(int larger, int smaller, int snapSize)
{
int halfway = snapSize / 2;
int drag = larger - smaller;
drag += (drag < 0) ? -halfway : halfway;
drag = (drag / snapSize) * snapSize;
return drag;
}
/*
* Adjust the drag value to be within the minimum and maximum range.
*/
private int getDragBounded(int drag, int snapSize, int dimension, int minimum, int maximum)
{
while (dimension + drag < minimum)
drag += snapSize;
while (dimension + drag > maximum)
drag -= snapSize;
return drag;
}
/*
* Keep the size of the component within the bounds of its parent.
*/
private Dimension getBoundingSize(Component source)
{
if (source instanceof Window)
{
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
return new Dimension(bounds.width, bounds.height);
}
else
{
return source.getParent().getSize();
}
}
}
}
I searched for a solution but no one was working...
The reason seems to be the redrawing of all component tree during the resize user action.
As the redrawing of the resized component and of its children components is done not only after mouse released (end of the resize) but during the resize too (during mouse dragging) I tried to comment the
source.validate();
line into
changeBounds method (it is continuously called during dragging).
I put the line at the beginning of mouseReleased method into a if block:
if(resizing == true) {
e.getComponent().validate();
}
It works (the flickering is almost absent).
Let me know if this solution works for you too...
I am making a webserver that generates checker-like tiles Images on java.
The class that models the checker is:
package app.app.image;
import app.app.image.iterate.ImageElementVisitorInterface;
import app.app.image.tiles.Tile;
import java.awt.*;
import java.util.ArrayList;
import org.apache.commons.collections4.CollectionUtils;
/**
* Created by pcmagas on 5/10/2016.
*
* Methos that Implements a Checkerboard with multiple color tiles
*/
public class Checker implements ImageElement
{
/**
* The tiles that can be used to Draw the checker
*/
private ArrayList<Tile> availableTiles=null;
/**
* The grid where the tiles are drawn
*/
private Tile[][] checkerGrid=null;
/**
* The checker width
*/
private int width=0;
/**
* The checker Height
*/
private int height=0;
private int tileSize=0;
/**
* Creates a new Checker
* #param width the checker's width
* #param height the checker's height
*/
public Checker(int width, int height)
{
availableTiles= new ArrayList<Tile>();
this.setHeight(height);
this.setWidth(width);
}
/**
* #param height The checker's height
*/
public void setHeight(int height)
{
this.height=height;
}
/**
* #return The checker's height
*/
public int getHeight()
{
return this.height;
}
/**
* Seth the tile width
* #param width
*/
public void setWidth(int width)
{
this.width=width;
}
/**
* Returns how wide is the tile
* #return
*/
public int getWidth()
{
return this.width;
}
/**
* Method that Allows us to append a tile to the Checker
*/
public void addTile(Tile t) throws Exception
{
if(this.tileSize >0 && t.getSize()!= this.tileSize)
{
throw new Exception("The tile does not have the same size with the orthers");
}
else if(!this.availableTiles.contains(t))
{
this.availableTiles.add(t);
this.tileSize=t.getSize();
}
}
/**
* Method that initializes the grid for the checker
*/
private void initGrid()
{
if(this.checkerGrid==null)
{
int width = this.width/this.tileSize;
int height = this.height/this.tileSize;
this.checkerGrid=new Tile[height][width];
}
}
/**
* Method that selects a tile during the grid generation
* #param top The tile that is on top of the current tile
* #param previous The tile that is on previously from the current tile
*
* #return The correctly selected tile
*/
private Tile selectTile(Tile top,Tile previous)
{
ArrayList<Tile> tilesNotToUse=new ArrayList<Tile>();
if(top==null)
{
tilesNotToUse.add(top);
}
if(previous==null)
{
tilesNotToUse.add(previous);
}
//TODO: Create a diff Between this.availableTiles and tilesNotToUse
return new Tile(Color.black,10,this);
}
#Override
public void draw(ImageElementVisitorInterface visitor)
{
this.initGrid();
for(int i=0;i<this.checkerGrid.length;i++)
{
for(int j=0;j<this.checkerGrid[i].length;j++)
{
Tile top=(i>0)?this.checkerGrid[i-1][j]:null;
Tile previous=(j>0)?this.checkerGrid[i][j-1]:null;
Tile currentTile=this.selectTile(top,previous);
this.checkerGrid[i][j]= currentTile;
currentTile.draw(visitor);
}
}
}
}
Please I have some do-not-know-hot-to-do on this method:
/**
* Method that selects a tile during the grid generation
* #param top The tile that is on top of the current tile
* #param previous The tile that is on previously from the current tile
*
* #return The correctly selected tile
*/
private Tile selectTile(Tile top,Tile previous)
{
ArrayList<Tile> tilesNotToUse=new ArrayList<Tile>();
if(top==null)
{
tilesNotToUse.add(top);
}
if(previous==null)
{
tilesNotToUse.add(previous);
}
//TODO: Create a diff Between this.availableTiles and tilesNotToUse
return new Tile(Color.black,10,this);
}
And I want to do a diff between this.availableTiles and tilesNotToUse. I have seen the CollectionUtils but I cannot figure out how to do it. I want to acheive similar result with php's http://php.net/manual/en/function.array-diff.php
You can use list1.removeAll(list2); Java Docs Thus your list1 will have only what belongs to list1 and not to list2. And the other way around if you want.
I am having trouble. I need to rotate an equilateral triangle around it's centre by using the drag listener and click listener. The triangle should grow but now change angles and be rotated by a point while being centred at the middle of the triangle. This is my problem, it is currently dragging by the point 3 and rotating around point 1. I have an array of values x and y and it stores 4 values each containing the initial point first at ordinal value 0 and point 1 2 and 3 at the corresponding values.
`
public class DrawTriangle extends JFrame {
enter code here
/** The Constant NUMBER_3. */
private static final int NUMBER_3 = 3;
/** The Constant EQUL_ANGLE. */
#SuppressWarnings("unused")
private static final double EQUL_ANGLE = 1;
/** The Constant TRIANGLE_POINTS. */
private static final int TRIANGLE_POINTS = 4;
/** The Constant _400. */
private static final int SIZE = 400;
/** The x points. */
private int [] xPoints = new int[TRIANGLE_POINTS];
/** The y points. */
private int [] yPoints = new int[TRIANGLE_POINTS];
private int xInitial;
private int yInitial;
/** The x. */
private double x = EQUL_ANGLE;
/** The new x. */
private double newX;
/** The new y. */
private double newY;
/**
* Instantiates a new draw triangle.
*/
public DrawTriangle() {
super("Dimitry Rakhlei");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setContentPane(new DrawTrianglePanel());
setSize(SIZE, SIZE); // you can change this size but don't make it HUGE!
setVisible(true);
}
/**
* The Class DrawTrianglePanel.
*/
private class DrawTrianglePanel extends JPanel implements MouseListener,
MouseMotionListener {
/**
* Instantiates a new draw triangle panel.
*/
public DrawTrianglePanel() {
addMouseListener(this);
addMouseMotionListener(this);
}
/**
* Drawing the triangle.
*
* #param g
* the g
* #see javax.swing.JComponent#paintComponent(java.awt.Graphics)
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
// DRAWING CODE HERE
g.drawPolygon(xPoints, yPoints, 3);
System.out.println("Paint called");
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mousePressed
* (java.awt.event.MouseEvent)
*/
public void mousePressed(MouseEvent e) {
System.out.println("Mouse pressed called");
e.getPoint();
xPoints[0] = e.getPoint().x;
yPoints[0] = e.getPoint().y;
repaint();
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mouseReleased
* (java.awt.event.MouseEvent)
*/
public void mouseReleased(MouseEvent e) {
System.out.println("Mouse released called");
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseMotionListener#mouseDragged
* (java.awt.event.MouseEvent)
*/
public void mouseDragged(MouseEvent e) {
System.out.println("Mouse dragged called");
newX = e.getPoint().x;
newY = e.getPoint().y;
xPoints[1] = (int) newX;
yPoints[1] = (int) newY;
newX = xPoints[0] + (xPoints[1]-xPoints[0])*Math.cos(x) - (yPoints[1]-yPoints[0])*Math.sin(x);
newY = yPoints[0] + (xPoints[1]-xPoints[0])*Math.sin(x) + (yPoints[1]-yPoints[0])*Math.cos(x);
xPoints[2] = (int) newX;
yPoints[2] = (int) newY;
newX = xPoints[0] + (xPoints[1]-xPoints[0])*Math.cos(x) - (yPoints[1]-yPoints[0])*Math.sin(x);
newY = yPoints[0] + (xPoints[1]-xPoints[0])*Math.sin(x) + (yPoints[1]-yPoints[0])*Math.cos(x);
xPoints[3] = (int) newX;
yPoints[3] = (int) newY;
repaint();
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mouseEntered
* (java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent e) {
System.out.println("Mouse Entered.");
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mouseExited
* (java.awt.event.MouseEvent)
*/
public void mouseExited(MouseEvent e) {
System.out.println("Mouse exited.");
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mouseClicked
* (java.awt.event.MouseEvent)
*/
public void mouseClicked(MouseEvent e) {
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseMotionListener#mouseMoved
* (java.awt.event.MouseEvent)
*/
public void mouseMoved(MouseEvent e) {
}
}
/**
* The main method.
*
* #param args
* the arguments
*/
public static void main(String[] args) {
new DrawTriangle();
}
};`
My issue is that this code basically runs correctly but I am told the vertex point of rotation has to be in the middle of the triangle. Mine is the first point.
Start by taking a look at 2D Graphics, in particular Transforming Shapes, Text, and Images.
Basically, your "polygon" will have a definable size (the maximum x/y point), from this, you can determine the center position of the "polygon", for example...
protected Dimension getTriangleSize() {
int maxX = 0;
int maxY = 0;
for (int index = 0; index < xPoints.length; index++) {
maxX = Math.max(maxX, xPoints[index]);
}
for (int index = 0; index < yPoints.length; index++) {
maxY = Math.max(maxY, yPoints[index]);
}
return new Dimension(maxX, maxY);
}
This just returns the maximum x and y bounds of your polygon. This allows you to calculate the center position of the polygon. You'll see why in a second why you don't need to actually specify the origin point...
Next, we calculate a AffineTransform, which is the applied to the Graphics context directly...
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = new AffineTransform();
Dimension size = getTriangleSize();
int x = clickPoint.x - (size.width / 2);
int y = clickPoint.y - (size.height / 2);
at.translate(x, y);
at.rotate(Math.toRadians(angle), clickPoint.x - x, clickPoint.y - y);
g2d.setTransform(at);
g2d.drawPolygon(xPoints, yPoints, 3);
// Guide
g2d.setColor(Color.RED);
g2d.drawLine(size.width / 2, 0, size.width / 2, size.height / 2);
g2d.dispose();
This not only translates the triangle position, but will also rotate it. What this means you can create a normalised polygon (whose origin point is 0x0) and allow the Graphics context to place it where you want it, this makes life SO much easier...
Now, the rotation calculation is based on calculating the angle between two points, the "click" point and the "drag" point...
angle = -Math.toDegrees(Math.atan2(e.getPoint().x - clickPoint.x, e.getPoint().y - clickPoint.y)) + 180;
Which is based on the solution in this question
For example...
The red line is simple a guide to show that the tip of the triangle is point towards the mouse...
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.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DrawTriangle extends JFrame {
/**
* The x points.
*/
private int[] xPoints = new int[]{0, 25, 50};
/**
* The y points.
*/
private int[] yPoints = new int[]{50, 0, 50};
double angle = 0f;
/**
* Instantiates a new draw triangle.
*/
public DrawTriangle() {
super("Dimitry Rakhlei");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setContentPane(new DrawTrianglePanel());
pack();
setLocationRelativeTo(null);
setVisible(true);
}
/**
* The Class DrawTrianglePanel.
*/
private class DrawTrianglePanel extends JPanel implements MouseListener,
MouseMotionListener {
private Point clickPoint;
/**
* Instantiates a new draw triangle panel.
*/
public DrawTrianglePanel() {
addMouseListener(this);
addMouseMotionListener(this);
clickPoint = new Point(100, 100);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected Dimension getTriangleSize() {
int maxX = 0;
int maxY = 0;
for (int index = 0; index < xPoints.length; index++) {
maxX = Math.max(maxX, xPoints[index]);
}
for (int index = 0; index < yPoints.length; index++) {
maxY = Math.max(maxY, yPoints[index]);
}
return new Dimension(maxX, maxY);
}
/**
* Drawing the triangle.
*
* #param g the g
* #see javax.swing.JComponent#paintComponent(java.awt.Graphics)
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = new AffineTransform();
Dimension size = getTriangleSize();
int x = clickPoint.x - (size.width / 2);
int y = clickPoint.y - (size.height / 2);
at.translate(x, y);
at.rotate(Math.toRadians(angle), clickPoint.x - x, clickPoint.y - y);
g2d.setTransform(at);
g2d.drawPolygon(xPoints, yPoints, 3);
// Guide
g2d.setColor(Color.RED);
g2d.drawLine(size.width / 2, 0, size.width / 2, size.height / 2);
g2d.dispose();
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mousePressed (java.awt.event.MouseEvent)
*/
#Override
public void mousePressed(MouseEvent e) {
System.out.println("Mouse pressed called");
// clickPoint = e.getPoint();
repaint();
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mouseReleased (java.awt.event.MouseEvent)
*/
#Override
public void mouseReleased(MouseEvent e) {
System.out.println("Mouse released called");
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseMotionListener#mouseDragged (java.awt.event.MouseEvent)
*/
public void mouseDragged(MouseEvent e) {
System.out.println("Mouse dragged called");
angle = -Math.toDegrees(Math.atan2(e.getPoint().x - clickPoint.x, e.getPoint().y - clickPoint.y)) + 180;
repaint();
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mouseEntered (java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent e) {
System.out.println("Mouse Entered.");
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mouseExited (java.awt.event.MouseEvent)
*/
public void mouseExited(MouseEvent e) {
System.out.println("Mouse exited.");
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mouseClicked (java.awt.event.MouseEvent)
*/
public void mouseClicked(MouseEvent e) {
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseMotionListener#mouseMoved (java.awt.event.MouseEvent)
*/
public void mouseMoved(MouseEvent e) {
}
}
/**
* The main method.
*
* #param args the arguments
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
new DrawTriangle();
}
});
}
}
Now, before you jump all over me and complain that the solution is "too complex", understand that I'm an idiot, seriously, my 2 year old has a better grasp on basic mathematics then I do, this is the most simplistic solution I can come up with that doesn't melt my brain and uses the dual array polygon API. Personally, I'd use the Shape API, but that's not what you started with...
I am trying to draw a curved Line arrow on a stacked bar graph.I have been able to draw the curved line and arrow.But i am not able to connect the arrow to the end of the curved line.I am using affine transformation to draw the curved line.The below link describes the curved line and arrow that i have been able to draw http://i58.tinypic.com/2m422hy.png.Can anyone guide me as to how to connect the arrow to the end of the curved line.
Here is the code
package Stack;
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author OSPL-B4
/
/
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.jfree.chart.annotations.CategoryAnnotation;
import org.jfree.chart.axis.CategoryAnchor;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.event.AnnotationChangeListener;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.io.SerialUtilities;
import org.jfree.ui.RectangleEdge;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PaintUtilities;
//import java.awt.Font;
/**
* A line annotation that can be placed on a
* {#link org.jfree.chart.plot.CategoryPlot}.
*/
public class CategoryLineAnnotation_demo1 implements CategoryAnnotation,
Cloneable, Serializable {
/** The category for the start of the line. */
private Comparable category1;
/** The value for the start of the line. */
private double value1;
/** The category for the end of the line. */
private Comparable category2;
/** The value for the end of the line. */
private double value2;
private final int ARR_SIZE = 4;
/** The line color. */
private transient Paint paint = Color.black;
/** The line stroke. */
private transient Stroke stroke = new BasicStroke(1.0f);
/**
* Creates a new annotation that draws a line between (category1, value1)
* and (category2, value2).
*
* #param category1 the category (<code>null</code> not permitted).
* #param value1 the value.
* #param category2 the category (<code>null</code> not permitted).
* #param value2 the value.
*/
public CategoryLineAnnotation_demo1(Comparable category1, double value1,
Comparable category2, double value2,
Paint paint, Stroke stroke) {
if (category1 == null) {
throw new IllegalArgumentException("Null 'category1' argument.");
}
if (category2 == null) {
throw new IllegalArgumentException("Null 'category2' argument.");
}
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
if (stroke == null) {
throw new IllegalArgumentException("Null 'stroke' argument.");
}
this.category1 = category1;
System.out.println("First Category value is "+category1);
this.value1 = value1;
this.category2 = category2;
System.out.println("Second Category value is "+category2);
this.value2 = value2;
this.paint = paint;
this.stroke = stroke;
}
/**
* Returns the category for the start of the line.
*
* #return The category for the start of the line (never <code>null</code>).
*/
public Comparable getCategory1() {
return this.category1;
}
/**
* Sets the category for the start of the line.
*
* #param category the category (<code>null</code> not permitted).
*/
public void setCategory1(Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
this.category1 = category;
}
/**
* Returns the y-value for the start of the line.
*
* #return The y-value for the start of the line.
*/
public double getValue1() {
return this.value1;
}
/**
* Sets the y-value for the start of the line.
*
* #param value the value.
*/
public void setValue1(double value) {
this.value1 = value;
}
/**
* Returns the category for the end of the line.
*
* #return The category for the end of the line (never <code>null</code>).
*/
public Comparable getCategory2() {
return this.category2;
}
/**
* Sets the category for the end of the line.
*
* #param category the category (<code>null</code> not permitted).
*/
public void setCategory2(Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
this.category2 = category;
}
/**
* Returns the y-value for the end of the line.
*
* #return The y-value for the end of the line.
*/
public double getValue2() {
return this.value2;
}
/**
* Sets the y-value for the end of the line.
*
* #param value the value.
*/
public void setValue2(double value) {
this.value2 = value;
}
/**
* Returns the paint used to draw the connecting line.
*
* #return The paint (never <code>null</code>).
*/
public Paint getPaint() {
return this.paint;
}
/**
* Sets the paint used to draw the connecting line.
*
* #param paint the paint (<code>null</code> not permitted).
*/
public void setPaint(Paint paint) {
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
this.paint = paint;
}
/**
* Returns the stroke used to draw the connecting line.
*
* #return The stroke (never <code>null</code>).
*/
public Stroke getStroke() {
// System.out.println("In Stacked bar Stroke is "+getStroke());
return this.stroke;
}
/**
* Sets the stroke used to draw the connecting line.
*
* #param stroke the stroke (<code>null</code> not permitted).
*/
public void setStroke(Stroke stroke) {
if (stroke == null) {
throw new IllegalArgumentException("Null 'stroke' argument.");
}
this.stroke = stroke;
}
/**
* Draws the annotation.
*
* #param g2 the graphics device.
* #param plot the plot.
* #param dataArea the data area.
* #param domainAxis the domain axis.
* #param rangeAxis the range axis.
*/
public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea,
CategoryAxis domainAxis, ValueAxis rangeAxis) {
CategoryDataset dataset = plot.getDataset();
int catIndex1 = dataset.getColumnIndex(this.category1);
int catIndex2 = dataset.getColumnIndex(this.category2);
int catCount = dataset.getColumnCount();
double lineX1 = 0.0f;
double lineY1 = 0.0f;
double lineX2 = 0.0f;
double lineY2 = 0.0f;
PlotOrientation orientation = plot.getOrientation();
RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
plot.getDomainAxisLocation(), orientation);
RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
plot.getRangeAxisLocation(), orientation);
if (orientation == PlotOrientation.HORIZONTAL) {
lineY1 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
domainEdge);
lineX1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
lineY2 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
domainEdge);
lineX2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
}
else if (orientation == PlotOrientation.VERTICAL) {
lineX1 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
domainEdge);
lineY1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
lineX2 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
domainEdge);
lineY2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
}
g2.setPaint(this.paint);
g2.setStroke(this.stroke);
drawArrow(g2,(int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2);
}
void drawArrow(Graphics g1, int x1, int y1, int x2, int y2) {
Graphics2D g = (Graphics2D) g1.create();
double dx = x2 - x1, dy = y2 - y1;
System.out.println("Value of DX "+dx);
System.out.println("Value of DY "+dy);
double angle = Math.atan2(dy, dx);
System.out.println("Getting angle "+angle);
int len = (int) Math.sqrt(dx*dx + dy*dy);
AffineTransform at = AffineTransform.getTranslateInstance(x1, y1);
at.concatenate(AffineTransform.getRotateInstance(angle));
g.transform(at);
System.out.println("Affine transform X co-ordinate value is "+at.getScaleX());
System.out.println("Affine transform Y co-ordinate value is "+at.getScaleY());
float center1=(x1+x2)/2-40;
float center2= (y1+y2)/2-40;
QuadCurve2D q=new QuadCurve2D.Float(0,0,center1,center2,x2,y2);
g.draw(q);
g.setColor(Color.RED);
System.out.println("Length of arrow is "+len);
System.out.println("Get Start point 2D "+q.getP1());
System.out.println("Get End point 2D "+q.getP2());
g.fillPolygon(new int[] {len, len-ARR_SIZE, len-ARR_SIZE-10, len-60},
new int[] {0, -ARR_SIZE, ARR_SIZE-20, 5}, 4);
}
public void paintComponent(Graphics g) {
for (int x = 15; x < 200; x += 16)
drawArrow(g, x, x, x, 150);
drawArrow(g, 30, 300, 300, 190);
}
/**
* Tests this object for equality with another.
*
* #param obj the object (<code>null</code> permitted).
*
* #return <code>true</code> or <code>false</code>.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CategoryLineAnnotation_demo1)) {
return false;
}
CategoryLineAnnotation_demo1 that = (CategoryLineAnnotation_demo1) obj;
if (!this.category1.equals(that.getCategory1())) {
return false;
}
if (this.value1 != that.getValue1()) {
return false;
}
if (!this.category2.equals(that.getCategory2())) {
return false;
}
if (this.value2 != that.getValue2()) {
return false;
}
if (!PaintUtilities.equal(this.paint, that.paint)) {
return false;
}
if (!ObjectUtilities.equal(this.stroke, that.stroke)) {
return false;
}
return true;
}
/**
* Returns a hash code for this instance.
*
* #return A hash code.
*/
public int hashCode() {
// TODO: this needs work
return this.category1.hashCode() + this.category2.hashCode();
}
/**
* Returns a clone of the annotation.
*
* #return A clone.
*
* #throws CloneNotSupportedException this class will not throw this
* exception, but subclasses (if any) might.
*/
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* Provides serialization support.
*
* #param stream the output stream.
*
* #throws IOException if there is an I/O error.
*/
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
SerialUtilities.writePaint(this.paint, stream);
SerialUtilities.writeStroke(this.stroke, stream);
}
/**
* Provides serialization support.
*
* #param stream the input stream.
*
* #throws IOException if there is an I/O error.
* #throws ClassNotFoundException if there is a classpath problem.
*/
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
this.paint = SerialUtilities.readPaint(stream);
this.stroke = SerialUtilities.readStroke(stream);
}
#Override
public void addChangeListener(AnnotationChangeListener al) {
}
#Override
public void removeChangeListener(AnnotationChangeListener al) {
}
}
You can create the arrow head based on the last line segment (which might already be transformed using an AffineTransform)
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ArrowPainter
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new ArrowPaintPanel();
f.getContentPane().add(panel);
f.setSize(500,500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ArrowPaintPanel extends JPanel implements MouseMotionListener
{
private Point2D startPoint = null;
private Point2D endPoint = null;
ArrowPaintPanel()
{
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
if (startPoint == null)
{
startPoint = new Point(getWidth()/2, getHeight()/2);
}
if (endPoint == null)
{
return;
}
Line2D line = new Line2D.Double(startPoint, endPoint);
Shape arrowHead = createArrowHead(line, 30, 20);
g.draw(line);
g.fill(arrowHead);
}
#Override
public void mouseDragged(MouseEvent e)
{
endPoint = e.getPoint();
repaint();
}
#Override
public void mouseMoved(MouseEvent e)
{
endPoint = e.getPoint();
repaint();
}
private static Shape createArrowHead(Line2D line, double length, double width)
{
Point2D p0 = line.getP1();
Point2D p1 = line.getP2();
double x0 = p0.getX();
double y0 = p0.getY();
double x1 = p1.getX();
double y1 = p1.getY();
double dx = x1 - x0;
double dy = y1 - y0;
double invLength = 1.0 / Math.sqrt(dx*dx+dy*dy);
double dirX = dx * invLength;
double dirY = dy * invLength;
double ax = x1 - length * dirX;
double ay = y1 - length * dirY;
double offsetX = width * -dirY * 0.5;
double offsetY = width * dirX * 0.5;
double c0x = ax + offsetX;
double c0y = ay + offsetY;
double c1x = ax - offsetX;
double c1y = ay - offsetY;
Path2D arrowHead = new Path2D.Double();
arrowHead.moveTo(x1, y1);
arrowHead.lineTo(c0x, c0y);
arrowHead.lineTo(c1x, c1y);
arrowHead.closePath();
return arrowHead;
}
}
EDIT: Update for the above EDIT and the comments: That's a lot of code, but still nothing that can be tested easily. What happens when you replace your line
drawArrow(g2,(int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2);
with
g.fill(createArrowHead(new Line2D.Double(lineX1, lineY1, lineX2, lineY2), 30, 20));
?