I am an experienced Java developer, but I am new to user interface development and I have been working on an application with Java AWT. I want to add dragging into the user interface.
To my surprise, a java.awt.Shape
is not a java.awt.Component
and so I don't see a "first class" way to add draggable hooks to my particular shapes. Instead, I am utilizing mouse event hooks of the application window, and then simply detecting whenever the mouse cursor is over a shape of interest before processing any dragging.
Since my application doesn't need the complexity of Z-axis layers, I restrict any dragged shape from intersecting another draggable shape. If the user drags one over another, it just "bumps" and remains there. It is then for the user first drag it free from the collision and then place it elsewhere. You will notice, the intersection is detected in the paint() method where I've left my comment for Stackoverflow.
The user interface works. Except, it is easy to generate a sort of race condition, where if I drag the shape very quickly, it can get nested deeply into the other shape and gets stuck.
Question: am I going about this in entirely the wrong way? Is there an elegant best practice to detect the collision "in time" before further events happen?
package test;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.ScrollPane;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.RoundRectangle2D;
/**
* Simple graphical application with two white rectangles that can be dragged around the application window
* */
final class DraggableTest extends Component implements MouseListener, MouseMotionListener {
public static void main( String[] args ) {
Frame window = new Frame( "Test Dragging Things" );
ScrollPane scrollPane = new ScrollPane();
DraggableTest app = new DraggableTest();
scrollPane.add( app );
window.add( scrollPane );
app.setSize( new Dimension( 400, 400 ) );
app.setPreferredSize( new Dimension( 400, 400 ) );
window.setSize( 200, 200 );
window.setVisible( true );
window.addWindowListener( new WindowAdapter() {
@Override
public void windowClosing( WindowEvent e ) {
window.dispose();
}
} );
app.addMouseMotionListener( app );
app.addMouseListener( app );
}
private static final long serialVersionUID = 2673458042104218875L;
@Override
public void paint( Graphics g ) {
Graphics2D g2 = ( Graphics2D ) g;
// redraw the draggable shapes in their last dragged position
draggableShape1 = new RoundRectangle2D.Double( lastDragged1.x, lastDragged1.y, 40, 20, 10, 10 );
g2.setPaint( Color.WHITE );
g2.fill( draggableShape1 );
g2.setPaint( Color.BLACK );
g2.setStroke( new BasicStroke() );
g2.draw( draggableShape1 );
draggableShape2 = new RoundRectangle2D.Double( lastDragged2.x, lastDragged2.y, 40, 20, 10, 10 );
g2.setPaint( Color.WHITE );
g2.fill( draggableShape2 );
g2.setPaint( Color.BLACK );
g2.setStroke( new BasicStroke( 2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 20.0f, new float[] { 10.0f / 2.0f }, 0.0f ) );
g2.draw( draggableShape2 );
// HERE IS WHERE MY STACKOVERFLOW QUESTION LIES
// Cancel any drag operation if a dragged object begins to intersect another.
// Because then we would have a requirement for differentiating and specifying which object to drag.
synchronized (this) {
if ( draggableShape1.getBounds2D().intersects( draggableShape2.getBounds2D() ) ) {
dragging1 = false;
dragging2 = false;
Toolkit.getDefaultToolkit().beep(); // alert the user to termination of the drag
}
}
}
// Two shapes drawn in method paint()
private Shape draggableShape1 = null;
private Shape draggableShape2 = null;
// initial locations of draggable shapes
private Point initialPosition1 = new Point( 0, 0 );
private Point initialPosition2 = new Point( 100, 100 );
// position shape last dropped to
private Point lastDragged1 = initialPosition1;
private Point lastDragged2 = initialPosition2;
// is the shape being dragged now?
boolean dragging1 = false;
boolean dragging2 = false;
// During drag, offset between mouse click position and the object being dragged
int dragMouseOffsetX = 0;
int dragMouseOffsetY = 0;
// Drag event sequence:
// Mouse button down.
// Mouse dragged to 1204, 452
// (repaint called from method mouseDragged )
// Mouse button released.
@Override
public void mousePressed(MouseEvent e) {
System.err.println( "Mouse button down." );
Point mouseDownPosition = e.getPoint();
// if mouse down in a draggable shape, assume we are starting a drag
if ( draggableShape1.contains( e.getPoint() ) ) {
dragging1 = true;
// Detect the offset between the mouse position and the shape's top left point
dragMouseOffsetX = draggableShape1.getBounds().x - mouseDownPosition.x; // negative is left
dragMouseOffsetY = draggableShape1.getBounds().y - mouseDownPosition.y; // negative is up
}
if ( draggableShape2.contains( e.getPoint() ) ) {
dragging2 = true;
// Detect the offset between the mouse position and the shape's top left point
dragMouseOffsetX = draggableShape2.getBounds().x - mouseDownPosition.x; // negative is left
dragMouseOffsetY = draggableShape2.getBounds().y - mouseDownPosition.y; // negative is up
}
}
/**
* The mouse button is down and is being moved.
* This has an effect IFF one of the draggable items was under the pointer when the button was pressed
* */
@Override
public void mouseDragged( MouseEvent e ) {
Point mouseDragPosition = e.getPoint();
if ( dragging1 || dragging2 ) {
System.err.println( "Mouse dragged to " + mouseDragPosition.x + ", " + mouseDragPosition.y );
System.err.println( "Shape dragged to " + ( mouseDragPosition.x + dragMouseOffsetX ) + ", " + ( mouseDragPosition.y + dragMouseOffsetY ) );
}
if ( dragging1 ) { // if we previously clicked on the shape
if ( ! mouseDragPosition.equals( lastDragged1 ) ) { // Ignore drag of less than one pixel (measured in granularity of int)
Point newDragPoint = new Point( mouseDragPosition.x + dragMouseOffsetX, mouseDragPosition.y + dragMouseOffsetY );
lastDragged1 = newDragPoint;
repaint();
}
}
if ( dragging2 ) { // if we previously clicked on the shape
if ( ! mouseDragPosition.equals( lastDragged2 ) ) { // Ignore drag of less than one pixel (measured in granularity of int)
lastDragged2 = new Point( mouseDragPosition.x + dragMouseOffsetX, mouseDragPosition.y + dragMouseOffsetY );
repaint();
}
}
}
/**
* Any time the mouse button comes up, any dragging state is concluded
* */
@Override
public void mouseReleased(MouseEvent e) {
System.err.println( "Mouse button released." );
dragging1 = false;
dragging2 = false;
}
@Override
public void mouseMoved( MouseEvent e ) {
// nothing to do
}
/**
* The mouse enters or re-enters the application window
* */
@Override
public void mouseEntered(MouseEvent e) {
System.err.println( "Mouse entered." );
}
/**
* The mouse leaves the application window
* */
@Override
public void mouseExited(MouseEvent e) {
System.err.println( "Mouse left." );
}
/**
* A combination of mouse down and mouse up has been detected as a click.
* Not implemented.
* */
@Override
public void mouseClicked(MouseEvent e) {
// Event sequence:
// Mouse button down.
// Mouse button released.
// Click.
System.err.println( "Click." );
}
}