() {
- @Override
- public String getName() {
- return "graphic";
- }
- };
-
- public final Node getGraphic() {
- return graphicProperty.get();
- }
-
- public final void setGraphic(Node graphic) {
- this.graphicProperty.setValue(graphic);
- }
-
- /**
- * Boolean property maintaining bidirectional state of the caption title for this node with the
- * dock title bar or stage.
- *
- * @defaultValue "Dock"
- */
- public final StringProperty titleProperty() {
- return titleProperty;
- }
-
- private StringProperty titleProperty = new SimpleStringProperty("Dock") {
- @Override
- public String getName() {
- return "title";
- }
- };
-
- public final String getTitle() {
- return titleProperty.get();
- }
-
- public final void setTitle(String title) {
- this.titleProperty.setValue(title);
- }
-
- /**
- * Boolean property maintaining whether this node is currently using a custom title bar. This can
- * be used to force the default title bar to show when the dock node is set to floating instead of
- * using native window borders.
- *
- * @defaultValue true
- */
- public final BooleanProperty customTitleBarProperty() {
- return customTitleBarProperty;
- }
-
- private BooleanProperty customTitleBarProperty = new SimpleBooleanProperty(true) {
- @Override
- public String getName() {
- return "customTitleBar";
+ /**
+ * Changes the title bar in the layout of this dock node. This can be used to remove the dock
+ * title bar from the dock node by passing null.
+ *
+ * @param dockTitleBar null The new title bar of this dock node, can be set null indicating no
+ * title bar is used.
+ */
+ public void setDockTitleBar(DockTitleBar dockTitleBar) {
+ if (dockTitleBar != null) {
+ if (getDockTitleBar() != null) {
+ this.getChildren().set(this.getChildren().indexOf(getDockTitleBar()), dockTitleBar);
+ } else {
+ this.getChildren().add(0, dockTitleBar);
+ }
+ } else {
+ this.getChildren().remove(getDockTitleBar());
+ }
+
+ this.dockTitleBar = dockTitleBar;
}
- };
- public final boolean isCustomTitleBar() {
- return customTitleBarProperty.get();
- }
+ /**
+ * The stage associated with this dock node. Can be null if the dock node was never set to
+ * floating.
+ *
+ * @return The stage associated with this node.
+ */
+ public final Stage getStage() {
+ return stage;
+ }
- public final void setUseCustomTitleBar(boolean useCustomTitleBar) {
- if (this.isFloating()) {
- dockTitleBar.setVisible(useCustomTitleBar);
- dockTitleBar.setManaged(useCustomTitleBar);
+ /**
+ * The border pane used to parent this dock node when floating. Can be null if the dock node was
+ * never set to floating.
+ *
+ * @return The stage associated with this node.
+ */
+ public final BorderPane getBorderPane() {
+ return borderPane;
}
- this.customTitleBarProperty.set(useCustomTitleBar);
- }
- /**
- * Boolean property maintaining whether this node is currently floating.
- *
- * @defaultValue false
- */
- public final BooleanProperty floatingProperty() {
- return floatingProperty;
- }
+ /**
+ * The contents managed by this dock node.
+ *
+ * @return The contents managed by this dock node.
+ */
+ public final Node getContents() {
+ return contents;
+ }
- private BooleanProperty floatingProperty = new SimpleBooleanProperty(false) {
- @Override
- protected void invalidated() {
- DockNode.this.pseudoClassStateChanged(FLOATING_PSEUDO_CLASS, get());
- if (borderPane != null) {
- borderPane.pseudoClassStateChanged(FLOATING_PSEUDO_CLASS, get());
- }
+ /**
+ * Changes the contents of the dock node.
+ *
+ * @param contents The new contents of this dock node.
+ */
+ public void setContents(Node contents) {
+ if (getChildren() != null && getChildren().indexOf(this.contents) > 0)
+ getChildren().set(getChildren().indexOf(this.contents), contents);
+ this.contents = contents;
+ initMe();
}
- @Override
- public String getName() {
- return "floating";
+ /**
+ * Object property maintaining bidirectional state of the caption graphic for this node with the
+ * dock title bar or stage.
+ *
+ * {@code @defaultValue} null
+ */
+ public final ObjectProperty graphicProperty() {
+ return graphicProperty;
}
- };
- public final boolean isFloating() {
- return floatingProperty.get();
- }
+ public final Node getGraphic() {
+ return graphicProperty.get();
+ }
- /**
- * Boolean property maintaining whether this node is currently floatable.
- *
- * @defaultValue true
- */
- public final BooleanProperty floatableProperty() {
- return floatableProperty;
- }
+ public final void setGraphic(Node graphic) {
+ this.graphicProperty.setValue(graphic);
+ initMe();
+ }
- private BooleanProperty floatableProperty = new SimpleBooleanProperty(true) {
- @Override
- public String getName() {
- return "floatable";
+ /**
+ * Boolean property maintaining bidirectional state of the caption title for this node with the
+ * dock title bar or stage.
+ *
+ * @defaultValue "Dock"
+ */
+ public final StringProperty titleProperty() {
+ return titleProperty;
}
- };
- public final boolean isFloatable() {
- return floatableProperty.get();
- }
+ public final String getTitle() {
+ return titleProperty.get();
+ }
- public final void setFloatable(boolean floatable) {
- if (!floatable && this.isFloating()) {
- this.setFloating(false);
+ public final void setTitle(String title) {
+ this.titleProperty.setValue(title);
+ initMe();
}
- this.floatableProperty.set(floatable);
- }
- /**
- * Boolean property maintaining whether this node is currently closable.
- *
- * @defaultValue true
- */
- public final BooleanProperty closableProperty() {
- return closableProperty;
- }
+ /**
+ * Boolean property maintaining whether this node is currently using a custom title bar. This can
+ * be used to force the default title bar to show when the dock node is set to floating instead of
+ * using native window borders.
+ *
+ * @defaultValue true
+ */
+ public final BooleanProperty customTitleBarProperty() {
+ return customTitleBarProperty;
+ }
- private BooleanProperty closableProperty = new SimpleBooleanProperty(true) {
- @Override
- public String getName() {
- return "closable";
+ public final boolean isCustomTitleBar() {
+ return customTitleBarProperty.get();
+ }
+
+ public final void setUseCustomTitleBar(boolean useCustomTitleBar) {
+ if (this.isFloating()) {
+ getDockTitleBar().setVisible(useCustomTitleBar);
+ getDockTitleBar().setManaged(useCustomTitleBar);
+ }
+ this.customTitleBarProperty.set(useCustomTitleBar);
+ }
+
+ /**
+ * Boolean property maintaining whether this node is currently floating.
+ *
+ * @defaultValue false
+ */
+ public final BooleanProperty floatingProperty() {
+ return floatingProperty;
}
- };
- public final boolean isClosable() {
- return closableProperty.get();
- }
+ public final boolean isFloating() {
+ return floatingProperty.get();
+ }
- public final void setClosable(boolean closable) {
- this.closableProperty.set(closable);
- }
+ /**
+ * Whether the node is currently floating.
+ *
+ * @param floating Whether the node is currently floating.
+ */
+ public void setFloating(boolean floating) {
+ setFloating(floating, null);
+ }
- /**
- * Boolean property maintaining whether this node is currently resizable.
- *
- * @defaultValue true
- */
- public final BooleanProperty resizableProperty() {
- return stageResizableProperty;
- }
+ /**
+ * Boolean property maintaining whether this node is currently floatable.
+ *
+ * {@code @defaultValue} true
+ */
+ public final BooleanProperty floatableProperty() {
+ return floatableProperty;
+ }
- private BooleanProperty stageResizableProperty = new SimpleBooleanProperty(true) {
- @Override
- public String getName() {
- return "resizable";
- }
- };
-
- public final boolean isStageResizable() {
- return stageResizableProperty.get();
- }
-
- public final void setStageResizable(boolean resizable) {
- stageResizableProperty.set(resizable);
- }
-
- /**
- * Boolean property maintaining whether this node is currently docked. This is used by the dock
- * pane to inform the dock node whether it is currently docked.
- *
- * @defaultValue false
- */
- public final BooleanProperty dockedProperty() {
- return dockedProperty;
- }
-
- private BooleanProperty dockedProperty = new SimpleBooleanProperty(false) {
- @Override
- protected void invalidated() {
- if (get()) {
- if (dockTitleBar != null) {
- dockTitleBar.setVisible(true);
- dockTitleBar.setManaged(true);
+ public final boolean isFloatable() {
+ return floatableProperty.get();
+ }
+
+ public final void setFloatable(boolean floatable) {
+ if (!floatable && this.isFloating()) {
+ this.setFloating(false);
}
- }
+ this.floatableProperty.set(floatable);
+ }
+
+ /**
+ * Boolean property maintaining whether this node is currently closable.
+ *
+ * @defaultValue true
+ */
+ public final BooleanProperty closableProperty() {
+ return closableProperty;
+ }
+
+ public final boolean isClosable() {
+ return closableProperty.get();
+ }
- DockNode.this.pseudoClassStateChanged(DOCKED_PSEUDO_CLASS, get());
+ public final void setClosable(boolean closable) {
+ this.closableProperty.set(closable);
+ }
+
+ /**
+ * Boolean property maintaining whether this node is currently resizable.
+ *
+ * @defaultValue true
+ */
+ public final BooleanProperty resizableProperty() {
+ return stageResizableProperty;
+ }
+
+ public final boolean isStageResizable() {
+ return stageResizableProperty.get();
+ }
+
+ public final void setStageResizable(boolean resizable) {
+ stageResizableProperty.set(resizable);
+ }
+
+ /**
+ * Boolean property maintaining whether this node is currently docked. This is used by the dock
+ * pane to inform the dock node whether it is currently docked.
+ *
+ * @defaultValue false
+ */
+ public final BooleanProperty dockedProperty() {
+ return dockedProperty;
+ }
+
+ public final boolean isDocked() {
+ return dockedProperty.get();
+ }
+
+ public final BooleanProperty maximizedProperty() {
+ return maximizedProperty;
+ }
+
+ public final boolean isMaximized() {
+ return maximizedProperty.get();
+ }
+
+ /**
+ * Whether the node is currently maximized.
+ *
+ * @param maximized Whether the node is currently maximized.
+ */
+ public final void setMaximized(boolean maximized) {
+ maximizedProperty.set(maximized);
+ }
+
+ public final boolean isDecorated() {
+ return stageStyle != StageStyle.TRANSPARENT && stageStyle != StageStyle.UNDECORATED;
+ }
+
+ /**
+ * Dock this node into a dock pane.
+ *
+ * @param dockPane The dock pane to dock this node into.
+ * @param dockPosition The docking position relative to the sibling of the dock pane.
+ * @param sibling The sibling node to dock this node relative to.
+ */
+ void dock(DockPane dockPane, DockPosition dockPosition, Node sibling) {
+ setDockPane(dockPane);
+ setDockPosition(dockPosition);
+ dockPane.dock(this, getDockPosition(), sibling);
+ }
+
+ public DockPosition getDockPosition() {
+ return dockPosition;
+ }
+
+ /**
+ * Dock this node into a dock pane.
+ *
+ * @param dockPane The dock pane to dock this node into.
+ * @param dockPosition The docking position relative to the sibling of the dock pane.
+ */
+ void dock(DockPane dockPane, DockPosition dockPosition) {
+ dockPane.dock(this, getDockPosition());
+ }
+
+ public void setDockPosition(DockPosition dockPos) {
+ this.dockPosition = dockPos;
+ initMe();
+ }
+
+ public void setDockPane(DockPane dockPane) {
+ if (isFloating()) {
+ setFloating(false);
+ }
+ this.dockPane = dockPane;
+ this.dockedProperty.set(true);
+ initMe();
+ }
+
+ private void initMe() {
+ if (!initializedProperty.get()
+ && dockPane != null
+ && dockPosition != null
+ && contents != null) {
+ initializedProperty.set(true);
+ initializeDockNode(contents, titleProperty.get(), graphicProperty.get());
+ }
+ }
+
+ /**
+ * Detach this node from its previous dock pane if it was previously docked.
+ */
+ public void undock() {
+ if (dockPane != null) {
+ dockPane.undock(this);
+ }
+ this.dockedProperty.set(false);
+ }
+
+ /**
+ * Close this dock node by setting it to not floating and making sure it is detached from any dock
+ * pane.
+ */
+ public void close() {
+ if (isFloating()) {
+ setFloating(false);
+ } else if (isDocked()) {
+ undock();
+ }
+ }
+
+ /**
+ * Gets whether the mouse is currently in this dock node's resize zone.
+ *
+ * @return Whether the mouse is currently in this dock node's resize zone.
+ */
+ public boolean isMouseResizeZone() {
+ return sizeWest || sizeEast || sizeNorth || sizeSouth;
}
@Override
- public String getName() {
- return "docked";
- }
- };
-
- public final boolean isDocked() {
- return dockedProperty.get();
- }
-
- public final BooleanProperty maximizedProperty() {
- return maximizedProperty;
- }
-
- public final boolean isMaximized() {
- return maximizedProperty.get();
- }
-
- public final boolean isDecorated() {
- return stageStyle != StageStyle.TRANSPARENT && stageStyle != StageStyle.UNDECORATED;
- }
-
- /**
- * Dock this node into a dock pane.
- *
- * @param dockPane The dock pane to dock this node into.
- * @param dockPos The docking position relative to the sibling of the dock pane.
- * @param sibling The sibling node to dock this node relative to.
- */
- public void dock(DockPane dockPane, DockPos dockPos, Node sibling) {
- dockImpl(dockPane);
- dockPane.dock(this, dockPos, sibling);
- }
-
- /**
- * Dock this node into a dock pane.
- *
- * @param dockPane The dock pane to dock this node into.
- * @param dockPos The docking position relative to the sibling of the dock pane.
- */
- public void dock(DockPane dockPane, DockPos dockPos) {
- dockImpl(dockPane);
- dockPane.dock(this, dockPos);
- }
-
- private final void dockImpl(DockPane dockPane) {
- if (isFloating()) {
- setFloating(false);
- }
- this.dockPane = dockPane;
- this.dockedProperty.set(true);
- }
-
- /**
- * Detach this node from its previous dock pane if it was previously docked.
- */
- public void undock() {
- if (dockPane != null) {
- dockPane.undock(this);
- }
- this.dockedProperty.set(false);
- }
-
- /**
- * Close this dock node by setting it to not floating and making sure it is detached from any dock
- * pane.
- */
- public void close() {
- if (isFloating()) {
- setFloating(false);
- } else if (isDocked()) {
- undock();
- }
- }
-
- /**
- * The last position of the mouse that was within the minimum layout bounds.
- */
- private Point2D sizeLast;
- /**
- * Whether we are currently resizing in a given direction.
- */
- private boolean sizeWest = false, sizeEast = false, sizeNorth = false, sizeSouth = false;
-
- /**
- * Gets whether the mouse is currently in this dock node's resize zone.
- *
- * @return Whether the mouse is currently in this dock node's resize zone.
- */
- public boolean isMouseResizeZone() {
- return sizeWest || sizeEast || sizeNorth || sizeSouth;
- }
-
- @Override
- public void handle(MouseEvent event) {
- Cursor cursor = Cursor.DEFAULT;
-
- // TODO: use escape to cancel resize/drag operation like visual studio
- if (!this.isFloating() || !this.isStageResizable()) {
- return;
- }
-
- if (event.getEventType() == MouseEvent.MOUSE_PRESSED) {
- sizeLast = new Point2D(event.getScreenX(), event.getScreenY());
- } else if (event.getEventType() == MouseEvent.MOUSE_MOVED) {
- Insets insets = borderPane.getPadding();
-
- sizeWest = event.getX() < insets.getLeft();
- sizeEast = event.getX() > borderPane.getWidth() - insets.getRight();
- sizeNorth = event.getY() < insets.getTop();
- sizeSouth = event.getY() > borderPane.getHeight() - insets.getBottom();
-
- if (sizeWest) {
- if (sizeNorth) {
- cursor = Cursor.NW_RESIZE;
- } else if (sizeSouth) {
- cursor = Cursor.SW_RESIZE;
- } else {
- cursor = Cursor.W_RESIZE;
+ public void handle(MouseEvent event) {
+ Cursor cursor = Cursor.DEFAULT;
+
+ // TODO: use escape to cancel resize/drag operation like visual studio
+ if (!this.isFloating() || !this.isStageResizable()) {
+ return;
}
- } else if (sizeEast) {
- if (sizeNorth) {
- cursor = Cursor.NE_RESIZE;
- } else if (sizeSouth) {
- cursor = Cursor.SE_RESIZE;
+
+ if (event.getEventType() == MouseEvent.MOUSE_PRESSED) {
+ sizeLast = new Point2D(event.getScreenX(), event.getScreenY());
+ } else if (event.getEventType() == MouseEvent.MOUSE_MOVED) {
+ Insets insets = borderPane.getPadding();
+
+ sizeWest = event.getX() < insets.getLeft();
+ sizeEast = event.getX() > borderPane.getWidth() - insets.getRight();
+ sizeNorth = event.getY() < insets.getTop();
+ sizeSouth = event.getY() > borderPane.getHeight() - insets.getBottom();
+
+ if (sizeWest) {
+ if (sizeNorth) {
+ cursor = Cursor.NW_RESIZE;
+ } else if (sizeSouth) {
+ cursor = Cursor.SW_RESIZE;
+ } else {
+ cursor = Cursor.W_RESIZE;
+ }
+ } else if (sizeEast) {
+ if (sizeNorth) {
+ cursor = Cursor.NE_RESIZE;
+ } else if (sizeSouth) {
+ cursor = Cursor.SE_RESIZE;
+ } else {
+ cursor = Cursor.E_RESIZE;
+ }
+ } else if (sizeNorth) {
+ cursor = Cursor.N_RESIZE;
+ } else if (sizeSouth) {
+ cursor = Cursor.S_RESIZE;
+ }
+
+ this.getScene().setCursor(cursor);
+ } else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED && this.isMouseResizeZone()) {
+ Point2D sizeCurrent = new Point2D(event.getScreenX(), event.getScreenY());
+ Point2D sizeDelta = sizeCurrent.subtract(sizeLast);
+
+ double newX = stage.getX(), newY = stage.getY(), newWidth = stage.getWidth(),
+ newHeight = stage.getHeight();
+
+ if (sizeNorth) {
+ newHeight -= sizeDelta.getY();
+ newY += sizeDelta.getY();
+ } else if (sizeSouth) {
+ newHeight += sizeDelta.getY();
+ }
+
+ if (sizeWest) {
+ newWidth -= sizeDelta.getX();
+ newX += sizeDelta.getX();
+ } else if (sizeEast) {
+ newWidth += sizeDelta.getX();
+ }
+
+ // TODO: find a way to do this synchronously and eliminate the flickering of moving the stage
+ // around, also file a bug report for this feature if a work around can not be found this
+ // primarily occurs when dragging north/west but it also appears in native windows and Visual
+ // Studio, so not that big of a concern.
+ // Bug report filed:
+ // https://bugs.openjdk.java.net/browse/JDK-8133332
+ double currentX = sizeLast.getX(), currentY = sizeLast.getY();
+ if (newWidth >= stage.getMinWidth()) {
+ stage.setX(newX);
+ stage.setWidth(newWidth);
+ currentX = sizeCurrent.getX();
+ }
+
+ if (newHeight >= stage.getMinHeight()) {
+ stage.setY(newY);
+ stage.setHeight(newHeight);
+ currentY = sizeCurrent.getY();
+ }
+ sizeLast = new Point2D(currentX, currentY);
+ // we do not want the title bar getting these events
+ // while we are actively resizing
+ if (sizeNorth || sizeSouth || sizeWest || sizeEast) {
+ event.consume();
+ }
+ }
+ }
+
+ /**
+ * Sets DockNodes contents, title and title bar graphic
+ *
+ * @param contents The contents of the dock node which may be a tree or another scene graph node.
+ * @param title The caption title of this dock node which maintains bidirectional state with the
+ * title bar and stage.
+ * @param graphic The caption title of this dock node which maintains bidirectional state with the
+ * title bar and stage.
+ */
+ private void initializeDockNode(Node contents, String title, Node graphic) {
+ if (contents == null) {
+ log.warn("contents is null, can not draw without contents");
+ return;
+ }
+ if (dockPosition == null) {
+ log.warn("dockPosition is null, can not draw without dockPosition");
+ return;
+ }
+ if (dockPane == null) {
+ log.warn("dockPane is null, can not draw without dockPane");
+ return;
+ }
+ if (getDockTitleBar() == null) {
+ log.warn("dockTitleBar is null, this can not be moved title:{}", titleProperty);
+ }
+ this.titleProperty.setValue(title);
+ this.graphicProperty.setValue(graphic);
+ this.contents = contents;
+
+ if (!"Dock".equals(title)) {
+ dockTitleBar = new DockTitleBar(this);
} else {
- cursor = Cursor.E_RESIZE;
+ log.warn("title is default value, not creating new title bar. this is the main central window,{}", contents);
+ }
+ if (getDockTitleBar() != null)
+ getChildren().addAll(getDockTitleBar(), contents);
+ else
+ getChildren().add(contents);
+ VBox.setVgrow(contents, Priority.ALWAYS);
+
+ this.getStyleClass().add("dock-node");
+ dock(dockPane, dockPosition);
+ dockPane.initializeDefaultUserAgentStylesheet();
+ }
+
+ /**
+ * Loads Node from fxml file located at FXMLPath and returns it.
+ *
+ * @param FXMLPath Path to fxml file.
+ * @return Node loaded from fxml file or StackPane with Label with error message.
+ */
+ private FXMLLoader loadNode(String FXMLPath) {
+ FXMLLoader loader = new FXMLLoader();
+ try {
+ loader.load(DockNode.class.getResourceAsStream(FXMLPath));
+ } catch (Exception e) {
+ e.printStackTrace();
+ loader.setRoot(new StackPane(new Label("Could not load FXML file")));
}
- } else if (sizeNorth) {
- cursor = Cursor.N_RESIZE;
- } else if (sizeSouth) {
- cursor = Cursor.S_RESIZE;
- }
-
- this.getScene().setCursor(cursor);
- } else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED && this.isMouseResizeZone()) {
- Point2D sizeCurrent = new Point2D(event.getScreenX(), event.getScreenY());
- Point2D sizeDelta = sizeCurrent.subtract(sizeLast);
-
- double newX = stage.getX(), newY = stage.getY(), newWidth = stage.getWidth(),
- newHeight = stage.getHeight();
-
- if (sizeNorth) {
- newHeight -= sizeDelta.getY();
- newY += sizeDelta.getY();
- } else if (sizeSouth) {
- newHeight += sizeDelta.getY();
- }
-
- if (sizeWest) {
- newWidth -= sizeDelta.getX();
- newX += sizeDelta.getX();
- } else if (sizeEast) {
- newWidth += sizeDelta.getX();
- }
-
- // TODO: find a way to do this synchronously and eliminate the flickering of moving the stage
- // around, also file a bug report for this feature if a work around can not be found this
- // primarily occurs when dragging north/west but it also appears in native windows and Visual
- // Studio, so not that big of a concern.
- // Bug report filed:
- // https://bugs.openjdk.java.net/browse/JDK-8133332
- double currentX = sizeLast.getX(), currentY = sizeLast.getY();
- if (newWidth >= stage.getMinWidth()) {
- stage.setX(newX);
- stage.setWidth(newWidth);
- currentX = sizeCurrent.getX();
- }
-
- if (newHeight >= stage.getMinHeight()) {
- stage.setY(newY);
- stage.setHeight(newHeight);
- currentY = sizeCurrent.getY();
- }
- sizeLast = new Point2D(currentX, currentY);
- // we do not want the title bar getting these events
- // while we are actively resizing
- if (sizeNorth || sizeSouth || sizeWest || sizeEast) {
- event.consume();
- }
- }
- }
+ initMe();
+ return loader;
+ }
}
diff --git a/src/main/java/org/dockfx/DockPane.java b/src/main/java/org/dockfx/DockPane.java
index d70bc5a..447fc1f 100644
--- a/src/main/java/org/dockfx/DockPane.java
+++ b/src/main/java/org/dockfx/DockPane.java
@@ -1,24 +1,18 @@
/**
* @file DockPane.java
* @brief Class implementing a generic dock pane for the layout of dock nodes.
- *
* @section License
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
**/
package org.dockfx;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Stack;
-
-import com.sun.javafx.css.StyleManager;
-
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
+import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
@@ -37,596 +31,588 @@
import javafx.stage.Popup;
import javafx.util.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Stack;
+
/**
* Base class for a dock pane that provides the layout of the dock nodes. Stacking the dock nodes to
* the center in a TabPane will be added in a future release. For now the DockPane uses the relative
* sizes of the dock nodes and lays them out in a tree of SplitPanes.
- *
- * @since DockFX 0.1
+ *
+ * @since 0.0.1
*/
public class DockPane extends StackPane implements EventHandler {
- /**
- * Package-private internal list of all DockPanes for event mouse picking.
- */
- static List dockPanes = new ArrayList();
-
- /**
- * The current root node of this dock pane's layout.
- */
- private Node root;
-
- /**
- * Whether a DOCK_ENTER event has been received by this dock pane since the last DOCK_EXIT event
- * was received.
- */
- private boolean receivedEnter = false;
-
- /**
- * The current node in this dock pane that we may be dragging over.
- */
- private Node dockNodeDrag;
- /**
- * The docking area of the current dock indicator button if any is selected. This is either the
- * root or equal to dock node drag.
- */
- private Node dockAreaDrag;
- /**
- * The docking position of the current dock indicator button if any is selected.
- */
- private DockPos dockPosDrag;
-
- /**
- * The docking area shape with a dotted animated border on the indicator overlay popup.
- */
- private Rectangle dockAreaIndicator;
- /**
- * The timeline used to animate the borer of the docking area indicator shape. Because JavaFX has
- * no CSS styling for timelines/animations yet we will make this private and offer an accessor for
- * the user to programmatically modify the animation or disable it.
- */
- private Timeline dockAreaStrokeTimeline;
- /**
- * The popup used to display the root dock indicator buttons and the docking area indicator.
- */
- private Popup dockIndicatorOverlay;
-
- /**
- * The grid pane used to lay out the local dock indicator buttons. This is the grid used to lay
- * out the buttons in the circular indicator.
- */
- private GridPane dockPosIndicator;
- /**
- * The popup used to display the local dock indicator buttons. This allows these indicator buttons
- * to be displayed outside the window of this dock pane.
- */
- private Popup dockIndicatorPopup;
-
- /**
- * Base class for a dock indicator button that allows it to be displayed during a dock event and
- * continue to receive input.
- *
- * @since DockFX 0.1
- */
- public class DockPosButton extends Button {
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DockPane.class);
/**
- * Whether this dock indicator button is used for docking a node relative to the root of the
- * dock pane.
+ * Package-private internal list of all DockPanes for event mouse picking.
*/
- private boolean dockRoot = true;
+ static List dockPanes = new ArrayList<>();
+
/**
- * The docking position indicated by this button.
+ * The current root node of this dock pane's layout.
*/
- private DockPos dockPos = DockPos.CENTER;
+ private Node root;
/**
- * Creates a new dock indicator button.
+ * Whether a DOCK_ENTER event has been received by this dock pane since the last DOCK_EXIT event
+ * was received.
*/
- public DockPosButton(boolean dockRoot, DockPos dockPos) {
- super();
- this.dockRoot = dockRoot;
- this.dockPos = dockPos;
- }
+ private boolean receivedEnter = false;
/**
- * Whether this dock indicator button is used for docking a node relative to the root of the
- * dock pane.
- *
- * @param dockRoot Whether this indicator button is used for docking a node relative to the root
- * of the dock pane.
+ * The current node in this dock pane that we may be dragging over.
*/
- public final void setDockRoot(boolean dockRoot) {
- this.dockRoot = dockRoot;
- }
+ private Node dockNodeDrag;
+ /**
+ * The docking area of the current dock indicator button if any is selected. This is either the
+ * root or equal to dock node drag.
+ */
+ private Node dockAreaDrag;
+ /**
+ * The docking position of the current dock indicator button if any is selected.
+ */
+ private DockPosition dockPositionDrag;
/**
- * The docking position indicated by this button.
- *
- * @param dockPos The docking position indicated by this button.
+ * The docking area shape with a dotted animated border on the indicator overlay popup.
*/
- public final void setDockPos(DockPos dockPos) {
- this.dockPos = dockPos;
- }
+ private final Rectangle dockAreaIndicator;
+ /**
+ * The timeline used to animate the borer of the docking area indicator shape. Because JavaFX has
+ * no CSS styling for timelines/animations yet we will make this private and offer an accessor for
+ * the user to programmatically modify the animation or disable it.
+ */
+ private final Timeline dockAreaStrokeTimeline;
+ /**
+ * The popup used to display the root dock indicator buttons and the docking area indicator.
+ */
+ private final Popup dockIndicatorOverlay;
/**
- * The docking position indicated by this button.
- *
- * @return The docking position indicated by this button.
+ * The grid pane used to lay out the local dock indicator buttons. This is the grid used to lay
+ * out the buttons in the circular indicator.
*/
- public final DockPos getDockPos() {
- return dockPos;
- }
+ private final GridPane dockPosIndicator;
+ /**
+ * The popup used to display the local dock indicator buttons. This allows these indicator buttons
+ * to be displayed outside the window of this dock pane.
+ */
+ private final Popup dockIndicatorPopup;
+ /**
+ * A collection used to manage the indicator buttons and automate hit detection during DOCK_OVER
+ * events.
+ */
+ private final ObservableList dockPosButtons;
+ /**
+ * A cache of all dock node event handlers that we have created for tracking the current docking
+ * area.
+ */
+ private final ObservableMap dockNodeEventFilters =
+ FXCollections.observableHashMap();
/**
- * Whether this dock indicator button is used for docking a node relative to the root of the
- * dock pane.
- *
- * @return Whether this indicator button is used for docking a node relative to the root of the
- * dock pane.
+ * Creates a new DockPane adding event handlers for dock events and creating the indicator
+ * overlays.
*/
- public final boolean isDockRoot() {
- return dockRoot;
+ public DockPane() {
+ super();
+ DockPane.dockPanes.add(this);
+
+ this.addEventHandler(DockEvent.ANY, this);
+ this.addEventFilter(DockEvent.ANY, event -> {
+ if (event.getEventType() == DockEvent.DOCK_ENTER) {
+ DockPane.this.receivedEnter = true;
+ } else if (event.getEventType() == DockEvent.DOCK_OVER) {
+ DockPane.this.dockNodeDrag = null;
+ }
+ });
+
+ dockIndicatorPopup = new Popup();
+ dockIndicatorPopup.setAutoFix(false);
+
+ dockIndicatorOverlay = new Popup();
+ dockIndicatorOverlay.setAutoFix(false);
+
+ StackPane dockRootPane = new StackPane();
+ dockRootPane.prefWidthProperty().bind(this.widthProperty());
+ dockRootPane.prefHeightProperty().bind(this.heightProperty());
+
+ dockAreaIndicator = new Rectangle();
+ dockAreaIndicator.setManaged(false);
+ dockAreaIndicator.setMouseTransparent(true);
+
+ dockAreaStrokeTimeline = new Timeline();
+ dockAreaStrokeTimeline.setCycleCount(Timeline.INDEFINITE);
+ // 12 is the cumulative offset of the stroke dash array in the default.css style sheet
+ // RFE filed for CSS styled timelines/animations:
+ // https://bugs.openjdk.java.net/browse/JDK-8133837
+ KeyValue kv = new KeyValue(dockAreaIndicator.strokeDashOffsetProperty(), 12);
+ KeyFrame kf = new KeyFrame(Duration.millis(500), kv);
+ dockAreaStrokeTimeline.getKeyFrames().add(kf);
+ dockAreaStrokeTimeline.play();
+
+ DockPosButton dockCenter = new DockPosButton(false, DockPosition.CENTER);
+ dockCenter.getStyleClass().add("dock-center");
+
+ DockPosButton dockTop = new DockPosButton(false, DockPosition.TOP);
+ dockTop.getStyleClass().add("dock-top");
+ DockPosButton dockRight = new DockPosButton(false, DockPosition.RIGHT);
+ dockRight.getStyleClass().add("dock-right");
+ DockPosButton dockBottom = new DockPosButton(false, DockPosition.BOTTOM);
+ dockBottom.getStyleClass().add("dock-bottom");
+ DockPosButton dockLeft = new DockPosButton(false, DockPosition.LEFT);
+ dockLeft.getStyleClass().add("dock-left");
+
+ DockPosButton dockTopRoot = new DockPosButton(true, DockPosition.TOP);
+ StackPane.setAlignment(dockTopRoot, Pos.TOP_CENTER);
+ dockTopRoot.getStyleClass().add("dock-top-root");
+
+ DockPosButton dockRightRoot = new DockPosButton(true, DockPosition.RIGHT);
+ StackPane.setAlignment(dockRightRoot, Pos.CENTER_RIGHT);
+ dockRightRoot.getStyleClass().add("dock-right-root");
+
+ DockPosButton dockBottomRoot = new DockPosButton(true, DockPosition.BOTTOM);
+ StackPane.setAlignment(dockBottomRoot, Pos.BOTTOM_CENTER);
+ dockBottomRoot.getStyleClass().add("dock-bottom-root");
+
+ DockPosButton dockLeftRoot = new DockPosButton(true, DockPosition.LEFT);
+ StackPane.setAlignment(dockLeftRoot, Pos.CENTER_LEFT);
+ dockLeftRoot.getStyleClass().add("dock-left-root");
+
+ // TODO: dockCenter goes first when tabs are added in a future version
+ dockPosButtons = FXCollections.observableArrayList(dockTop, dockRight, dockBottom, dockLeft,
+ dockTopRoot, dockRightRoot, dockBottomRoot, dockLeftRoot);
+
+ dockPosIndicator = new GridPane();
+ dockPosIndicator.add(dockTop, 1, 0);
+ dockPosIndicator.add(dockRight, 2, 1);
+ dockPosIndicator.add(dockBottom, 1, 2);
+ dockPosIndicator.add(dockLeft, 0, 1);
+
+ dockRootPane.getChildren().addAll(dockAreaIndicator, dockTopRoot, dockRightRoot, dockBottomRoot,
+ dockLeftRoot);
+
+ dockIndicatorOverlay.getContent().add(dockRootPane);
+ dockIndicatorPopup.getContent().addAll(dockPosIndicator);
+
+ this.getStyleClass().add("dock-pane");
+ dockRootPane.getStyleClass().add("dock-root-pane");
+ dockPosIndicator.getStyleClass().add("dock-pos-indicator");
+ dockAreaIndicator.getStyleClass().add("dock-area-indicator");
}
- }
-
- /**
- * A collection used to manage the indicator buttons and automate hit detection during DOCK_OVER
- * events.
- */
- private ObservableList dockPosButtons;
-
- /**
- * Creates a new DockPane adding event handlers for dock events and creating the indicator
- * overlays.
- */
- public DockPane() {
- super();
- DockPane.dockPanes.add(this);
-
- this.addEventHandler(DockEvent.ANY, this);
- this.addEventFilter(DockEvent.ANY, new EventHandler() {
-
- @Override
- public void handle(DockEvent event) {
-
- if (event.getEventType() == DockEvent.DOCK_ENTER) {
- DockPane.this.receivedEnter = true;
- } else if (event.getEventType() == DockEvent.DOCK_OVER) {
- DockPane.this.dockNodeDrag = null;
- }
- }
-
- });
-
- dockIndicatorPopup = new Popup();
- dockIndicatorPopup.setAutoFix(false);
-
- dockIndicatorOverlay = new Popup();
- dockIndicatorOverlay.setAutoFix(false);
-
- StackPane dockRootPane = new StackPane();
- dockRootPane.prefWidthProperty().bind(this.widthProperty());
- dockRootPane.prefHeightProperty().bind(this.heightProperty());
-
- dockAreaIndicator = new Rectangle();
- dockAreaIndicator.setManaged(false);
- dockAreaIndicator.setMouseTransparent(true);
-
- dockAreaStrokeTimeline = new Timeline();
- dockAreaStrokeTimeline.setCycleCount(Timeline.INDEFINITE);
- // 12 is the cumulative offset of the stroke dash array in the default.css style sheet
- // RFE filed for CSS styled timelines/animations:
- // https://bugs.openjdk.java.net/browse/JDK-8133837
- KeyValue kv = new KeyValue(dockAreaIndicator.strokeDashOffsetProperty(), 12);
- KeyFrame kf = new KeyFrame(Duration.millis(500), kv);
- dockAreaStrokeTimeline.getKeyFrames().add(kf);
- dockAreaStrokeTimeline.play();
-
- DockPosButton dockCenter = new DockPosButton(false, DockPos.CENTER);
- dockCenter.getStyleClass().add("dock-center");
-
- DockPosButton dockTop = new DockPosButton(false, DockPos.TOP);
- dockTop.getStyleClass().add("dock-top");
- DockPosButton dockRight = new DockPosButton(false, DockPos.RIGHT);
- dockRight.getStyleClass().add("dock-right");
- DockPosButton dockBottom = new DockPosButton(false, DockPos.BOTTOM);
- dockBottom.getStyleClass().add("dock-bottom");
- DockPosButton dockLeft = new DockPosButton(false, DockPos.LEFT);
- dockLeft.getStyleClass().add("dock-left");
-
- DockPosButton dockTopRoot = new DockPosButton(true, DockPos.TOP);
- StackPane.setAlignment(dockTopRoot, Pos.TOP_CENTER);
- dockTopRoot.getStyleClass().add("dock-top-root");
-
- DockPosButton dockRightRoot = new DockPosButton(true, DockPos.RIGHT);
- StackPane.setAlignment(dockRightRoot, Pos.CENTER_RIGHT);
- dockRightRoot.getStyleClass().add("dock-right-root");
-
- DockPosButton dockBottomRoot = new DockPosButton(true, DockPos.BOTTOM);
- StackPane.setAlignment(dockBottomRoot, Pos.BOTTOM_CENTER);
- dockBottomRoot.getStyleClass().add("dock-bottom-root");
-
- DockPosButton dockLeftRoot = new DockPosButton(true, DockPos.LEFT);
- StackPane.setAlignment(dockLeftRoot, Pos.CENTER_LEFT);
- dockLeftRoot.getStyleClass().add("dock-left-root");
-
- // TODO: dockCenter goes first when tabs are added in a future version
- dockPosButtons = FXCollections.observableArrayList(dockTop, dockRight, dockBottom, dockLeft,
- dockTopRoot, dockRightRoot, dockBottomRoot, dockLeftRoot);
-
- dockPosIndicator = new GridPane();
- dockPosIndicator.add(dockTop, 1, 0);
- dockPosIndicator.add(dockRight, 2, 1);
- dockPosIndicator.add(dockBottom, 1, 2);
- dockPosIndicator.add(dockLeft, 0, 1);
- // dockPosIndicator.add(dockCenter, 1, 1);
-
- dockRootPane.getChildren().addAll(dockAreaIndicator, dockTopRoot, dockRightRoot, dockBottomRoot,
- dockLeftRoot);
-
- dockIndicatorOverlay.getContent().add(dockRootPane);
- dockIndicatorPopup.getContent().addAll(dockPosIndicator);
-
- this.getStyleClass().add("dock-pane");
- dockRootPane.getStyleClass().add("dock-root-pane");
- dockPosIndicator.getStyleClass().add("dock-pos-indicator");
- dockAreaIndicator.getStyleClass().add("dock-area-indicator");
- }
-
- /**
- * The Timeline used to animate the docking area indicator in the dock indicator overlay for this
- * dock pane.
- *
- * @return The Timeline used to animate the docking area indicator in the dock indicator overlay
- * for this dock pane.
- */
- public final Timeline getDockAreaStrokeTimeline() {
- return dockAreaStrokeTimeline;
- }
-
- /**
- * Helper function to retrieve the URL of the default style sheet used by DockFX.
- *
- * @return The URL of the default style sheet used by DockFX.
- */
- public final static String getDefaultUserAgentStyleheet() {
- return DockPane.class.getResource("default.css").toExternalForm();
- }
-
- /**
- * Helper function to add the default style sheet of DockFX to the user agent style sheets.
- */
- public final static void initializeDefaultUserAgentStylesheet() {
- StyleManager.getInstance()
- .addUserAgentStylesheet(DockPane.class.getResource("default.css").toExternalForm());
- }
-
- /**
- * A cache of all dock node event handlers that we have created for tracking the current docking
- * area.
- */
- private ObservableMap dockNodeEventFilters =
- FXCollections.observableHashMap();
-
- /**
- * A wrapper to the type parameterized generic EventHandler that allows us to remove it from its
- * listener when the dock node becomes detached. It is specifically used to monitor which dock
- * node in this dock pane's layout we are currently dragging over.
- *
- * @since DockFX 0.1
- */
- private class DockNodeEventHandler implements EventHandler {
/**
- * The node associated with this event handler that reports to the encapsulating dock pane.
+ * Helper function to retrieve the URL of the default style sheet used by DockFX.
+ *
+ * @return The URL of the default style sheet used by DockFX.
*/
- private Node node = null;
+ public String getDefaultUserAgentStylesheet() {
+ return Objects.requireNonNull(DockPane.class.getResource("default.css")).toExternalForm();
+ }
/**
- * Creates a default dock node event handler that will help this dock pane track the current
- * docking area.
- *
- * @param node The node that is to listen for docking events and report to the encapsulating
- * docking pane.
+ * Helper function to add the default style sheet of DockFX to the user agent style sheets.
*/
- public DockNodeEventHandler(Node node) {
- this.node = node;
+ public void initializeDefaultUserAgentStylesheet() {
+ Platform.runLater(() -> {
+ if (getScene() != null && !getScene().getStylesheets().contains(getDefaultUserAgentStylesheet()))
+ getScene().getStylesheets().add(getDefaultUserAgentStylesheet());
+ });
}
- @Override
- public void handle(DockEvent event) {
- DockPane.this.dockNodeDrag = node;
- }
- }
-
- /**
- * Dock the node into this dock pane at the given docking position relative to the sibling in the
- * layout. This is used to relatively position the dock nodes to other nodes given their preferred
- * size.
- *
- * @param node The node that is to be docked into this dock pane.
- * @param dockPos The docking position of the node relative to the sibling.
- * @param sibling The sibling of this node in the layout.
- */
- public void dock(Node node, DockPos dockPos, Node sibling) {
- DockNodeEventHandler dockNodeEventHandler = new DockNodeEventHandler(node);
- dockNodeEventFilters.put(node, dockNodeEventHandler);
- node.addEventFilter(DockEvent.DOCK_OVER, dockNodeEventHandler);
-
- SplitPane split = (SplitPane) root;
- if (split == null) {
- split = new SplitPane();
- split.getItems().add(node);
- root = split;
- this.getChildren().add(root);
- return;
+ /**
+ * The Timeline used to animate the docking area indicator in the dock indicator overlay for this
+ * dock pane.
+ *
+ * @return The Timeline used to animate the docking area indicator in the dock indicator overlay
+ * for this dock pane.
+ */
+ public final Timeline getDockAreaStrokeTimeline() {
+ return dockAreaStrokeTimeline;
}
- // find the parent of the sibling
- if (sibling != null && sibling != root) {
- Stack stack = new Stack();
- stack.push((Parent) root);
- while (!stack.isEmpty()) {
- Parent parent = stack.pop();
+ /**
+ * Dock the node into this dock pane at the given docking position relative to the sibling in the
+ * layout. This is used to relatively position the dock nodes to other nodes given their preferred
+ * size.
+ *
+ * @param node The node that is to be docked into this dock pane.
+ * @param dockPosition The docking position of the node relative to the sibling.
+ * @param sibling The sibling of this node in the layout.
+ */
+ public void dock(Node node, DockPosition dockPosition, Node sibling) {
+ DockNodeEventHandler dockNodeEventHandler = new DockNodeEventHandler(node);
+ dockNodeEventFilters.put(node, dockNodeEventHandler);
+ node.addEventFilter(DockEvent.DOCK_OVER, dockNodeEventHandler);
+
+ SplitPane split = (SplitPane) root;
+ if (split == null) {
+ split = new SplitPane();
+ split.getItems().add(node);
+ root = split;
+ this.getChildren().add(root);
+ return;
+ }
+
+ // find the parent of the sibling
+ if (sibling != null && sibling != root) {
+ Stack stack = new Stack<>();
+ stack.push((Parent) root);
+ while (!stack.isEmpty()) {
+ Parent parent = stack.pop();
+
+ ObservableList children = parent.getChildrenUnmodifiable();
- ObservableList children = parent.getChildrenUnmodifiable();
+ if (parent instanceof SplitPane splitPane) {
+ children = splitPane.getItems();
+ }
- if (parent instanceof SplitPane) {
- SplitPane splitPane = (SplitPane) parent;
- children = splitPane.getItems();
+ for (int i = 0; i < children.size(); i++) {
+ if (children.get(i) == sibling) {
+ assert parent instanceof SplitPane;
+ split = (SplitPane) parent;
+ } else if (children.get(i) instanceof Parent) {
+ stack.push((Parent) children.get(i));
+ }
+ }
+ }
}
- for (int i = 0; i < children.size(); i++) {
- if (children.get(i) == sibling) {
- split = (SplitPane) parent;
- } else if (children.get(i) instanceof Parent) {
- stack.push((Parent) children.get(i));
- }
+ Orientation requestedOrientation = (dockPosition == DockPosition.LEFT || dockPosition == DockPosition.RIGHT)
+ ? Orientation.HORIZONTAL : Orientation.VERTICAL;
+
+ // if the orientation is different then reparent the split pane
+ if (split.getOrientation() != requestedOrientation) {
+ if (split.getItems().size() > 1) {
+ SplitPane splitPane = new SplitPane();
+ if (split == root && sibling == root) {
+ this.getChildren().set(this.getChildren().indexOf(root), splitPane);
+ splitPane.getItems().add(split);
+ root = splitPane;
+ } else {
+ split.getItems().set(split.getItems().indexOf(sibling), splitPane);
+ splitPane.getItems().add(sibling);
+ }
+
+ split = splitPane;
+ }
+ split.setOrientation(requestedOrientation);
}
- }
- }
- Orientation requestedOrientation = (dockPos == DockPos.LEFT || dockPos == DockPos.RIGHT)
- ? Orientation.HORIZONTAL : Orientation.VERTICAL;
-
- // if the orientation is different then reparent the split pane
- if (split.getOrientation() != requestedOrientation) {
- if (split.getItems().size() > 1) {
- SplitPane splitPane = new SplitPane();
- if (split == root && sibling == root) {
- this.getChildren().set(this.getChildren().indexOf(root), splitPane);
- splitPane.getItems().add(split);
- root = splitPane;
- } else {
- split.getItems().set(split.getItems().indexOf(sibling), splitPane);
- splitPane.getItems().add(sibling);
+ // finally dock the node to the correct split pane
+ ObservableList splitItems = split.getItems();
+
+ double magnitude = 0;
+
+ if (!splitItems.isEmpty()) {
+ if (split.getOrientation() == Orientation.HORIZONTAL) {
+ for (Node splitItem : splitItems) {
+ magnitude += splitItem.prefWidth(0);
+ }
+ } else {
+ for (Node splitItem : splitItems) {
+ magnitude += splitItem.prefHeight(0);
+ }
+ }
}
- split = splitPane;
- }
- split.setOrientation(requestedOrientation);
- }
+ if (dockPosition == DockPosition.LEFT || dockPosition == DockPosition.TOP) {
+ int relativeIndex = 0;
+ if (sibling != null && sibling != root) {
+ relativeIndex = splitItems.indexOf(sibling);
+ }
- // finally dock the node to the correct split pane
- ObservableList splitItems = split.getItems();
+ splitItems.add(relativeIndex, node);
- double magnitude = 0;
+ if (splitItems.size() > 1) {
+ if (split.getOrientation() == Orientation.HORIZONTAL) {
+ split.setDividerPosition(relativeIndex,
+ node.prefWidth(0) / (magnitude + node.prefWidth(0)));
+ } else {
+ split.setDividerPosition(relativeIndex,
+ node.prefHeight(0) / (magnitude + node.prefHeight(0)));
+ }
+ }
+ } else if (dockPosition == DockPosition.RIGHT || dockPosition == DockPosition.BOTTOM) {
+ int relativeIndex = splitItems.size();
+ if (sibling != null && sibling != root) {
+ relativeIndex = splitItems.indexOf(sibling) + 1;
+ }
- if (splitItems.size() > 0) {
- if (split.getOrientation() == Orientation.HORIZONTAL) {
- for (Node splitItem : splitItems) {
- magnitude += splitItem.prefWidth(0);
- }
- } else {
- for (Node splitItem : splitItems) {
- magnitude += splitItem.prefHeight(0);
+ splitItems.add(relativeIndex, node);
+ if (splitItems.size() > 1) {
+ if (split.getOrientation() == Orientation.HORIZONTAL) {
+ split.setDividerPosition(relativeIndex - 1,
+ 1 - node.prefWidth(0) / (magnitude + node.prefWidth(0)));
+ } else {
+ split.setDividerPosition(relativeIndex - 1,
+ 1 - node.prefHeight(0) / (magnitude + node.prefHeight(0)));
+ }
+ }
}
- }
}
- if (dockPos == DockPos.LEFT || dockPos == DockPos.TOP) {
- int relativeIndex = 0;
- if (sibling != null && sibling != root) {
- relativeIndex = splitItems.indexOf(sibling);
- }
-
- splitItems.add(relativeIndex, node);
-
- if (splitItems.size() > 1) {
- if (split.getOrientation() == Orientation.HORIZONTAL) {
- split.setDividerPosition(relativeIndex,
- node.prefWidth(0) / (magnitude + node.prefWidth(0)));
- } else {
- split.setDividerPosition(relativeIndex,
- node.prefHeight(0) / (magnitude + node.prefHeight(0)));
+ /**
+ * Dock the node into this dock pane at the given docking position relative to the root in the
+ * layout. This is used to relatively position the dock nodes to other nodes given their preferred
+ * size.
+ *
+ * @param node The node that is to be docked into this dock pane.
+ * @param dockPosition The docking position of the node relative to the sibling.
+ */
+ public void dock(Node node, DockPosition dockPosition) {
+ dock(node, dockPosition, root);
+ }
+
+ /**
+ * Detach the node from this dock pane removing it from the layout.
+ *
+ * @param node The node that is to be removed from this dock pane.
+ */
+ public void undock(DockNode node) {
+ DockNodeEventHandler dockNodeEventHandler = dockNodeEventFilters.get(node);
+ if (dockNodeEventHandler != null) {
+ node.removeEventFilter(DockEvent.DOCK_OVER, dockNodeEventHandler);
+ dockNodeEventFilters.remove(node);
}
- }
- } else if (dockPos == DockPos.RIGHT || dockPos == DockPos.BOTTOM) {
- int relativeIndex = splitItems.size();
- if (sibling != null && sibling != root) {
- relativeIndex = splitItems.indexOf(sibling) + 1;
- }
-
- splitItems.add(relativeIndex, node);
- if (splitItems.size() > 1) {
- if (split.getOrientation() == Orientation.HORIZONTAL) {
- split.setDividerPosition(relativeIndex - 1,
- 1 - node.prefWidth(0) / (magnitude + node.prefWidth(0)));
- } else {
- split.setDividerPosition(relativeIndex - 1,
- 1 - node.prefHeight(0) / (magnitude + node.prefHeight(0)));
+
+ // depth first search to find the parent of the node
+ Stack findStack = new Stack<>();
+ findStack.push((Parent) root);
+ while (!findStack.isEmpty()) {
+ Parent parent = findStack.pop();
+
+ ObservableList children = parent.getChildrenUnmodifiable();
+
+ if (parent instanceof SplitPane split) {
+ children = split.getItems();
+ }
+
+ for (int i = 0; i < children.size(); i++) {
+ if (children.get(i) == node) {
+ children.remove(i);
+
+ // start from the root again and remove any SplitPane's with no children in them
+ Stack clearStack = new Stack<>();
+ clearStack.push((Parent) root);
+ while (!clearStack.isEmpty()) {
+ parent = clearStack.pop();
+
+ children = parent.getChildrenUnmodifiable();
+
+ if (parent instanceof SplitPane split) {
+ children = split.getItems();
+ }
+
+ for (i = 0; i < children.size(); i++) {
+ if (children.get(i) instanceof SplitPane split) {
+ if (split.getItems().isEmpty()) {
+ children.remove(i);
+ } else {
+ clearStack.push(split);
+ }
+ }
+
+ }
+ }
+ return;
+ } else if (children.get(i) instanceof Parent) {
+ findStack.push((Parent) children.get(i));
+ }
+ }
}
- }
}
- }
-
- /**
- * Dock the node into this dock pane at the given docking position relative to the root in the
- * layout. This is used to relatively position the dock nodes to other nodes given their preferred
- * size.
- *
- * @param node The node that is to be docked into this dock pane.
- * @param dockPos The docking position of the node relative to the sibling.
- */
- public void dock(Node node, DockPos dockPos) {
- dock(node, dockPos, root);
- }
-
- /**
- * Detach the node from this dock pane removing it from the layout.
- *
- * @param node The node that is to be removed from this dock pane.
- */
- public void undock(DockNode node) {
- DockNodeEventHandler dockNodeEventHandler = dockNodeEventFilters.get(node);
- node.removeEventFilter(DockEvent.DOCK_OVER, dockNodeEventHandler);
- dockNodeEventFilters.remove(node);
-
- // depth first search to find the parent of the node
- Stack findStack = new Stack();
- findStack.push((Parent) root);
- while (!findStack.isEmpty()) {
- Parent parent = findStack.pop();
-
- ObservableList children = parent.getChildrenUnmodifiable();
-
- if (parent instanceof SplitPane) {
- SplitPane split = (SplitPane) parent;
- children = split.getItems();
- }
-
- for (int i = 0; i < children.size(); i++) {
- if (children.get(i) == node) {
- children.remove(i);
-
- // start from the root again and remove any SplitPane's with no children in them
- Stack clearStack = new Stack();
- clearStack.push((Parent) root);
- while (!clearStack.isEmpty()) {
- parent = clearStack.pop();
-
- children = parent.getChildrenUnmodifiable();
-
- if (parent instanceof SplitPane) {
- SplitPane split = (SplitPane) parent;
- children = split.getItems();
+ @Override
+ public void handle(DockEvent event) {
+ if (event.getEventType() == DockEvent.DOCK_ENTER) {
+ if (!dockIndicatorOverlay.isShowing()) {
+ Point2D topLeft = DockPane.this.localToScreen(0, 0);
+ dockIndicatorOverlay.show(DockPane.this, topLeft.getX(), topLeft.getY());
}
+ } else if (event.getEventType() == DockEvent.DOCK_OVER) {
+ this.receivedEnter = false;
+
+ dockPositionDrag = null;
+ dockAreaDrag = dockNodeDrag;
+
+ for (DockPosButton dockIndicatorButton : dockPosButtons) {
+ if (dockIndicatorButton.contains(dockIndicatorButton.screenToLocal(event.getScreenX(), event.getScreenY()))) {
+ dockPositionDrag = dockIndicatorButton.getDockPos();
+ if (dockIndicatorButton.isDockRoot()) {
+ dockAreaDrag = root;
+ }
+ dockIndicatorButton.pseudoClassStateChanged(PseudoClass.getPseudoClass("focused"), true);
+ break;
+ } else {
+ dockIndicatorButton.pseudoClassStateChanged(PseudoClass.getPseudoClass("focused"), false);
+ }
+ }
+
+ if (dockPositionDrag != null) {
+ Point2D originToScene = dockAreaDrag.localToScene(0, 0).subtract(this.localToScene(0, 0));
+
+ dockAreaIndicator.setVisible(true);
+ dockAreaIndicator.relocate(originToScene.getX(), originToScene.getY());
+ if (dockPositionDrag == DockPosition.RIGHT) {
+ dockAreaIndicator.setTranslateX(dockAreaDrag.getLayoutBounds().getWidth() / 2);
+ } else {
+ dockAreaIndicator.setTranslateX(0);
+ }
- for (i = 0; i < children.size(); i++) {
- if (children.get(i) instanceof SplitPane) {
- SplitPane split = (SplitPane) children.get(i);
- if (split.getItems().size() < 1) {
- children.remove(i);
- continue;
+ if (dockPositionDrag == DockPosition.BOTTOM) {
+ dockAreaIndicator.setTranslateY(dockAreaDrag.getLayoutBounds().getHeight() / 2);
} else {
- clearStack.push(split);
+ dockAreaIndicator.setTranslateY(0);
}
- }
+ if (dockPositionDrag == DockPosition.LEFT || dockPositionDrag == DockPosition.RIGHT) {
+ dockAreaIndicator.setWidth(dockAreaDrag.getLayoutBounds().getWidth() / 2);
+ } else {
+ dockAreaIndicator.setWidth(dockAreaDrag.getLayoutBounds().getWidth());
+ }
+ if (dockPositionDrag == DockPosition.TOP || dockPositionDrag == DockPosition.BOTTOM) {
+ dockAreaIndicator.setHeight(dockAreaDrag.getLayoutBounds().getHeight() / 2);
+ } else {
+ dockAreaIndicator.setHeight(dockAreaDrag.getLayoutBounds().getHeight());
+ }
+ } else {
+ dockAreaIndicator.setVisible(false);
}
- }
- return;
- } else if (children.get(i) instanceof Parent) {
- findStack.push((Parent) children.get(i));
- }
- }
- }
- }
-
- @Override
- public void handle(DockEvent event) {
- if (event.getEventType() == DockEvent.DOCK_ENTER) {
- if (!dockIndicatorOverlay.isShowing()) {
- Point2D topLeft = DockPane.this.localToScreen(0, 0);
- dockIndicatorOverlay.show(DockPane.this, topLeft.getX(), topLeft.getY());
- }
- } else if (event.getEventType() == DockEvent.DOCK_OVER) {
- this.receivedEnter = false;
-
- dockPosDrag = null;
- dockAreaDrag = dockNodeDrag;
-
- for (DockPosButton dockIndicatorButton : dockPosButtons) {
- if (dockIndicatorButton
- .contains(dockIndicatorButton.screenToLocal(event.getScreenX(), event.getScreenY()))) {
- dockPosDrag = dockIndicatorButton.getDockPos();
- if (dockIndicatorButton.isDockRoot()) {
- dockAreaDrag = root;
- }
- dockIndicatorButton.pseudoClassStateChanged(PseudoClass.getPseudoClass("focused"), true);
- break;
- } else {
- dockIndicatorButton.pseudoClassStateChanged(PseudoClass.getPseudoClass("focused"), false);
+ if (dockNodeDrag != null) {
+ Point2D originToScreen = dockNodeDrag.localToScreen(0, 0);
+
+ double posX = originToScreen.getX() + dockNodeDrag.getLayoutBounds().getWidth() / 2
+ - dockPosIndicator.getWidth() / 2;
+ double posY = originToScreen.getY() + dockNodeDrag.getLayoutBounds().getHeight() / 2
+ - dockPosIndicator.getHeight() / 2;
+
+ if (!dockIndicatorPopup.isShowing()) {
+ dockIndicatorPopup.show(DockPane.this, posX, posY);
+ } else {
+ dockIndicatorPopup.setX(posX);
+ dockIndicatorPopup.setY(posY);
+ }
+
+ // set visible after moving the popup
+ dockPosIndicator.setVisible(true);
+ } else {
+ dockPosIndicator.setVisible(false);
+ }
}
- }
- if (dockPosDrag != null) {
- Point2D originToScene = dockAreaDrag.localToScene(0, 0).subtract(this.localToScene(0, 0));
+ if (event.getEventType() == DockEvent.DOCK_RELEASED && event.getContents() != null) {
+ if (dockPositionDrag != null && dockIndicatorOverlay.isShowing()) {
+ DockNode dockNode = (DockNode) event.getContents();
+ dockNode.dock(this, dockPositionDrag, dockAreaDrag);
+ }
+ }
- dockAreaIndicator.setVisible(true);
- dockAreaIndicator.relocate(originToScene.getX(), originToScene.getY());
- if (dockPosDrag == DockPos.RIGHT) {
- dockAreaIndicator.setTranslateX(dockAreaDrag.getLayoutBounds().getWidth() / 2);
- } else {
- dockAreaIndicator.setTranslateX(0);
+ if ((event.getEventType() == DockEvent.DOCK_EXIT && !this.receivedEnter) || event.getEventType() == DockEvent.DOCK_RELEASED) {
+ if (dockIndicatorPopup.isShowing()) {
+ dockIndicatorOverlay.hide();
+ dockIndicatorPopup.hide();
+ }
}
+ }
- if (dockPosDrag == DockPos.BOTTOM) {
- dockAreaIndicator.setTranslateY(dockAreaDrag.getLayoutBounds().getHeight() / 2);
- } else {
- dockAreaIndicator.setTranslateY(0);
+ /**
+ * Base class for a dock indicator button that allows it to be displayed during a dock event and
+ * continue to receive input.
+ *
+ * @since 0.1
+ */
+ public static class DockPosButton extends Button {
+ /**
+ * Whether this dock indicator button is used for docking a node relative to the root of the
+ * dock pane.
+ */
+ private boolean dockRoot;
+ /**
+ * The docking position indicated by this button.
+ */
+ private DockPosition dockPosition;
+
+ /**
+ * Creates a new dock indicator button.
+ */
+ public DockPosButton(boolean dockRoot, DockPosition dockPosition) {
+ super();
+ this.dockRoot = dockRoot;
+ this.dockPosition = dockPosition;
}
- if (dockPosDrag == DockPos.LEFT || dockPosDrag == DockPos.RIGHT) {
- dockAreaIndicator.setWidth(dockAreaDrag.getLayoutBounds().getWidth() / 2);
- } else {
- dockAreaIndicator.setWidth(dockAreaDrag.getLayoutBounds().getWidth());
+ /**
+ * The docking position indicated by this button.
+ *
+ * @return The docking position indicated by this button.
+ */
+ public final DockPosition getDockPos() {
+ return dockPosition;
}
- if (dockPosDrag == DockPos.TOP || dockPosDrag == DockPos.BOTTOM) {
- dockAreaIndicator.setHeight(dockAreaDrag.getLayoutBounds().getHeight() / 2);
- } else {
- dockAreaIndicator.setHeight(dockAreaDrag.getLayoutBounds().getHeight());
+
+ /**
+ * The docking position indicated by this button.
+ *
+ * @param dockPosition The docking position indicated by this button.
+ */
+ public final void setDockPos(DockPosition dockPosition) {
+ this.dockPosition = dockPosition;
}
- } else {
- dockAreaIndicator.setVisible(false);
- }
-
- if (dockNodeDrag != null) {
- Point2D originToScreen = dockNodeDrag.localToScreen(0, 0);
-
- double posX = originToScreen.getX() + dockNodeDrag.getLayoutBounds().getWidth() / 2
- - dockPosIndicator.getWidth() / 2;
- double posY = originToScreen.getY() + dockNodeDrag.getLayoutBounds().getHeight() / 2
- - dockPosIndicator.getHeight() / 2;
-
- if (!dockIndicatorPopup.isShowing()) {
- dockIndicatorPopup.show(DockPane.this, posX, posY);
- } else {
- dockIndicatorPopup.setX(posX);
- dockIndicatorPopup.setY(posY);
+
+ /**
+ * Whether this dock indicator button is used for docking a node relative to the root of the
+ * dock pane.
+ *
+ * @return Whether this indicator button is used for docking a node relative to the root of the
+ * dock pane.
+ */
+ public final boolean isDockRoot() {
+ return dockRoot;
}
- // set visible after moving the popup
- dockPosIndicator.setVisible(true);
- } else {
- dockPosIndicator.setVisible(false);
- }
+ /**
+ * Whether this dock indicator button is used for docking a node relative to the root of the
+ * dock pane.
+ *
+ * @param dockRoot Whether this indicator button is used for docking a node relative to the root
+ * of the dock pane.
+ */
+ public final void setDockRoot(boolean dockRoot) {
+ this.dockRoot = dockRoot;
+ }
}
- if (event.getEventType() == DockEvent.DOCK_RELEASED && event.getContents() != null) {
- if (dockPosDrag != null && dockIndicatorOverlay.isShowing()) {
- DockNode dockNode = (DockNode) event.getContents();
- dockNode.dock(this, dockPosDrag, dockAreaDrag);
- }
- }
+ /**
+ * A wrapper to the type parameterized generic EventHandler that allows us to remove it from its
+ * listener when the dock node becomes detached. It is specifically used to monitor which dock
+ * node in this dock pane's layout we are currently dragging over.
+ *
+ * @since 0.1
+ */
+ private class DockNodeEventHandler implements EventHandler {
+ /**
+ * The node associated with this event handler that reports to the encapsulating dock pane.
+ */
+ private final Node node;
+
+ /**
+ * Creates a default dock node event handler that will help this dock pane track the current
+ * docking area.
+ *
+ * @param node The node that is to listen for docking events and report to the encapsulating
+ * docking pane.
+ */
+ public DockNodeEventHandler(Node node) {
+ this.node = node;
+ }
- if ((event.getEventType() == DockEvent.DOCK_EXIT && !this.receivedEnter)
- || event.getEventType() == DockEvent.DOCK_RELEASED) {
- if (dockIndicatorPopup.isShowing()) {
- dockIndicatorOverlay.hide();
- dockIndicatorPopup.hide();
- }
+ @Override
+ public void handle(DockEvent event) {
+ DockPane.this.dockNodeDrag = node;
+ }
}
- }
}
diff --git a/src/main/java/org/dockfx/DockPos.java b/src/main/java/org/dockfx/DockPos.java
deleted file mode 100644
index b82710f..0000000
--- a/src/main/java/org/dockfx/DockPos.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * @file DockArea.java
- * @brief Enumeration of dock area alignment constants.
- *
- * @section License
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- **/
-
-package org.dockfx;
-
-/**
- * DockPos
- *
- * @since DockFX 0.1
- */
-public enum DockPos {
- /**
- * Dock to the center by stacking inside a TabPane. This should be considered the equivalent of
- * null.
- */
- CENTER,
-
- /**
- * Dock to the left using a splitter.
- */
- LEFT,
-
- /**
- * Dock to the right using a splitter.
- */
- RIGHT,
-
- /**
- * Dock to the top using a splitter.
- */
- TOP,
-
- /**
- * Dock to the bottom using a splitter.
- */
- BOTTOM;
-}
diff --git a/src/main/java/org/dockfx/DockPosition.java b/src/main/java/org/dockfx/DockPosition.java
new file mode 100644
index 0000000..3398e44
--- /dev/null
+++ b/src/main/java/org/dockfx/DockPosition.java
@@ -0,0 +1,43 @@
+/**
+ * @file DockArea.java
+ * @brief Enumeration of dock area alignment constants.
+ * @section License
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ **/
+
+package org.dockfx;
+
+/**
+ * DockPos
+ *
+ * @since DockFX 0.1
+ */
+public enum DockPosition {
+ /**
+ * Dock to the center by stacking inside a TabPane. This should be considered the equivalent of
+ * null.
+ */
+ CENTER,
+
+ /**
+ * Dock to the left using a splitter.
+ */
+ LEFT,
+
+ /**
+ * Dock to the right using a splitter.
+ */
+ RIGHT,
+
+ /**
+ * Dock to the top using a splitter.
+ */
+ TOP,
+
+ /**
+ * Dock to the bottom using a splitter.
+ */
+ BOTTOM
+}
diff --git a/src/main/java/org/dockfx/DockTitleBar.java b/src/main/java/org/dockfx/DockTitleBar.java
index 3406522..4e57a64 100644
--- a/src/main/java/org/dockfx/DockTitleBar.java
+++ b/src/main/java/org/dockfx/DockTitleBar.java
@@ -1,20 +1,14 @@
/**
* @file DockTitleBar.java
* @brief Class implementing a generic base for a dock node title bar.
- *
* @section License
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
**/
package org.dockfx;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Stack;
-
-import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
@@ -31,390 +25,382 @@
import javafx.stage.Stage;
import javafx.stage.Window;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
/**
* Base class for a dock node title bar that provides the mouse dragging functionality, captioning,
* docking, and state manipulation.
- *
+ *
* @since DockFX 0.1
*/
public class DockTitleBar extends HBox implements EventHandler {
- /**
- * The DockNode this node is a title bar for.
- */
- private DockNode dockNode;
- /**
- * The label node used for captioning and the graphic.
- */
- private Label label;
- /**
- * State manipulation buttons including close, maximize, detach, and restore.
- */
- private Button closeButton, stateButton;
-
- /**
- * Creates a default DockTitleBar with captions and dragging behavior.
- *
- * @param dockNode The docking node that requires a title bar.
- */
- public DockTitleBar(DockNode dockNode) {
- this.dockNode = dockNode;
-
- label = new Label("Dock Title Bar");
- label.textProperty().bind(dockNode.titleProperty());
- label.graphicProperty().bind(dockNode.graphicProperty());
-
- stateButton = new Button();
- stateButton.setOnAction(new EventHandler() {
- @Override
- public void handle(ActionEvent event) {
- if (dockNode.isFloating()) {
- dockNode.setMaximized(!dockNode.isMaximized());
- } else {
- dockNode.setFloating(true);
- }
- }
- });
-
- closeButton = new Button();
- closeButton.setOnAction(new EventHandler() {
- @Override
- public void handle(ActionEvent event) {
- dockNode.close();
- }
- });
- closeButton.visibleProperty().bind(dockNode.closableProperty());
-
- // create a pane that will stretch to make the buttons right aligned
- Pane fillPane = new Pane();
- HBox.setHgrow(fillPane, Priority.ALWAYS);
-
- getChildren().addAll(label, fillPane, stateButton, closeButton);
-
- this.addEventHandler(MouseEvent.MOUSE_PRESSED, this);
- this.addEventHandler(MouseEvent.DRAG_DETECTED, this);
- this.addEventHandler(MouseEvent.MOUSE_DRAGGED, this);
- this.addEventHandler(MouseEvent.MOUSE_RELEASED, this);
-
- label.getStyleClass().add("dock-title-label");
- closeButton.getStyleClass().add("dock-close-button");
- stateButton.getStyleClass().add("dock-state-button");
- this.getStyleClass().add("dock-title-bar");
- }
-
- /**
- * Whether this title bar is currently being dragged.
- *
- * @return Whether this title bar is currently being dragged.
- */
- public final boolean isDragging() {
- return dragging;
- }
-
- /**
- * The label used for captioning and to provide a graphic.
- *
- * @return The label used for captioning and to provide a graphic.
- */
- public final Label getLabel() {
- return label;
- }
-
- /**
- * The button used for closing this title bar and its associated dock node.
- *
- * @return The button used for closing this title bar and its associated dock node.
- */
- public final Button getCloseButton() {
- return closeButton;
- }
-
- /**
- * The button used for detaching, maximizing, or restoring this title bar and its associated dock
- * node.
- *
- * @return The button used for detaching, maximizing, or restoring this title bar and its
- * associated dock node.
- */
- public final Button getStateButton() {
- return stateButton;
- }
-
- /**
- * The dock node that is associated with this title bar.
- *
- * @return The dock node that is associated with this title bar.
- */
- public final DockNode getDockNode() {
- return dockNode;
- }
-
- /**
- * The mouse location of the original click which we can use to determine the offset during drag.
- * Title bar dragging is asynchronous so it will not be negatively impacted by less frequent or
- * lagging mouse events as in the case of most current JavaFX implementations on Linux.
- */
- private Point2D dragStart;
- /**
- * Whether this title bar is currently being dragged.
- */
- private boolean dragging = false;
- /**
- * The current node being dragged over for each window so we can keep track of enter/exit events.
- */
- private HashMap dragNodes = new HashMap();
-
- /**
- * The task that is to be executed when the dock event target is picked. This provides context for
- * what specific events and what order the events should be fired.
- *
- * @since DockFX 0.1
- */
- private abstract class EventTask {
/**
- * The number of times this task has been executed.
+ * The DockNode this node is a title bar for.
+ */
+ private final DockNode dockNode;
+ /**
+ * The label node used for captioning and the graphic.
+ */
+ private final Label label;
+ /**
+ * State manipulation buttons including close, maximize, detach, and restore.
+ */
+ private final Button closeButton;
+ private final Button stateButton;
+ /**
+ * The mouse location of the original click which we can use to determine the offset during drag.
+ * Title bar dragging is asynchronous so it will not be negatively impacted by less frequent or
+ * lagging mouse events as in the case of most current JavaFX implementations on Linux.
+ */
+ private Point2D dragStart;
+ /**
+ * Whether this title bar is currently being dragged.
+ */
+ private boolean dragging = false;
+ /**
+ * The current node being dragged over for each window so we can keep track of enter/exit events.
*/
- protected int executions = 0;
+ private final HashMap dragNodes = new HashMap<>();
/**
* Creates a default DockTitleBar with captions and dragging behavior.
- *
- * @param node The node that was chosen as the event target.
- * @param dragNode The node that was last event target.
+ *
+ * @param dockNode The docking node that requires a title bar.
*/
- public abstract void run(Node node, Node dragNode);
+ public DockTitleBar(DockNode dockNode) {
+ this.dockNode = dockNode;
+
+ label = new Label("Dock Title Bar");
+ label.textProperty().bind(dockNode.titleProperty());
+ label.graphicProperty().bind(dockNode.graphicProperty());
+
+ stateButton = new Button();
+ stateButton.setOnAction(event -> {
+ if (dockNode.isFloating()) {
+ dockNode.setMaximized(!dockNode.isMaximized());
+ } else {
+ dockNode.setFloating(true);
+ }
+ });
+
+ closeButton = new Button();
+ closeButton.setOnAction(event -> dockNode.close());
+ closeButton.visibleProperty().bind(dockNode.closableProperty());
+
+ // create a pane that will stretch to make the buttons right aligned
+ Pane fillPane = new Pane();
+ HBox.setHgrow(fillPane, Priority.ALWAYS);
+
+ getChildren().addAll(label, fillPane, stateButton, closeButton);
+
+ this.addEventHandler(MouseEvent.MOUSE_PRESSED, this);
+ this.addEventHandler(MouseEvent.DRAG_DETECTED, this);
+ this.addEventHandler(MouseEvent.MOUSE_DRAGGED, this);
+ this.addEventHandler(MouseEvent.MOUSE_RELEASED, this);
+
+ label.getStyleClass().add("dock-title-label");
+ closeButton.getStyleClass().add("dock-close-button");
+ stateButton.getStyleClass().add("dock-state-button");
+ this.getStyleClass().add("dock-title-bar");
+ }
/**
- * The number of times this task has been executed.
+ * Whether this title bar is currently being dragged.
*
- * @return The number of times this task has been executed.
+ * @return Whether this title bar is currently being dragged.
*/
- public int getExecutions() {
- return executions;
+ public final boolean isDragging() {
+ return dragging;
}
/**
- * Reset the execution count to zero.
+ * The label used for captioning and to provide a graphic.
+ *
+ * @return The label used for captioning and to provide a graphic.
*/
- public void reset() {
- executions = 0;
+ public final Label getLabel() {
+ return label;
}
- }
-
- /**
- * Traverse the scene graph for all open stages and pick an event target for a dock event based on
- * the location. Once the event target is chosen run the event task with the target and the
- * previous target of the last dock event if one is cached. If an event target is not found fire
- * the explicit dock event on the stage root if one is provided.
- *
- * @param location The location of the dock event in screen coordinates.
- * @param eventTask The event task to be run when the event target is found.
- * @param explicit The explicit event to be fired on the stage root when no event target is found.
- */
- private void pickEventTarget(Point2D location, EventTask eventTask, Event explicit) {
- // RFE for public scene graph traversal API filed but closed:
- // https://bugs.openjdk.java.net/browse/JDK-8133331
-
- List dockPanes = DockPane.dockPanes;
-
- // fire the dock over event for the active stages
- for (DockPane dockPane : dockPanes) {
- Window window = dockPane.getScene().getWindow();
- if (!(window instanceof Stage)) continue;
- Stage targetStage = (Stage) window;
-
- // obviously this title bar does not need to receive its own events
- // though users of this library may want to know when their
- // dock node is being dragged by subclassing it or attaching
- // an event listener in which case a new event can be defined or
- // this continue behavior can be removed
- if (targetStage == this.dockNode.getStage())
- continue;
-
- eventTask.reset();
-
- Node dragNode = dragNodes.get(targetStage);
-
- Parent root = targetStage.getScene().getRoot();
- Stack stack = new Stack();
- if (root.contains(root.screenToLocal(location.getX(), location.getY()))
- && !root.isMouseTransparent()) {
- stack.push(root);
- }
- // depth first traversal to find the deepest node or parent with no children
- // that intersects the point of interest
- while (!stack.isEmpty()) {
- Parent parent = stack.pop();
- // if this parent contains the mouse click in screen coordinates in its local bounds
- // then traverse its children
- boolean notFired = true;
- for (Node node : parent.getChildrenUnmodifiable()) {
- if (node.contains(node.screenToLocal(location.getX(), location.getY()))
- && !node.isMouseTransparent()) {
- if (node instanceof Parent) {
- stack.push((Parent) node);
- } else {
- eventTask.run(node, dragNode);
- }
- notFired = false;
- break;
- }
- }
- // if none of the children fired the event or there were no children
- // fire it with the parent as the target to receive the event
- if (notFired) {
- eventTask.run(parent, dragNode);
- }
- }
- if (explicit != null && dragNode != null && eventTask.getExecutions() < 1) {
- Event.fireEvent(dragNode, explicit.copyFor(this, dragNode));
- dragNodes.put(targetStage, null);
- }
+ /**
+ * The button used for closing this title bar and its associated dock node.
+ *
+ * @return The button used for closing this title bar and its associated dock node.
+ */
+ public final Button getCloseButton() {
+ return closeButton;
}
- }
-
- @Override
- public void handle(MouseEvent event) {
- if (event.getEventType() == MouseEvent.MOUSE_PRESSED) {
- if (dockNode.isFloating() && event.getClickCount() == 2
- && event.getButton() == MouseButton.PRIMARY) {
- dockNode.setMaximized(!dockNode.isMaximized());
- } else {
- // drag detected is used in place of mouse pressed so there is some threshold for the
- // dragging which is determined by the default drag detection threshold
- dragStart = new Point2D(event.getX(), event.getY());
- }
- } else if (event.getEventType() == MouseEvent.DRAG_DETECTED) {
- if (!dockNode.isFloating()) {
- // if we are not using a custom title bar and the user
- // is not forcing the default one for floating and
- // the dock node does have native window decorations
- // then we need to offset the stage position by
- // the height of this title bar
- if (!dockNode.isCustomTitleBar() && dockNode.isDecorated()) {
- dockNode.setFloating(true, new Point2D(0, DockTitleBar.this.getHeight()));
- } else {
- dockNode.setFloating(true);
- }
- // TODO: Find a better solution.
- // Temporary work around for nodes losing the drag event when removed from
- // the scene graph.
- // A possible alternative is to use "ghost" panes in the DockPane layout
- // while making DockNode simply an overlay stage that is always shown.
- // However since flickering when popping out was already eliminated that would
- // be overkill and is not a suitable solution for native decorations.
- // Bug report open: https://bugs.openjdk.java.net/browse/JDK-8133335
- DockPane dockPane = this.getDockNode().getDockPane();
- if (dockPane != null) {
- dockPane.addEventFilter(MouseEvent.MOUSE_DRAGGED, this);
- dockPane.addEventFilter(MouseEvent.MOUSE_RELEASED, this);
- }
- } else if (dockNode.isMaximized()) {
- double ratioX = event.getX() / this.getDockNode().getWidth();
- double ratioY = event.getY() / this.getDockNode().getHeight();
-
- // Please note that setMaximized is ruined by width and height changes occurring on the
- // stage and there is currently a bug report filed for this though I did not give them an
- // accurate test case which I should and wish I would have. This was causing issues in the
- // original release requiring maximized behavior to be implemented manually by saving the
- // restored bounds. The problem was that the resize functionality in DockNode.java was
- // executing at the same time canceling the maximized change.
- // https://bugs.openjdk.java.net/browse/JDK-8133334
-
- // restore/minimize the window after we have obtained its dimensions
- dockNode.setMaximized(false);
-
- // scale the drag start location by our restored dimensions
- dragStart = new Point2D(ratioX * dockNode.getWidth(), ratioY * dockNode.getHeight());
- }
- dragging = true;
- event.consume();
- } else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
- if (dockNode.isFloating() && event.getClickCount() == 2
- && event.getButton() == MouseButton.PRIMARY) {
- event.setDragDetect(false);
- event.consume();
- return;
- }
-
- if (!dragging)
- return;
-
- Stage stage = dockNode.getStage();
- Insets insetsDelta = this.getDockNode().getBorderPane().getInsets();
-
- // dragging this way makes the interface more responsive in the event
- // the system is lagging as is the case with most current JavaFX
- // implementations on Linux
- stage.setX(event.getScreenX() - dragStart.getX() - insetsDelta.getLeft());
- stage.setY(event.getScreenY() - dragStart.getY() - insetsDelta.getTop());
-
- // TODO: change the pick result by adding a copyForPick()
- DockEvent dockEnterEvent =
- new DockEvent(this, DockEvent.NULL_SOURCE_TARGET, DockEvent.DOCK_ENTER, event.getX(),
- event.getY(), event.getScreenX(), event.getScreenY(), null);
- DockEvent dockOverEvent =
- new DockEvent(this, DockEvent.NULL_SOURCE_TARGET, DockEvent.DOCK_OVER, event.getX(),
- event.getY(), event.getScreenX(), event.getScreenY(), null);
- DockEvent dockExitEvent =
- new DockEvent(this, DockEvent.NULL_SOURCE_TARGET, DockEvent.DOCK_EXIT, event.getX(),
- event.getY(), event.getScreenX(), event.getScreenY(), null);
-
- EventTask eventTask = new EventTask() {
- @Override
- public void run(Node node, Node dragNode) {
- executions++;
-
- if (dragNode != node) {
- Event.fireEvent(node, dockEnterEvent.copyFor(DockTitleBar.this, node));
-
- if (dragNode != null) {
- // fire the dock exit first so listeners
- // can actually keep track of the node we
- // are currently over and know when we
- // aren't over any which DOCK_OVER
- // does not provide
- Event.fireEvent(dragNode, dockExitEvent.copyFor(DockTitleBar.this, dragNode));
+ /**
+ * The button used for detaching, maximizing, or restoring this title bar and its associated dock
+ * node.
+ *
+ * @return The button used for detaching, maximizing, or restoring this title bar and its
+ * associated dock node.
+ */
+ public final Button getStateButton() {
+ return stateButton;
+ }
+
+ /**
+ * The dock node that is associated with this title bar.
+ *
+ * @return The dock node that is associated with this title bar.
+ */
+ public final DockNode getDockNode() {
+ return dockNode;
+ }
+
+ /**
+ * Traverse the scene graph for all open stages and pick an event target for a dock event based on
+ * the location. Once the event target is chosen run the event task with the target and the
+ * previous target of the last dock event if one is cached. If an event target is not found fire
+ * the explicit dock event on the stage root if one is provided.
+ *
+ * @param location The location of the dock event in screen coordinates.
+ * @param eventTask The event task to be run when the event target is found.
+ * @param explicit The explicit event to be fired on the stage root when no event target is found.
+ */
+ private void pickEventTarget(Point2D location, EventTask eventTask, Event explicit) {
+ // RFE for public scene graph traversal API filed but closed:
+ // https://bugs.openjdk.java.net/browse/JDK-8133331
+
+ List dockPanes = DockPane.dockPanes;
+
+ // fire the dock over event for the active stages
+ for (DockPane dockPane : dockPanes) {
+ Window window = dockPane.getScene().getWindow();
+ if (!(window instanceof Stage targetStage)) continue;
+
+ // obviously this title bar does not need to receive its own events
+ // though users of this library may want to know when their
+ // dock node is being dragged by subclassing it or attaching
+ // an event listener in which case a new event can be defined or
+ // this continue behavior can be removed
+ if (targetStage == this.dockNode.getStage())
+ continue;
+
+ eventTask.reset();
+
+ Node dragNode = dragNodes.get(targetStage);
+
+ Parent root = targetStage.getScene().getRoot();
+ Stack stack = new Stack<>();
+ if (root.contains(root.screenToLocal(location.getX(), location.getY()))
+ && !root.isMouseTransparent()) {
+ stack.push(root);
+ }
+ // depth first traversal to find the deepest node or parent with no children
+ // that intersects the point of interest
+ while (!stack.isEmpty()) {
+ Parent parent = stack.pop();
+ // if this parent contains the mouse click in screen coordinates in its local bounds
+ // then traverse its children
+ boolean notFired = true;
+ for (Node node : parent.getChildrenUnmodifiable()) {
+ if (node.contains(node.screenToLocal(location.getX(), location.getY()))
+ && !node.isMouseTransparent()) {
+ if (node instanceof Parent) {
+ stack.push((Parent) node);
+ } else {
+ eventTask.run(node, dragNode);
+ }
+ notFired = false;
+ break;
+ }
+ }
+ // if none of the children fired the event or there were no children
+ // fire it with the parent as the target to receive the event
+ if (notFired) {
+ eventTask.run(parent, dragNode);
+ }
}
- dragNodes.put(node.getScene().getWindow(), node);
- }
- Event.fireEvent(node, dockOverEvent.copyFor(DockTitleBar.this, node));
- }
- };
-
- this.pickEventTarget(new Point2D(event.getScreenX(), event.getScreenY()), eventTask,
- dockExitEvent);
- } else if (event.getEventType() == MouseEvent.MOUSE_RELEASED) {
- dragging = false;
-
- DockEvent dockReleasedEvent =
- new DockEvent(this, DockEvent.NULL_SOURCE_TARGET, DockEvent.DOCK_RELEASED, event.getX(),
- event.getY(), event.getScreenX(), event.getScreenY(), null, this.getDockNode());
-
- EventTask eventTask = new EventTask() {
- @Override
- public void run(Node node, Node dragNode) {
- executions++;
- if (dragNode != node) {
- Event.fireEvent(node, dockReleasedEvent.copyFor(DockTitleBar.this, node));
- }
- Event.fireEvent(node, dockReleasedEvent.copyFor(DockTitleBar.this, node));
+ if (explicit != null && dragNode != null && eventTask.getExecutions() < 1) {
+ Event.fireEvent(dragNode, explicit.copyFor(this, dragNode));
+ dragNodes.put(targetStage, null);
+ }
}
- };
+ }
+
+ @Override
+ public void handle(MouseEvent event) {
+ if (event.getEventType() == MouseEvent.MOUSE_PRESSED) {
+ if (dockNode.isFloating() && event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
+ dockNode.setMaximized(!dockNode.isMaximized());
+ } else {
+ // drag detected is used in place of mouse pressed so there is some threshold for the
+ // dragging which is determined by the default drag detection threshold
+ dragStart = new Point2D(event.getX(), event.getY());
+ }
+ } else if (event.getEventType() == MouseEvent.DRAG_DETECTED) {
+ if (!dockNode.isFloating()) {
+ // if we are not using a custom title bar and the user
+ // is not forcing the default one for floating and
+ // the dock node does have native window decorations
+ // then we need to offset the stage position by
+ // the height of this title bar
+ if (!dockNode.isCustomTitleBar() && dockNode.isDecorated()) {
+ dockNode.setFloating(true, new Point2D(0, DockTitleBar.this.getHeight()));
+ } else {
+ dockNode.setFloating(true);
+ }
+
+ // TODO: Find a better solution.
+ // Temporary work around for nodes losing the drag event when removed from
+ // the scene graph.
+ // A possible alternative is to use "ghost" panes in the DockPane layout
+ // while making DockNode simply an overlay stage that is always shown.
+ // However since flickering when popping out was already eliminated that would
+ // be overkill and is not a suitable solution for native decorations.
+ // Bug report open: https://bugs.openjdk.java.net/browse/JDK-8133335
+ DockPane dockPane = this.getDockNode().getDockPane();
+ if (dockPane != null) {
+ dockPane.addEventFilter(MouseEvent.MOUSE_DRAGGED, this);
+ dockPane.addEventFilter(MouseEvent.MOUSE_RELEASED, this);
+ }
+ } else if (dockNode.isMaximized()) {
+ double ratioX = event.getX() / this.getDockNode().getWidth();
+ double ratioY = event.getY() / this.getDockNode().getHeight();
+
+ // Please note that setMaximized is ruined by width and height changes occurring on the
+ // stage and there is currently a bug report filed for this though I did not give them an
+ // accurate test case which I should and wish I would have. This was causing issues in the
+ // original release requiring maximized behavior to be implemented manually by saving the
+ // restored bounds. The problem was that the resize functionality in DockNode.java was
+ // executing at the same time canceling the maximized change.
+ // https://bugs.openjdk.java.net/browse/JDK-8133334
+
+ // restore/minimize the window after we have obtained its dimensions
+ dockNode.setMaximized(false);
+
+ // scale the drag start location by our restored dimensions
+ dragStart = new Point2D(ratioX * dockNode.getWidth(), ratioY * dockNode.getHeight());
+ }
+ dragging = true;
+ event.consume();
+ } else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
+ if (dockNode.isFloating() && event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
+ event.setDragDetect(false);
+ event.consume();
+ return;
+ }
- this.pickEventTarget(new Point2D(event.getScreenX(), event.getScreenY()), eventTask, null);
+ if (!dragging)
+ return;
+
+ Stage stage = dockNode.getStage();
+ Insets insetsDelta = this.getDockNode().getBorderPane().getInsets();
+
+ // dragging this way makes the interface more responsive in the event
+ // the system is lagging as is the case with most current JavaFX
+ // implementations on Linux
+ stage.setX(event.getScreenX() - dragStart.getX() - insetsDelta.getLeft());
+ stage.setY(event.getScreenY() - dragStart.getY() - insetsDelta.getTop());
+
+ // TODO: change the pick result by adding a copyForPick()
+ DockEvent dockEnterEvent =
+ new DockEvent(this, DockEvent.NULL_SOURCE_TARGET, DockEvent.DOCK_ENTER, event.getX(),
+ event.getY(), event.getScreenX(), event.getScreenY(), null);
+ DockEvent dockOverEvent =
+ new DockEvent(this, DockEvent.NULL_SOURCE_TARGET, DockEvent.DOCK_OVER, event.getX(),
+ event.getY(), event.getScreenX(), event.getScreenY(), null);
+ DockEvent dockExitEvent =
+ new DockEvent(this, DockEvent.NULL_SOURCE_TARGET, DockEvent.DOCK_EXIT, event.getX(),
+ event.getY(), event.getScreenX(), event.getScreenY(), null);
+
+ EventTask eventTask = new EventTask() {
+ @Override
+ public void run(Node node, Node dragNode) {
+ executions++;
+
+ if (dragNode != node) {
+ Event.fireEvent(node, dockEnterEvent.copyFor(DockTitleBar.this, node));
+
+ if (dragNode != null) {
+ // fire the dock exit first so listeners
+ // can actually keep track of the node we
+ // are currently over and know when we
+ // aren't over any which DOCK_OVER
+ // does not provide
+ Event.fireEvent(dragNode, dockExitEvent.copyFor(DockTitleBar.this, dragNode));
+ }
+
+ dragNodes.put(node.getScene().getWindow(), node);
+ }
+ Event.fireEvent(node, dockOverEvent.copyFor(DockTitleBar.this, node));
+ }
+ };
+
+ this.pickEventTarget(new Point2D(event.getScreenX(), event.getScreenY()), eventTask,
+ dockExitEvent);
+ } else if (event.getEventType() == MouseEvent.MOUSE_RELEASED) {
+ dragging = false;
+
+ DockEvent dockReleasedEvent =
+ new DockEvent(this, DockEvent.NULL_SOURCE_TARGET, DockEvent.DOCK_RELEASED, event.getX(),
+ event.getY(), event.getScreenX(), event.getScreenY(), null, this.getDockNode());
+
+ EventTask eventTask = new EventTask() {
+ @Override
+ public void run(Node node, Node dragNode) {
+ executions++;
+ if (dragNode != node) {
+ Event.fireEvent(node, dockReleasedEvent.copyFor(DockTitleBar.this, node));
+ }
+ Event.fireEvent(node, dockReleasedEvent.copyFor(DockTitleBar.this, node));
+ }
+ };
+
+ this.pickEventTarget(new Point2D(event.getScreenX(), event.getScreenY()), eventTask, null);
+
+ dragNodes.clear();
+
+ // Remove temporary event handler for bug mentioned above.
+ DockPane dockPane = this.getDockNode().getDockPane();
+ if (dockPane != null) {
+ dockPane.removeEventFilter(MouseEvent.MOUSE_DRAGGED, this);
+ dockPane.removeEventFilter(MouseEvent.MOUSE_RELEASED, this);
+ }
+ }
+ }
- dragNodes.clear();
+ /**
+ * The task that is to be executed when the dock event target is picked. This provides context for
+ * what specific events and what order the events should be fired.
+ *
+ * @since DockFX 0.1
+ */
+ private abstract static class EventTask {
+ /**
+ * The number of times this task has been executed.
+ * -- GETTER --
+ * The number of times this task has been executed.
+ *
+ * Holds the number of times this task has been executed.
+ */
+ protected int executions = 0;
+
+ /**
+ * Creates a default DockTitleBar with captions and dragging behavior.
+ *
+ * @param node The node that was chosen as the event target.
+ * @param dragNode The node that was last event target.
+ */
+ public abstract void run(Node node, Node dragNode);
+
+ /**
+ * Reset the execution count to zero.
+ */
+ public void reset() {
+ executions = 0;
+ }
- // Remove temporary event handler for bug mentioned above.
- DockPane dockPane = this.getDockNode().getDockPane();
- if (dockPane != null) {
- dockPane.removeEventFilter(MouseEvent.MOUSE_DRAGGED, this);
- dockPane.removeEventFilter(MouseEvent.MOUSE_RELEASED, this);
- }
+ public int getExecutions() {
+ return executions;
+ }
}
- }
}
diff --git a/src/main/java/org/dockfx/demo/DockFX.java b/src/main/java/org/dockfx/demo/DockFX.java
deleted file mode 100644
index 5cf8935..0000000
--- a/src/main/java/org/dockfx/demo/DockFX.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * @file DockFX.java
- * @brief Driver demonstrating basic dock layout with prototypes. Maintained in a separate package
- * to ensure the encapsulation of org.dockfx private package members.
- *
- * @section License
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- **/
-
-package org.dockfx.demo;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.Random;
-
-import org.dockfx.DockNode;
-import org.dockfx.DockPane;
-import org.dockfx.DockPos;
-
-import javafx.application.Application;
-import javafx.scene.Scene;
-import javafx.scene.control.Button;
-import javafx.scene.control.Menu;
-import javafx.scene.control.MenuBar;
-import javafx.scene.control.Separator;
-import javafx.scene.control.Tab;
-import javafx.scene.control.TabPane;
-import javafx.scene.control.TableColumn;
-import javafx.scene.control.TableView;
-import javafx.scene.control.ToolBar;
-import javafx.scene.control.TreeItem;
-import javafx.scene.control.TreeView;
-import javafx.scene.image.Image;
-import javafx.scene.image.ImageView;
-import javafx.scene.layout.Priority;
-import javafx.scene.layout.VBox;
-import javafx.scene.web.HTMLEditor;
-import javafx.stage.Stage;
-
-public class DockFX extends Application {
-
- public static void main(String[] args) {
- launch(args);
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void start(Stage primaryStage) {
- primaryStage.setTitle("DockFX");
-
- // create a dock pane that will manage our dock nodes and handle the layout
- DockPane dockPane = new DockPane();
-
- // create a default test node for the center of the dock area
- TabPane tabs = new TabPane();
- HTMLEditor htmlEditor = new HTMLEditor();
- try {
- htmlEditor.setHtmlText(new String(Files.readAllBytes(Paths.get("readme.html"))));
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- // empty tabs ensure that dock node has its own background color when floating
- tabs.getTabs().addAll(new Tab("Tab 1", htmlEditor), new Tab("Tab 2"), new Tab("Tab 3"));
-
- TableView tableView = new TableView();
- // this is why @SupressWarnings is used above
- // we don't care about the warnings because this is just a demonstration
- // for docks not the table view
- tableView.getColumns().addAll(new TableColumn("A"),
- new TableColumn("B"), new TableColumn("C"));
-
- // load an image to caption the dock nodes
- Image dockImage = new Image(DockFX.class.getResource("docknode.png").toExternalForm());
-
- // create and dock some prototype dock nodes to the middle of the dock pane
- // the preferred sizes are used to specify the relative size of the node
- // to the other nodes
-
- // we can use this to give our central content a larger area where
- // the top and bottom nodes have a preferred width of 300 which means that
- // when a node is docked relative to them such as the left or right dock below
- // they will have 300 / 100 + 300 (400) or 75% of their previous width
- // after both the left and right node's are docked the center docks end up with 50% of the width
-
- DockNode tabsDock = new DockNode(tabs, "Tabs Dock", new ImageView(dockImage));
- tabsDock.setPrefSize(300, 100);
- tabsDock.dock(dockPane, DockPos.TOP);
- DockNode tableDock = new DockNode(tableView);
- // let's disable our table from being undocked
- tableDock.setDockTitleBar(null);
- tableDock.setPrefSize(300, 100);
- tableDock.dock(dockPane, DockPos.BOTTOM);
-
- final Menu menu1 = new Menu("File");
- final Menu menu2 = new Menu("Options");
- final Menu menu3 = new Menu("Help");
-
- MenuBar menuBar = new MenuBar();
- menuBar.getMenus().addAll(menu1, menu2, menu3);
-
- ToolBar toolBar = new ToolBar(
- new Button("New"),
- new Button("Open"),
- new Button("Save"),
- new Separator(),
- new Button("Clean"),
- new Button("Compile"),
- new Button("Run"),
- new Separator(),
- new Button("Debug"),
- new Button("Profile")
- );
-
- VBox vbox = new VBox();
- vbox.getChildren().addAll(menuBar, toolBar, dockPane);
- VBox.setVgrow(dockPane, Priority.ALWAYS);
-
- primaryStage.setScene(new Scene(vbox, 800, 500));
- primaryStage.sizeToScene();
-
- primaryStage.show();
-
- // can be created and docked before or after the scene is created
- // and the stage is shown
- DockNode treeDock = new DockNode(generateRandomTree(), "Tree Dock", new ImageView(dockImage));
- treeDock.setPrefSize(100, 100);
- treeDock.dock(dockPane, DockPos.LEFT);
- treeDock = new DockNode(generateRandomTree(), "Tree Dock", new ImageView(dockImage));
- treeDock.setPrefSize(100, 100);
- treeDock.dock(dockPane, DockPos.RIGHT);
-
- // test the look and feel with both Caspian and Modena
- Application.setUserAgentStylesheet(Application.STYLESHEET_MODENA);
- // initialize the default styles for the dock pane and undocked nodes using the DockFX
- // library's internal Default.css stylesheet
- // unlike other custom control libraries this allows the user to override them globally
- // using the style manager just as they can with internal JavaFX controls
- // this must be called after the primary stage is shown
- // https://bugs.openjdk.java.net/browse/JDK-8132900
- DockPane.initializeDefaultUserAgentStylesheet();
-
- // TODO: after this feel free to apply your own global stylesheet using the StyleManager class
- }
-
- private TreeView generateRandomTree() {
- // create a demonstration tree view to use as the contents for a dock node
- TreeItem root = new TreeItem("Root");
- TreeView treeView = new TreeView(root);
- treeView.setShowRoot(false);
-
- // populate the prototype tree with some random nodes
- Random rand = new Random();
- for (int i = 4 + rand.nextInt(8); i > 0; i--) {
- TreeItem treeItem = new TreeItem("Item " + i);
- root.getChildren().add(treeItem);
- for (int j = 2 + rand.nextInt(4); j > 0; j--) {
- TreeItem childItem = new TreeItem("Child " + j);
- treeItem.getChildren().add(childItem);
- }
- }
-
- return treeView;
- }
-}
diff --git a/src/main/resources/META-INF/native-image/filter-file.json b/src/main/resources/META-INF/native-image/filter-file.json
new file mode 100644
index 0000000..d173ee0
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/filter-file.json
@@ -0,0 +1,11 @@
+{ "rules": [
+ {"excludeClasses" : "com.sun.glass.ui.mac.*"},
+ {"excludeClasses" : "com.sun.glass.ui.gtk.*"},
+ {"excludeClasses" : "com.sun.glass.ui.win.*"},
+ {"excludeClasses" : "com.sun.prism.es2.*"},
+ {"excludeClasses" : "com.sun.prism.d3d.*"},
+ {"excludeClasses" : "com.sun.scenario.effect.impl.es2.*"},
+ {"excludeClasses" : "com.sun.scenario.effect.impl.hw.d3d.*"},
+ {"excludeClasses" : "com.gluonhq.attach.**"}
+ ]
+}
diff --git a/src/main/resources/META-INF/native-image/jni-config.json b/src/main/resources/META-INF/native-image/jni-config.json
new file mode 100644
index 0000000..b98a190
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/jni-config.json
@@ -0,0 +1,88 @@
+[
+{
+ "name":"[Lcom.sun.glass.ui.Screen;"
+},
+{
+ "name":"com.sun.glass.ui.Cursor",
+ "methods":[
+ {"name":"getNativeCursor","parameterTypes":[] },
+ {"name":"getType","parameterTypes":[] }
+ ]
+},
+{
+ "name":"com.sun.glass.ui.Screen",
+ "methods":[{"name":"","parameterTypes":["long","int","int","int","int","int","int","int","int","int","int","int","int","int","int","int","float","float","float","float"] }]
+},
+{
+ "name":"com.sun.glass.ui.Size",
+ "methods":[{"name":"","parameterTypes":["int","int"] }]
+},
+{
+ "name":"com.sun.javafx.font.directwrite.DWRITE_GLYPH_METRICS",
+ "fields":[
+ {"name":"advanceHeight"},
+ {"name":"advanceWidth"},
+ {"name":"bottomSideBearing"},
+ {"name":"leftSideBearing"},
+ {"name":"rightSideBearing"},
+ {"name":"topSideBearing"},
+ {"name":"verticalOriginY"}
+ ],
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.javafx.font.directwrite.DWRITE_GLYPH_RUN",
+ "fields":[
+ {"name":"advanceOffset"},
+ {"name":"ascenderOffset"},
+ {"name":"bidiLevel"},
+ {"name":"fontEmSize"},
+ {"name":"fontFace"},
+ {"name":"glyphAdvances"},
+ {"name":"glyphIndices"},
+ {"name":"isSideways"}
+ ],
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.javafx.font.directwrite.RECT",
+ "fields":[
+ {"name":"bottom"},
+ {"name":"left"},
+ {"name":"right"},
+ {"name":"top"}
+ ],
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.prism.impl.PrismSettings",
+ "fields":[
+ {"name":"disableD3D9Ex"},
+ {"name":"forceGPU"},
+ {"name":"isVsyncEnabled"},
+ {"name":"verbose"}
+ ]
+},
+{
+ "name":"java.lang.Class",
+ "methods":[{"name":"forName","parameterTypes":["java.lang.String","boolean","java.lang.ClassLoader"] }]
+},
+{
+ "name":"java.lang.ClassLoader",
+ "methods":[
+ {"name":"getPlatformClassLoader","parameterTypes":[] },
+ {"name":"loadClass","parameterTypes":["java.lang.String"] }
+ ]
+},
+{
+ "name":"java.lang.Runnable",
+ "methods":[{"name":"run","parameterTypes":[] }]
+},
+{
+ "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"
+},
+{
+ "name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints",
+ "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }]
+}
+]
diff --git a/src/main/resources/META-INF/native-image/predefined-classes-config.json b/src/main/resources/META-INF/native-image/predefined-classes-config.json
new file mode 100644
index 0000000..0e79b2c
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/predefined-classes-config.json
@@ -0,0 +1,8 @@
+[
+ {
+ "type":"agent-extracted",
+ "classes":[
+ ]
+ }
+]
+
diff --git a/src/main/resources/META-INF/native-image/proxy-config.json b/src/main/resources/META-INF/native-image/proxy-config.json
new file mode 100644
index 0000000..0d4f101
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/proxy-config.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/src/main/resources/META-INF/native-image/reflect-config.json b/src/main/resources/META-INF/native-image/reflect-config.json
new file mode 100644
index 0000000..764c5f2
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/reflect-config.json
@@ -0,0 +1,199 @@
+[
+{
+ "name":"[Ljavafx.collections.transformation.SortedList$Element;"
+},
+{
+ "name":"com.sun.glass.ui.Screen"
+},
+{
+ "name":"com.sun.javafx.font.directwrite.DWFactory",
+ "methods":[{"name":"getFactory","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.javafx.logging.PrintLogger",
+ "methods":[{"name":"createInstance","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.javafx.logging.jfr.JFRPulseLogger",
+ "methods":[{"name":"createInstance","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.javafx.scene.control.skin.Utils",
+ "methods":[{"name":"getResource","parameterTypes":["java.lang.String"] }]
+},
+{
+ "name":"com.sun.javafx.tk.quantum.QuantumToolkit",
+ "methods":[{"name":"","parameterTypes":[] }]
+},
+{
+ "name":"com.sun.prism.GraphicsPipeline",
+ "methods":[
+ {"name":"getFontFactory","parameterTypes":[] },
+ {"name":"getPipeline","parameterTypes":[] }
+ ]
+},
+{
+ "name":"com.sun.prism.shader.DrawPgram_Color_Loader",
+ "methods":[{"name":"loadShader","parameterTypes":["com.sun.prism.ps.ShaderFactory","java.io.InputStream"] }]
+},
+{
+ "name":"com.sun.prism.shader.FillPgram_Color_Loader",
+ "methods":[{"name":"loadShader","parameterTypes":["com.sun.prism.ps.ShaderFactory","java.io.InputStream"] }]
+},
+{
+ "name":"com.sun.prism.shader.FillPgram_LinearGradient_PAD_Loader",
+ "methods":[{"name":"loadShader","parameterTypes":["com.sun.prism.ps.ShaderFactory","java.io.InputStream"] }]
+},
+{
+ "name":"com.sun.prism.shader.Solid_Color_Loader",
+ "methods":[{"name":"loadShader","parameterTypes":["com.sun.prism.ps.ShaderFactory","java.io.InputStream"] }]
+},
+{
+ "name":"com.sun.prism.shader.Solid_TextureFirstPassLCD_Loader",
+ "methods":[{"name":"loadShader","parameterTypes":["com.sun.prism.ps.ShaderFactory","java.io.InputStream"] }]
+},
+{
+ "name":"com.sun.prism.shader.Solid_TextureRGB_Loader",
+ "methods":[{"name":"loadShader","parameterTypes":["com.sun.prism.ps.ShaderFactory","java.io.InputStream"] }]
+},
+{
+ "name":"com.sun.prism.shader.Solid_TextureSecondPassLCD_Loader",
+ "methods":[{"name":"loadShader","parameterTypes":["com.sun.prism.ps.ShaderFactory","java.io.InputStream"] }]
+},
+{
+ "name":"com.sun.prism.shader.Texture_Color_Loader",
+ "methods":[{"name":"loadShader","parameterTypes":["com.sun.prism.ps.ShaderFactory","java.io.InputStream"] }]
+},
+{
+ "name":"com.sun.prism.shader.Texture_LinearGradient_PAD_Loader",
+ "methods":[{"name":"loadShader","parameterTypes":["com.sun.prism.ps.ShaderFactory","java.io.InputStream"] }]
+},
+{
+ "name":"com.sun.scenario.effect.impl.prism.PrRenderer",
+ "methods":[{"name":"createRenderer","parameterTypes":["com.sun.scenario.effect.FilterContext"] }]
+},
+{
+ "name":"com.sun.scenario.effect.impl.prism.ps.PPSLinearConvolveShadowPeer",
+ "methods":[{"name":"","parameterTypes":["com.sun.scenario.effect.FilterContext","com.sun.scenario.effect.impl.Renderer","java.lang.String"] }]
+},
+{
+ "name":"com.sun.scenario.effect.impl.prism.ps.PPSRenderer",
+ "methods":[{"name":"createRenderer","parameterTypes":["com.sun.scenario.effect.FilterContext"] }]
+},
+{
+ "name":"java.lang.Character",
+ "methods":[{"name":"isIdeographic","parameterTypes":["int"] }]
+},
+{
+ "name":"java.lang.Class",
+ "methods":[{"name":"forName","parameterTypes":["java.lang.Module","java.lang.String"] }],
+ "queriedMethods":[{"name":"getModule","parameterTypes":[] }]
+},
+{
+ "name":"java.lang.Module",
+ "queriedMethods":[
+ {"name":"getDescriptor","parameterTypes":[] },
+ {"name":"getLayer","parameterTypes":[] },
+ {"name":"getName","parameterTypes":[] }
+ ]
+},
+{
+ "name":"java.lang.ModuleLayer",
+ "methods":[
+ {"name":"boot","parameterTypes":[] },
+ {"name":"findModule","parameterTypes":["java.lang.String"] }
+ ]
+},
+{
+ "name":"java.nio.ByteBuffer",
+ "methods":[{"name":"order","parameterTypes":["java.nio.ByteOrder"] }]
+},
+{
+ "name":"java.nio.ByteOrder",
+ "methods":[{"name":"nativeOrder","parameterTypes":[] }]
+},
+{
+ "name":"javafx.animation.KeyValue"
+},
+{
+ "name":"javafx.scene.Camera"
+},
+{
+ "name":"javafx.scene.Group"
+},
+{
+ "name":"javafx.scene.Node"
+},
+{
+ "name":"javafx.scene.ParallelCamera"
+},
+{
+ "name":"javafx.scene.Parent"
+},
+{
+ "name":"javafx.scene.Scene"
+},
+{
+ "name":"javafx.scene.control.Control"
+},
+{
+ "name":"javafx.scene.control.TableColumnBase"
+},
+{
+ "name":"javafx.scene.effect.Effect"
+},
+{
+ "name":"javafx.scene.image.Image"
+},
+{
+ "name":"javafx.scene.image.ImageView"
+},
+{
+ "name":"javafx.scene.layout.Pane"
+},
+{
+ "name":"javafx.scene.layout.Region"
+},
+{
+ "name":"javafx.scene.shape.Rectangle"
+},
+{
+ "name":"javafx.scene.shape.SVGPath"
+},
+{
+ "name":"javafx.scene.shape.Shape"
+},
+{
+ "name":"javafx.scene.text.Font"
+},
+{
+ "name":"javafx.scene.text.Text"
+},
+{
+ "name":"javafx.scene.transform.Transform"
+},
+{
+ "name":"javafx.stage.PopupWindow"
+},
+{
+ "name":"javafx.stage.Stage"
+},
+{
+ "name":"javafx.stage.Window"
+},
+{
+ "name":"org.dockfx.DockFX",
+ "methods":[
+ {"name":"","parameterTypes":[] },
+ {"name":"main","parameterTypes":["java.lang.String[]"] }
+ ]
+},
+{
+ "name":"sun.misc.Unsafe",
+ "fields":[{"name":"theUnsafe"}]
+},
+{
+ "name":"sun.security.provider.MD5",
+ "methods":[{"name":"","parameterTypes":[] }]
+}
+]
diff --git a/src/main/resources/META-INF/native-image/resource-config.json b/src/main/resources/META-INF/native-image/resource-config.json
new file mode 100644
index 0000000..9271ca7
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/resource-config.json
@@ -0,0 +1,30 @@
+{
+ "resources":{
+ "includes":[{
+ "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
+ }]},
+ "bundles":[
+ {
+ "name":"com.sun.javafx.tk.quantum.QuantumMessagesBundle",
+ "locales":[
+ "",
+ "und"
+ ]
+ },
+ {
+ "name":"com/sun/glass/ui/win/themes",
+ "locales":[
+ "",
+ "en",
+ "und"
+ ]
+ },
+ {
+ "name":"com/sun/javafx/scene/control/skin/resources/controls",
+ "locales":[
+ "",
+ "und"
+ ]
+ }
+ ]
+}
diff --git a/src/main/resources/META-INF/native-image/serialization-config.json b/src/main/resources/META-INF/native-image/serialization-config.json
new file mode 100644
index 0000000..bf554e0
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/serialization-config.json
@@ -0,0 +1,6 @@
+{
+ "types":[
+ ],
+ "lambdaCapturingTypes":[
+ ]
+}
diff --git a/src/main/resources/org/dockfx/default.css b/src/main/resources/org/dockfx/default.css
index f64aa2a..b2a5312 100644
--- a/src/main/resources/org/dockfx/default.css
+++ b/src/main/resources/org/dockfx/default.css
@@ -124,21 +124,17 @@
* tabs will be fully transparent.
*/
.dock-node {
- -fx-background-color: -fx-background;
}
.dock-title-bar {
- -fx-background-color: -fx-background;
-fx-padding: 2;
-fx-spacing: 3;
-fx-border-width: 1;
- -fx-border-color: -fx-outer-border;
}
.dock-title-label {
-fx-graphic: url(docknode.png);
-fx-padding: 0 0 3 0;
- -fx-text-fill: -fx-text-base-color;
-fx-effect: dropshadow( gaussian, rgba(255,255,255,0.2), 0,0,0,1 );
}
diff --git a/src/main/resources/org/dockfx/demo/docknode.png b/src/main/resources/org/dockfx/docknode.png
similarity index 100%
rename from src/main/resources/org/dockfx/demo/docknode.png
rename to src/main/resources/org/dockfx/docknode.png
diff --git a/src/test/java/org/dockfx/DockNodeTest.java b/src/test/java/org/dockfx/DockNodeTest.java
new file mode 100644
index 0000000..2ff3dbd
--- /dev/null
+++ b/src/test/java/org/dockfx/DockNodeTest.java
@@ -0,0 +1,11 @@
+package org.dockfx;
+
+
+import org.junit.jupiter.api.Test;
+
+class DockNodeTest {
+
+ @Test
+ void setFloating() {
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/dockfx/demo/DockFX.java b/src/test/java/org/dockfx/demo/DockFX.java
new file mode 100644
index 0000000..9618587
--- /dev/null
+++ b/src/test/java/org/dockfx/demo/DockFX.java
@@ -0,0 +1,171 @@
+/**
+ * @file DockFX.java
+ * @brief Driver demonstrating basic dock layout with prototypes. Maintained in a separate package
+ * to ensure the encapsulation of org.dockfx private package members.
+ * @section License
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ **/
+
+package org.dockfx.demo;
+
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+import javafx.scene.web.HTMLEditor;
+import javafx.stage.Stage;
+import org.dockfx.DockNode;
+import org.dockfx.DockPane;
+import org.dockfx.DockPosition;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Objects;
+import java.util.Random;
+
+public class DockFX extends Application {
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DockFX.class);
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void start(Stage primaryStage) {
+ primaryStage.setTitle("DockFX");
+
+ // create a dock pane that will manage our dock nodes and handle the layout
+ DockPane dockPane = new DockPane();
+
+ // create a default test node for the center of the dock area
+ TabPane tabs = new TabPane();
+ HTMLEditor htmlEditor = new HTMLEditor();
+ try {
+ htmlEditor.setHtmlText(new String(Files.readAllBytes(Paths.get("readme.html"))));
+ } catch (IOException e) {
+ log.error("Error reading readme.html", e);
+ }
+
+ // empty tabs ensure that dock node has its own background color when floating
+ tabs.getTabs().addAll(new Tab("Tab 1", htmlEditor), new Tab("Tab 2"), new Tab("Tab 3"));
+
+ TableView tableView = new TableView<>();
+ // this is why @SupressWarnings is used above
+ // we don't care about the warnings because this is just a demonstration
+ // for docks not the table view
+ tableView.getColumns().addAll(new TableColumn("A"),
+ new TableColumn("B"), new TableColumn("C"));
+
+ // load an image to caption the dock nodes
+ Image dockImage = new Image(Objects.requireNonNull(DockFX.class.getResource("/org/dockfx/docknode.png")).toExternalForm());
+
+ // create and dock some prototype dock nodes to the middle of the dock pane
+ // the preferred sizes are used to specify the relative size of the node
+ // to the other nodes
+
+ // we can use this to give our central content a larger area where
+ // the top and bottom nodes have a preferred width of 300 which means that
+ // when a node is docked relative to them such as the left or right dock below
+ // they will have 300 / 100 + 300 (400) or 75% of their previous width
+ // after both the left and right node's are docked the center docks end up with 50% of the width
+
+ DockNode tabsDock = new DockNode();
+ tabsDock.setPrefSize(300, 100);
+ tabsDock.setTitle("Tabs Dock");
+ tabsDock.setGraphic(new ImageView(new Image(Objects.requireNonNull(DockFX.class.getResource("/org/dockfx/docknode.png")).toExternalForm())));
+ tabsDock.setContents(tabs);
+ tabsDock.setDockPosition(DockPosition.TOP);
+ tabsDock.setDockPane(dockPane);
+
+ DockNode tableDock = new DockNode();
+ tableDock.setContents(tableView);
+ // let's disable our table from being undocked
+ tableDock.setDockTitleBar(null);
+ tableDock.setPrefSize(300, 100);
+ tableDock.setDockPosition(DockPosition.BOTTOM);
+ tableDock.setDockPane(dockPane);
+
+ final Menu menu1 = new Menu("File");
+ final Menu menu2 = new Menu("Options");
+ final Menu menu3 = new Menu("Help");
+
+ MenuBar menuBar = new MenuBar();
+ menuBar.getMenus().addAll(menu1, menu2, menu3);
+
+ ToolBar toolBar = new ToolBar(
+ new Button("New"),
+ new Button("Open"),
+ new Button("Save"),
+ new Separator(),
+ new Button("Clean"),
+ new Button("Compile"),
+ new Button("Run"),
+ new Separator(),
+ new Button("Debug"),
+ new Button("Profile")
+ );
+
+ VBox vbox = new VBox();
+ vbox.getChildren().addAll(menuBar, toolBar, dockPane);
+ VBox.setVgrow(dockPane, Priority.ALWAYS);
+
+ primaryStage.setScene(new Scene(vbox, 800, 500));
+ primaryStage.sizeToScene();
+
+ primaryStage.show();
+
+ // can be created and docked before or after the scene is created
+ // and the stage is shown
+ DockNode treeDock = new DockNode();
+ treeDock.setContents(generateRandomTree());
+ treeDock.setPrefSize(100, 100);
+ treeDock.setTitle("Tree dock 1");
+ treeDock.setGraphic(new ImageView(dockImage));
+ treeDock.setDockPosition(DockPosition.LEFT);
+ treeDock.setDockPane(dockPane);
+ treeDock = new DockNode();
+ treeDock.setContents(generateRandomTree());
+ treeDock.setTitle("Tree dock 2");
+ treeDock.setGraphic(new ImageView(dockImage));
+ treeDock.setPrefSize(100, 100);
+ treeDock.setDockPosition(DockPosition.RIGHT);
+ treeDock.setDockPane(dockPane);
+
+ // test the look and feel with both Caspian and Modena
+ Application.setUserAgentStylesheet(Application.STYLESHEET_MODENA);
+ // initialize the default styles for the dock pane and undocked nodes using the DockFX
+ // library's internal Default.css stylesheet
+ // unlike other custom control libraries this allows the user to override them globally
+ // using the style manager just as they can with internal JavaFX controls
+ // this must be called after the primary stage is shown
+ // https://bugs.openjdk.java.net/browse/JDK-8132900
+ // TODO: after this feel free to apply your own global stylesheet using the StyleManager class
+ }
+
+ private TreeView generateRandomTree() {
+ // create a demonstration tree view to use as the contents for a dock node
+ TreeItem root = new TreeItem<>("Root");
+ TreeView treeView = new TreeView<>(root);
+ treeView.setShowRoot(false);
+
+ // populate the prototype tree with some random nodes
+ Random rand = new Random();
+ for (int i = 4 + rand.nextInt(8); i > 0; i--) {
+ TreeItem treeItem = new TreeItem<>("Item " + i);
+ root.getChildren().add(treeItem);
+ for (int j = 2 + rand.nextInt(4); j > 0; j--) {
+ TreeItem childItem = new TreeItem<>("Child " + j);
+ treeItem.getChildren().add(childItem);
+ }
+ }
+
+ return treeView;
+ }
+}
diff --git a/src/test/java/org/dockfx/fxmldemo/FxmlDemo.java b/src/test/java/org/dockfx/fxmldemo/FxmlDemo.java
new file mode 100644
index 0000000..91a04e2
--- /dev/null
+++ b/src/test/java/org/dockfx/fxmldemo/FxmlDemo.java
@@ -0,0 +1,19 @@
+package org.dockfx.fxmldemo;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+public class FxmlDemo extends Application {
+ @Override
+ public void start(Stage stage) throws Exception {
+ Application.setUserAgentStylesheet(Application.STYLESHEET_MODENA);
+ FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/main.fxml"));
+ Parent root = fxmlLoader.load();
+ stage.setTitle("FXML Welcome");
+ stage.setScene(new Scene(root, 800, 600));
+ stage.show();
+ }
+}
diff --git a/src/test/java/org/dockfx/fxmldemo/MainController.java b/src/test/java/org/dockfx/fxmldemo/MainController.java
new file mode 100644
index 0000000..c533ff3
--- /dev/null
+++ b/src/test/java/org/dockfx/fxmldemo/MainController.java
@@ -0,0 +1,48 @@
+package org.dockfx.fxmldemo;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.TextArea;
+import org.dockfx.DockNode;
+import org.dockfx.DockPane;
+import org.dockfx.DockPosition;
+
+public class MainController {
+ @FXML
+ DockNode debugDockNode;
+ @FXML
+ DockPane dockPaneMain;
+ @FXML
+ DockNode mainNode;
+ @FXML
+ private TextArea console;
+ private DockNode storedDockNode;
+
+ @FXML
+ public void initialize() {
+ // could not find a way to set this from fxml , for now I am setting from here.
+ mainNode.setDockPane(dockPaneMain);
+ debugDockNode.setDockPane(dockPaneMain);
+ }
+
+ public void menuFileExit() {
+ System.exit(0);
+ }
+
+ public void openDebugWindow() {
+ console.setText("Opening debug window");
+ if (debugDockNode != null && debugDockNode.isVisible()) {
+ storedDockNode = debugDockNode;
+ debugDockNode.setVisible(!debugDockNode.isVisible());
+ dockPaneMain.undock(debugDockNode);
+ } else if (storedDockNode != null) {
+ debugDockNode = storedDockNode;
+ debugDockNode.setVisible(true);
+ dockPaneMain.dock(debugDockNode, DockPosition.BOTTOM);
+ storedDockNode = null;
+ }
+ }
+
+ public void someAction() {
+ console.setText("Some action \n" + console.getText());
+ }
+}
diff --git a/src/test/resources/main.fxml b/src/test/resources/main.fxml
new file mode 100644
index 0000000..2475f93
--- /dev/null
+++ b/src/test/resources/main.fxml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+