package customnodes; import java.io.IOException; import application.tools.InfoTool; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.beans.InvalidationListener; import javafx.beans.property.StringProperty; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.layout.Pane; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.util.Duration; /** * When the screen element is not big enough to show the text then an animation will start automatically * * @author GOXR3PLUS * */ public class Marquee extends Pane { @FXML private Text text; // minimum distance to Pane bounds private static final double OFFSET = 5; private Timeline timeline = new Timeline(); /** * Constructor */ public Marquee() { // FXMLLOADER try { FXMLLoader loader = new FXMLLoader(getClass().getResource(InfoTool.FXMLS + "Marquee.fxml")); loader.setController(this); loader.setRoot(this); loader.load(); } catch (IOException ex) { ex.printStackTrace(); } } /** * Called as soon as .fxml is initialized [[SuppressWarningsSpartan]] */ @FXML private void initialize() { //Clip Rectangle rectangle = new Rectangle(25, 25); rectangle.widthProperty().bind(widthProperty()); rectangle.heightProperty().bind(heightProperty()); setClip(rectangle); // Text text.setManaged(false); startAnimation(); } /** * This method changes the text of the Marquee * * @param value * @return this */ public Marquee setText(String value) { // text text.setText(value); return this; } /** * Defines text string that is to be displayed. * * @return The TextProperty */ public StringProperty textProperty() { return text.textProperty(); } /** * This method starts the Animation of the marquee */ private final void startAnimation() { // KeyFrame KeyFrame updateFrame = new KeyFrame(Duration.millis(35), new EventHandler<ActionEvent>() { private boolean rightMovement; @Override public void handle(ActionEvent event) { double textWidth = text.getLayoutBounds().getWidth(); double paneWidth = getWidth(); double layoutX = text.getLayoutX(); if (2 * OFFSET + textWidth <= paneWidth && layoutX >= OFFSET) { // stop, if the pane is large enough and the position is // correct text.setLayoutX(OFFSET); timeline.stop(); } else { if ((rightMovement && layoutX >= OFFSET) || (!rightMovement && layoutX + textWidth + OFFSET <= paneWidth)) { // invert movement, if bounds are reached rightMovement = !rightMovement; } // update position if (rightMovement) { layoutX += 1; } else { layoutX -= 1; } text.setLayoutX(layoutX); } } }); timeline.getKeyFrames().add(updateFrame); timeline.setCycleCount(Animation.INDEFINITE); // listen to bound changes of the elements to start/stop the // animation InvalidationListener listener = o -> { double textWidth = text.getLayoutBounds().getWidth(); double paneWidth = getWidth(); text.setLayoutX(5); if (textWidth + 2 * OFFSET > paneWidth && timeline.getStatus() != Animation.Status.RUNNING) timeline.play(); }; text.layoutBoundsProperty().addListener(listener); widthProperty().addListener(listener); } }