Mastodon

Extendable Navigation Pane in JavaFX

This week, I implemented a navigation pane in JavaFX that can be used to navigate within an application. It has four buttons with icons. At first, only the icon of each button is shown. At MouseOver, the pane expands and shows the button texts. One button can then be choosen and is highlighted with a drop shadow.

Extendable Navigation Pane with JavaFX

Here’s the code as a SSCCE.

This is the controller ExtendableNavigation.java:

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class ExtendableNavigation extends Application {

	private static final int deltaXNavButton1 = 20;
	private static final int deltaXNavButton2 = 10;
	private static final int deltaXNavButton3 = -10;
	private static final int deltaXNavButton4 = -20;
 
	@FXML
	private AnchorPane extendableNavigationPane;
 
	@FXML
	private Button navButton1;
 
	@FXML
	private Button navButton2;
 
	@FXML
	private Button navButton3;
 
	@FXML
	private Button navButton4;
 
	private Rectangle clipRect;
 
	private DropShadow dropShadowForSelectedPane;
 
	public static void main(String[] args) {
		launch(args);
	}
 
	@Override
	public void start(Stage stage) throws Exception {
		Parent root = FXMLLoader.load(getClass().getResource("extendableNavigation.fxml"));
 
		Scene scene = new Scene(root, 600, 200);
		scene.getStylesheets().add("my.css");
 
		stage.setTitle("Extendable navigation pane demo");
		stage.setScene(scene);
		stage.show();
	}
 
	@FXML
	void initialize() {
		clipRect = new Rectangle();
		clipRect.setWidth(extendableNavigationPane.getPrefWidth());
		setIcon(navButton1, "16px-Asclepius_staff.svg.png");
		setIcon(navButton2, "32px-Simple_crossed_circle.svg.png");
		setIcon(navButton3, "32px-Sinnbild_Autobahnkreuz.svg.png");
		setIcon(navButton4, "32px-Yin_yang.svg.png");
		hidePane();
 
		dropShadowForSelectedPane = new DropShadow(BlurType.THREE_PASS_BOX, Color.RED, 7, 0.2, 0, 1);
	}
 
	private void setIcon(Button button, String name) {
		Image image = new Image(getClass().getResourceAsStream(name));
		button.setGraphic(new ImageView(image));
		button.setContentDisplay(ContentDisplay.TOP);
	}
 
	@FXML
	private void showPane() {
		System.out.println("Showing pane ... ");
 
		// Animation for showing the pane completely
		Timeline timelineDown = new Timeline();
 
		final KeyValue kvDwn1 = new KeyValue(clipRect.heightProperty(), extendableNavigationPane.getHeight());
		final KeyValue kvDwn2 = new KeyValue(clipRect.translateYProperty(), 0);
		final KeyValue kvDwn3 = new KeyValue(extendableNavigationPane.translateYProperty(), 0);
		final KeyFrame kfDwn = new KeyFrame(Duration.millis(100), createBouncingEffect(extendableNavigationPane.getHeight()), kvDwn1, kvDwn2,
				kvDwn3);
 
		// Animation for moving button 1
		final KeyValue kvB1 = new KeyValue(navButton1.translateXProperty(), -deltaXNavButton1);
		final KeyFrame kfB1 = new KeyFrame(Duration.millis(200), kvB1);
 
		// Animation for moving button 2
		final KeyValue kvB2 = new KeyValue(navButton2.translateXProperty(), -deltaXNavButton2);
		final KeyFrame kfB2 = new KeyFrame(Duration.millis(200), kvB2);
 
		// Animation for moving button 3
		final KeyValue kvB3 = new KeyValue(navButton3.translateXProperty(), -deltaXNavButton3);
		final KeyFrame kfB3 = new KeyFrame(Duration.millis(200), kvB3);
 
		// Animation for moving button 1
		final KeyValue kvB4 = new KeyValue(navButton4.translateXProperty(), -deltaXNavButton4);
		final KeyFrame kfB4 = new KeyFrame(Duration.millis(200), kvB4);
 
		timelineDown.getKeyFrames().addAll(kfDwn, kfB1, kfB2, kfB3, kfB4);
		timelineDown.play();
	}
 
	@FXML
	private void hidePane() {
		System.out.println("Hding pane ... ");
 
		// Animation for hiding the pane..
		Timeline timelineUp = new Timeline();
 
		final KeyValue kvUp1 = new KeyValue(clipRect.heightProperty(), 55);
		final KeyValue kvUp2 = new KeyValue(extendableNavigationPane.translateYProperty(), 10);
		final KeyFrame kfUp = new KeyFrame(Duration.millis(200), kvUp1, kvUp2);
 
		// Animation for moving button 1
		final KeyValue kvB1 = new KeyValue(navButton1.translateXProperty(), deltaXNavButton1);
		final KeyFrame kfB1 = new KeyFrame(Duration.millis(200), kvB1);
 
		final KeyValue kvB2 = new KeyValue(navButton2.translateXProperty(), deltaXNavButton2);
		final KeyFrame kfB2 = new KeyFrame(Duration.millis(200), kvB2);
 
		final KeyValue kvB3 = new KeyValue(navButton3.translateXProperty(), deltaXNavButton3);
		final KeyFrame kfB3 = new KeyFrame(Duration.millis(200), kvB3);
 
		final KeyValue kvB4 = new KeyValue(navButton4.translateXProperty(), deltaXNavButton4);
		final KeyFrame kfB4 = new KeyFrame(Duration.millis(200), kvB4);
 
		timelineUp.getKeyFrames().addAll(kfUp, kfB1, kfB2, kfB3, kfB4);
		timelineUp.play();
	}
 
	@FXML
	private void selectPane1() {
		System.out.println("Selecting pane 1");
		deselectAllPanes();
		navButton1.setEffect(dropShadowForSelectedPane);
	}
 
	@FXML
	private void selectPane2() {
		System.out.println("Selecting pane 2");
		deselectAllPanes();
		navButton2.setEffect(dropShadowForSelectedPane);
	}
 
	@FXML
	private void selectPane3() {
		System.out.println("Selecting pane 3");
		deselectAllPanes();
		navButton3.setEffect(dropShadowForSelectedPane);
	}
 
	@FXML
	private void selectPane4() {
		System.out.println("Selecting pane 4");
		deselectAllPanes();
		navButton4.setEffect(dropShadowForSelectedPane);
	}
 
	private void deselectAllPanes() {
		navButton1.setEffect(null);
		navButton2.setEffect(null);
		navButton3.setEffect(null);
		navButton4.setEffect(null);
	}
 
	private EventHandler<ActionEvent> createBouncingEffect(double height) {
		final Timeline timelineBounce = new Timeline();
		timelineBounce.setCycleCount(2);
		timelineBounce.setAutoReverse(true);
		final KeyValue kv1 = new KeyValue(clipRect.heightProperty(), (height - 15));
		final KeyValue kv2 = new KeyValue(clipRect.translateYProperty(), 15);
		final KeyValue kv3 = new KeyValue(extendableNavigationPane.translateYProperty(), -15);
		final KeyFrame kf1 = new KeyFrame(Duration.millis(100), kv1, kv2, kv3);
		timelineBounce.getKeyFrames().add(kf1);
 
		// Event handler to call bouncing effect after the scroll down is
		// finished.
		EventHandler<ActionEvent> handler = new EventHandler<ActionEvent>() {
			@Override
			public void handle(ActionEvent event) {
				timelineBounce.play();
			}
		};
		return handler;
	}
}

Styles are defined in my.css:

.pane{
-fx-background-color: white;
}

.navigation-button{
-fx-font-size: 0.9em;
-fx-background-color: transparent;
-fx-graphic-text-gap: 0px;
-fx-text-fill: gray;
}

.navigation-button:pressed:hover{
-fx-text-fill: black;
}

.navigation-button:hover{
-fx-text-fill: red;
}

Last but not least, extendableNavigation.fxml:

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

<?import java.lang.*?>
<?import java.net.*?>
<?import javafx.collections.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane minHeight="200.0" prefHeight="200.0" prefWidth="600.0" styleClass="pane" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="ExtendableNavigation">
  <children>
    <AnchorPane fx:id="extendableNavigationPane" layoutX="129.0" layoutY="136.0" minHeight="64.0" onMouseEntered="#showPane" onMouseExited="#hidePane" prefHeight="64.0" prefWidth="371.0">
      <children>
        <Button fx:id="navButton1" layoutX="53.0" layoutY="19.0" mnemonicParsing="false" onAction="#selectPane1" style="" styleClass="navigation-button" text="Medic Stuff" />
        <Button fx:id="navButton2" layoutX="114.0" layoutY="18.0" mnemonicParsing="false" onAction="#selectPane2" styleClass="navigation-button" text="Cross Stuff" />
        <Button fx:id="navButton3" layoutX="168.0" layoutY="30.0" mnemonicParsing="false" onAction="#selectPane3" styleClass="navigation-button" text="Highway Stuff" />
        <Button fx:id="navButton4" layoutX="241.0" layoutY="18.0" mnemonicParsing="false" onAction="#selectPane4" styleClass="navigation-button" text="Peace, man!" />
      </children>
    </AnchorPane>
  </children>
  <stylesheets>
    <URL value="@my.css" />
  </stylesheets>
</AnchorPane>

Get the complete code (with the missing four images) from Github. As I stated there: This project is just a prototype and doesn’t have the best code styling and architecture. Just copy what you need and don’t laugh. ;)