/* * Copyright 2014-2016 Arnaud Nouard. All rights reserved. * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package insidefx.undecorator; import java.net.URL; import java.util.Locale; import java.util.Properties; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; import javafx.animation.FadeTransition; import javafx.animation.ParallelTransition; import javafx.animation.TranslateTransition; import javafx.application.Platform; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.geometry.Bounds; import javafx.geometry.Side; import javafx.scene.CacheHint; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckMenuItem; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.Tooltip; import javafx.scene.effect.BlurType; import javafx.scene.effect.DropShadow; import javafx.scene.effect.Effect; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.shape.Shape; import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.util.Duration; /** * This class, with the UndecoratorController, is the central class for the * decoration of Transparent Stages. The Stage Undecorator TODO: Themes, manage * Quit (main stage) * * Bugs (Mac only?): Accelerators + Fullscreen crashes JVM KeyCombination does * not respect keyboard's locale. Multi screen: On second screen JFX returns * wrong value for MinY (300) */ public class Undecorator extends StackPane { public int SHADOW_WIDTH = 15; public int SAVED_SHADOW_WIDTH = 15; static public int RESIZE_PADDING = 7; static public int FEEDBACK_STROKE = 4; static public double ROUNDED_DELTA = 5; public static final Logger LOGGER = Logger.getLogger("Undecorator"); public static final ResourceBundle LOC; StageStyle stageStyle; @FXML private Button menu; @FXML private Button close; @FXML private Button maximize; @FXML private Button minimize; @FXML private Button resize; @FXML private Button fullscreen; @FXML private Label title; @FXML private Pane decorationRoot; @FXML private ContextMenu contextMenu; MenuItem maximizeMenuItem; CheckMenuItem fullScreenMenuItem; Region clientArea; Pane stageDecoration = null; Rectangle shadowRectangle; Pane glassPane; Rectangle dockFeedback; FadeTransition dockFadeTransition; Stage dockFeedbackPopup; ParallelTransition parallelTransition; Effect dsFocused; Effect dsNotFocused; UndecoratorController undecoratorController; Stage stage; Rectangle backgroundRect; SimpleBooleanProperty maximizeProperty; SimpleBooleanProperty minimizeProperty; SimpleBooleanProperty closeProperty; SimpleBooleanProperty fullscreenProperty; String shadowBackgroundStyleClass = "decoration-shadow"; String decorationBackgroundStyle = "decoration-background"; TranslateTransition fullscreenButtonTransition; final Rectangle internal = new Rectangle(); final Rectangle external = new Rectangle(); static { LOC = ResourceBundle.getBundle("resources/localization", Locale.getDefault()); } public SimpleBooleanProperty maximizeProperty() { return maximizeProperty; } public SimpleBooleanProperty minimizeProperty() { return minimizeProperty; } public SimpleBooleanProperty closeProperty() { return closeProperty; } public SimpleBooleanProperty fullscreenProperty() { return fullscreenProperty; } public Undecorator(Stage stage, Region root) { this(stage, root, "stagedecoration.fxml", StageStyle.UNDECORATED); } public Undecorator(Stage stag, Region clientArea, String stageDecorationFxml, StageStyle st) { create(stag, clientArea, getClass().getResource(stageDecorationFxml), st); } public Undecorator(Stage stag, Region clientArea, URL stageDecorationFxmlAsURL, StageStyle st) { create(stag, clientArea, stageDecorationFxmlAsURL, st); } public void create(Stage stag, Region clientArea, URL stageDecorationFxmlAsURL, StageStyle st) { this.stage = stag; this.clientArea = clientArea; setStageStyle(st); loadConfig(); // Properties maximizeProperty = new SimpleBooleanProperty(false); maximizeProperty.addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { getController().maximizeOrRestore(); }); minimizeProperty = new SimpleBooleanProperty(false); minimizeProperty.addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { getController().minimize(); }); closeProperty = new SimpleBooleanProperty(false); closeProperty.addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { getController().close(); }); fullscreenProperty = new SimpleBooleanProperty(false); fullscreenProperty.addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { getController().setFullScreen(!stage.isFullScreen()); }); // The controller undecoratorController = new UndecoratorController(this); if (stage.getUserData() != null) { undecoratorController.setAsStageDraggable(stage, (Node) stage.getUserData()); } else { undecoratorController.setAsStageDraggable(stage, clientArea); } // Focus drop shadows: radius, spread, offsets dsFocused = new DropShadow(BlurType.THREE_PASS_BOX, Color.BLACK, SHADOW_WIDTH, 0.1, 0, 0); dsNotFocused = new DropShadow(BlurType.THREE_PASS_BOX, Color.DARKGREY, SHADOW_WIDTH, 0, 0, 0); shadowRectangle = new Rectangle(); shadowRectangle.layoutBoundsProperty().addListener((ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds newBounds) -> { if (SHADOW_WIDTH != 0) { shadowRectangle.setVisible(true); setShadowClip(newBounds); } else { shadowRectangle.setVisible(false); } }); // UI part of the decoration try { FXMLLoader fxmlLoader = new FXMLLoader(stageDecorationFxmlAsURL); fxmlLoader.setController(this); stageDecoration = (Pane) fxmlLoader.load(); } catch (Exception ex) { LOGGER.log(Level.SEVERE, "Decorations not found", ex); } initDecoration(); /* * Resize rectangle */ undecoratorController.setStageResizableWith(stage, decorationRoot, RESIZE_PADDING, SHADOW_WIDTH); // If not resizable (quick fix) if (fullscreen != null) { fullscreen.setVisible(stage.isResizable()); } if (resize != null) { resize.setVisible(stage.isResizable()); } if (minimize != null) { minimize.setVisible(stage.isResizable()); } if (maximize != null) { maximize.setVisible(stage.isResizable()); } if (menu != null) { menu.setVisible(stage.isResizable()); } if (minimize != null && !stage.isResizable()) { AnchorPane.setRightAnchor(minimize, 34d); } // Glass Pane glassPane = new Pane(); glassPane.setMouseTransparent(true); buildDockFeedbackStage(); title.getStyleClass().add("undecorator-label-titlebar"); shadowRectangle.getStyleClass().add(shadowBackgroundStyleClass); // resizeRect.getStyleClass().add(resizeStyleClass); // Do not intercept mouse events on stage's shadow shadowRectangle.setMouseTransparent(true); // Is it possible to apply an effect without affecting decendent? super.setStyle("-fx-background-color:transparent;"); // Or this: // super.setStyle("-fx-background-color:transparent;-fx-border-color:white;-fx-border-radius:30;-fx-border-width:1;-fx-border-insets:"+SHADOW_WIDTH+";"); // super.setEffect(dsFocused); // super.getChildren().addAll(clientArea,stageDecoration, glassPane); backgroundRect = new Rectangle(); backgroundRect.getStyleClass().add(decorationBackgroundStyle); backgroundRect.setMouseTransparent(true); // Add all layers super.getChildren().addAll(shadowRectangle, backgroundRect, clientArea, stageDecoration, glassPane); // super.getChildren().addAll(shadowRectangle, backgroundRect); /* * Focused stage */ stage.focusedProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { setShadowFocused(t1); }); /* * Fullscreen */ if (fullscreen != null) { stage.fullScreenProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean fullscreenState) -> { setShadow(!fullscreenState); fullScreenMenuItem.setSelected(fullscreenState); maximize.setVisible(!fullscreenState); minimize.setVisible(!fullscreenState); if (resize != null) { resize.setVisible(!fullscreenState); } if (fullscreenState) { // String and icon fullscreen.getStyleClass().add("decoration-button-unfullscreen"); fullscreen.setTooltip(new Tooltip(LOC.getString("Restore"))); undecoratorController.saveFullScreenBounds(); if (fullscreenButtonTransition != null) { fullscreenButtonTransition.stop(); } // Animate the fullscreen button fullscreenButtonTransition = new TranslateTransition(); fullscreenButtonTransition.setDuration(Duration.millis(2000)); fullscreenButtonTransition.setToX(80); fullscreenButtonTransition.setNode(fullscreen); fullscreenButtonTransition.setOnFinished(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent t) { fullscreenButtonTransition = null; } }); fullscreenButtonTransition.play(); // fullscreen.setOpacity(0.2); } else { // String and icon fullscreen.getStyleClass().remove("decoration-button-unfullscreen"); fullscreen.setTooltip(new Tooltip(LOC.getString("FullScreen"))); undecoratorController.restoreFullScreenSavedBounds(stage); // fullscreen.setOpacity(1); if (fullscreenButtonTransition != null) { fullscreenButtonTransition.stop(); } // Animate the change fullscreenButtonTransition = new TranslateTransition(); fullscreenButtonTransition.setDuration(Duration.millis(1000)); fullscreenButtonTransition.setToX(0); fullscreenButtonTransition.setNode(fullscreen); fullscreenButtonTransition.setOnFinished((ActionEvent t1) -> { fullscreenButtonTransition = null; }); fullscreenButtonTransition.play(); } }); } computeAllSizes(); } /** * Compute the needed clip for stage's shadow border * * @param newBounds * @param shadowVisible */ void setShadowClip(Bounds newBounds) { external.relocate( newBounds.getMinX() - SHADOW_WIDTH, newBounds.getMinY() - SHADOW_WIDTH ); internal.setX(SHADOW_WIDTH); internal.setY(SHADOW_WIDTH); internal.setWidth(newBounds.getWidth()); internal.setHeight(newBounds.getHeight()); internal.setArcWidth(shadowRectangle.getArcWidth()); // shadowRectangle CSS cannot be applied on this internal.setArcHeight(shadowRectangle.getArcHeight()); external.setWidth(newBounds.getWidth() + SHADOW_WIDTH * 2); external.setHeight(newBounds.getHeight() + SHADOW_WIDTH * 2); Shape clip = Shape.subtract(external, internal); shadowRectangle.setClip(clip); } /** * Install default accelerators * * @param scene */ public void installAccelerators(Scene scene) { // Accelerators if (stage.isResizable()) { scene.getAccelerators().put(new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN, KeyCombination.SHORTCUT_DOWN), (Runnable) () -> { switchFullscreen(); }); } scene.getAccelerators().put(new KeyCodeCombination(KeyCode.M, KeyCombination.SHORTCUT_DOWN), (Runnable) () -> { switchMinimize(); }); scene.getAccelerators().put(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN), (Runnable) () -> { switchClose(); }); } /** * Init the minimum/pref/max sizes in order to be reflected in the primary * stage */ private void computeAllSizes() { double minWidth = minWidth(getWidth()); setMinWidth(minWidth); double minHeight = minHeight(getHeight()); setMinHeight(minHeight); double prefWidth = prefWidth(getWidth()); setPrefWidth(prefWidth); setWidth(prefWidth); double prefHeight = prefHeight(getHeight()); setPrefHeight(prefHeight); setHeight(prefHeight); double maxWidth = maxWidth(getWidth()); if (maxWidth > minWidth) { setMaxWidth(maxWidth); } double maxHeight = maxHeight(getHeight()); if (maxHeight > minHeight) { setMaxHeight(maxHeight); } } /* * The sizing is based on client area's bounds. */ @Override protected double computePrefWidth(double d) { return clientArea.getPrefWidth() + SHADOW_WIDTH * 2 + RESIZE_PADDING * 2; } @Override protected double computePrefHeight(double d) { return clientArea.getPrefHeight() + SHADOW_WIDTH * 2 + RESIZE_PADDING * 2; } @Override protected double computeMaxHeight(double d) { double maxHeight = clientArea.getMaxHeight(); if (maxHeight > 0) { return maxHeight + SHADOW_WIDTH * 2 + RESIZE_PADDING * 2; } return maxHeight; } @Override protected double computeMinHeight(double d) { double d2 = super.computeMinHeight(d); d2 += SHADOW_WIDTH * 2 + RESIZE_PADDING * 2; return d2; } @Override protected double computeMaxWidth(double d) { double maxWidth = clientArea.getMaxWidth(); if (maxWidth > 0) { return maxWidth + SHADOW_WIDTH * 2 + RESIZE_PADDING * 2; } return maxWidth; } @Override protected double computeMinWidth(double d) { double d2 = super.computeMinWidth(d); d2 += SHADOW_WIDTH * 2 + RESIZE_PADDING * 2; return d2; } public void setStageStyle(StageStyle st) { stageStyle = st; } public StageStyle getStageStyle() { return stageStyle; } /** * Activate fade in transition on showing event */ public void setFadeInTransition() { super.setOpacity(0); stage.showingProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { if (t1) { FadeTransition fadeTransition = new FadeTransition(Duration.seconds(2), Undecorator.this); fadeTransition.setToValue(1); fadeTransition.play(); } }); } /** * Launch the fade out transition. Must be invoked when the * application/window is supposed to be closed */ public void setFadeOutTransition() { FadeTransition fadeTransition = new FadeTransition(Duration.seconds(1), Undecorator.this); fadeTransition.setToValue(0); fadeTransition.play(); fadeTransition.setOnFinished((ActionEvent t) -> { stage.hide(); if (dockFeedbackPopup != null && dockFeedbackPopup.isShowing()) { dockFeedbackPopup.hide(); } }); } public void removeDefaultBackgroundStyleClass() { shadowRectangle.getStyleClass().remove(shadowBackgroundStyleClass); } public Rectangle getShadowNode() { return shadowRectangle; } public Rectangle getBackgroundRectangle() { return backgroundRect; } /** * Background opacity * * @param opacity */ public void setBackgroundOpacity(double opacity) { shadowRectangle.setOpacity(opacity); } /** * Manage buttons and menu items */ public void initDecoration() { MenuItem minimizeMenuItem = null; // Menu contextMenu.setAutoHide(true); if (minimize != null) { // Utility Stage minimizeMenuItem = new MenuItem(LOC.getString("Minimize")); minimizeMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.M, KeyCombination.SHORTCUT_DOWN)); minimizeMenuItem.setOnAction((ActionEvent e) -> { switchMinimize(); }); contextMenu.getItems().add(minimizeMenuItem); } if (maximize != null && stage.isResizable()) { // Utility Stage type maximizeMenuItem = new MenuItem(LOC.getString("Maximize")); maximizeMenuItem.setOnAction((ActionEvent e) -> { switchMaximize(); contextMenu.hide(); // Stay stuck on screen }); contextMenu.getItems().addAll(maximizeMenuItem, new SeparatorMenuItem()); } // Fullscreen if (stageStyle != StageStyle.UTILITY && stage.isResizable()) { fullScreenMenuItem = new CheckMenuItem(LOC.getString("FullScreen")); fullScreenMenuItem.setOnAction((ActionEvent e) -> { // fake //maximizeProperty().set(!maximizeProperty().get()); switchFullscreen(); }); fullScreenMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN, KeyCombination.SHORTCUT_DOWN)); contextMenu.getItems().addAll(fullScreenMenuItem, new SeparatorMenuItem()); } // Close MenuItem closeMenuItem = new MenuItem(LOC.getString("Close")); closeMenuItem.setOnAction((ActionEvent e) -> { switchClose(); }); closeMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN)); contextMenu.getItems().add(closeMenuItem); menu.setOnMousePressed((MouseEvent t) -> { if (contextMenu.isShowing()) { contextMenu.hide(); } else { contextMenu.show(menu, Side.BOTTOM, 0, 0); } }); // Close button close.setTooltip(new Tooltip(LOC.getString("Close"))); close.setOnAction((ActionEvent t) -> { switchClose(); }); // Maximize button // If changed via contextual menu maximizeProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { if (!stage.isResizable()) { return; } Tooltip tooltip = maximize.getTooltip(); if (tooltip.getText().equals(LOC.getString("Maximize"))) { tooltip.setText(LOC.getString("Restore")); maximizeMenuItem.setText(LOC.getString("Restore")); maximize.getStyleClass().add("decoration-button-restore"); if (resize != null) { resize.setVisible(false); } } else { tooltip.setText(LOC.getString("Maximize")); maximizeMenuItem.setText(LOC.getString("Maximize")); maximize.getStyleClass().remove("decoration-button-restore"); if (resize != null) { resize.setVisible(true); } } }); if (maximize != null) { // Utility Stage maximize.setTooltip(new Tooltip(LOC.getString("Maximize"))); maximize.setOnAction((ActionEvent t) -> { switchMaximize(); }); } if (fullscreen != null) { // Utility Stage fullscreen.setTooltip(new Tooltip(LOC.getString("FullScreen"))); fullscreen.setOnAction((ActionEvent t) -> { switchFullscreen(); }); } // Minimize button if (minimize != null) { // Utility Stage minimize.setTooltip(new Tooltip(LOC.getString("Minimize"))); minimize.setOnAction((ActionEvent t) -> { switchMinimize(); }); } // Transfer stage title to undecorator tiltle label stage.titleProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { title.setText(stage.getTitle()); } }); title.setText(stage.getTitle()); } public void switchFullscreen() { // Invoke runLater even if it's on EDT: Crash apps on Mac Platform.runLater(() -> { undecoratorController.setFullScreen(!stage.isFullScreen()); }); } public void switchMinimize() { minimizeProperty().set(!minimizeProperty().get()); } public void switchMaximize() { maximizeProperty().set(!maximizeProperty().get()); } public void switchClose() { closeProperty().set(!closeProperty().get()); } /** * Bridge to the controller to enable the specified node to drag the stage * * @param stage * @param node */ public void setAsStageDraggable(Stage stage, Node node) { undecoratorController.setAsStageDraggable(stage, node); } /** * Switch the visibility of the window's drop shadow * * @param shadow */ protected void setShadow(boolean shadow) { // Already removed? if (!shadow && shadowRectangle.getEffect() == null) { return; } // From fullscreen to maximize case if (shadow && maximizeProperty.get()) { return; } if (!shadow) { shadowRectangle.setEffect(null); SAVED_SHADOW_WIDTH = SHADOW_WIDTH; SHADOW_WIDTH = 0; } else { shadowRectangle.setEffect(dsFocused); SHADOW_WIDTH = SAVED_SHADOW_WIDTH; } } /** * Set on/off the stage shadow effect * * @param b */ protected void setShadowFocused(boolean b) { // Do not change anything while maximized (in case of dialog closing for instance) if (stage.isFullScreen()) { return; } if (maximizeProperty().get()) { return; } if (b) { shadowRectangle.setEffect(dsFocused); } else { shadowRectangle.setEffect(dsNotFocused); } } /** * Set the layout of different layers of the stage */ @Override public void layoutChildren() { Bounds b = super.getLayoutBounds(); double w = b.getWidth(); double h = b.getHeight(); ObservableList<Node> list = super.getChildren(); // ROUNDED_DELTA=shadowRectangle.getArcWidth()/4; ROUNDED_DELTA = 0; for (Node node : list) { if (node == shadowRectangle) { shadowRectangle.setWidth(w - SHADOW_WIDTH * 2); shadowRectangle.setHeight(h - SHADOW_WIDTH * 2); shadowRectangle.setX(SHADOW_WIDTH); shadowRectangle.setY(SHADOW_WIDTH); } else if (node == backgroundRect) { backgroundRect.setWidth(w - SHADOW_WIDTH * 2); backgroundRect.setHeight(h - SHADOW_WIDTH * 2); backgroundRect.setX(SHADOW_WIDTH); backgroundRect.setY(SHADOW_WIDTH); } else if (node == stageDecoration) { stageDecoration.resize(w - SHADOW_WIDTH * 2 - ROUNDED_DELTA * 2, h - SHADOW_WIDTH * 2 - ROUNDED_DELTA * 2); stageDecoration.setLayoutX(SHADOW_WIDTH + ROUNDED_DELTA); stageDecoration.setLayoutY(SHADOW_WIDTH + ROUNDED_DELTA); } // else if (node == resizeRect) { // resizeRect.setWidth(w - SHADOW_WIDTH * 2); // resizeRect.setHeight(h - SHADOW_WIDTH * 2); // resizeRect.setLayoutX(SHADOW_WIDTH); // resizeRect.setLayoutY(SHADOW_WIDTH); // } else { node.resize(w - SHADOW_WIDTH * 2 - ROUNDED_DELTA * 2, h - SHADOW_WIDTH * 2 - ROUNDED_DELTA * 2); node.setLayoutX(SHADOW_WIDTH + ROUNDED_DELTA); node.setLayoutY(SHADOW_WIDTH + ROUNDED_DELTA); // node.resize(w - SHADOW_WIDTH * 2 - RESIZE_PADDING * 2, h - SHADOW_WIDTH * 2 - RESIZE_PADDING * 2); // node.setLayoutX(SHADOW_WIDTH + RESIZE_PADDING); // node.setLayoutY(SHADOW_WIDTH + RESIZE_PADDING); } } } public int getShadowBorderSize() { return SHADOW_WIDTH * 2 + RESIZE_PADDING * 2; } public UndecoratorController getController() { return undecoratorController; } public Stage getStage() { return stage; } protected Pane getGlassPane() { return glassPane; } public void addGlassPane(Node node) { glassPane.getChildren().add(node); } public void removeGlassPane(Node node) { glassPane.getChildren().remove(node); } /** * Returns the decoration (buttons...) * * @return */ public Pane getStageDecorationNode() { return stageDecoration; } /** * Prepare Stage for dock feedback display */ void buildDockFeedbackStage() { dockFeedbackPopup = new Stage(StageStyle.TRANSPARENT); dockFeedback = new Rectangle(0, 0, 100, 100); dockFeedback.setArcHeight(10); dockFeedback.setArcWidth(10); dockFeedback.setFill(Color.TRANSPARENT); dockFeedback.setStroke(Color.BLACK); dockFeedback.setStrokeWidth(2); dockFeedback.setCache(true); dockFeedback.setCacheHint(CacheHint.SPEED); dockFeedback.setEffect(new DropShadow(BlurType.TWO_PASS_BOX, Color.BLACK, 10, 0.2, 3, 3)); dockFeedback.setMouseTransparent(true); BorderPane borderpane = new BorderPane(); borderpane.setStyle("-fx-background-color:transparent"); //J8 borderpane.setCenter(dockFeedback); Scene scene = new Scene(borderpane); scene.setFill(Color.TRANSPARENT); dockFeedbackPopup.setScene(scene); dockFeedbackPopup.sizeToScene(); } /** * Activate dock feedback on screen's bounds * * @param x * @param y * @param width * @param height */ public void setDockFeedbackVisible(double x, double y, double width, double height) { dockFeedbackPopup.setX(x); dockFeedbackPopup.setY(y); dockFeedback.setX(SHADOW_WIDTH); dockFeedback.setY(SHADOW_WIDTH); dockFeedback.setHeight(height - SHADOW_WIDTH * 2); dockFeedback.setWidth(width - SHADOW_WIDTH * 2); dockFeedbackPopup.setWidth(width); dockFeedbackPopup.setHeight(height); dockFeedback.setOpacity(1); dockFeedbackPopup.show(); dockFadeTransition = new FadeTransition(); dockFadeTransition.setDuration(Duration.millis(200)); dockFadeTransition.setNode(dockFeedback); dockFadeTransition.setFromValue(0); dockFadeTransition.setToValue(1); dockFadeTransition.setAutoReverse(true); dockFadeTransition.setCycleCount(3); dockFadeTransition.play(); } public void setDockFeedbackInvisible() { if (dockFeedbackPopup.isShowing()) { dockFeedbackPopup.hide(); if (dockFadeTransition != null) { dockFadeTransition.stop(); } } } void loadConfig() { Properties prop = new Properties(); try { prop.load(Undecorator.class.getClassLoader().getResourceAsStream("skin/undecorator.properties")); SHADOW_WIDTH = Integer.parseInt(prop.getProperty("window-shadow-width")); RESIZE_PADDING = Integer.parseInt(prop.getProperty("window-resize-padding")); } catch (Exception ex) { LOGGER.log(Level.SEVERE, "Error while loading confguration flie", ex); } } }