package com.bekwam.examples.javafx.woodland; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.fxml.FXML; import javafx.geometry.Bounds; import javafx.geometry.Insets; import javafx.geometry.Point2D; import javafx.scene.Node; import javafx.scene.effect.DropShadow; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; import javafx.util.Duration; public class WoodlandController { @FXML VBox boardContainer; @FXML Pane board; @FXML Rectangle sq0_0; @FXML Rectangle sq0_1; @FXML Rectangle sq0_2; @FXML Rectangle sq1_0; @FXML Rectangle sq1_1; @FXML Rectangle sq1_2; @FXML Rectangle sq2_0; @FXML Rectangle sq2_1; @FXML Rectangle sq2_2; @FXML ImageView gamePiece; /** * Convenience for working with squares all at once */ private final List<Rectangle> squares = new ArrayList<>(); /** * Effect that will be recycled across squares */ private final DropShadow dropShadow = new DropShadow(); /** * Keeps track of square so that it can be un-highlighted */ private Optional<Rectangle> lastHoverSquare = Optional.empty(); /** * Overall control for whether or not there is a current selection */ private boolean gpSelected = false; private double gpCenteringX = 0.0d; private double gpCenteringY = 0.0d; /** * (x,y) coordinates of placement of mouse within the ImageView * * For snapping */ private double selectInImageX = 0.0d; private double selectInImageY = 0.0d; /** * Offset for in board check */ private double topLeftSelectInImageX = 0.0d; private double topLeftSelectInImageY = 0.0d; /** * Calculated value of center coordinate to help with selectInImage* * fields */ private double gpMidpointX = 0.0d; private double gpMidpointY = 0.0d; private double gpWidth = 0.0d; private double gpHeight = 0.0d; @FXML public void initialize() { gpCenteringX = gamePiece.getLayoutX(); gpCenteringY = gamePiece.getLayoutY(); Bounds gpBounds = gamePiece.getBoundsInLocal(); gpWidth = gpBounds.getWidth(); gpHeight = gpBounds.getHeight(); gpMidpointX = (gpWidth)/2; gpMidpointY = (gpHeight)/2; squares.addAll( Arrays.asList(sq0_0, sq0_1, sq0_2, sq1_0, sq1_1, sq1_2, sq2_0, sq2_1, sq2_2) ); board.setOnMousePressed((evt) -> { if( selectGamePiece(evt.getSceneX(), evt.getSceneY()) ) { Point2D selectInImageScenePt = new Point2D( evt.getSceneX(), evt.getSceneY() ); Point2D selectInImagePt = gamePiece.sceneToLocal( selectInImageScenePt ); selectInImageX = gpMidpointX - selectInImagePt.getX(); selectInImageY = gpMidpointY - selectInImagePt.getY(); topLeftSelectInImageX = selectInImagePt.getX(); topLeftSelectInImageY = selectInImagePt.getY(); gamePiece.setOpacity( 0.4d ); gpSelected = true; } }); board.setOnMouseReleased((evt) -> { if( gpSelected ) { Optional<Rectangle> onSquare = pickSquare( evt.getSceneX(), evt.getSceneY() ); snap(onSquare); } clearSelection(); // unconditionally clear in case missed event }); board.setOnMouseDragged( (evt) -> { if( !inBoard( evt.getSceneX(), evt.getSceneY() ) ) { // // End the drag operation if left the board // MouseEvent releaseEvent = new MouseEvent( MouseEvent.MOUSE_RELEASED, evt.getSceneX(), evt.getSceneY(), evt.getScreenX(), evt.getScreenY(), evt.getButton(), evt.getClickCount(), evt.isShiftDown(), evt.isControlDown(), evt.isAltDown(), evt.isMetaDown(), evt.isPrimaryButtonDown(), evt.isMiddleButtonDown(), evt.isSecondaryButtonDown(), true, evt.isPopupTrigger(), evt.isStillSincePress(), evt.getPickResult() ); board.fireEvent( releaseEvent ); } if( gpSelected ) { Point2D sceneEvtPt = new Point2D(evt.getSceneX(), evt.getSceneY()); Point2D parentEvtPt = board.sceneToLocal(sceneEvtPt); Insets padding = boardContainer.getPadding(); gamePiece.relocate( parentEvtPt.getX()-padding.getLeft()+selectInImageX, parentEvtPt.getY()-padding.getTop()+selectInImageY); } Optional<Rectangle> hoverSquare = pickSquare( evt.getSceneX(), evt.getSceneY() ); if( lastHoverSquare.isPresent() ) { lastHoverSquare.get().setEffect( null ); } if( hoverSquare.isPresent() ) { hoverSquare.get().setEffect(dropShadow); lastHoverSquare = hoverSquare; } }); board.setOnMouseMoved((evt) -> { if( !inBoard( evt.getSceneX(), evt.getSceneY() ) ) { clearSelection(); } Optional<Rectangle> hoverSquare = pickSquare( evt.getSceneX(), evt.getSceneY() ); if( lastHoverSquare.isPresent() ) { lastHoverSquare.get().setEffect( null ); } if( hoverSquare.isPresent() ) { hoverSquare.get().setEffect(dropShadow); lastHoverSquare = hoverSquare; } }); } private void snap(Optional<Rectangle> onSquare) { if( onSquare.isPresent() ) { Point2D onSquarePt = new Point2D(onSquare.get().getLayoutX(), onSquare.get().getLayoutY()); final Timeline timeline = new Timeline(); timeline.setCycleCount(1); timeline.setAutoReverse(false); timeline.getKeyFrames() .add(new KeyFrame(Duration.millis(200), new KeyValue(gamePiece.layoutXProperty(), onSquarePt.getX()+gpCenteringX), new KeyValue(gamePiece.layoutYProperty(), onSquarePt.getY()+gpCenteringY)) ); timeline.play(); } } private void clearSelection() { selectInImageX = 0.0d; selectInImageY = 0.0d; gamePiece.setOpacity(1.0d); gpSelected = false; } private boolean selectGamePiece(double sceneX, double sceneY) { return isPicked( gamePiece, sceneX, sceneY ); } private Optional<Rectangle> pickSquare(double sceneX, double sceneY) { Optional<Rectangle> sq =squares .stream() .filter((square) -> isPicked(square, sceneX, sceneY)) .findFirst(); return sq; } private boolean inBoard(double sceneX, double sceneY) { boolean retval = ( isPicked( board, sceneX-topLeftSelectInImageX, sceneY-topLeftSelectInImageY ) && isPicked( board, sceneX+(gpWidth-topLeftSelectInImageX), sceneY+(gpHeight-topLeftSelectInImageY)) ); return retval; } private boolean isPicked(Node n, double sceneX, double sceneY) { Bounds bounds = n.getLayoutBounds(); Bounds boundsScene = n.localToScene( bounds ); if( (sceneX >= boundsScene.getMinX()) && (sceneY >= boundsScene.getMinY()) && (sceneX <= boundsScene.getMaxX()) && ( sceneY <= boundsScene.getMaxY() ) ) { return true; } return false; } }