How do I stop the hiding of a node when it is active?

Viewed 210

The code to which I refer: Show / Hide a Node within a stage

Following the previous question and answer on autohiding: How to show / hide / auto hide a node

Thanks to c0der for solving the previous question.

There is a problem with it as if it is active (like moving the cursor or clicking), the Vbox node will still autohide. How do I make the Vbox node stay visible and not hide when I move the cursor or some event is happening?

The current behavior is if I move the cursor or click something on the VBox node, after 5 seconds it will still hide.

Testinggg.java:

package testinggg;

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Testinggg extends Application {

private TestController controller;
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Test.fxml"));
        Parent root = loader.load();
        controller = loader.getController();
        stage.setScene(new Scene(root));
        stage.setFullScreen(true);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

TestController.java:

package testinggg;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.VBox;
import javafx.util.Duration;

public class TestController implements Initializable {

    @FXML private VBox statusContainer;
    private TranslateTransition showStatus;
    private TranslateTransition hideStatus;
    private boolean showsStatus = false;
    private static final int AUTO_HIDE_DELAY = 5;  

    public void toggleStatus() {
        if( showsStatus ) {
            hide();
        }
        else {
            show();
        }
    }
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        showStatus = new TranslateTransition(Duration.millis(250), statusContainer);
        showStatus.setByY(-1080.0);
        showStatus.setOnFinished(event -> {
            showsStatus = true;
            autoHide();
        });

        hideStatus = new TranslateTransition(Duration.millis(250), statusContainer);
        hideStatus.setByY(1080.0);
        hideStatus.setOnFinished(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent event) {
                showsStatus = false;
            }
        });
    }  

    private void show(){
        hideStatus.stop();
        showStatus.play();
    }

    private void hide(){
        showStatus.stop();
        hideStatus.play();
    }

    private void autoHide(){
        Duration duration = Duration.seconds(AUTO_HIDE_DELAY);
        PauseTransition transition = new PauseTransition(duration);
        transition.setOnFinished(evt ->{
            if( showsStatus ) {
                hide();
            }
        });
        transition.play();
    }
}

Test.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.text.*?>
<?import javafx.scene.image.*?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="testinggg.TestController">
   <children>
      <AnchorPane id="AnchorPane" maxHeight="-Infinity" prefHeight="1080.0" prefWidth="1920.0" StackPane.alignment="TOP_LEFT">
         <children>
            <Button mnemonicParsing="false" onAction="#toggleStatus" prefHeight="1080.0" prefWidth="1920.0" text="Button" />
         </children>
      </AnchorPane>
      <VBox fx:id="statusContainer" maxHeight="1080.0" prefHeight="1080.0" translateY="1080.0" StackPane.alignment="BOTTOM_LEFT">
         <children>
            <AnchorPane prefHeight="668.0" prefWidth="1266.0">
               <VBox.margin>
                  <Insets bottom="50.0" left="50.0" right="50.0" top="50.0" />
               </VBox.margin>
               <children>
                  <ImageView fitHeight="540.0" fitWidth="1820.0" layoutY="43.0" pickOnBounds="true">
                     <image>
                        <Image url="@../Rainbow%20Poro.png" />
                     </image>
                  </ImageView>
                  <ImageView fitHeight="44.0" fitWidth="153.0" layoutX="857.0" pickOnBounds="true" preserveRatio="true">
                     <image>
                        <Image url="@../logo.png" />
                     </image>
                  </ImageView>
                  <ScrollPane layoutY="582.0" prefHeight="391.0" prefViewportHeight="208.0" prefViewportWidth="1266.0" prefWidth="1820.0">
                     <content>
                        <TextArea editable="false" layoutY="460.0" prefHeight="515.0" prefWidth="1804.0" text="Sometexthere SometexthereSometexthereSometexthereSometexthereSometexthereSometexthereSometexthereSometexthereSometexthere&#10;Some&#10;Text&#10;Here&#10;Some&#10;Text &#10;Here&#10;Some&#10;Text &#10;Here&#10;Some&#10;Text &#10;Here&#10;Some&#10;Text &#10;Here&#10;Some&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;Here&#10;Some&#10;Text &#10;Here" />
                     </content>
                  </ScrollPane>
                  <Button layoutX="1775.0" mnemonicParsing="false" onAction="#toggleStatus" text="Close" />
               </children>
            </AnchorPane>
         </children>
      </VBox>
   </children>
   <stylesheets>
    <URL value="@test1.css" />
  </stylesheets>
</StackPane>
2 Answers

You can keep a reference to the PauseTransition and call playFromStart() for every input event (or only specific ones). Here's the documentation of playFromStart():

Plays an Animation from initial position in forward direction.

It is equivalent to

animation.stop();
animation.setRate = setRate(Math.abs(animation.getRate()));
animation.jumpTo(Duration.ZERO);
animation.play();

Note:

  • playFromStart() is an asynchronous call, Animation may not start immediately.

You'll want to do this in an event filter since child nodes may consume the events, meaning they wouldn't reach an event handler. Don't consume the events in the filter as that will prevent child nodes from receiving them.

Note that this approach will only stop the status container from hiding if the user is doing something within it. If the mouse is just hovering over the status container not doing anything then the status container will still auto-hide. If you don't want that then consider using @coder's approach; that approach makes more sense if you want to pause the auto-hide timer "indefinitely" until the user moves the focus away from the status container. My approach is better suited for hiding the status container after X amount of time has passed since the last input, no matter what. Of course, you could always combine the two approaches if you find an appropriate use case.


Here's a small example:

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>

<StackPane xmlns="http://javafx.com/javafx/12.0.1" xmlns:fx="http://javafx.com/fxml/1"
           fx:controller="com.example.Controller" prefWidth="600.0" prefHeight="400.0">

    <VBox fx:id="statusContainer" prefHeight="150" maxHeight="-Infinity" 
          style="-fx-background-color: firebrick;" StackPane.alignment="BOTTOM_CENTER"/>

    <Button fx:id="toggleStatusBtn" text="Hide Status" onAction="#handleToggleStatus"/>

</StackPane>

Controller:

package com.example;

import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.input.InputEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.util.Duration;

public class Controller {

    @FXML private Button toggleStatusBtn;
    @FXML private VBox statusContainer;

    private TranslateTransition showHideAnimation;
    private PauseTransition autoHideTimer;
    private boolean statusShowing = true;

    @FXML
    private void initialize() {
        showHideAnimation = new TranslateTransition(Duration.millis(250), statusContainer);
        showHideAnimation.setFromY(0);
        showHideAnimation.toYProperty().bind(statusContainer.prefHeightProperty());

        autoHideTimer = new PauseTransition(Duration.seconds(5));
        autoHideTimer.setOnFinished(event -> hideStatusContainer());

        statusContainer.addEventFilter(InputEvent.ANY, event -> {
            if (statusShowing) {
                autoHideTimer.playFromStart(); // restart timer
            } else if (event.getEventType() != MouseEvent.MOUSE_EXITED) {
                showStatusContainer(); // user did something while container was hiding
            }
        });
    }

    @FXML
    private void handleToggleStatus(ActionEvent event) {
        event.consume();
        if (statusShowing) {
            hideStatusContainer();
        } else {
            showStatusContainer();
        }
    }

    private void hideStatusContainer() {
        if (statusShowing) {
            statusShowing = false;
            showHideAnimation.setRate(1);
            showHideAnimation.play();
            toggleStatusBtn.setText("Show Status");
        }
    }

    private void showStatusContainer() {
        if (!statusShowing) {
            statusShowing = true;
            showHideAnimation.setRate(-1); // reverse animation
            showHideAnimation.play();
            autoHideTimer.playFromStart();
            toggleStatusBtn.setText("Hide Status");
        }
    }

}

Note: The statusContainer is initially showing and the auto-hide feature doesn't kick in until it's hidden and then shown again.

Note: The above uses the same TranslateTransition for both hiding and showing. This typically makes the animation smoother—especially with rapid toggling.

You could add a control boolean to the controller:

private boolean isStatusContainerBusy = false;

Let the event of interest toggle it. For example set it to true when mouse enters, and false when mouse exits:

statusContainer.setOnMouseEntered(e-> isStatusContainerBusy = true);
statusContainer.setOnMouseExited(e-> isStatusContainerBusy = false);

And use it to control auto-hide:

transition.setOnFinished(evt ->{
    if( showsStatus ) {
        if(isStatusContainerBusy) {
            transition.play(); //start auto hide transition again
        }else{
            hide();
        }
    }
});

Putting it all together:

public class TestController {

    @FXML private VBox statusContainer;

    private TranslateTransition showStatus;
    private TranslateTransition hideStatus;
    private boolean showsStatus = false;
    private static final int AUTO_HIDE_DEALY = 5;
    private boolean isStatusContainerBusy = false;

    @FXML void initialize() {

        showStatus = new TranslateTransition(Duration.millis(250), statusContainer);
        showStatus.setByY(-100.0);
        showStatus.setOnFinished(event -> {
            showsStatus = true;
            autoHide();
        });
        hideStatus = new TranslateTransition(Duration.millis(250), statusContainer);
        hideStatus.setByY(100.0);
        hideStatus.setOnFinished(event -> showsStatus = false);
        statusContainer.setOnMouseEntered(e-> isStatusContainerBusy = true);
        statusContainer.setOnMouseExited(e-> isStatusContainerBusy = false);
    }

    public void toggleStatus() {
        if( showsStatus ) {
            hide();
        }
        else {
            show();
        }
    }

    private void show(){
        hideStatus.stop();
        showStatus.play();
    }

    private void hide(){
        showStatus.stop();
        hideStatus.play();
    }

    private void autoHide() {
        Duration duration = Duration.seconds(AUTO_HIDE_DEALY);
        PauseTransition transition = new PauseTransition(duration);
        transition.setOnFinished(evt ->{
            if( showsStatus ) {
                if(isStatusContainerBusy) {
                    transition.play(); //start auto hide transition again
                }else{
                    hide();
                }
            }
        });
        transition.play();
    }
} 
Related