Search code examples
javajavafxtransparencystageutility

Is it possible to have a transparent utility stage in javafx?


I know that you can set a stage to have a utility style "Stage.InitStyle(StageStyle.UTILITY);" and you can set it to have a transparent style "Stage.InitStyle(StageStyle.TRANSPARENT);" but can you have both in the same stage? I am tiring to make it so that the stage does not show as a window down in the start menu and I would like the stage to be invisible so that you can only see the scene.


Solution

  • You can always do it the old way using Swing where that feature was available. And Swing allows you to embed JavaFX. Of course it would be preferred to have a clean mechanism without Swing, but afaik it doesn't exist (yet).

    Example:

    import javafx.application.Platform;
    import javafx.embed.swing.JFXPanel;
    import javafx.scene.Scene;
    import javafx.scene.control.ContextMenu;
    import javafx.scene.control.Label;
    import javafx.scene.control.MenuItem;
    import javafx.scene.layout.Background;
    import javafx.scene.layout.StackPane;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.CycleMethod;
    import javafx.scene.paint.RadialGradient;
    import javafx.scene.paint.Stop;
    
    import javax.swing.JFrame;
    import javax.swing.SwingUtilities;
    
    import java.awt.geom.GeneralPath;
    
    public class Widget extends JFrame {
    
        class DragContext { 
            double x;
            double y; 
        } 
    
        public Widget() {
    
            // decoration
            setType(Type.UTILITY);
            setUndecorated(true);
    
            setSize(200, 200);
    
            toBack();
    
            // position
            // setLocation(100, 100);
            setLocationRelativeTo(null); // centers on screen
    
            // frame operations
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            // frame shape (a star)
            double points[][] = { { 0, 85 }, { 75, 75 }, { 100, 10 }, { 125, 75 }, { 200, 85 }, { 150, 125 }, { 160, 190 }, { 100, 150 }, { 40, 190 }, { 50, 125 }, { 0, 85 } };
            GeneralPath star = new GeneralPath();
            star.moveTo(points[0][0], points[0][1]);
            for (int k = 1; k < points.length; k++)
                star.lineTo(points[k][0], points[k][2]);
            star.closePath();
    
            setShape(star);
    
            // embed fx into swing
            JFXPanel fxPanel = new JFXPanel();
    
            Widget.this.getContentPane().add(fxPanel);
    
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
    
                    // set scene in JFXPanel
                    fxPanel.setScene( createFxScene());
    
                    // show frame
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
    
                            Widget.this.setVisible(true);
    
                            // send it to the desktop, behind all other existing windows
                            // Widget.this.toBack();
                            // Widget.this.repaint();
                        }
                    });
                }
            });
    
        }
    
        private Scene createFxScene() {
    
            StackPane rootPane = new StackPane();
            rootPane.setBackground(Background.EMPTY);
    
            // add some node
            Label label = new Label("Bright & Shiny");
            label.setTextFill(Color.RED);
    
            rootPane.getChildren().add(label);
    
            // create scene
            Scene scene = new Scene(rootPane);
    
            // gradient fill
            RadialGradient radialGradient = new RadialGradient( 270, 0.8, 0.5, 0.5, 0.7, true, CycleMethod.NO_CYCLE, new Stop( .5f, Color.YELLOW), new Stop( .7f, Color.ORANGE), new Stop( .9f, Color.ORANGERED));
            scene.setFill(radialGradient);
    
            // context menu with close button
            ContextMenu contextMenu = new ContextMenu();
    
            MenuItem closeMenuItem = new MenuItem("Close");
            closeMenuItem.setOnAction(actionEvent -> System.exit(0));
    
            contextMenu.getItems().add(closeMenuItem);
    
            // set context menu for scene
            scene.setOnMousePressed(mouseEvent -> {
                if (mouseEvent.isSecondaryButtonDown()) {
                    contextMenu.show(rootPane, mouseEvent.getScreenX(), mouseEvent.getScreenY());
                }
            });
    
            // allow the frame to be dragged around
            final DragContext dragDelta = new DragContext();
    
            rootPane.setOnMousePressed(mouseEvent -> {
    
                dragDelta.x = Widget.this.getLocation().getX() - mouseEvent.getScreenX();
                dragDelta.y = Widget.this.getLocation().getY() - mouseEvent.getScreenY();
    
            });
    
            rootPane.setOnMouseDragged(mouseEvent -> Widget.this.setLocation((int) (mouseEvent.getScreenX() + dragDelta.x), (int) (mouseEvent.getScreenY() + dragDelta.y)));
    
            return scene;
        }
    
        public static void main(String[] args) {
            new Widget();
        }
    
    }
    

    Here's a screenshot of the widget that's not showing up in the task bar. Drag it with the left mouse button. Right mouse button offers a context menu with a close button.

    Widget

    The code above uses the swing frame's shape. The code below uses the javafx control's shape.

    Here's a version in which only the control is visible. I use a reflecting label control.

    If you want to send the control directly to the desktop, you need to activate the toBack() invocation. You can zoom the control with the mouse wheel. The maximum zoom size is limited to the size of the jframe.

    All you need to do for your custom control is implement the code in createFxControl()

    import javafx.application.Platform;
    import javafx.embed.swing.JFXPanel;
    import javafx.event.EventHandler;
    import javafx.scene.Scene;
    import javafx.scene.control.ContextMenu;
    import javafx.scene.control.Control;
    import javafx.scene.control.Label;
    import javafx.scene.control.MenuItem;
    import javafx.scene.effect.Reflection;
    import javafx.scene.input.ScrollEvent;
    import javafx.scene.layout.Background;
    import javafx.scene.layout.StackPane;
    import javafx.scene.paint.Color;
    import javafx.scene.text.Font;
    
    import javax.swing.JFrame;
    import javax.swing.SwingUtilities;
    
    public class LabelWidget extends JFrame {
    
        class DragContext { 
            double x;
            double y; 
        } 
    
        public LabelWidget() {
    
            // decoration
            setType(Type.UTILITY);
            setUndecorated(true);
    
            // make frame transparent, we only want the control to be visible
            setBackground(new java.awt.Color(0,0,0,0));
    
            setSize(400, 400);
    
            // position
            // setLocation(100, 100);
            setLocationRelativeTo(null); // centers on screen
    
            // frame operations
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            // embed fx into swing
            JFXPanel fxPanel = new JFXPanel();
    
            LabelWidget.this.getContentPane().add(fxPanel);
    
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
    
                    // set scene in JFXPanel
                    fxPanel.setScene( createFxScene());
    
                    // show frame
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
    
                            LabelWidget.this.setVisible(true);
    
                            // send it to the desktop, behind all other existing windows
                            // ClockWidget.this.toBack();
                            // ClockWidget.this.repaint();
                        }
                    });
                }
            });
    
        }
    
        private Scene createFxScene() {
    
            StackPane rootPane = new StackPane();
    
            //  make pane transparent, we only want the control to be visible
            rootPane.setBackground(Background.EMPTY);
    
            // add control
            Control control = createFxControl();
            rootPane.getChildren().add( control);
    
            // create scene
            Scene scene = new Scene(rootPane);
    
            // make scene transparent, we only want the control to be visible
            scene.setFill( Color.TRANSPARENT);
    
            // context menu with close button
            ContextMenu contextMenu = new ContextMenu();
    
            MenuItem closeMenuItem = new MenuItem("Close");
            closeMenuItem.setOnAction(actionEvent -> System.exit(0));
    
            contextMenu.getItems().add(closeMenuItem);
    
            control.setContextMenu(contextMenu);
    
            // allow the frame to be dragged around
            makeDraggable( control);
    
            // allow zooming
            makeZoomable( control);
    
            return scene;
        }
        /**
         * Create the JavaFX control of which we use the shape.
         * @return
         */
        private Control createFxControl() {
    
            Label label = new Label( "I'm a Label");
            label.setFont(new Font("Tahoma", 24));
            label.setEffect(new Reflection());
    
            return label;
        }
    
    
    
        /**
         * Allow dragging of the stage / control on the desktop
         * @param control
         * @param stage
         */
        public void makeDraggable( Control control) {
    
            final DragContext dragDelta = new DragContext();
    
            control.setOnMousePressed(mouseEvent -> {
    
                dragDelta.x = LabelWidget.this.getLocation().getX() - mouseEvent.getScreenX();
                dragDelta.y = LabelWidget.this.getLocation().getY() - mouseEvent.getScreenY();
    
            });
    
            control.setOnMouseDragged(mouseEvent -> LabelWidget.this.setLocation((int) (mouseEvent.getScreenX() + dragDelta.x), (int) (mouseEvent.getScreenY() + dragDelta.y)));
    
        }
    
        /**
         * Allow zooming
         * @param control
         */
        public void makeZoomable( Control control) {
    
            // note: in order to make it larger, we'd have to resize the stage/frame => we limit the size to 1.0 for now and allow only making the control smaller
            final double MAX_SCALE = 1.0;
            final double MIN_SCALE = 0.1;
            control.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
                @Override
                public void handle(ScrollEvent event) {
                    double delta = 1.2;
                    double scale = control.getScaleX();
                    if (event.getDeltaY() < 0) {
                        scale /= delta;
                    } else {
                        scale *= delta;
                    }
                    scale = clamp(scale, MIN_SCALE, MAX_SCALE);
                    control.setScaleX(scale);
                    control.setScaleY(scale);
                    event.consume();
                }
            });
    
        }
    
        /**
         * Limit bounds of value
         * @param value
         * @param min
         * @param max
         * @return
         */
        public static double clamp( double value, double min, double max) {
            if( Double.compare(value, min) < 0)
                return min;
            if( Double.compare(value, max) > 0)
                return max;
            return value;
        }
    
        public static void main(String[] args) {
            new LabelWidget();
        }
    
    }
    

    Or if you have the awesome Enzo library from https://github.com/HanSolo/Enzo at hand, you can use this code:

    private Control createFxControl() {
    
        // create a clock using the enzo library from https://github.com/HanSolo/Enzo
        Clock clock = ClockBuilder.create()
                // .prefSize(400, 400)
                .design(Clock.Design.DB)
                .running(true)
                .text("Berlin")
                .autoNightMode(true)
                .build();
    
    
        return clock;
    }
    

    to create this:

    enter image description here