3

As the title basically. I have a node in JavaFX which I want to be displayed in front of all other nodes according to certain CSS rules. I do not want this to change the ordering of the nodes in my VBox which .toFront() appears to do. See this question.

Is this even possible?

EDIT: To clarify. The situation is the following. I have a VBox containing a bunch of tightly packed ImageViews. When I hover over one I want it to grow slightly to give it the feel that the image is being lifted off of the screen. But since the ImageViews are so tightly packed only the top edge grows (visibly). The bottom edge grows but is below the following image and cannot be seen.

EDIT 2: Upon request here is a screenshot of what I am doing.

enter image description here

The different colour gradients are ImageViews and as I hover over one it should grow as the top edge of the top gradient has in this image (look closely at the top right corner next to the X). However as is also visible in this image the bottom edge of this ImageView has become hidden by the next gradient in this VBox and the grow is not visible.

Ivar Eriksson
  • 863
  • 1
  • 15
  • 30
  • Wouldn't a `StackPane` do what you need? I'm not sure this can be done with just CSS. – Zephyr Jun 27 '18 at 20:43
  • @Zephyr it would not be ideal. See update for a more in depth explanation of the problem (soon). – Ivar Eriksson Jun 27 '18 at 20:45
  • 1
    From what I know its not possible to set a Node to be on top without actually reordering the Nodes (inside a pane etc) and that can only be happened either by calling `toFront()` or manually reordering the nodes. – JKostikiadis Jun 27 '18 at 20:52
  • Have you actually tried `toFront()`? – Zephyr Jun 27 '18 at 20:53
  • @JKostikiadis as I feared. This is a major drawback of JavaFX then. :,( – Ivar Eriksson Jun 27 '18 at 20:54
  • I wouldn't call it a drawback; JavaFX is not a web page :). Just spitballing, but you could theoretically create a new stage when mousing over an image and display that at the correct coordinates. – Zephyr Jun 27 '18 at 20:55
  • 1
    I will guess this is a [XY problem](https://en.wikipedia.org/wiki/XY_problem) and by providing a small runnable example we could give you alternatives or workarounds. To be completely honest I am not 100% sure that I understand what you are trying to achieve here. Maybe a screenshot could clarify things more :P – JKostikiadis Jun 27 '18 at 20:56
  • @Zephyr that would be a solution but it seems very labor intensive. I will have to settle for a different layout idea. – Ivar Eriksson Jun 27 '18 at 20:57
  • 1
    Hmm after reading it couple of times I think I got it :P . You could play with the spacing of the VBox when the Image is "selected" in order to show the glow on the bottom. I will try to recreate it and maybe I think something better. – JKostikiadis Jun 27 '18 at 20:59
  • @JKostikiadis that is exactly what I have settled for. Glad that I have started to think like a real JavaFX programmer. – Ivar Eriksson Jun 27 '18 at 21:04
  • You need to post a sample app of what you have. – SedJ601 Jun 27 '18 at 21:14
  • 1
    I did create a working prototype of the new Stage method, but didn't complete it. But it is doable pretty simply if you decide to go that route. The challenge would be capturing the correct mouse events and setting the coordinates for the new stage. – Zephyr Jun 27 '18 at 21:14
  • 2
    Take a look at the [`viewOrderProperty`](https://docs.oracle.com/javase/10/docs/api/javafx/scene/Node.html#viewOrderProperty()) (Java 9+) of `Node` to see if this helps you. – Slaw Jun 27 '18 at 21:19
  • @Slaw that seems like the perfect solution. Thanks! – Ivar Eriksson Jun 27 '18 at 21:25
  • 1
    @Slaw I feel so outdated still using java 8. Indeed it seems by far the best solution. – JKostikiadis Jun 27 '18 at 21:39
  • 1
    Turns out you can also set the view order from CSS (edited my answer to address this). – Slaw Jun 27 '18 at 22:13

3 Answers3

8

This sounds like the perfect situation for using the viewOrder property of Node added in Java 9. The viewOrder controls how Nodes are drawn in relation to other Nodes of the same Parent without changing the order of the Nodes in the child list. Here's the Javadoc:

Defines the rendering and picking order of this Node within its parent.

This property is used to alter the rendering and picking order of a node within its parent without reordering the parent's children list. For example, this can be used as a more efficient way to implement transparency sorting. To do this, an application can assign the viewOrder value of each node to the computed distance between that node and the viewer.

The parent will traverse its children in decreasing viewOrder order. This means that a child with a lower viewOrder will be in front of a child with a higher viewOrder. If two children have the same viewOrder, the parent will traverse them in the order they appear in the parent's children list.

However, viewOrder does not alter the layout and focus traversal order of this Node within its parent. A parent always traverses its children list in order when doing layout or focus traversal.

Here's an example using this property:

import javafx.animation.ScaleTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.ArrayList;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        var box = new HBox(createRectangles(Color.DARKBLUE, Color.FIREBRICK, 25));
        box.setAlignment(Pos.CENTER);
        box.setPadding(new Insets(50, 20, 50, 20));
        primaryStage.setScene(new Scene(box));
        primaryStage.show();
    }

    private Rectangle[] createRectangles(Color start, Color end, int count) {
        var list = new ArrayList<Rectangle>(count);
        for (double i = 0; i < count; i++) {
            var rect = new Rectangle(30, 60, start.interpolate(end, i / count));

            var scaleTrans = new ScaleTransition(Duration.millis(250), rect);
            scaleTrans.setFromX(1.0);
            scaleTrans.setFromY(1.0);
            scaleTrans.setToX(1.2);
            scaleTrans.setToY(1.2);

            rect.setOnMouseEntered(e -> {
                scaleTrans.stop(); // <--- doesn't seem necessary*
                scaleTrans.setRate(1.0);
                rect.setViewOrder(-1.0);
                scaleTrans.play();
            });
            rect.setOnMouseExited(e -> {
                scaleTrans.stop(); // <--- doesn't seem necessary*
                scaleTrans.setRate(-1.0);
                rect.setViewOrder(0.0);
                scaleTrans.play();
            });
            // *the "stop()"'s don't seem to be necessary. When I commented
            // them out the animation still worked. In fact, the animation
            // actually seems smoother in the situation where you move the
            // mouse over and then away quickly (before the zoom-in completes).

            list.add(rect);
        }
        return list.toArray(new Rectangle[0]);
    }

}

It uses Rectangles instead of ImageViews but the concept is the same. When the mouse hovers over a Rectangle it sets the view order to be lower than the others and then plays a ScaleTransition to make it bigger. When the mouse exits it resets the view order back to 0 and then reverses the ScaleTransition.

Note: I used the var keyword which was added in Java 10.

And here is a GIF of the example in action:

the GIF of the application in action

Edit: Since you brought up CSS I went and checked if the view order could be set from a stylesheet. And it appears it can. Looking at the CSS Reference Guide there is a CSS property defined for Node named -fx-view-order.

Slaw
  • 37,820
  • 8
  • 53
  • 80
2

Here is one such solution, creating a new stage to show the zoomed in image.

I do not set the proper coordinates in this sample, but this works as a proof of concept.

In a nutshell: capture the onMouseEntered and onMouseExited events and hide or show the new stage.

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class Main extends Application {

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

    @Override
    public void start(Stage primaryStage) {

        FlowPane root = new FlowPane();
        root.setHgap(5);
        root.setVgap(5);
        for (int i = 0; i < 25; i++) {
            root.getChildren().add(getImagePane());
        }

        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    private VBox getImagePane() {

        VBox pane = new VBox() {{
            setAlignment(Pos.CENTER);
            setPadding(new Insets(5));
            setWidth(50);
            setHeight(50);
            setStyle("-fx-border-color: black");
        }};

        ImageView img = new ImageView("sample/imageGallery/cerulean.png") {{
            setFitWidth(50);
            setFitHeight(50);
            setPreserveRatio(true);

            Stage stage = zoomedStage(pane, this);

            setOnMouseEntered(mouseEvent -> stage.show());
            setOnMouseExited(mouseEvent -> stage.hide());

        }};

        pane.getChildren().add(img);
        return pane;

    }

    private Stage zoomedStage(VBox parent, ImageView img) {

        Stage stage = new Stage();
        stage.setWidth(110);
        stage.setHeight(110);

        VBox pane = new VBox() {{
            setAlignment(Pos.CENTER);
            setPadding(new Insets(5));
            setWidth(110);
            setHeight(110);
            setStyle("-fx-border-color: black");
        }};

        pane.setPickOnBounds(false);

        ImageView zoomedImage = new ImageView(img.getImage());
        zoomedImage.setFitHeight(100);
        zoomedImage.setFitWidth(100);

        pane.getChildren().add(zoomedImage);

        stage.setScene(new Scene(pane));
        stage.initStyle(StageStyle.UNDECORATED);
        return stage;
    }
}

From here, it should just be a matter or fixing the stage's coordinates to be centered over the image and then remove the stage's decoration.

Known Issues:

You will also need to handle the issue with the mouse cursor being blocked by the new stage. This will lead to a loop where the mouse is constantly entering and exiting the thumbnail of the image, causing the zoomed in stage to flicker.

Zephyr
  • 9,885
  • 4
  • 28
  • 63
2

From my point of view, you have a VBox with spacing 0 so each ImageView is tightly packed to each other so the glow effect is not well visible in every image. In that case you could just add a margin each time you want to select an ImageView in order to 'help' the glowing effect to appear.

For Java 8 :

Sadly, this can't be happened from a CSS cause the ImageView does not provide any rule for setting margin or padding. So you are more or less (in my opinion) bound to write that behaviour through code. :

private Node createImageView(String imageLink) {
        // Setting the image view
        ImageView imageView = new ImageView(imageLink);

        // setting the fit width of the image view
        imageView.setFitWidth(400);

        // Setting the preserve ratio of the image view
        imageView.setPreserveRatio(true);

        // Instantiating the Glow class
        Glow glow = new Glow();

        imageView.setOnMouseEntered(e -> {
            // setting level of the glow effect
            glow.setLevel(0.9);
            // Adding a margin on TOP and Bottom side just to make the 
            // glowing effect visible
            VBox.setMargin(imageView, new Insets(2,0,2,0));
        });

        imageView.setOnMouseExited(e -> {
            // remove the glow effect
            glow.setLevel(0.0);
            // remove the margins 
            VBox.setMargin(imageView, new Insets(0));
        });

        // Applying bloom effect to text
        imageView.setEffect(glow);

        return imageView;
    }
JKostikiadis
  • 2,847
  • 2
  • 22
  • 34