There you can add a transition as soon as the user clicks the login button using the Dialog API before closing the window.
Using dialog.show() instead of dialog.showAndWait () `, the trick simply captures the action of clicking the button, consumes the event, and then performs the required logic.
dialog.initModality(Modality.APPLICATION_MODAL); dialog.show(); loginButton.addEventFilter(EventType.ROOT, e->{ if(e.getEventType().equals(ActionEvent.ACTION)){ e.consume(); // (hardcoded) Login Validation boolean isSuccessful = false; if (isSuccessful) { dialog.close(); } else { // perform animation and close the dialog (or any other action) ShakeTransition anim = new ShakeTransition(dialog.getDialogPane(), t->dialog.close()); anim.playFromStart(); } } });
For the shake animation, I changed the ShakeTransition from Jasper Potts to move the dialog, as @jewelsea already indicated:
class ShakeTransition extends Transition { private final Interpolator WEB_EASE = Interpolator.SPLINE(0.25, 0.1, 0.25, 1); private final Timeline timeline; private final Node node; private boolean oldCache = false; private CacheHint oldCacheHint = CacheHint.DEFAULT; private final boolean useCache=true; private final double xIni; private final DoubleProperty x = new SimpleDoubleProperty(); public ShakeTransition(final Node node, EventHandler<ActionEvent> event) { this.node=node; statusProperty().addListener((ov, t, newStatus) -> { switch(newStatus) { case RUNNING: starting(); break; default: stopping(); break; } }); this.timeline= new Timeline( new KeyFrame(Duration.millis(0), new KeyValue(x, 0, WEB_EASE)), new KeyFrame(Duration.millis(100), new KeyValue(x, -10, WEB_EASE)), new KeyFrame(Duration.millis(200), new KeyValue(x, 10, WEB_EASE)), new KeyFrame(Duration.millis(300), new KeyValue(x, -10, WEB_EASE)), new KeyFrame(Duration.millis(400), new KeyValue(x, 10, WEB_EASE)), new KeyFrame(Duration.millis(500), new KeyValue(x, -10, WEB_EASE)), new KeyFrame(Duration.millis(600), new KeyValue(x, 10, WEB_EASE)), new KeyFrame(Duration.millis(700), new KeyValue(x, -10, WEB_EASE)), new KeyFrame(Duration.millis(800), new KeyValue(x, 10, WEB_EASE)), new KeyFrame(Duration.millis(900), new KeyValue(x, -10, WEB_EASE)), new KeyFrame(Duration.millis(1000), new KeyValue(x, 0, WEB_EASE)) ); xIni=node.getScene().getWindow().getX(); x.addListener((ob,n,n1)->(node.getScene().getWindow()).setX(xIni+n1.doubleValue())); setCycleDuration(Duration.seconds(1)); setDelay(Duration.seconds(0.2)); setOnFinished(event); } protected final void starting() { if (useCache) { oldCache = node.isCache(); oldCacheHint = node.getCacheHint(); node.setCache(true); node.setCacheHint(CacheHint.SPEED); } } protected final void stopping() { if (useCache) { node.setCache(oldCache); node.setCacheHint(oldCacheHint); } } @Override protected void interpolate(double d) { timeline.playFrom(Duration.seconds(d)); timeline.stop(); } }
And this will be a JavaFX application using the login dialog:
@Override public void start(Stage primaryStage) { Button btn = new Button(); btn.setText("Show Login Dialog"); btn.setOnAction(mevent -> { // Create the custom dialog. Dialog<Pair<String, String>> dialog = new Dialog<>(); dialog.setTitle("Mars Simulation Project"); dialog.setHeaderText("Log in"); dialog.setContentText("Enter your username and password : "); dialog.initModality(Modality.NONE); // Set the button types. ButtonType loginButtonType = new ButtonType("Login", ButtonData.OK_DONE); dialog.getDialogPane().getButtonTypes().addAll(loginButtonType, ButtonType.CANCEL); // Create the username and password labels and fields. GridPane grid = new GridPane(); grid.setHgap(10); grid.setVgap(10); grid.setPadding(new Insets(20, 150, 10, 10)); TextField tfPlayer = new TextField(); tfPlayer.setPromptText("eg m03j"); PasswordField tfPassword = new PasswordField(); tfPassword.setPromptText("xxxx"); Button defaultPWB = new Button("Use Default"); Button guestB = new Button("As Guest"); defaultPWB.setOnAction(event -> { tfPassword.setText("msp0"); } ); guestB.setOnAction(event -> { tfPlayer.setText("Guest_"); tfPassword.setText("msp0"); } ); grid.add(new Label("Player Name :"), 0, 0); grid.add(tfPlayer, 1, 0); grid.add(guestB, 2, 0); grid.add(new Label("Password :"), 0, 1); grid.add(tfPassword, 1, 1); grid.add(defaultPWB, 2, 1); // Enable/Disable login button depending on whether a username was entered. Node loginButton = dialog.getDialogPane().lookupButton(loginButtonType); loginButton.setDisable(true); // Do some validation (using the Java 8 lambda syntax). tfPlayer.textProperty().addListener((observable, oldValue, newValue) -> { loginButton.setDisable(newValue.trim().isEmpty()); } ); dialog.getDialogPane().setContent(grid); // Request focus on the player name field by default. Platform.runLater(() -> tfPlayer.requestFocus()); dialog.initModality(Modality.APPLICATION_MODAL); dialog.show(); loginButton.addEventFilter(EventType.ROOT, e->{ if(e.getEventType().equals(ActionEvent.ACTION)){ e.consume(); // (hardcoded) Login Validation boolean isSuccessful = false; if (isSuccessful) { dialog.close(); } else { ShakeTransition anim = new ShakeTransition(dialog.getDialogPane(), t->dialog.close()); anim.playFromStart(); } } }); }); StackPane root = new StackPane(); root.getChildren().add(btn); Scene scene = new Scene(root, 300, 250); primaryStage.setTitle("Shaky Login Dialog"); primaryStage.setScene(scene); primaryStage.show(); }