As the title states, I am trying to detect a mouse hover over an object that is not a JComponent.
Right now I have a window with a green JPanel. When you left-click on this JPanel you create a point.
What I am trying to do is to have extra information displayed when I hover over these points. However, I have no idea how to even begin detecting if I am hovering my mouse over a point. I tried looking into the MouseListener interface but I could not find any examples of people using MouseListener with an object. I have only seen people use MouseListener with JComponents. I would preferably like to have this mouse hover detection code in my Point class if possible to keep my code clean.
JPanel Code
class Map extends JPanel implements MouseListener {
public static ArrayList<Point> points = new ArrayList<Point>(); //array for the points
public Map() {
this.setBackground(Color.green);
this.setPreferredSize(new Dimension(1280, 720));
this.addMouseListener(this);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D graphics = (Graphics2D) g;
drawPoints(graphics);
}
private void drawPoints(Graphics2D graphics) {
for(int i = 0; i < points.size(); i++) {
points.get(i).drawPoint(graphics);
}
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1) { //Left Click
points.add(new Point(e.getX(), e.getY()));
repaint();
}
else if(e.getButton() == MouseEvent.BUTTON3) { //right click
for(int i = points.size() - 1; i >= 0; i--) { //loop backwards so if points overlap remove the one on top first
Point current = points.get(i);
if( Math.abs( e.getX() - current.x ) < current.size/2 && Math.abs( e.getY() - current.y ) < current.size/2 ) {
points.remove(i);
repaint();
break;
}
}
}
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
}
Point Code
public class Point {
public int x, y;
public int size = 10;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public Point() {
this.x = 0;
this.y = 0;
}
public void drawPoint(Graphics2D graphics) {
graphics.setPaint(Color.black);
graphics.setStroke(new BasicStroke(5));
graphics.drawOval(x - (size/2), y - (size/2), size, size);
graphics.setPaint(Color.red);
graphics.fillOval(x - (size/2), y - (size/2), size, size);
}
public void drawInfo(Graphics2D graphics) {
graphics.drawString("test", x, y);
}
}
I had the same issue with a Packet-Tracer-Like program, where I drew rects.
If they are just points, I would check if the mouse cords are the same as the point cords when the mouse is moved.
#Override
public void mouseMoved(MouseEvent e) {
entered = false;
if(point.x == e.getX() && point.y == e.getY()){
entered = true;
}
}
If although, like in my case, the drawn object has a width and a height, it gets messier.
#Override
public void mouseMoved(MouseEvent e) {
entered = false;
if((e.getX() <= point.x+width) && (e.getX() >= point.x)){
if((e.getY() <= point.y+height) && (e.getY() >= point.y)){
entered = true;
}
}
}
Related
I'm attempting to make a little paint app where you input the color you want, and change the size of the brush. I did this, and it works, but if I try to paint quickly, the app doesn't update for a short period of time, leaving dots looking like this:
60 times per second (because 60 times per second should be enough to not leave dots/gaps. See tick method. As a test, I tried putting no limit on the ticksPerSecond, and I got the same output), if the user is causing a mouseDragged or mousePressed event, I add a an instance of the Circle class (seen below)
class Circle {
int x, y;
int radius;
Color color;
Circle(int x, int y, int radius, Color color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
}
My tick method: (Called 60 times each second)
private void tick() {
this.middle = this.brush.radius / 2; // Used because java.awt.Graphics will paint from your cursors xy, which will paint the circle down and to the right of the cursor
if (this.frame.isDragging) {
add(new Circle(this.frame.x - this.middle, this.frame.y - this.middle, this.brush.radius, this.brush.color));
}
}
To render, I just run through a foreach loop, seen below
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // Paint over everything else
try {
for (Circle circle : this.circles) {
g.setColor(circle.color);
g.fillOval(circle.x, circle.y, circle.radius, circle.radius);
}
} catch (ConcurrentModificationException ignore) {} //TODO MAKE THIS BETTER
}
Ignore that I'm catching a ConcurrentModifictionException, this is a temporary fix because I'm adding to a list that's being read here. (It only causes the paint to flash while I am painting, so it works for now)
I was asked for my Frame, here it is (You probably don't need this, but I included any code you needed. I remove implement methods that I didn't use):
class Frame extends JFrame implements MouseListener, MouseMotionListener {
int x, y;
boolean isDragging, isMouseOnScreen;
Frame(int width) {
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.getContentPane().setPreferredSize(new Dimension(width, width / 3 * 2));
this.setResizable(false);
this.pack();
this.setLocationRelativeTo(null);
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Cursor cursor = this.getToolkit().createCustomCursor(image, new Point(0, 0), "Cursor");
this.setCursor(cursor);
this.setFocusable(true);
this.requestFocus();
this.addMouseListener(this);
this.addMouseMotionListener(this);
this.x = this.y = 0;
this.isDragging = this.isMouseOnScreen = false;
}
#Override
public void mouseDragged(MouseEvent e) {
this.x = e.getX() - 5;
this.y = e.getY() - 35;
}
#Override
public void mouseMoved(MouseEvent e) {
this.x = e.getX() - 5;
this.y = e.getY() - 35;
}
#Override
public void mousePressed(MouseEvent e) {
this.isDragging = true;
}
#Override
public void mouseReleased(MouseEvent e) {
this.isDragging = false;
}
#Override
public void mouseEntered(MouseEvent e) {
isMouseOnScreen = true;
}
#Override
public void mouseExited(MouseEvent e) {
isMouseOnScreen = false;
}
}
I am trying to make the sprite stop continuously moving. Right now, the code makes the sprite move in the direction indicated by the user through arrow keys without stopping. I want to press an arrow key and make the sprite move once (ex. 10 steps only) in that direction.
public class Contents extends JPanel implements ActionListener, KeyListener {
Timer t = new Timer(100, this);
private Image man;
int x=0, y=0;
public Contents() {
super.setDoubleBuffered(true);
t.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
#Override
public void paintComponent (Graphics g) {
super.paintComponent(g);
super.setBackground(Color.white);
ImageIcon ii = new ImageIcon("guy.png");
man = ii.getImage();
Graphics2D g2d = (Graphics2D)g;
g2d.drawImage(man, x, y, this);
}
double xv = 0;
double yv = 0;
public void move() {
x += xv;
y += yv;
}
public void up() {
yv = -1;
xv = 0;
}
public void down() {
yv = 1;
xv = 0;
}
public void left () {
xv = -1;
yv = 0;
}
public void right () {
xv = 1;
yv = 0;
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
//Collision for left and right walls
if (x==0) {
xv = 1;
}
else if (x==900-240) {
xv = -1;
}
// Collision for top and bottom walls
if (y==0) {
yv = 1;
}
else if (y==600-320) {
yv = -1;
}
move();
}
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code == KeyEvent.VK_UP) {
up();
}
if (code == KeyEvent.VK_DOWN) {
down();
}
if (code == KeyEvent.VK_LEFT) {
left();
}
if (code == KeyEvent.VK_RIGHT) {
right();
}
}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
}
It works when I delete the boundaries under ActionEvent. The boundaries prevent the sprite from going off screen. But I want to keep the boundaries and control the sprite too.
I figured it out. Under keyReleased, set xv=0. Then when you release the arrow key, the sprite will stop moving.
My boolean drawFlag is being set to false in my code with nothing visible actually telling it to change value to false. This is preventing the code inside method mouseDragged(mouseEvent) to execute. If someone could point out what's making the flag become false so this would stop happening? Thanks.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class DrawAndDrag {
public static void main(String[] args) throws InterruptedException {
GraphicsFrame window = new GraphicsFrame("Draw Rectangle");
window.init();
}
}
class GraphicsFrame extends JFrame {
public GraphicsFrame(String title) {
super(title);
}
public void init() {
Container pane = this.getContentPane();
pane.add(new GraphicsContent().init());
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(600, 600);
this.setVisible(true);
}
}
class GraphicsContent extends JPanel {
private int xStart, yStart;
private int width, height;
public JPanel init() {
this.setBackground(Color.WHITE);
this.addMouseListener(new MouseDrag());
this.addMouseMotionListener(new MouseDrag());
return this;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillRect(xStart, yStart, width, height);
}
class MouseDrag implements MouseListener, MouseMotionListener {
private boolean drawFlag;
public void mousePressed(MouseEvent e) {
if(isOutside(e)) {
this.drawFlag = true;
xStart = e.getX();
yStart = e.getY();
width = 0; height = 0;
System.out.println(drawFlag);
}else {
// this.drawFlag = false;
}
}
public void mouseDragged(MouseEvent e) {
System.out.println(drawFlag);
if(drawFlag) {
width = e.getX() - xStart;
height = e.getY() - yStart;
repaint();
}
}
public boolean isOutside(MouseEvent e) {
int xMin = Math.min(xStart, xStart + width); int yMin = Math.min(yStart, yStart + height);
int xMax = Math.max(xStart, xStart + width); int yMax = Math.max(yStart, yStart + height);
if((e.getX() < xMin || e.getX() > xMax)
|| (e.getY() < yMin || e.getY() > yMax)) {
return true;
}else {
return false;
}
}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
}
}
Try to move drawFlag attribute from the inner class MouseDrag to the outer class GraphicsContent as follow
class GraphicsContent extends JPanel {
private int xStart, yStart;
private int width, height;
private volatile boolean drawFlag;
// other code
}
However as you drag, the value is constantly being reported as being false
You've got two listeners. One is a motion listener:
this.addMouseListener(new MouseDrag());
this.addMouseMotionListener(new MouseDrag());
mousePressed will not be called on a motion listener, so it will always be false in the drag event.
I expect what you meant is this:
MouseDrag listener = new MouseDrag();
this.addMouseListener(listener);
this.addMouseMotionListener(listener);
Are you sure you need two MouseDrag elements? I.e. using something like
public JPanel init() {
this.setBackground(Color.WHITE);
MouseDrag md = new MouseDrag();
this.addMouseListener(md);
this.addMouseMotionListener(md);
return this;
}
I can at least plot a blue square....
How to drag free-form lines (paths) with AffineTransform?
I'm making a Paint-like application in Java and one of the requirements is being able to drag drawn free-form shapes to a new location on a JPanel used for drawing. I've been trying to get AffineTransform to do this for a day now and what it does at the moment is while the required line(which is stored as a Path2D) is selected and dragged it does move. However, once there is no more line selected, the line goes back to its original location. Also, when I select it again, it is immediately shown in that new location (if that makes any sense); this is probably to the coordinate system being translated, but I'm not sure...
Any help will be highly appreciated! Perhaps there is an simpler way of doing this.
PS I also noticed that when moving any of the drawn lines, except for the one drawn last, all of the lines drawn prior to the line, which is being moved, are moved together with it.
Here's the code:
public class DrawPanel extends JPanel {
public double translateX=0;
public double translateY=0;
public int lastOffsetX;
public int lastOffsetY;
class Line {
public Point start;
public Point end;
public Color color;
public Path2D path;
}
ArrayList<Line> lines = new ArrayList<Line>();
ArrayList<Path2D> paths = new ArrayList<Path2D>();
boolean moveMode = false;
Path2D selectedLine;
int xDistance;
int yDistance;
public DrawPanel() {
setBackground(Color.WHITE);
setFocusable(true);
requestFocusInWindow();
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
//System.out.println(resizeMode);
if (moveMode) {
if (selectedLine!=null) {
int newX = e.getX() - lastOffsetX;
int newY = e.getY() - lastOffsetY;
lastOffsetX += newX;
lastOffsetY += newY;
translateX += newX;
translateY += newY;
repaint();
}
} else {
Path2D p = paths.get(paths.size() - 1);
p.lineTo(e.getX(), e.getY());
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e) {
super.mouseMoved(e);
if (resizeMode) {
selectedLine = null;
for (Path2D l : paths) {
if (l.contains(e.getPoint())) {
selectedLine = l;
}
}
repaint();
}
}
});
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
if (!moveMode) {
Line l = new Line(e.getPoint());
l.path = new Path2D.Double();
l.path.moveTo(e.getX(), e.getY());
lines.add(l);
paths.add(l.path);
} else {
lastOffsetX = e.getX();
lastOffsetY = e.getY();
}
}
}
#Override
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
if (!resizeMode) {
if (selectedLine == null) {
Line l = lines.get(lines.size() - 1);
l.end = e.getPoint();
l.path.lineTo(e.getX(), e.getY());
Path2D p = paths.get(paths.size() - 1);
p.lineTo(e.getX(), e.getY());
repaint();
}
} else {
for (int j=0; j<paths.size();j++) {
if (selectedLine!=null && selectedLine.equals(paths.get(j))) {
paths.set(j, selectedLine);
}
}
repaint();
}
}
});
}
private void setKeyBindings() {
ActionMap actionMap = getActionMap();
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition );
String ctrl = "VK_CONTROL";
String ctrl_rel = "control_released";
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, KeyEvent.CTRL_DOWN_MASK, false), ctrl);
inputMap.put(KeyStroke.getKeyStroke(("released CONTROL")), ctrl_rel);
actionMap.put(ctrl, new KeyAction(ctrl));
actionMap.put(ctrl_rel, new KeyAction(ctrl_rel));
}
private class KeyAction extends AbstractAction {
public KeyAction(String actionCommand) {
putValue(ACTION_COMMAND_KEY, actionCommand);
}
#Override
public void actionPerformed(ActionEvent actionEvt) {
if(actionEvt.getActionCommand() == "VK_CONTROL") {
moveMode = true;
}
else if(actionEvt.getActionCommand() == "control_released") {
moveMode = false;
repaint();
}
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(10.0f));
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
if (moveMode) {
for (int j=0; j<paths.size();j++) {
Path2D path = paths.get(j);
if (selectedLine!=null && selectedLine.equals(path)) {
AffineTransform at = new AffineTransform();
at.translate(translateX, translateY);
g2d.transform(at);
g2d.setColor(Color.RED);
g2d.draw(path);
g2d.setColor(Color.BLACK);
continue;
}
g2d.draw(path);
g2d.setColor(Color.BLACK);
}
} else {
for (int i =0; i < paths.size();i++) {
Path2D path = paths.get(i);
g2d.draw(path); // outline
}
}
}
Edit to include resolution: So in the end what I did is I saved all of the path's coordinates (which I got from PathIterator) to and ArrayList, created a new empty path, added the previous path's coordinates from the ArrayList to the new path (via moveTo, lineTo) and appended the new path to the ArrayList of all drawn paths.
As you suspect, your AffineTransform alters the graphics context's coordinate system for all subsequent drawing.
In the example cited here, each shape is an instance of the class Node. Each Node includes a selected attribute, which allows the shape to be selected independently. The value determines the effect of updatePosition() when called from mouseDragged(). The implementation of updatePosition() simply updates each selected node's coordinates, but you can also use the createTransformedShape() of AffineTransform.
I'm making an A* pathfinding application with a GUI. I have:
- an enum TileType
START(Color.GREEN),
WALL(Color.BLACK),
BLANK(new Color(240, 240, 240)),
END(Color.RED);
- a Grid class
public class Grid extends JPanel {
private final Color lineColour = new Color(200, 200, 200);
private Color defaultColour;
private final int pixelsPerTile;
private final int rows;
private final int columns;
private TileType[][] tiles;
public Grid(int rows, int columns, int pixelsPerTile) {
this.pixelsPerTile = pixelsPerTile;
this.rows = rows;
this.columns = columns;
this.tiles = new TileType[rows][columns];
this.setPreferredSize(new Dimension(rows * pixelsPerTile, columns * pixelsPerTile));
setTile(rows / 2 - 5, columns / 2, TileType.START);
setTile(rows / 2 + 5, columns / 2, TileType.END);
}
public void resetBoard() {
for (int i = 0; i < tiles.length; i++) {
for (int j = 0; j < tiles[0].length; j++) {
if (getTile(i, j) != TileType.START && getTile(i, j) != TileType.END) {
tiles[i][j] = null;
}
}
}
}
public void removeTile(int x, int y) {
tiles[x][y] = TileType.BLANK;
}
public TileType getTile(int x, int y) {
return tiles[x][y];
}
public Point getTile(boolean start) {
for (int x = 0; x < tiles.length; x++) {
for (int y = 0; y < tiles[0].length; y++) {
if (tiles[x][y] == (start ? TileType.START : TileType.END)) {
return new Point(x, y);
}
}
}
return null;
}
public void setTile(int x, int y, TileType tile) {
if (getTile(x, y) != TileType.START && getTile(x, y) != TileType.END) {
tiles[x][y] = tile;
}
}
#Override
public void paint(Graphics g1) {
Graphics2D g = (Graphics2D) g1;
for (int x = 0; x < columns; x++) {
for (int y = 0; y < columns; y++) {
if (getTile(x, y) != null) {
g.setColor(getTile(x, y).getColour());
g.fillRect(x * pixelsPerTile, y * pixelsPerTile, pixelsPerTile, pixelsPerTile);
}
}
}
// have the grid appear on top of blank tiles
g.setColor(lineColour);
for (int x = 0; x < columns; x++) {
for (int y = 0; y < rows; y++) {
g.drawLine(x * pixelsPerTile, 0, x * pixelsPerTile, getHeight());
g.drawLine(0, y * pixelsPerTile, getWidth(), y * pixelsPerTile);
}
}
}
public int getPixelsPerTile() {
return pixelsPerTile;
}
public int getRows() {
return rows;
}
public int getColumns() {
return columns;
}
}
- a Frame class
public class Frame extends JFrame {
private Grid grid;
public Frame() {
grid = new Grid(33, 33, 15); // odd so there is a definitive middle point
this.setAutoRequestFocus(true);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setTitle("A* Path finder");
this.setBounds(new Rectangle((grid.getRows()) * grid.getPixelsPerTile(),
(grid.getColumns() + 2) * grid.getPixelsPerTile()));
this.setResizable(true);
this.add(grid);
this.setVisible(true);
this.getContentPane().addMouseListener(new MouseLsntr()); // fixes alignment problems
ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(5);
se.scheduleAtFixedRate(new RedrawGrid(), 0L, 20L, TimeUnit.MILLISECONDS);
}
private class RedrawGrid implements Runnable {
#Override
public void run() {
grid.repaint();
}
}
private class MouseLsntr implements MouseListener {
#Override
public void mouseClicked(MouseEvent me) {
int x = me.getX() / grid.getPixelsPerTile();
int y = me.getY() / grid.getPixelsPerTile();
switch (me.getButton()) {
case MouseEvent.BUTTON1:
if (grid.getTile(x, y) != TileType.WALL) {
grid.setTile(x, y, TileType.WALL);
System.out.println(String.format("(%d, %d) (%d, %d) wall", x, y, x, y));
} else {
grid.removeTile(x, y);
System.out.println(String.format("(%d, %d) (%d, %d) blank", x, y, x, y));
}
break;
}
}
#Override
public void mousePressed(MouseEvent me) {}
#Override
public void mouseReleased(MouseEvent me) {}
#Override
public void mouseEntered(MouseEvent me) {}
#Override
public void mouseExited(MouseEvent me) {}
}
}
and obviously a main class which calls Frame()
When the user left clicks on a grid, they make a WALL at that location. If it's already a WALL, it becomes BLANK. I want the user to be able to move the START and END tile by clicking on it and then dragging to the location they want to stop at, which I'm having trouble doing. Any help would be appreciated since I've been stumped about this for a while now.
You will need to implement the mousePressed and mouseReleased handlers in MouseListener interface, and the mouseDragged handler in MouseMotionListener.
First, make your MouseLsntr extend MouseAdapter class, which implements MouseListener and MouseMotionListener by default.
private class MouseLsntr extends MouseAdapter {
private int dragStartX, dragStartY;
private boolean dragging;
#Override
public void mouseClicked(MouseEvent me) {
int x = me.getX() / grid.getPixelsPerTile();
int y = me.getY() / grid.getPixelsPerTile();
switch (me.getButton()) {
case MouseEvent.BUTTON1:
if (grid.getTile(x, y) != TileType.WALL) {
grid.setTile(x, y, TileType.WALL);
System.out.println(String.format("(%d, %d) (%d, %d) wall", x, y, x, y));
} else {
grid.removeTile(x, y);
System.out.println(String.format("(%d, %d) (%d, %d) blank", x, y, x, y));
}
break;
}
}
#Override
public void mousePressed(MouseEvent me) {
switch (me.getButton()) {
case MouseEvent.BUTTON1:
// Save the drag starting position.
this.dragStartX = me.getX() / grid.getPixelsPerTile();
this.dragStartY = me.getY() / grid.getPixelsPerTile();
this.dragging = true;
break;
}
}
#Override
public void mouseReleased(MouseEvent me) {
switch (me.getButton()) {
case MouseEvent.BUTTON1:
if (this.dragging) {
moveTile(me);
}
this.dragging = false;
break;
}
}
#Override
public void mouseExited(MouseEvent me) {
this.dragging = false;
}
#Override
public void mouseDragged(MouseEvent me) {
if (this.dragging) {
moveTile(me);
}
}
private void moveTile(MouseEvent me) {
int dragEndX = me.getX() / grid.getPixelsPerTile();
int dragEndY = me.getY() / grid.getPixelsPerTile();
TileType dragStartType = grid.getTile(this.dragStartX, this.dragStartY);
TileType dragEndType = grid.getTile(dragEndX, dragEndY);
// If the starting tile was either START or END, move the tile to the destination.
if ((dragEndType == TileType.BLANK || dragEndType == null) &&
(dragStartType == TileType.START || dragStartType == TileType.END)) {
grid.removeTile(this.dragStartX, this.dragStartY);
grid.setTile(dragEndX, dragEndY, dragStartType);
// update the drag starting points
this.dragStartX = dragEndX;
this.dragStartY = dragEndY;
}
}
}
Next, add your MouseLsntr instance as a MouseMotionListener as well, so that mouseDragged handler is called when you are dragging. Note that you should not create two separate instances of MouseLsntr, because the member fields should be shared. You should do it like the following in the Frame() constructor.
MouseLsntr listener = new MouseLsntr();
this.getContentPane().addMouseListener(listener);
this.getContentPane().addMouseMotionListener(listener);
After doing this, the TileType.START / TileType.END tiles will follow the mouse when being dragged.