diff --git a/src/main/java/org/dockfx/DockNode.java b/src/main/java/org/dockfx/DockNode.java index 554ae7c..d01e02b 100644 --- a/src/main/java/org/dockfx/DockNode.java +++ b/src/main/java/org/dockfx/DockNode.java @@ -645,7 +645,7 @@ public String getName() { }; public final boolean isTabbed() { - return floatingProperty.get(); + return tabbedProperty.get(); } diff --git a/src/main/java/org/dockfx/DockPane.java b/src/main/java/org/dockfx/DockPane.java index 1ad38ed..c1c3f48 100644 --- a/src/main/java/org/dockfx/DockPane.java +++ b/src/main/java/org/dockfx/DockPane.java @@ -25,10 +25,8 @@ import com.sun.javafx.css.StyleManager; -import org.dockfx.pane.ContentPane; -import org.dockfx.pane.ContentSplitPane; -import org.dockfx.pane.ContentTabPane; -import org.dockfx.pane.DockNodeTab; +import javafx.application.Platform; +import org.dockfx.pane.*; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; @@ -366,7 +364,10 @@ public void dock(Node node, DockPos dockPos, Node sibling) { ContentPane pane = (ContentPane) root; if (pane == null) { - pane = new ContentSplitPane(node); + ContentTabPane contentTabPane = new ContentTabPane(); + pane = new ContentSplitPane(contentTabPane); + contentTabPane.setContentParent(pane); + contentTabPane.addNode(null, null, node, dockPos); root = (Node) pane; this.getChildren().add(root); return; @@ -422,6 +423,11 @@ public void dock(Node node, DockPos dockPos, Node sibling) { pane = split; } + ContentTabPane contentTabPane = new ContentTabPane(); + contentTabPane.setContentParent(pane); + pane.addNode(root, sibling, contentTabPane, dockPos); + pane = contentTabPane; + } else if (pane instanceof ContentTabPane) { ContentSplitPane split = (ContentSplitPane) pane.getContentParent(); @@ -445,12 +451,16 @@ public void dock(Node node, DockPos dockPos, Node sibling) { } split.setOrientation(requestedOrientation); - pane = split; + ContentTabPane contentTabPane = new ContentTabPane(); + contentTabPane.setContentParent(split); + split.addNode(root, sibling, contentTabPane, dockPos); + pane = contentTabPane; } } // Add a node to the proper pane pane.addNode(root, sibling, node, dockPos); + Platform.runLater(DockPaneManager::checkEmptyPanes); } /** diff --git a/src/main/java/org/dockfx/DockTitleBar.java b/src/main/java/org/dockfx/DockTitleBar.java index 4b1a2df..b98a158 100644 --- a/src/main/java/org/dockfx/DockTitleBar.java +++ b/src/main/java/org/dockfx/DockTitleBar.java @@ -25,6 +25,7 @@ import com.sun.javafx.stage.StageHelper; +import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; @@ -43,6 +44,7 @@ import javafx.scene.layout.Priority; import javafx.stage.Stage; import javafx.stage.Window; +import org.dockfx.pane.DockPaneManager; /** * Base class for a dock node title bar that provides the mouse dragging functionality, captioning, @@ -85,6 +87,7 @@ public void handle(ActionEvent event) { dockNode.setMaximized(!dockNode.isMaximized()); } else { dockNode.setFloating(true); + Platform.runLater(() -> DockPaneManager.createUndockedWindow(dockNode)); } } }); @@ -426,6 +429,11 @@ public void run(Node node, Node dragNode) { dockPane.removeEventFilter(MouseEvent.MOUSE_DRAGGED, this); dockPane.removeEventFilter(MouseEvent.MOUSE_RELEASED, this); } + + if (!dockNode.isDocked()) { + Platform.runLater(() -> DockPaneManager.createUndockedWindow(dockNode)); + Platform.runLater(DockPaneManager::checkEmptyPanes); + } } } } diff --git a/src/main/java/org/dockfx/demo/DockFX.java b/src/main/java/org/dockfx/demo/DockFX.java index 6810fff..129e0ca 100644 --- a/src/main/java/org/dockfx/demo/DockFX.java +++ b/src/main/java/org/dockfx/demo/DockFX.java @@ -21,30 +21,24 @@ package org.dockfx.demo; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.Random; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; import org.dockfx.DockNode; import org.dockfx.DockPane; import org.dockfx.DockPos; import javafx.application.Application; import javafx.scene.Scene; -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.web.HTMLEditor; import javafx.stage.Stage; public class DockFX extends Application { + private DockPane dockPane = new DockPane(); + public static void main(String[] args) { launch(args); } @@ -54,64 +48,14 @@ public static void main(String[] args) { 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(); - } + BorderPane borderPane = new BorderPane(dockPane); + borderPane.setTop(createMenuBar()); - // 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); - - primaryStage.setScene(new Scene(dockPane, 800, 500)); + primaryStage.setScene(new Scene(borderPane, 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 @@ -144,4 +88,21 @@ private TreeView generateRandomTree() { return treeView; } + + private MenuBar createMenuBar() { + final MenuBar menuBar = new MenuBar(); + Menu menu = new Menu("Window"); + MenuItem mi = new MenuItem("New Window"); + mi.setOnAction(event -> { + // load an image to caption the dock nodes + Image dockImage = new Image(DockFX.class.getResource("docknode.png").toExternalForm()); + DockNode treeDock = new DockNode(generateRandomTree(), "Tree Dock", new ImageView(dockImage)); + treeDock.setPrefSize(100, 100); + treeDock.dock(dockPane, DockPos.LEFT); + + }); + menu.getItems().add(mi); + menuBar.getMenus().add(menu); + return menuBar; + } } diff --git a/src/main/java/org/dockfx/pane/ContentTabPane.java b/src/main/java/org/dockfx/pane/ContentTabPane.java index ec3c4b9..3e08c4e 100644 --- a/src/main/java/org/dockfx/pane/ContentTabPane.java +++ b/src/main/java/org/dockfx/pane/ContentTabPane.java @@ -10,7 +10,6 @@ import javafx.scene.Node; import javafx.scene.Parent; -import javafx.scene.control.Tab; import javafx.scene.control.TabPane; /** @@ -24,6 +23,7 @@ public class ContentTabPane extends TabPane implements ContentPane { public ContentTabPane() { this.setStyle("-fx-skin: \"org.dockfx.pane.skin.ContentTabPaneSkin\";"); + setPrefSize(100.0, 100.0); } public Type getType() { @@ -89,6 +89,8 @@ public List getChildrenList() { public void addNode(Node root, Node sibling, Node node, DockPos dockPos) { DockNode newNode = (DockNode) node; - getTabs().add(new DockNodeTab(newNode)); + final DockNodeTab tab = new DockNodeTab(newNode); + getTabs().add(tab); + getSelectionModel().select(tab); } } diff --git a/src/main/java/org/dockfx/pane/DockNodeTab.java b/src/main/java/org/dockfx/pane/DockNodeTab.java index 76d4ab6..e1daa1c 100644 --- a/src/main/java/org/dockfx/pane/DockNodeTab.java +++ b/src/main/java/org/dockfx/pane/DockNodeTab.java @@ -1,14 +1,8 @@ package org.dockfx.pane; -import javafx.beans.InvalidationListener; -import javafx.beans.binding.Bindings; import org.dockfx.DockNode; import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.scene.control.Label; import javafx.scene.control.Tab; /** @@ -32,6 +26,7 @@ public DockNodeTab(DockNode node) { setGraphic(dockNode.getDockTitleBar()); setContent(dockNode); dockNode.tabbedProperty().set(true); + dockNode.setPrefSize(100.0, 100.0); } public String getTitle() { diff --git a/src/main/java/org/dockfx/pane/DockPaneManager.java b/src/main/java/org/dockfx/pane/DockPaneManager.java new file mode 100644 index 0000000..9921993 --- /dev/null +++ b/src/main/java/org/dockfx/pane/DockPaneManager.java @@ -0,0 +1,69 @@ +package org.dockfx.pane; + +import java.util.HashSet; +import java.util.Set; + +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import org.dockfx.DockNode; +import org.dockfx.DockPane; +import org.dockfx.DockPos; +import org.dockfx.demo.DockFX; + +/** + * Utility class to handle dock panes and undocked windows management. + */ +public class DockPaneManager { + // load an image to caption the dock nodes + private static final Image DOCK_IMG = new Image(DockFX.class.getResource("docknode.png").toExternalForm()); + + private static final Set DOCK_PANES = new HashSet<>(); + private static final DockPane ROOT_PANE = new DockPane(); + + public static DockPane rootPane() { + return ROOT_PANE; + } + + private static DockPane createDockPane() { + final DockPane dockPane = new DockPane(); + DOCK_PANES.add(dockPane); + return dockPane; + } + + public static void createUndockedWindow(final DockNode dockNode) { + undockedWindowImpl(dockNode).show(); + } + + private static Stage undockedWindowImpl(final DockNode dockNode) { + final DockPane dockPane = DockPaneManager.createDockPane(); + final BorderPane borderPane = new BorderPane(); + borderPane.setCenter(dockPane); + final Scene scene = new Scene(borderPane); + final Stage stage = new Stage(); + stage.setScene(scene); + + borderPane.setPrefSize(dockNode.getStage().getWidth(), dockNode.getStage().getHeight()); + dockNode.dock(dockPane, DockPos.LEFT); + stage.setX(dockNode.getStage().getX()); + stage.setY(dockNode.getStage().getY()); + stage.sizeToScene(); + return stage; + } + + public static void checkEmptyPanes() { + DOCK_PANES.forEach(dp -> { + dp.getChildren().forEach(node -> { + if (node instanceof ContentSplitPane) { + ContentSplitPane csp = (ContentSplitPane) node; + if (csp.getChildrenList().isEmpty()) { + final Stage stage = (Stage) dp.getScene().getWindow(); + Platform.runLater(stage::close); + } + } + }); + }); + } +} \ No newline at end of file