javafx - Navigation Sidebar with Toggle When Hovered

127 views Asked by At

So in windows 10 you have the windows menu with the icons on the left side:

left

When Hover the left side the menu expands and text is show

expanded

The expanded part is overlaying the content.

In my application I want to create that kind of design using Javafx and SceneBuilder.

I managed to design the button like when I hover the button it will expand and show the text but, I don't know how to expand the whole pane which contain the button. I put my buttons in VBox and give the same condition which I gave for my button expand design but, it doesn't work.

Any help would be appreciated, especially examples.

2

There are 2 answers

0
jewelsea On

Simplistically, you can use a MenuButton with a popupSide of Side.TOP. It may not be exactly what you are after. In particular, there is no "Toggle when Hovered" functionality.

The example does not exactly match how the Windows button and menus are working. The Windows start button has lots of stuff going on (and I think it can also be configured to work different ways). You probably don't want all of that functionality. For example, the Windows button displays two menus side by side on pressing the button, one with the icons for various actions and another with an alphabetical application list. The UI is optimized to be able to display both unless you hover on the icons on left side, in which case it removes the right alphabetical list and displays the text for the icons instead. But if you only need to display one menu list (likely) then you may as well display both the icons and text for the left side menu at the same time, which is what the example I provided does.

MenuButton

My example is in code, but most of the stuff will translate to FXML and SceneBuilder if you wish to use that. Most of the icons I used are provided in this question.

import javafx.application.Application;
import javafx.geometry.Side;
import javafx.scene.Scene;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.stage.Stage;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;

public class MenuApp extends Application {
    private static final String CSS = "data:text/css,.root { -fx-base: #3c3f4a; -fx-font-size: 18px; }";

    @Override
    public void start(Stage stage) {
        StackPane content = new StackPane();
        content.setPrefSize(400, 300);
        content.setBackground(createGradient());

        MenuButton windowsMenuButton = new MenuButton(
                null,
                createImageView("Dragon-icon.png")
        );
        MenuItem documents = new MenuItem(
                "Documents",
                createImageView("Medusa-icon.png")
        );
        MenuItem pictures = new MenuItem(
                "Pictures",
                createImageView("Unicorn-icon.png")
        );
        MenuItem settings = new MenuItem(
                "Settings",
                createImageView("Treant-icon.png")
        );
        MenuItem power = new MenuItem(
                "Power",
                createImageView("spaceShip.png")
        );
        windowsMenuButton.getItems().addAll(documents, pictures, settings, power);
        windowsMenuButton.setPopupSide(Side.TOP);

        HBox bottomBar = new HBox(windowsMenuButton);

        BorderPane borderPane = new BorderPane();
        borderPane.setCenter(content);
        borderPane.setBottom(bottomBar);
        borderPane.getStylesheets().add(CSS);

        stage.setScene(new Scene(borderPane));
        stage.show();
    }

    @NotNull
    private static Background createGradient() {
        return Background.fill(
                new LinearGradient(
                        0, 0, 1, 0,
                        true,
                        CycleMethod.NO_CYCLE,
                        new Stop(0, Color.LIGHTBLUE),
                        new Stop(1, Color.PALEGREEN)
                )
        );
    }

    @NotNull
    private static ImageView createImageView(String iconFilename) {
        return new ImageView(
                new Image(
                        Objects.requireNonNull(
                                MenuApp.class.getResource(
                                        iconFilename
                                )
                        ).toExternalForm(),
                        48, 48, true, true
                )
        );
    }
}

When I tried this, there was a slight bug for JavaFX 21, OS X, in that the pop-up menu did not appear in the correct location the very first time I clicked on it (it displayed fine on subsequent clicks). Your outcome may vary depending on your JavaFX version.

0
David Weber On

Solution

Step 1: Create the complete view inclusive the opened content as scene. Use a BorderLayout as root node, put a vbox into left and into center. Put the content and the menu into this vboxes.

Step 2: Both vboxes need an ID / must be annotated with @FXML, so that you can access them in the controller.

Step 3: On initialize, hide the content-vbox and set an onHoverListener to the menu-vbox. I think this is done like this menuVbox.hoverProperty().addListener(...).

Step 4: Add functionality to the listener. On hover, show the element, otherwise hide the element.

Tip: Use computed sizes. Check if the stages resizes automatically. If not, resize the stage in the listener manually.