Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw a gradient over the scene on JavaFX?

Is there a way to draw a radial gradient from black to transparent over the entire scene of a JavaFX stage? I want to achieve something like this: Example

like image 445
Mateus Viccari Avatar asked Jan 10 '23 07:01

Mateus Viccari


2 Answers

Here is a quick answer which uses a StackPane as the scene root to allow addition of an overlay with a radial gradient background varying from black to transparent.

  • You can adjust the radial gradient colors or radius, e.g. make the inner color Color.BLACK.deriveColor(0, 1, 1, 0.2), to change the amount of highlight and intensity in the effect.
  • Gradients such as this can be set in CSS as well, so you could use CSS rather than Java code to style the effect if you wished.
  • The output of this implementation suffers a little bit from banding of the radial gradient (not sure how to improve that further), but it's kind of OK depending on your tolerance.

shaded

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.effect.BoxBlur;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.stage.Stage;

// java 8
// click on the scene to place a shaded lens effect over the scene.
public class ShadedScene extends Application {
    @Override
    public void start(Stage stage) {
        StackPane layout = new StackPane(
                new Label("Click to shade/unshade")
        );
        layout.setPrefSize(400, 300);

        Scene scene = new Scene(layout);
        makeShadeable(scene);

        stage.setScene(scene);
        stage.show();
    }

    /**
     * Applies a lens effect gradient to a scene root node.
     * The effect is kind of like a flashlight shining against the wall in the dark.
     *
     * For the gradient to be applied, the scene's root must be defined and a Pane
     * to which the effect can added and removed as a child.
     * 
     * @param scene the scene to have the effect applied.
     */
    private void makeShadeable(Scene scene) {
        if (scene.getRoot() == null ||
            !(scene.getRoot() instanceof Pane)) {
            return;
        }

        Pane shade = new Pane();
        RadialGradient shadePaint = new RadialGradient(
                0, 0, 0.5, 0.5, 1, true, CycleMethod.NO_CYCLE,
                new Stop(1, Color.BLACK),
                new Stop(0, Color.TRANSPARENT)
        );

        shade.setBackground(
                new Background(
                        new BackgroundFill(
                                shadePaint, null, new Insets(-10)
                        )
                )
        );

        // blur helps reduce visible banding of the radial gradient.
        shade.setEffect(new BoxBlur(5, 5, 3));

        Pane root = (Pane) scene.getRoot();
        scene.setOnMouseClicked(event -> {
            if (root.getChildren().contains(shade)) {
                root.getChildren().remove(shade);
            } else {
                root.getChildren().add(shade);
            }
        });
    }

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

There are other ways to accomplish something similar to the desired effect. For example the technique below applies an inner shadow and color adjustment to the root pane directly, so it requires no additional nodes.

shaded inner shadow

private void makeShadeableByInnerShadow(Scene scene) {
    InnerShadow shade = new InnerShadow();
    shade.setWidth(120);
    shade.setHeight(120);
    shade.setInput(new ColorAdjust(0, 0, -0.3, 0));

    Parent root = scene.getRoot();
    scene.setOnMouseClicked(event -> {
        if (root.getEffect() == null) {
            root.setEffect(shade);
        } else {
            root.setEffect(null);
        }
    });
}
like image 96
jewelsea Avatar answered Jan 21 '23 14:01

jewelsea


Considering your example, you could post this on http://pt.stackoverflow.com. as most of your interface is in Brazilian Portuguese.

The answer is exactly what jewelsea posted. But as I was also answering when I got notified that there was an answer, I posted mine as well.

Test.java

package br;

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


    public class Test extends Application {

            public static Stage thisStage;
            @Override
            public void start(Stage stage) throws Exception {
                thisStage = stage;
                Parent root = FXMLLoader.load(getClass().getResource("doc.fxml"));
                Scene scene = new Scene(root);
                scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
                stage.setScene(scene);
                stage.setTitle("Happy Client - Overlay");
                stage.show();

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

TestController.java

package br;
import java.net.URL;
import java.util.ResourceBundle;

import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;

    public class TestController implements Initializable{
        @FXML Pane mainPane;
        @FXML Pane overlayPane;
        @Override
        public void initialize(URL arg0, ResourceBundle arg1) {
            overlayPane.getStyleClass().add("grad");
            overlayPane.toFront();
            overlayPane.addEventHandler(MouseEvent.MOUSE_PRESSED,
                    new EventHandler<MouseEvent>(){
                        @Override
                        public void handle(MouseEvent arg0) {
                            overlayPane.toBack();
                        }
            });
        }
    }

doc.fxml

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

    <?import java.lang.*?>
    <?import javafx.scene.control.*?>
    <?import javafx.scene.layout.*?>
    <?import javafx.scene.shape.*?>

    <StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="br.TestController">
        <children>
        <Pane fx:id="mainPane" prefHeight="98.0" prefWidth="600.0">
        <children>
            <TableView layoutX="-2.0" layoutY="99.0" prefHeight="300.0" prefWidth="600.0">
                <columns>
                    <TableColumn prefWidth="75.0" text="Nome" />
                    <TableColumn prefWidth="75.0" text="RG" />
                    <TableColumn prefWidth="75.0" text="CPF" />
                    <TableColumn prefWidth="75.0" text="Idade" />
                </columns>
            </TableView>
            <Label layoutX="162.0" layoutY="41.0" style="-fx-font-size: 32;" text="Informe o cliente" />
            <Line endX="500.0" layoutX="100.0" layoutY="98.0" startX="-100.0" />
        </children>
        </Pane>
        <Pane fx:id="overlayPane" opacity="0.8" prefHeight="200.0" prefWidth="200.0" />
    </children>
    </StackPane>

style.css

.grad{
    -fx-padding: 40;
    -fx-background-color:  radial-gradient(center 50% 50%, radius 140%, rgba(200,200,200,70) 5%,rgba(0,0,0,100) 35%);
}

enter image description here

Note that you will have to use a StackPane and not other such as AnchorPane because if you set the style directly on an AnchorPane the style would not work for your elements above it such as your table, as you may see below:

enter image description here

like image 26
Mansueli Avatar answered Jan 21 '23 15:01

Mansueli