diff --git a/demo-multi-app/basic_demo_layout.xml b/demo-multi-app/basic_demo_layout.xml
deleted file mode 100644
index fefe7cd4..00000000
--- a/demo-multi-app/basic_demo_layout.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/api/DockingAPI.java b/docking-api/src/ModernDocking/api/DockingAPI.java
index 81d4efc5..338c25e6 100644
--- a/docking-api/src/ModernDocking/api/DockingAPI.java
+++ b/docking-api/src/ModernDocking/api/DockingAPI.java
@@ -30,6 +30,7 @@ of this software and associated documentation files (the "Software"), to deal
import ModernDocking.exception.RootDockingPanelNotFoundException;
import ModernDocking.exception.RootDockingPanelRegistrationFailureException;
import ModernDocking.floating.FloatListener;
+import ModernDocking.floating.Floating;
import ModernDocking.internal.*;
import ModernDocking.layouts.WindowLayout;
import ModernDocking.ui.ToolbarLocation;
@@ -218,7 +219,7 @@ public void registerDockingPanel(RootDockingPanelAPI panel, JFrame parent) {
}
rootPanels.put(parent, panel);
- FloatListener.registerDockingWindow(this, parent, panel);
+ Floating.registerDockingWindow(this, parent, panel);
appStatePersister.addWindow(parent);
}
@@ -239,7 +240,7 @@ public void registerDockingPanel(RootDockingPanelAPI panel, JDialog parent) {
}
rootPanels.put(parent, panel);
- FloatListener.registerDockingWindow(this, parent, panel);
+ Floating.registerDockingWindow(this, parent, panel);
appStatePersister.addWindow(parent);
}
@@ -257,7 +258,7 @@ public void deregisterDockingPanel(Window parent) {
}
rootPanels.remove(parent);
- FloatListener.deregisterDockingWindow(parent);
+ Floating.deregisterDockingWindow(parent);
appStatePersister.removeWindow(parent);
}
@@ -645,7 +646,7 @@ public void undock(Dockable dockable) {
DockingListeners.fireUndockedEvent(dockable);
// make sure that can dispose this window, and we're not floating the last dockable in it
- if (canDisposeWindow(window) && root.isEmpty() && !FloatListener.isFloating()) {
+ if (canDisposeWindow(window) && root.isEmpty() && !Floating.isFloating()) {
deregisterDockingPanel(window);
window.dispose();
}
@@ -653,7 +654,7 @@ public void undock(Dockable dockable) {
appState.persist();
// force this dockable to dock again if we're not floating it
- if (!dockable.isClosable() && !FloatListener.isFloating() && !deregistering) {
+ if (!dockable.isClosable() && !Floating.isFloating() && !deregistering) {
dock(dockable, mainWindow);
}
}
diff --git a/docking-api/src/ModernDocking/api/RootDockingPanelAPI.java b/docking-api/src/ModernDocking/api/RootDockingPanelAPI.java
index 7ef53ce6..2fd5335a 100644
--- a/docking-api/src/ModernDocking/api/RootDockingPanelAPI.java
+++ b/docking-api/src/ModernDocking/api/RootDockingPanelAPI.java
@@ -84,13 +84,6 @@ protected RootDockingPanelAPI(DockingAPI docking, Window window) {
setLayout(new GridBagLayout());
this.docking = docking;
- if (window instanceof JFrame) {
- docking.registerDockingPanel(this, (JFrame) window);
- }
- else {
- docking.registerDockingPanel(this, (JDialog) window);
- }
-
southToolbar = new DockableToolbar(docking, window, this, ToolbarLocation.SOUTH);
westToolbar = new DockableToolbar(docking, window, this, ToolbarLocation.WEST);
eastToolbar = new DockableToolbar(docking, window, this, ToolbarLocation.EAST);
@@ -138,7 +131,7 @@ public void setWindow(Window window) {
eastToolbar = new DockableToolbar(docking, window, this, ToolbarLocation.EAST);
supportedToolbars = EnumSet.allOf(ToolbarLocation.class);
- pinningSupported = !supportedToolbars.isEmpty();
+ autoHideSupported = !supportedToolbars.isEmpty();
}
/**
diff --git a/docking-api/src/ModernDocking/floating/DisplayPanelFloatListener.java b/docking-api/src/ModernDocking/floating/DisplayPanelFloatListener.java
new file mode 100644
index 00000000..81ba72d5
--- /dev/null
+++ b/docking-api/src/ModernDocking/floating/DisplayPanelFloatListener.java
@@ -0,0 +1,126 @@
+/*
+Copyright (c) 2024 Andrew Auclair
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+package ModernDocking.floating;
+
+import ModernDocking.Dockable;
+import ModernDocking.DockingRegion;
+import ModernDocking.api.DockingAPI;
+import ModernDocking.api.RootDockingPanelAPI;
+import ModernDocking.internal.DisplayPanel;
+import ModernDocking.internal.DockableWrapper;
+import ModernDocking.internal.DockingComponentUtils;
+import ModernDocking.internal.FloatingFrame;
+import ModernDocking.settings.Settings;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.dnd.DragGestureEvent;
+import java.util.Collections;
+
+public class DisplayPanelFloatListener extends FloatListener {
+ private final DockingAPI docking;
+ private final DisplayPanel panel;
+
+ public DisplayPanelFloatListener(DockingAPI docking, DisplayPanel panel) {
+ super(docking, panel);
+ this.docking = docking;
+ this.panel = panel;
+ }
+
+ public DisplayPanelFloatListener(DockingAPI docking, DisplayPanel panel, JComponent dragComponent) {
+ super(docking, panel, dragComponent);
+ this.docking = docking;
+ this.panel = panel;
+ }
+
+ @Override
+ protected boolean allowDrag(DragGestureEvent dragGestureEvent) {
+ return true;
+ }
+
+ public Dockable getDockable() {
+ return panel.getWrapper().getDockable();
+ }
+
+ @Override
+ protected Window getOriginalWindow() {
+ return panel.getWrapper().getWindow();
+ }
+
+ @Override
+ protected void undock() {
+ docking.undock(panel.getWrapper().getDockable());
+ }
+
+ @Override
+ protected JFrame createFloatingFrame() {
+ if (Settings.alwaysDisplayTabsMode(panel.getWrapper().getDockable())) {
+ return new TempFloatingFrame(Collections.singletonList(panel.getWrapper()), 0, panel, panel.getSize());
+ }
+ return new TempFloatingFrame(panel.getWrapper(), panel, panel.getSize());
+ }
+
+ @Override
+ protected boolean dropPanel(FloatUtilsFrame utilsFrame, JFrame floatingFrame, Point mousePosOnScreen) {
+ DockableWrapper floatingDockable = panel.getWrapper();
+
+ if (utilsFrame != null) {
+ Window targetFrame = DockingComponentUtils.findRootAtScreenPos(docking, mousePosOnScreen);
+ RootDockingPanelAPI root = DockingComponentUtils.rootForWindow(docking, targetFrame);
+
+ if (utilsFrame.isOverRootHandle()) {
+ docking.dock(floatingDockable.getDockable(), targetFrame, utilsFrame.rootHandleRegion());
+ }
+ else if (utilsFrame.isOverDockableHandle()) {
+ Dockable dockableAtPos = DockingComponentUtils.findDockableAtScreenPos(mousePosOnScreen, targetFrame);
+
+ docking.dock(floatingDockable.getDockable(), dockableAtPos, utilsFrame.dockableHandle());
+ }
+ else if (utilsFrame.isOverPinHandle()) {
+ docking.unpinDockable(floatingDockable.getDockable(), utilsFrame.pinRegion(), targetFrame, root);
+ }
+ else if (utilsFrame.isOverTab()) {
+ // TODO get the tab index, if none then we're adding to the end
+
+ // TODO get the tab panel and add panel, dock it I think?
+ }
+ else {
+ // docking to a dockable region
+ Dockable dockableAtPos = DockingComponentUtils.findDockableAtScreenPos(mousePosOnScreen, targetFrame);
+
+ DockingRegion region = utilsFrame.getDockableRegion(dockableAtPos, panel.getWrapper().getDockable(), mousePosOnScreen);
+
+ docking.dock(floatingDockable.getDockable(), dockableAtPos, region);
+ }
+ }
+ else if (floatingDockable.getDockable().isFloatingAllowed()) {
+ // floating
+ FloatingFrame newFloatingFrame = new FloatingFrame(docking, floatingDockable.getDockable(), mousePosOnScreen, floatingDockable.getDisplayPanel().getSize(), 0);
+ docking.dock(floatingDockable.getDockable(), newFloatingFrame);
+ }
+ else {
+ // failed to dock, restore the previous layout
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/floating/DockableHandles.java b/docking-api/src/ModernDocking/floating/DockableHandles.java
new file mode 100644
index 00000000..87981f7a
--- /dev/null
+++ b/docking-api/src/ModernDocking/floating/DockableHandles.java
@@ -0,0 +1,253 @@
+/*
+Copyright (c) 2024 Andrew Auclair
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+package ModernDocking.floating;
+
+import ModernDocking.Dockable;
+import ModernDocking.DockableStyle;
+import ModernDocking.DockingRegion;
+import ModernDocking.ui.DockingSettings;
+
+import javax.swing.*;
+import java.awt.*;
+
+import static ModernDocking.floating.DockingHandle.HANDLE_ICON_SIZE;
+
+public class DockableHandles {
+ private final DockingHandle dockableCenter = new DockingHandle(DockingRegion.CENTER, false);
+ private final DockingHandle dockableWest = new DockingHandle(DockingRegion.WEST, false);
+ private final DockingHandle dockableNorth = new DockingHandle(DockingRegion.NORTH, false);
+ private final DockingHandle dockableEast = new DockingHandle(DockingRegion.EAST, false);
+ private final DockingHandle dockableSouth = new DockingHandle(DockingRegion.SOUTH, false);
+ private final JFrame frame;
+ private final Dockable targetDockable;
+ private final Dockable floatingDockable;
+
+ public DockableHandles(JFrame frame, Dockable targetDockable) {
+ this.frame = frame;
+ this.targetDockable = targetDockable;
+ this.floatingDockable = null;
+
+ setupHandle(frame, dockableCenter);
+ setupHandle(frame, dockableWest);
+ setupHandle(frame, dockableNorth);
+ setupHandle(frame, dockableEast);
+ setupHandle(frame, dockableSouth);
+
+ setDockableHandleLocations();
+ }
+
+ public DockableHandles(JFrame frame, Dockable targetDockable, Dockable floatingDockable) {
+ this.frame = frame;
+ this.targetDockable = targetDockable;
+ this.floatingDockable = floatingDockable;
+
+ setupHandle(frame, dockableCenter);
+ setupHandle(frame, dockableWest);
+ setupHandle(frame, dockableNorth);
+ setupHandle(frame, dockableEast);
+ setupHandle(frame, dockableSouth);
+
+ setDockableHandleLocations();
+ }
+
+ public void mouseMoved(Point mousePosOnScreen) {
+ Point framePoint = new Point(mousePosOnScreen);
+ SwingUtilities.convertPointFromScreen(framePoint, frame);
+
+ dockableCenter.mouseMoved(framePoint);
+ dockableWest.mouseMoved(framePoint);
+ dockableNorth.mouseMoved(framePoint);
+ dockableEast.mouseMoved(framePoint);
+ dockableSouth.mouseMoved(framePoint);
+ }
+
+ private void setupHandle(JFrame frame, DockingHandle label) {
+ label.setVisible(false);
+ frame.add(label);
+ }
+
+ private void setDockableHandleLocations() {
+ dockableCenter.setVisible(true);
+ dockableWest.setVisible(isRegionAllowed(targetDockable, DockingRegion.WEST));
+ dockableNorth.setVisible(isRegionAllowed(targetDockable, DockingRegion.NORTH));
+ dockableEast.setVisible(isRegionAllowed(targetDockable, DockingRegion.EAST));
+ dockableSouth.setVisible(isRegionAllowed(targetDockable, DockingRegion.SOUTH));
+
+ if (floatingDockable != null) {
+ if (!isRegionAllowed(floatingDockable, DockingRegion.WEST)) {
+ dockableWest.setVisible(false);
+ }
+ if (!isRegionAllowed(floatingDockable, DockingRegion.NORTH)) {
+ dockableNorth.setVisible(false);
+ }
+ if (!isRegionAllowed(floatingDockable, DockingRegion.EAST)) {
+ dockableEast.setVisible(false);
+ }
+ if (!isRegionAllowed(floatingDockable, DockingRegion.SOUTH)) {
+ dockableSouth.setVisible(false);
+ }
+ }
+
+ if (((Component) targetDockable).getParent() != null) {
+ Point location = ((Component) targetDockable).getLocation();
+ Dimension size = ((Component) targetDockable).getSize();
+
+ // if this dockable is wrapped in a JScrollPane we need to set the handle to the center of the JScrollPane
+ // not to the center of the dockable (which will more than likely be at a different location)
+ if (targetDockable.isWrappableInScrollpane()) {
+ Component parent = ((Component) targetDockable).getParent();
+
+ while (parent != null && !(parent instanceof JScrollPane)) {
+ parent = parent.getParent();
+ }
+
+ if (parent != null) {
+ JScrollPane display = (JScrollPane) parent;
+
+ location = display.getLocation();
+ size = display.getSize();
+ }
+ }
+
+ location.x += size.width / 2;
+ location.y += size.height / 2;
+
+ location.y -= (int) (HANDLE_ICON_SIZE * (1.75/2));
+
+ SwingUtilities.convertPointToScreen(location, ((Component) targetDockable).getParent());
+ SwingUtilities.convertPointFromScreen(location, frame);
+
+ setLocation(dockableCenter, location.x, location.y);
+ setLocation(dockableWest, location.x - handleSpacing(dockableWest), location.y);
+ setLocation(dockableNorth, location.x, location.y - handleSpacing(dockableNorth));
+ setLocation(dockableEast, location.x + handleSpacing(dockableEast), location.y);
+ setLocation(dockableSouth, location.x, location.y + handleSpacing(dockableSouth));
+ }
+ }
+
+ private void setLocation(Component component, int x, int y) {
+ component.setLocation(x - (HANDLE_ICON_SIZE / 2), y - (HANDLE_ICON_SIZE / 2));
+ }
+
+ private boolean isRegionAllowed(Dockable dockable, DockingRegion region) {
+ if (dockable.getStyle() == DockableStyle.BOTH) {
+ return true;
+ }
+ if (region == DockingRegion.NORTH || region == DockingRegion.SOUTH) {
+ return dockable.getStyle() == DockableStyle.HORIZONTAL;
+ }
+ return dockable.getStyle() == DockableStyle.VERTICAL;
+ }
+
+ public void paint(Graphics2D g2) {
+ int centerX = dockableCenter.getX() + (dockableCenter.getWidth() / 2);
+ int centerY = dockableCenter.getY() + (dockableCenter.getWidth() / 2);
+
+ int spacing = handleSpacing(dockableCenter) - dockableCenter.getWidth();
+ int half_icon = dockableCenter.getWidth() / 2;
+ int one_and_a_half_icons = (int) (dockableCenter.getWidth() * 1.5);
+
+ // create a polygon of the docking handles background
+ Polygon poly = new Polygon(
+ new int[] {
+ centerX - half_icon - spacing,
+ centerX + half_icon + spacing,
+ centerX + half_icon + spacing,
+ centerX + half_icon + (spacing * 2),
+ centerX + one_and_a_half_icons + (spacing * 2),
+ centerX + one_and_a_half_icons + (spacing * 2),
+ centerX + half_icon + (spacing * 2),
+ centerX + half_icon + spacing,
+ centerX + half_icon + spacing,
+ centerX - half_icon - spacing,
+ centerX - half_icon - spacing,
+ centerX - half_icon - (spacing * 2),
+ centerX - one_and_a_half_icons - (spacing * 2),
+ centerX - one_and_a_half_icons - (spacing * 2),
+ centerX - half_icon - (spacing * 2),
+ centerX - half_icon - spacing,
+ centerX - half_icon - spacing
+ },
+ new int[] {
+ centerY - one_and_a_half_icons - (spacing * 2),
+ centerY - one_and_a_half_icons - (spacing * 2),
+ centerY - half_icon - (spacing * 2),
+ centerY - half_icon - spacing,
+ centerY - half_icon - spacing,
+ centerY + half_icon + spacing,
+ centerY + half_icon + spacing,
+ centerY + half_icon + (spacing * 2),
+ centerY + one_and_a_half_icons + (spacing * 2),
+ centerY + one_and_a_half_icons + (spacing * 2),
+ centerY + half_icon + (spacing * 2),
+ centerY + half_icon + spacing,
+ centerY + half_icon + spacing,
+ centerY - half_icon - spacing,
+ centerY - half_icon - spacing,
+ centerY - half_icon - (spacing * 2),
+ centerY - one_and_a_half_icons - (spacing * 2),
+ },
+ 17
+ );
+ Color background = DockingSettings.getHandleBackground();//DockingProperties.getHandlesBackground();
+ Color border = DockingSettings.getHandleForeground();//DockingProperties.getHandlesBackgroundBorder();
+
+ // draw the dockable handles background over the root handles in case they overlap
+ // fill the dockable handles background
+ g2.setColor(background);
+ g2.fillPolygon(poly.xpoints, poly.ypoints, poly.npoints);
+
+ // draw the dockable handles border
+ g2.setColor(border);
+ g2.drawPolygon(poly.xpoints, poly.ypoints, poly.npoints);
+
+ // draw the docking handles over the docking handles background
+ dockableCenter.paintHandle(g2);
+ dockableEast.paintHandle(g2);
+ dockableWest.paintHandle(g2);
+ dockableNorth.paintHandle(g2);
+ dockableSouth.paintHandle(g2);
+ }
+
+ private int handleSpacing(JLabel handle) {
+ return handle.getWidth() + 8;
+ }
+
+ public DockingRegion getRegion() {
+ if (dockableCenter.isMouseOver()) {
+ return DockingRegion.CENTER;
+ }
+ if (dockableEast.isMouseOver()) {
+ return DockingRegion.EAST;
+ }
+ if (dockableWest.isMouseOver()) {
+ return DockingRegion.WEST;
+ }
+ if (dockableNorth.isMouseOver()) {
+ return DockingRegion.NORTH;
+ }
+ if (dockableSouth.isMouseOver()) {
+ return DockingRegion.SOUTH;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/floating/DockedTabFloatListener.java b/docking-api/src/ModernDocking/floating/DockedTabFloatListener.java
new file mode 100644
index 00000000..b86f4d77
--- /dev/null
+++ b/docking-api/src/ModernDocking/floating/DockedTabFloatListener.java
@@ -0,0 +1,31 @@
+package ModernDocking.floating;
+
+import ModernDocking.api.DockingAPI;
+import ModernDocking.internal.DisplayPanel;
+import ModernDocking.internal.DockedTabbedPanel;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.dnd.DragGestureEvent;
+
+public class DockedTabFloatListener extends DisplayPanelFloatListener {
+ private final DockedTabbedPanel tabs;
+ private final DisplayPanel displayPanel;
+
+ public DockedTabFloatListener(DockingAPI docking, DockedTabbedPanel tabs, DisplayPanel displayPanel, JComponent dragComponent) {
+ super(docking, displayPanel, dragComponent);
+ this.tabs = tabs;
+ this.displayPanel = displayPanel;
+ }
+
+ @Override
+ protected boolean allowDrag(DragGestureEvent dragGestureEvent) {
+ // if we're dragging from a tab then we need to use the normal drag event
+ Point dragOrigin = new Point(dragGestureEvent.getDragOrigin());
+ SwingUtilities.convertPointToScreen(dragOrigin, dragGestureEvent.getComponent());
+
+ int targetTabIndex = tabs.getTargetTabIndex(dragOrigin);
+
+ return targetTabIndex == tabs.getIndexOfPanel(displayPanel);
+ }
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/floating/DockedTabbedPanelFloatListener.java b/docking-api/src/ModernDocking/floating/DockedTabbedPanelFloatListener.java
new file mode 100644
index 00000000..0baa6102
--- /dev/null
+++ b/docking-api/src/ModernDocking/floating/DockedTabbedPanelFloatListener.java
@@ -0,0 +1,131 @@
+/*
+Copyright (c) 2024 Andrew Auclair
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+package ModernDocking.floating;
+
+import ModernDocking.Dockable;
+import ModernDocking.DockingRegion;
+import ModernDocking.api.DockingAPI;
+import ModernDocking.internal.DockableWrapper;
+import ModernDocking.internal.DockedTabbedPanel;
+import ModernDocking.internal.DockingComponentUtils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.dnd.DragGestureEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DockedTabbedPanelFloatListener extends FloatListener {
+ private final DockingAPI docking;
+ protected final DockedTabbedPanel tabs;
+
+ private DisplayPanelFloatListener listener = null;
+
+ public DockedTabbedPanelFloatListener(DockingAPI docking, DockedTabbedPanel tabs, JComponent dragComponent) {
+ super(docking, tabs, dragComponent);
+
+ this.docking = docking;
+ this.tabs = tabs;
+ }
+
+ @Override
+ protected boolean allowDrag(DragGestureEvent dragGestureEvent) {
+ // if we're dragging from a tab then we need to use the normal drag event
+ Point dragOrigin = new Point(dragGestureEvent.getDragOrigin());
+ SwingUtilities.convertPointToScreen(dragOrigin, dragGestureEvent.getComponent());
+
+ int targetTabIndex = tabs.getTargetTabIndex(dragOrigin);
+
+ return targetTabIndex == -1;
+ }
+
+ @Override
+ protected Window getOriginalWindow() {
+ return SwingUtilities.windowForComponent(tabs);
+ }
+
+ @Override
+ protected void undock() {
+ List wrappers = new ArrayList<>(tabs.getDockables());
+
+ for (DockableWrapper wrapper : wrappers) {
+ docking.undock(wrapper.getDockable());
+ }
+ }
+
+ @Override
+ protected JFrame createFloatingFrame() {
+ List wrappers = new ArrayList<>(tabs.getDockables());
+
+ return new TempFloatingFrame(wrappers, tabs.getSelectedTabIndex(), tabs, tabs.getSize());
+ }
+
+ @Override
+ protected boolean dropPanel(FloatUtilsFrame utilsFrame, JFrame floatingFrame, Point mousePosOnScreen) {
+ if (!(floatingFrame instanceof TempFloatingFrame)) {
+ return false;
+ }
+
+ TempFloatingFrame tempFloatingFrame = (TempFloatingFrame) floatingFrame;
+
+ List dockables = new ArrayList<>(tempFloatingFrame.getDockables());
+
+ boolean first = true;
+ Dockable firstDockable = null;
+
+ Window targetFrame = DockingComponentUtils.findRootAtScreenPos(docking, mousePosOnScreen);
+ Dockable dockableAtPos = DockingComponentUtils.findDockableAtScreenPos(mousePosOnScreen, targetFrame);
+ DockingRegion region = utilsFrame.getDockableRegion(dockableAtPos, null, mousePosOnScreen);
+
+ boolean overRootHandle = utilsFrame.isOverRootHandle();
+
+ if (utilsFrame.isOverDockableHandle()) {
+ region = utilsFrame.dockableHandle();
+ }
+
+ Dockable selectedDockable = dockables.get(tempFloatingFrame.getSelectedIndex()).getDockable();
+ for (DockableWrapper dockable : dockables) {
+ if (first) {
+ if (utilsFrame.isOverRootHandle()) {
+ docking.dock(dockable.getDockable(), targetFrame, utilsFrame.rootHandleRegion());
+ }
+ else if (dockableAtPos != null){// && currentTopWindow != null && dockingPanel != null && activeUtilsFrame != null && activeUtilsFrame.isDockingToDockable()) {
+ docking.dock(dockable.getDockable(), dockableAtPos, region);
+ }
+ else {
+// new FloatingFrame(docking, dockable.getDockable(), tabs);
+ }
+ firstDockable = dockable.getDockable();
+ }
+ else {
+ docking.dock(dockable.getDockable(), firstDockable, DockingRegion.CENTER);
+ }
+ first = false;
+ }
+
+ if (selectedDockable != null) {
+ docking.bringToFront(selectedDockable);
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/floating/DockingHandle.java b/docking-api/src/ModernDocking/floating/DockingHandle.java
index b7e9d74c..2033d7de 100644
--- a/docking-api/src/ModernDocking/floating/DockingHandle.java
+++ b/docking-api/src/ModernDocking/floating/DockingHandle.java
@@ -46,6 +46,8 @@ public class DockingHandle extends JLabel {
private final boolean isRoot;
private final boolean isPin;
+ private boolean mouseOver = false;
+
/**
* Create a new DockingHandle
*
@@ -72,6 +74,17 @@ public DockingHandle(DockingRegion region) {
setVisible(false);
}
+ public void mouseMoved(Point mousePosition) {
+ mouseOver = getBounds().contains(mousePosition);
+ }
+
+ public boolean isMouseOver() {
+ if (!isVisible()) {
+ return false;
+ }
+ return mouseOver;
+ }
+
/**
* Get the region for this handle
*
@@ -104,9 +117,12 @@ public boolean isPin() {
*
* @param g used to do the main paint operations
* @param g2 used to draw the dashed lines on top
- * @param mouseOver is the mouse over this handle?
*/
- public void paintHandle(Graphics g, Graphics2D g2, boolean mouseOver) {
+ public void paintHandle(Graphics2D g2) {
+ if (!isVisible()) {
+ return;
+ }
+
Rectangle bounds = getBounds();
Color background = DockingSettings.getHandleBackground();
@@ -116,23 +132,22 @@ public void paintHandle(Graphics g, Graphics2D g2, boolean mouseOver) {
// each root handle has its own background. we have to draw them here.
// the dockables all share one big root that is drawn in DockingHandles
if (isRoot || isPin) {
- g.setColor(background);
- drawBackground(g);
+ g2.setColor(background);
+ drawBackground(g2);
}
if (mouseOver && isPin) {
int quarterWidth = bounds.width / 4;
int x1 = getX() + quarterWidth;
- g.fillRect(x1, bounds.y, bounds.width / 2, bounds.height / 2);
+ g2.fillRect(x1, bounds.y, bounds.width / 2, bounds.height / 2);
}
else if (mouseOver) {
- g.setColor(hover);
- fillMouseOverRegion(g);
+ g2.setColor(hover);
+ fillMouseOverRegion(g2);
}
// draw the outline over the mouse over
- g.setColor(outline);
g2.setColor(outline);
// only draw the dashed line if the region isn't center and these are not root handles
@@ -141,22 +156,22 @@ else if (mouseOver) {
}
if (isRoot && region != DockingRegion.CENTER) {
- drawRootOutline(g);
+ drawRootOutline(g2);
}
else if (isPin) {
int quarterWidth = bounds.width / 4;
int x1 = getX() + quarterWidth;
- g.drawLine(x1, bounds.y, x1 + (bounds.width / 2), bounds.y);
- g.drawLine(x1, bounds.y+ (bounds.height / 2), x1 + (bounds.width / 2), bounds.y+ (bounds.height / 2));
+ g2.drawLine(x1, bounds.y, x1 + (bounds.width / 2), bounds.y);
+ g2.drawLine(x1, bounds.y+ (bounds.height / 2), x1 + (bounds.width / 2), bounds.y+ (bounds.height / 2));
- g.drawLine(x1, bounds.y, x1, bounds.y + (bounds.height / 2));
- g.drawLine(x1 + (bounds.width / 2), bounds.y, x1 + (bounds.width / 2), bounds.y + (bounds.height / 2));
+ g2.drawLine(x1, bounds.y, x1, bounds.y + (bounds.height / 2));
+ g2.drawLine(x1 + (bounds.width / 2), bounds.y, x1 + (bounds.width / 2), bounds.y + (bounds.height / 2));
- g.drawLine(x1 + quarterWidth, bounds.y + (bounds.height / 2), x1 + quarterWidth, bounds.y + (bounds.height));
+ g2.drawLine(x1 + quarterWidth, bounds.y + (bounds.height / 2), x1 + quarterWidth, bounds.y + (bounds.height));
}
else {
- g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
+ g2.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
@@ -221,6 +236,11 @@ private void fillMouseOverRegion(Graphics g) {
}
private void drawDashedLine(Graphics2D g2) {
+ Stroke currentStroke = g2.getStroke();
+
+ Stroke dashed = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{3}, 0);
+ g2.setStroke(dashed);
+
Rectangle bounds = getBounds();
boolean north = region == DockingRegion.NORTH;
@@ -239,5 +259,7 @@ private void drawDashedLine(Graphics2D g2) {
else {
g2.drawLine(x, y, x2, y2);
}
+
+ g2.setStroke(currentStroke);
}
-}
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/floating/DockingHandles.java b/docking-api/src/ModernDocking/floating/DockingHandles.java
deleted file mode 100644
index e2a76ac1..00000000
--- a/docking-api/src/ModernDocking/floating/DockingHandles.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
-Copyright (c) 2022 Andrew Auclair
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
- */
-package ModernDocking.floating;
-
-import ModernDocking.Dockable;
-import ModernDocking.DockableStyle;
-import ModernDocking.DockingRegion;
-import ModernDocking.api.RootDockingPanelAPI;
-import ModernDocking.internal.DisplayPanel;
-import ModernDocking.internal.DockedSimplePanel;
-import ModernDocking.internal.DockedTabbedPanel;
-import ModernDocking.ui.DockingSettings;
-import ModernDocking.ui.ToolbarLocation;
-
-import javax.swing.*;
-import java.awt.*;
-import java.util.HashMap;
-import java.util.Map;
-
-import static ModernDocking.floating.DockingHandle.HANDLE_ICON_SIZE;
-
-/**
- * handles displaying the handles for docking overlaid on the application
- * only displayed over the currently hit docking panel
- */
-public class DockingHandles {
- private final DockingHandle rootCenter = new DockingHandle(DockingRegion.CENTER, true);
- private final DockingHandle rootWest = new DockingHandle(DockingRegion.WEST, true);
- private final DockingHandle rootNorth = new DockingHandle(DockingRegion.NORTH, true);
- private final DockingHandle rootEast = new DockingHandle(DockingRegion.EAST, true);
- private final DockingHandle rootSouth = new DockingHandle(DockingRegion.SOUTH, true);
-
- private final DockingHandle pinWest = new DockingHandle(DockingRegion.WEST);
- private final DockingHandle pinEast = new DockingHandle(DockingRegion.EAST);
- private final DockingHandle pinSouth = new DockingHandle(DockingRegion.SOUTH);
-
- private final DockingHandle dockableCenter = new DockingHandle(DockingRegion.CENTER, false);
- private final DockingHandle dockableWest = new DockingHandle(DockingRegion.WEST, false);
- private final DockingHandle dockableNorth = new DockingHandle(DockingRegion.NORTH, false);
- private final DockingHandle dockableEast = new DockingHandle(DockingRegion.EAST, false);
- private final DockingHandle dockableSouth = new DockingHandle(DockingRegion.SOUTH, false);
-
- private final Map mouseOver = new HashMap<>();
-
- private DockingRegion rootRegion = null;
- private DockingRegion dockableRegion = null;
- private ToolbarLocation pinRegion = null;
-
- private final JFrame utilFrame;
- private final RootDockingPanelAPI targetRoot;
-
- // the dockable that we're currently trying to dock and is floating in a TempFloatingFrame
- private JPanel floating;
- // the dockable that the mouse is currently over, can be null
- private Dockable targetDockable = null;
-
- public boolean overTab = false;
-
- /**
- * Create a new instance of the DockingHandles
- *
- * @param utilFrame The utility frame to draw the handles on
- * @param root The root panel of the window we're drawing over
- */
- public DockingHandles(JFrame utilFrame, RootDockingPanelAPI root) {
- this.utilFrame = utilFrame;
-
- this.targetRoot = root;
-
- setupHandle(rootCenter);
- setupHandle(rootWest);
- setupHandle(rootNorth);
- setupHandle(rootEast);
- setupHandle(rootSouth);
-
- setupHandle(pinWest);
- setupHandle(pinEast);
- setupHandle(pinSouth);
-
- setupHandle(dockableCenter);
- setupHandle(dockableWest);
- setupHandle(dockableNorth);
- setupHandle(dockableEast);
- setupHandle(dockableSouth);
- }
-
- /**
- * Get the current region that we are moused over
- *
- * @return The current region, possibly null
- */
- public DockingRegion getDockableRegion() {
- return dockableRegion;
- }
-
- /**
- * Get the current root region that we are moused over
- *
- * @return The current root region, possibly null
- */
- public DockingRegion getRootRegion() {
- return rootRegion;
- }
-
- /**
- * Get the current pin region that we are moused over
- *
- * @return The current pin region, possibly null
- */
- public ToolbarLocation getPinningRegion() {
- return pinRegion;
- }
-
- /**
- * Set this docking handle active
- *
- * @param active Active state of handles
- */
- public void setActive(boolean active) {
- utilFrame.setVisible(active);
- }
-
- /**
- * Set the floating dockable
- *
- * @param dockable Dockable that is floating
- */
- public void setFloating(JPanel dockable) {
- floating = dockable;
- }
-
- private void setupHandle(DockingHandle label) {
- mouseOver.put(label, false);
- utilFrame.add(label);
- }
-
- private void setRootHandleLocations() {
- rootCenter.setVisible(targetRoot != null && targetRoot.getPanel() == null);
- rootWest.setVisible(targetRoot != null && targetRoot.getPanel() != null && isRegionAllowed(DockingRegion.WEST));
- rootNorth.setVisible(targetRoot != null && targetRoot.getPanel() != null && isRegionAllowed(DockingRegion.NORTH));
- rootEast.setVisible(targetRoot != null && targetRoot.getPanel() != null && isRegionAllowed(DockingRegion.EAST));
- rootSouth.setVisible(targetRoot != null && targetRoot.getPanel() != null && isRegionAllowed(DockingRegion.SOUTH));
-
- pinWest.setVisible(targetRoot != null && isPinningRegionAllowed(DockingRegion.WEST));
- pinEast.setVisible(targetRoot != null && isPinningRegionAllowed(DockingRegion.EAST));
- pinSouth.setVisible(targetRoot != null && isPinningRegionAllowed(DockingRegion.SOUTH));
-
- if (targetRoot != null) {
- Point location = targetRoot.getLocation();
- Dimension size = targetRoot.getSize();
- location.x += size.width / 2;
- location.y += size.height / 2;
-
- SwingUtilities.convertPointToScreen(location, targetRoot.getParent());
- SwingUtilities.convertPointFromScreen(location, utilFrame);
-
- setLocation(rootCenter, location.x, location.y);
- setLocation(rootWest, location.x - (size.width / 2) + rootHandleSpacing(rootWest), location.y);
- setLocation(rootNorth, location.x, location.y - (size.height / 2) + rootHandleSpacing(rootNorth));
- setLocation(rootEast, location.x + (size.width / 2) - rootHandleSpacing(rootEast), location.y);
- setLocation(rootSouth, location.x, location.y + (size.height / 2) - rootHandleSpacing(rootSouth));
-
- setLocation(pinWest, location.x - (size.width / 2) + rootHandleSpacing(pinWest), location.y - (size.height / 3));
- setLocation(pinEast, location.x + (size.width / 2) - rootHandleSpacing(pinEast), location.y - (size.height / 3));
- setLocation(pinSouth, location.x - (size.width / 3), location.y + (size.height / 2) - rootHandleSpacing(pinSouth));
- }
- }
-
- /**
- * Retrieve the spacing for the handle
- *
- * @param handle The handle label
- * @return width
- */
- private int handleSpacing(JLabel handle) {
- return handle.getWidth() + 8;
- }
-
- /**
- * Retrieve the spacing for the root handle
- *
- * @param handle The handle label
- * @return width
- */
- private int rootHandleSpacing(JLabel handle) {
- return handle.getWidth() + 16;
- }
-
- /**
- * set the specific Dockable target which we'll show a basic handle in the center of
- *
- * @param dockable target dockable
- */
- public void setTarget(Dockable dockable) {
- if (dockable == targetDockable) {
- return;
- }
-
- targetDockable = dockable;
-
- dockableCenter.setVisible(false);
- dockableWest.setVisible(false);
- dockableNorth.setVisible(false);
- dockableEast.setVisible(false);
- dockableSouth.setVisible(false);
- }
-
- private boolean isRegionAllowed(DockingRegion region) {
- if (floating instanceof DockedSimplePanel) {
- DockedSimplePanel panel = (DockedSimplePanel) this.floating;
- Dockable floating = panel.getWrapper().getDockable();
-
- if (floating.getStyle() == DockableStyle.BOTH) {
- return true;
- }
- if (region == DockingRegion.NORTH || region == DockingRegion.SOUTH) {
- return floating.getStyle() == DockableStyle.HORIZONTAL;
- }
- return floating.getStyle() == DockableStyle.VERTICAL;
- }
- return true;
- }
-
- private boolean isPinningRegionAllowed(DockingRegion region) {
- if (floating instanceof DockedTabbedPanel) {
- return false;
- }
- Dockable floating = ((DisplayPanel) this.floating).getWrapper().getDockable();
-
- if (!floating.isAutoHideAllowed()) {
- return false;
- }
-
- if (floating.getAutoHideStyle() == DockableStyle.BOTH) {
- return true;
- }
- if (region == DockingRegion.NORTH || region == DockingRegion.SOUTH) {
- return floating.getAutoHideStyle() == DockableStyle.HORIZONTAL;
- }
- return floating.getAutoHideStyle() == DockableStyle.VERTICAL;
- }
-
- public boolean isMouseOverHandle() {
- Point mousePos = MouseInfo.getPointerInfo().getLocation();
-
- SwingUtilities.convertPointFromScreen(mousePos, dockableCenter.getParent());
-
- Rectangle northSouth = new Rectangle(dockableNorth.getX(), dockableNorth.getY(), dockableNorth.getWidth(), dockableNorth.getHeight() * 2 + handleSpacing(dockableNorth) * 2);
- Rectangle westEast = new Rectangle(dockableWest.getX(), dockableWest.getY(), dockableWest.getWidth() * 2 + handleSpacing(dockableWest) * 2, dockableWest.getHeight());
-
- return northSouth.contains(mousePos) || westEast.contains(mousePos);
- }
-
- private void setDockableHandleLocations() {
- dockableCenter.setVisible(targetDockable != null);
- dockableWest.setVisible(targetDockable != null && isRegionAllowed(DockingRegion.WEST));
- dockableNorth.setVisible(targetDockable != null && isRegionAllowed(DockingRegion.NORTH));
- dockableEast.setVisible(targetDockable != null && isRegionAllowed(DockingRegion.EAST));
- dockableSouth.setVisible(targetDockable != null && isRegionAllowed(DockingRegion.SOUTH));
-
- if (targetDockable != null && ((Component) targetDockable).getParent() != null) {
- Point location = ((Component) targetDockable).getLocation();
- Dimension size = ((Component) targetDockable).getSize();
-
- // if this dockable is wrapped in a JScrollPane we need to set the handle to the center of the JScrollPane
- // not to the center of the dockable (which will more than likely be at a different location)
- if (targetDockable.isWrappableInScrollpane()) {
- Component parent = ((Component) targetDockable).getParent();
-
- while (parent != null && !(parent instanceof JScrollPane)) {
- parent = parent.getParent();
- }
-
- if (parent != null) {
- JScrollPane display = (JScrollPane) parent;
-
- location = display.getLocation();
- size = display.getSize();
- }
- }
-
- location.x += size.width / 2;
- location.y += size.height / 2;
-
- location.y -= (int) (DockingHandle.HANDLE_ICON_SIZE * (1.75/2));
-
- SwingUtilities.convertPointToScreen(location, ((Component) targetDockable).getParent());
- SwingUtilities.convertPointFromScreen(location, utilFrame);
-
- setLocation(dockableCenter, location.x, location.y);
- setLocation(dockableWest, location.x - handleSpacing(dockableWest), location.y);
- setLocation(dockableNorth, location.x, location.y - handleSpacing(dockableNorth));
- setLocation(dockableEast, location.x + handleSpacing(dockableEast), location.y);
- setLocation(dockableSouth, location.x, location.y + handleSpacing(dockableSouth));
-
- }
- }
-
- /**
- * update the positions of the handles
- *
- * @param screenPos New mouse position
- */
- public void update(Point screenPos) {
- setRootHandleLocations();
- setDockableHandleLocations();
-
- Point framePoint = new Point(screenPos);
- SwingUtilities.convertPointFromScreen(framePoint, utilFrame);
-
- rootRegion = null;
- dockableRegion = null;
- pinRegion = null;
-
- for (DockingHandle handle : mouseOver.keySet()) {
- boolean over = handle.isVisible() && handle.getBounds().contains(framePoint);
-
- mouseOver.put(handle, over);
-
- if (over) {
- if (handle.isRoot()) {
- rootRegion = handle.getRegion();
- }
- else if (handle.isPin()) {
- switch (handle.getRegion()) {
- case WEST:
- pinRegion = ToolbarLocation.WEST;
- break;
- case EAST:
- pinRegion = ToolbarLocation.EAST;
- break;
- case SOUTH:
- pinRegion = ToolbarLocation.SOUTH;
- break;
- }
- }
- else {
- dockableRegion = handle.getRegion();
- }
- }
- }
-
- utilFrame.revalidate();
- utilFrame.repaint();
- }
-
- private void setLocation(Component component, int x, int y) {
- component.setLocation(x - (HANDLE_ICON_SIZE / 2), y - (HANDLE_ICON_SIZE / 2));
- }
-
- /**
- * Paint the handles
- *
- * @param g Graphics instance to use
- */
- public void paint(Graphics g) {
- int centerX = dockableCenter.getX() + (dockableCenter.getWidth() / 2);
- int centerY = dockableCenter.getY() + (dockableCenter.getWidth() / 2);
-
- int spacing = handleSpacing(dockableCenter) - dockableCenter.getWidth();
- int half_icon = dockableCenter.getWidth() / 2;
- int one_and_a_half_icons = (int) (dockableCenter.getWidth() * 1.5);
-
- // create a polygon of the docking handles background
- Polygon poly = new Polygon(
- new int[] {
- centerX - half_icon - spacing,
- centerX + half_icon + spacing,
- centerX + half_icon + spacing,
- centerX + half_icon + (spacing * 2),
- centerX + one_and_a_half_icons + (spacing * 2),
- centerX + one_and_a_half_icons + (spacing * 2),
- centerX + half_icon + (spacing * 2),
- centerX + half_icon + spacing,
- centerX + half_icon + spacing,
- centerX - half_icon - spacing,
- centerX - half_icon - spacing,
- centerX - half_icon - (spacing * 2),
- centerX - one_and_a_half_icons - (spacing * 2),
- centerX - one_and_a_half_icons - (spacing * 2),
- centerX - half_icon - (spacing * 2),
- centerX - half_icon - spacing,
- centerX - half_icon - spacing
- },
- new int[] {
- centerY - one_and_a_half_icons - (spacing * 2),
- centerY - one_and_a_half_icons - (spacing * 2),
- centerY - half_icon - (spacing * 2),
- centerY - half_icon - spacing,
- centerY - half_icon - spacing,
- centerY + half_icon + spacing,
- centerY + half_icon + spacing,
- centerY + half_icon + (spacing * 2),
- centerY + one_and_a_half_icons + (spacing * 2),
- centerY + one_and_a_half_icons + (spacing * 2),
- centerY + half_icon + (spacing * 2),
- centerY + half_icon + spacing,
- centerY + half_icon + spacing,
- centerY - half_icon - spacing,
- centerY - half_icon - spacing,
- centerY - half_icon - (spacing * 2),
- centerY - one_and_a_half_icons - (spacing * 2),
- },
- 17
- );
-
- Color background = DockingSettings.getHandleBackground();//DockingProperties.getHandlesBackground();
- Color border = DockingSettings.getHandleForeground();//DockingProperties.getHandlesBackgroundBorder();
-
- Graphics2D g2 = (Graphics2D) g.create();
- Stroke dashed = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{3}, 0);
- g2.setStroke(dashed);
-
- // draw root handles
- paintHandle(g, g2, rootCenter);
- paintHandle(g, g2, rootEast);
- paintHandle(g, g2, rootWest);
- paintHandle(g, g2, rootNorth);
- paintHandle(g, g2, rootSouth);
-
- paintHandle(g, g2, pinWest);
- paintHandle(g, g2, pinEast);
- paintHandle(g, g2, pinSouth);
-
- // draw the dockable handles background over the root handles in case they overlap
- if (targetDockable != null) {
- // fill the dockable handles background
- g.setColor(background);
- g.fillPolygon(poly.xpoints, poly.ypoints, poly.npoints);
-
- // draw the dockable handles border
- g.setColor(border);
- g.drawPolygon(poly.xpoints, poly.ypoints, poly.npoints);
- }
-
- // draw the docking handles over the docking handles background
- paintHandle(g, g2, dockableCenter);
- paintHandle(g, g2, dockableEast);
- paintHandle(g, g2, dockableWest);
- paintHandle(g, g2, dockableNorth);
- paintHandle(g, g2, dockableSouth);
-
- g2.dispose();
- }
-
- private void paintHandle(Graphics g, Graphics2D g2, DockingHandle handle) {
- if (handle.isVisible()) {
- handle.paintHandle(g, g2, mouseOver.get(handle));
- }
- }
-}
diff --git a/docking-api/src/ModernDocking/floating/DockingOverlay.java b/docking-api/src/ModernDocking/floating/DockingOverlay.java
deleted file mode 100644
index e2cabd82..00000000
--- a/docking-api/src/ModernDocking/floating/DockingOverlay.java
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
-Copyright (c) 2022 Andrew Auclair
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
- */
-package ModernDocking.floating;
-
-import ModernDocking.Dockable;
-import ModernDocking.DockableStyle;
-import ModernDocking.DockingRegion;
-import ModernDocking.api.DockingAPI;
-import ModernDocking.api.RootDockingPanelAPI;
-import ModernDocking.internal.DisplayPanel;
-import ModernDocking.internal.DockingInternal;
-import ModernDocking.ui.DockingSettings;
-import ModernDocking.ui.ToolbarLocation;
-
-import javax.swing.*;
-import java.awt.*;
-
-/**
- * displays the overlay highlight of where the panel will be docked
- */
-public class DockingOverlay {
- // determines how close to the edge the user has to drag the panel before they see an overlay other than CENTER
- private static final double REGION_SENSITIVITY = 0.35;
-
- // the target root for this overlay, always the same
- private final RootDockingPanelAPI targetRoot;
-
- // the dockable that is currently floating in its own undecoarted frame
- private JPanel floating;
-
- // the target dockable that the mouse is currently over, could be null
- private Dockable targetDockable;
-
- // the region on the dockable that is being docked to, this comes from the handles? I think
- private DockingRegion dockableRegion;
-
- // the region on the root that is being docked to, this comes from the handles? I think
- private DockingRegion rootRegion;
- private ToolbarLocation pinToolbarLocation;
-
- // the top left location where the overlay starts
- private Point location = new Point(0, 0);
- // the total size of the overlay, used for drawing
- private Dimension size;
-
- private final DockingAPI docking;
- // the utility frame that this overlay belongs to
- private final JFrame utilFrame;
-
- // whether to draw this overlay, different from swing visibility because we're manually painting
- private boolean visible = false;
-
- // override for the visible flag, sometimes internally we don't want to draw but we might be active
- private boolean visibleOverride = false;
-
- private boolean overTab = false;
- public Rectangle targetTab;
- public boolean beforeTab = true;
-
- /**
- * Construct a new overlay for a utility frame and root panel
- *
- * @param utilFrame The utility frame this overlay covers
- * @param root The root of the frame under the utility frame
- */
- public DockingOverlay(DockingAPI docking, JFrame utilFrame, RootDockingPanelAPI root) {
- this.docking = docking;
- this.utilFrame = utilFrame;
-
- targetRoot = root;
- size = utilFrame.getSize();
- }
-
- /**
- * Set this overlay active. Sets the overlay to visible.
- *
- * @param active Should the overlay be displayed?
- */
- public void setActive(boolean active) {
- visible = active;
-
- floating = null;
- targetDockable = null;
- dockableRegion = null;
- rootRegion = null;
- size = new Dimension(0, 0);
- }
-
- /**
- * Set a reference to the dockable currently being floated
- *
- * @param dockable Current floating dockable
- */
- public void setFloating(JPanel dockable) {
- floating = dockable;
- }
-
- /**
- * Set the target dockable which is currently under the mouse position.
- *
- * @param dockable Target dockable under mouse
- */
- public void setTargetDockable(Dockable dockable) {
- targetDockable = dockable;
- }
-
- // check if the floating dockable is allowed to dock to this region
- private boolean isRegionAllowed(DockingRegion region) {
- if (floating instanceof DisplayPanel) {
- DisplayPanel panel = (DisplayPanel) this.floating;
- Dockable floating = panel.getWrapper().getDockable();
-
- if (floating.getStyle() == DockableStyle.BOTH) {
- return true;
- }
- if (region == DockingRegion.NORTH || region == DockingRegion.SOUTH) {
- return floating.getStyle() == DockableStyle.HORIZONTAL;
- }
- return floating.getStyle() == DockableStyle.VERTICAL;
- }
- return true;
- }
-
- public void update(Point screenPos) {
- // the last region that we calculated. used for painting
- DockingRegion lastSelectedRegion;
-
- if (targetRoot != null && rootRegion != null) {
- Point point = targetRoot.getLocation();
- Dimension size = targetRoot.getSize();
-
- point = SwingUtilities.convertPoint(targetRoot.getParent(), point, utilFrame);
-
- lastSelectedRegion = rootRegion;
-
- final double DROP_SIZE = 4;
-
- switch (rootRegion) {
- case WEST: {
- size = new Dimension((int) (size.width / DROP_SIZE), size.height);
- break;
- }
- case NORTH: {
- size = new Dimension(size.width, (int) (size.height / DROP_SIZE));
- break;
- }
- case EAST: {
- point.x += size.width - (size.width / DROP_SIZE);
- size = new Dimension((int) (size.width / DROP_SIZE), size.height);
- break;
- }
- case SOUTH: {
- point.y += size.height - (size.height / DROP_SIZE);
- size = new Dimension(size.width, (int) (size.height / DROP_SIZE));
- break;
- }
- }
-
- this.location = point;
- this.size = size;
- }
- else if (targetDockable != null && dockableRegion != null) {
- JComponent component = DockingInternal.get(docking).getWrapper(targetDockable).getDisplayPanel();
-
- Point point = component.getLocation();
- Dimension size = component.getSize();
-
- point = SwingUtilities.convertPoint(component.getParent(), point, utilFrame);
-
- lastSelectedRegion = dockableRegion;
-
- final double DROP_SIZE = 2;
-
- switch (dockableRegion) {
- case WEST: {
- size = new Dimension((int) (size.width / DROP_SIZE), size.height);
- break;
- }
- case NORTH: {
- size = new Dimension(size.width, (int) (size.height / DROP_SIZE));
- break;
- }
- case EAST: {
- point.x += size.width / DROP_SIZE;
- size = new Dimension((int) (size.width / DROP_SIZE), size.height);
- break;
- }
- case SOUTH: {
- point.y += size.height / DROP_SIZE;
- size = new Dimension(size.width, (int) (size.height / DROP_SIZE));
- break;
- }
- }
-
- this.location = point;
- this.size = size;
- }
- else if (targetDockable != null) {
- JComponent component = DockingInternal.get(docking).getWrapper(targetDockable).getDisplayPanel();
-
- Point framePoint = new Point(screenPos);
- SwingUtilities.convertPointFromScreen(framePoint, utilFrame);
-
- Point point = (component).getLocation();
- Dimension size = component.getSize();
-
- point = SwingUtilities.convertPoint(component.getParent(), point, utilFrame);
-
- double horizontalPct = (framePoint.x - point.x) / (double) size.width;
- double verticalPct = (framePoint.y - point.y) / (double) size.height;
-
- double horizontalEdgeDist = horizontalPct > 0.5 ? 1.0 - horizontalPct : horizontalPct;
- double verticalEdgeDist = verticalPct > 0.5 ? 1.0 - verticalPct : verticalPct;
-
- if (horizontalEdgeDist < verticalEdgeDist) {
- if (horizontalPct < REGION_SENSITIVITY && isRegionAllowed(DockingRegion.WEST)) {
- lastSelectedRegion = DockingRegion.WEST;
- size = new Dimension(size.width / 2, size.height);
- }
- else if (horizontalPct > (1.0 - REGION_SENSITIVITY) && isRegionAllowed(DockingRegion.EAST)) {
- lastSelectedRegion = DockingRegion.EAST;
- point.x += size.width / 2;
- size = new Dimension(size.width / 2, size.height);
- }
- }
- else {
- if (verticalPct < REGION_SENSITIVITY && isRegionAllowed(DockingRegion.NORTH)) {
- lastSelectedRegion = DockingRegion.NORTH;
- size = new Dimension(size.width, size.height / 2);
- }
- else if (verticalPct > (1.0 - REGION_SENSITIVITY) && isRegionAllowed(DockingRegion.SOUTH)) {
- lastSelectedRegion = DockingRegion.SOUTH;
- point.y += size.height / 2;
- size = new Dimension(size.width, size.height / 2);
- }
- }
-
- this.location = point;
- this.size = size;
- }
- else if (targetRoot != null) {
- JComponent component = targetRoot;
-
- Point point = (component).getLocation();
- Dimension size = component.getSize();
-
- point = SwingUtilities.convertPoint(component.getParent(), point, utilFrame);
-
- lastSelectedRegion = DockingRegion.CENTER;
-
- this.location = point;
- this.size = size;
- }
-
- utilFrame.revalidate();
- utilFrame.repaint();
- }
-
- /**
- * get the region that we're currently displaying an overlay for
- *
- * @param screenPos Screen position to find the region for
- * @return Region of the given screen position
- */
- public DockingRegion getRegion(Point screenPos) {
- // if we're moused over a root handle, use the region of the handle
- if (rootRegion != null) {
- return rootRegion;
- }
-
- // if we're moused over a dockable handle, use the region of the handle
- if (dockableRegion != null) {
- return dockableRegion;
- }
-
- // force the region to always be the center if the root is empty
- if (targetRoot.getPanel() == null) {
- return DockingRegion.CENTER;
- }
-
- // use the target dockable if we have one, otherwise use the root
- JComponent component;
- if (targetDockable != null) {
- component = DockingInternal.get(docking).getWrapper(targetDockable).getDisplayPanel();
- }
- else {
- component = targetRoot;
- }
-
- // find the mouse position over the component
- Point framePoint = new Point(screenPos);
- SwingUtilities.convertPointFromScreen(framePoint, component.getParent());
-
- Point point = (component).getLocation();
- Dimension size = component.getSize();
-
- // calculate a percentage along the horizontal axis and vertical axis. we need to determine if we're in the center or one of the other 4 regions
- double horizontalPct = (framePoint.x - point.x) / (double) size.width;
- double verticalPct = (framePoint.y - point.y) / (double) size.height;
-
- // find out if we are in a horizontal region (NORTH / SOUTH) or a vertical region (WEST / EAST)
- double horizontalEdgeDist = horizontalPct > 0.5 ? 1.0 - horizontalPct : horizontalPct;
- double verticalEdgeDist = verticalPct > 0.5 ? 1.0 - verticalPct : verticalPct;
-
- // if we're close to the sides than we are to the bottom or bottom, then we might be in the WEST or EAST region
- if (horizontalEdgeDist < verticalEdgeDist) {
- // horizontal percentage is less than our sensitivity for the edge, we're in the WEST region
- if (horizontalPct < REGION_SENSITIVITY && isRegionAllowed(DockingRegion.WEST)) {
- return DockingRegion.WEST;
- }
- // horizontal percentage is greater than our sensitivity for the edge, we're in the EAST region
- else if (horizontalPct > (1.0 - REGION_SENSITIVITY) && isRegionAllowed(DockingRegion.EAST)) {
- return DockingRegion.EAST;
- }
- // we didn't exceed the sensitivity in the WEST or EAST regions. we're in the CENTER region
- }
- else {
- // vertical percentage is less than our sensitivity for the edge, we're in the NORTH region
- if (verticalPct < REGION_SENSITIVITY && isRegionAllowed(DockingRegion.NORTH)) {
- return DockingRegion.NORTH;
- }
- // vertical percentage is greater than our sensitivity for the edge, we're in the SOUTH region
- else if (verticalPct > (1.0 - REGION_SENSITIVITY) && isRegionAllowed(DockingRegion.SOUTH)) {
- return DockingRegion.SOUTH;
- }
- // we didn't exceed the sensitivity in the NORTH or SOUTH regions. we're in the CENTER region
- }
- return DockingRegion.CENTER;
- }
-
- public ToolbarLocation getToolbarLocation() {
- return pinToolbarLocation;
- }
-
- /**
- * Check if the floating dockable is targeting a root docking handle
- *
- * @return True if the currently selected docking handle target or area is on the root panel
- */
- public boolean isDockingToRoot() {
- // force the region to always be the center if the root is empty
- if (targetRoot.getPanel() == null) {
- return true;
- }
- return rootRegion != null;
- }
-
- /**
- * Checks if we're docking to another dockable
- *
- * @return True if the target dockable or dockable region are not null
- */
- public boolean isDockingToDockable() {
- return dockableRegion != null || targetDockable != null;
- }
-
- public boolean isDockingToPin() {
- return pinToolbarLocation != null;
- }
-
- // set a region from the handles if we're moused over a root handle
- public void setTargetRootRegion(DockingRegion region) {
- rootRegion = region;
-
- // we should only be visible if we're docking to a root or dockable. otherwise the overlay should be hidden.
- visibleOverride = !isDockingToRoot() && !isDockingToDockable();
- }
-
- // set a region from the handles if we're moused over a dockable handle
- public void setTargetDockableRegion(DockingRegion region) {
- if (overTab) {
- System.out.println(dockableRegion);
- return;
- }
- dockableRegion = region;
-
- // we should only be visible if we're docking to a root or dockable. otherwise the overlay should be hidden.
- visibleOverride = !isDockingToRoot() && !isDockingToDockable();
- }
-
- public void setOverTab(boolean overTab) {
- this.overTab = overTab;
-
- if (overTab) {
- dockableRegion = DockingRegion.CENTER;
- }
- }
-
- public void setTargetPinRegion(ToolbarLocation region) {
- pinToolbarLocation = region;
-
- // we should only be visible if we're docking to a root or dockable. otherwise the overlay should be hidden.
- visibleOverride = !isDockingToRoot() && !isDockingToDockable();
- }
-
- /**
- * Paint the docking overlay if visible
- *
- * @param g Graphics to use for painting
- */
- public void paint(Graphics g) {
- if (!isDockingToRoot() && !isDockingToDockable()) {
- return;
- }
-
- if (isDockingToPin()) {
- return;
- }
-
- if (visible) {
- g.setColor(DockingSettings.getOverlayBackground());
- g.fillRect(location.x, location.y, size.width, size.height);
-
- if (overTab) {
- g.fillRect(targetTab.x, targetTab.y, targetTab.width, targetTab.height);
- }
- }
- }
-}
diff --git a/docking-api/src/ModernDocking/floating/DockingUtilsFrame.java b/docking-api/src/ModernDocking/floating/DockingUtilsFrame.java
deleted file mode 100644
index 851a2bcf..00000000
--- a/docking-api/src/ModernDocking/floating/DockingUtilsFrame.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
-Copyright (c) 2022 Andrew Auclair
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
- */
-package ModernDocking.floating;
-
-import ModernDocking.Dockable;
-import ModernDocking.DockingRegion;
-import ModernDocking.api.DockingAPI;
-import ModernDocking.api.RootDockingPanelAPI;
-import ModernDocking.ui.ToolbarLocation;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
-import java.awt.event.WindowEvent;
-import java.awt.event.WindowListener;
-
-/**
- * utility frame that is used to draw handles and overlay highlighting
- */
-public class DockingUtilsFrame extends JFrame implements ComponentListener, WindowListener {
- /**
- * Handles display for this utility frame
- */
- private final DockingHandles handles;
- /**
- * Overlay display for this utility frame
- */
- private final DockingOverlay overlay;
- /**
- * The window from the application that this utility frame is directly over
- */
- private final Window referenceDockingWindow;
-
- private boolean overTab = false;
-
- /**
- * create a new DockingUtilsFrame with a frame and its root panel
- *
- * @param referenceDockingWindow Window that this utility frame is tied to
- * @param root The root of the tied window
- */
- public DockingUtilsFrame(DockingAPI docking, Window referenceDockingWindow, RootDockingPanelAPI root) {
- setLayout(null); // don't use a layout manager for this custom painted frame
- setUndecorated(true); // don't want to see a frame border
- setType(Type.UTILITY); // hide this frame from the task bar
-
- setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
-
- setBackground(new Color(0, 0, 0, 0)); // don't want a background for this frame
- getRootPane().setBackground(new Color(0, 0, 0, 0)); // don't want a background for the root pane either. Workaround for a FlatLaf macOS issue.
- getContentPane().setBackground(new Color(0, 0, 0, 0)); // don't want a background for the content frame either.
-
- try {
- if (getContentPane() instanceof JComponent) {
- ((JComponent) getContentPane()).setOpaque(false);
- }
- }
- catch (IllegalComponentStateException e) {
- // TODO we need to handle platforms that don't support translucent display
- // this exception indicates that the platform doesn't support changing the opacity
- }
-
- setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); // always moving a dockable when this frame is visible. use the moving cursor to indicate such
-
- // remember the reference docking frame and create the handles and over components
- this.referenceDockingWindow = referenceDockingWindow;
-
- handles = new DockingHandles(this, root);
- overlay = new DockingOverlay(docking, this, root);
-
- }
-
- @Override
- public void addNotify() {
- super.addNotify();
-
- // listen for the reference frame to move and resize. this frame must match it exactly
- referenceDockingWindow.addComponentListener(this);
- referenceDockingWindow.addWindowListener(this);
-
- SwingUtilities.invokeLater(this::setSizeAndLocation);
- }
-
- @Override
- public void removeNotify() {
- referenceDockingWindow.removeComponentListener(this);
- referenceDockingWindow.removeWindowListener(this);
-
- super.removeNotify();
- }
-
- /**
- * set the current dockable that the mouse is over, can be null
- *
- * @param target Target dockable
- */
- public void setTargetDockable(Dockable target) {
- // don't change the target dockable if we're over a handle
- if (!handles.isMouseOverHandle()) {
- handles.setTarget(target);
- overlay.setTargetDockable(target);
- }
-
- overlay.setTargetRootRegion(handles.getRootRegion());
- overlay.setTargetDockableRegion(handles.getDockableRegion());
- overlay.setTargetPinRegion(handles.getPinningRegion());
- }
-
- /**
- * set the floating panel, doesn't change once the panel is first floated
- *
- * @param floating Floating dockable
- */
- public void setFloating(JPanel floating) {
- handles.setFloating(floating);
- overlay.setFloating(floating);
- }
-
- public void setOverTab(boolean overTab, Rectangle rect, boolean last) {
- this.overTab = overTab;
-
- overlay.setTargetDockableRegion(overTab ? DockingRegion.CENTER : null);
- handles.overTab = overTab;
-// overlay.overTab = overTab;
- overlay.setOverTab(overTab);
- overlay.targetTab = rect;
- overlay.beforeTab = !last;
- }
-
- /**
- * update the overlay with the current mouse position
- *
- * @param screenPos New mouse position
- */
- public void update(Point screenPos) {
- handles.update(screenPos);
- overlay.update(screenPos);
- }
-
- /**
- * activate the overlays, sets them to visible
- *
- * @param active New active state
- */
- public void setActive(boolean active) {
- handles.setActive(active);
- overlay.setActive(active);
- }
-
- /**
- * get the current region from the overlay. this is either a root region or dockable region
- *
- * @param screenPos Screen position to find region of
- * @return The root or dockable region at the screen position
- */
- public DockingRegion getRegion(Point screenPos) {
- return overlay.getRegion(screenPos);
- }
-
- public ToolbarLocation getToolbarLocation() {
- return overlay.getToolbarLocation();
- }
- /**
- * checks if docking to the root. This is only possible when the mouse is over a root docking handle
- *
- * @return Is docking to root
- */
- public boolean isDockingToRoot() {
- return overlay.isDockingToRoot();
- }
-
- public boolean isDockingToPin() {
- return overlay.isDockingToPin();
- }
-
- /**
- * checks if docking to a dockable. Returns false if isDockingToRoot() is true.
- *
- * @return false if not over a frame
- */
- public boolean isDockingToDockable() {
- return overlay.isDockingToDockable();
- }
-
- @Override
- public void componentResized(ComponentEvent e) {
- SwingUtilities.invokeLater(this::setSizeAndLocation);
- }
-
- @Override
- public void componentMoved(ComponentEvent e) {
- SwingUtilities.invokeLater(this::setSizeAndLocation);
- }
-
- @Override
- public void componentShown(ComponentEvent e) {
- SwingUtilities.invokeLater(this::setSizeAndLocation);
- }
-
- @Override
- public void componentHidden(ComponentEvent e) {
- }
-
- @Override
- public void paint(Graphics g) {
- // nothing to really paint, but this will give us a clean slate
- super.paint(g);
-
- // paint the handles and overlays. nothing is painted if they aren't visible
- if (!overTab) {
- handles.paint(g);
- }
- overlay.paint(g);
- }
-
- private void setSizeAndLocation() {
- int padding = (int) (DockingHandle.HANDLE_ICON_SIZE * 1.75);
-
- Point location = new Point(referenceDockingWindow.getLocationOnScreen());
- Dimension size = new Dimension(referenceDockingWindow.getSize());
-
- location.x -= padding;
- location.y -= padding;
-
- size.width += padding * 2;
- size.height += padding * 2;
-
- // set location and size based on the reference docking frame
- setLocation(location);
- setSize(size);
- }
-
- @Override
- public void windowOpened(WindowEvent e) {
- }
-
- @Override
- public void windowClosing(WindowEvent e) {
- }
-
- @Override
- public void windowClosed(WindowEvent e) {
- dispose();
- }
-
- @Override
- public void windowIconified(WindowEvent e) {
- }
-
- @Override
- public void windowDeiconified(WindowEvent e) {
- }
-
- @Override
- public void windowActivated(WindowEvent e) {
- }
-
- @Override
- public void windowDeactivated(WindowEvent e) {
- }
-}
diff --git a/docking-api/src/ModernDocking/floating/FloatListener.java b/docking-api/src/ModernDocking/floating/FloatListener.java
index da62c9e8..9a33c01b 100644
--- a/docking-api/src/ModernDocking/floating/FloatListener.java
+++ b/docking-api/src/ModernDocking/floating/FloatListener.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2022 Andrew Auclair
+Copyright (c) 2024 Andrew Auclair
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,525 +21,207 @@ of this software and associated documentation files (the "Software"), to deal
*/
package ModernDocking.floating;
-import ModernDocking.Dockable;
-import ModernDocking.DockingRegion;
import ModernDocking.api.DockingAPI;
import ModernDocking.api.RootDockingPanelAPI;
-import ModernDocking.internal.*;
+import ModernDocking.internal.DisplayPanel;
+import ModernDocking.internal.DockedTabbedPanel;
+import ModernDocking.internal.DockingComponentUtils;
import ModernDocking.layouts.WindowLayout;
-import ModernDocking.settings.Settings;
-import ModernDocking.ui.DockingHeaderUI;
import javax.swing.*;
import java.awt.*;
-import java.awt.Dialog.ModalityType;
import java.awt.datatransfer.StringSelection;
import java.awt.dnd.*;
-import java.util.List;
-import java.util.*;
-
-/**
- * Listener responsible for tracking dockables both when they are first dragged and while being dragged
- */
-public class FloatListener extends DragSourceAdapter implements DragSourceListener, DragSourceMotionListener {
- /**
- * Flag indicating if there is a dockable currently floating
- */
- private static boolean isFloating = false;
-
- public static boolean isFloating() { return isFloating; }
-
- private static boolean isOverTab = false;
-
- // current floating dockable
- private final JPanel source;
- private JPanel floatingPanel;
+public abstract class FloatListener extends DragSourceAdapter implements DragSourceMotionListener, DragSourceListener {
private final DockingAPI docking;
+ private final JPanel panel;
+ private final JComponent dragComponent;
// our drag source to support dragging the dockables
private final DragSource dragSource = new DragSource();
- private final Component draggedObject;
-
- private Point dragOffset = new Point(0, 0);
- private TempFloatingFrame floatingFrame;
-
- private static final Map utilFrames = new HashMap<>();
-
- private DockingUtilsFrame activeUtilsFrame = null;
- private static Window windowToDispose = null;
+ private Point dragComponentDragOffset = new Point();
- private Window currentTopWindow = null;
- private Window currentTargetWindow = null;
private Window originalWindow;
+ private WindowLayout originalWindowLayout;
+ private JFrame floatingFrame;
- private WindowLayout windowLayout;
-
- private ModalityType modalityType = ModalityType.MODELESS;
+ private Window currentWindow;
+ private FloatUtilsFrame currentUtilFrame;
+ private DragGestureRecognizer alternateDragGesture;
public FloatListener(DockingAPI docking, DisplayPanel panel) {
this(docking, panel, (JComponent) panel.getWrapper().getHeaderUI());
}
- public FloatListener(DockingAPI docking, DockedTabbedPanel tabs, JComponent dragSource) {
- this(docking, (JPanel) tabs, dragSource);
+ public FloatListener(DockingAPI docking, DisplayPanel panel, JComponent dragComponent) {
+ this(docking, (JPanel) panel, dragComponent);
+ }
+
+ public FloatListener(DockingAPI docking, DockedTabbedPanel tabs, JComponent dragComponent) {
+ this(docking, (JPanel) tabs, dragComponent);
}
- private FloatListener(DockingAPI docking, JPanel dockable, JComponent dragSource) {
- this.source = dockable;
+ private FloatListener(DockingAPI docking, JPanel panel, JComponent dragComponent) {
this.docking = docking;
+ this.panel = panel;
+ this.dragComponent = dragComponent;
- draggedObject = dragSource;
-
- if (draggedObject != null) {
- this.dragSource.addDragSourceMotionListener(FloatListener.this);
-
- this.dragSource.createDefaultDragGestureRecognizer(dragSource, DnDConstants.ACTION_MOVE, dge -> {
- if (isFloating) {
- return;
- }
- try {
- if (source instanceof DockedTabbedPanel) {
- Point mousePos = new Point(dge.getDragOrigin());
- SwingUtilities.convertPointToScreen(mousePos, draggedObject);
-
- DockedTabbedPanel tabs = (DockedTabbedPanel) source;
-
- // if this tabbed panel is empty then remove the listeners
- if (tabs.getDockables().isEmpty()) {
- removeListeners();
- return;
- }
-
- int targetTabIndex = tabs.getTargetTabIndex(mousePos);
-
- if (targetTabIndex != -1) {
- floatingPanel = tabs.getDockables().get(targetTabIndex).getDisplayPanel();
- }
- else if (tabs.isDraggingFromTabGutter(mousePos)) {
- floatingPanel = tabs;
- }
- else {
- DockingHeaderUI headerUI = tabs.getDockables().get(tabs.getSelectedTabIndex()).getHeaderUI();
- JPanel panel = (JPanel) headerUI;
-
- if (panel.contains(mousePos)) {
- floatingPanel = tabs.getDockables().get(tabs.getSelectedTabIndex()).getDisplayPanel();
- }
- else {
- return;
- }
- }
- }
- this.dragSource.startDrag(dge, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), new StringSelection(""), FloatListener.this);
- }
- catch (InvalidDnDOperationException ignored) {
- // someone beat us to it
- return;
- }
- mouseDragStarted(dge.getDragOrigin());
-
- if (originalWindow instanceof JDialog) {
- modalityType = ((JDialog) originalWindow).getModalityType();
-
- ((JDialog) originalWindow).setModalityType(ModalityType.MODELESS);
-
- // Set all of these as invokeLater to force the order they happen in
- SwingUtilities.invokeLater(() -> {
- // check that the floating frame still exists since we invoked later and time might have passed
- if (floatingFrame != null) {
- floatingFrame.toFront();
- }
- });
- SwingUtilities.invokeLater(() -> {
- // check that the utils frame still exists since we invoked later and time might have passed
- if (activeUtilsFrame != null) {
- activeUtilsFrame.toFront();
- }
- });
- }
- });
+ if (dragComponent != null) {
+ dragSource.addDragSourceMotionListener(this);
+ dragSource.createDefaultDragGestureRecognizer(dragComponent, DnDConstants.ACTION_MOVE, this::startDrag);
}
}
- public void removeListeners() {
- dragSource.removeDragSourceMotionListener(this);
+ public void addAlternateDragSource(JComponent dragComponent, DragGestureListener listener) {
+ alternateDragGesture = dragSource.createDefaultDragGestureRecognizer(dragComponent, DnDConstants.ACTION_MOVE, listener);
}
- public static void registerDockingWindow(DockingAPI docking, Window window, RootDockingPanelAPI root) {
- utilFrames.put(window, new DockingUtilsFrame(docking, window, root));
+ public void removeAlternateDragSource(DragGestureListener listener) {
+ alternateDragGesture.removeDragGestureListener(listener);
+ alternateDragGesture = null;
}
- public static void deregisterDockingWindow(Window window) {
- utilFrames.remove(window);
+ public JPanel getPanel() {
+ return panel;
}
- private void updateFramePosition(Point mousePosOnScreen) {
- // update the frames position to our mouse position
- Point framePos = new Point(mousePosOnScreen.x - dragOffset.x, mousePosOnScreen.y - dragOffset.y);
- floatingFrame.setLocation(framePos);
-
- // find the frame at our current position
- Window frame = DockingComponentUtils.findRootAtScreenPos(docking, mousePosOnScreen);
+ protected abstract boolean allowDrag(DragGestureEvent dragGestureEvent);
- // findRootAtScreenPos has a tendency to find the last added frame at the position. meaning it ignores Z order. override it here because we know better.
- if (currentTopWindow != null && currentTopWindow.getBounds().contains(mousePosOnScreen)) {
- frame = currentTopWindow;
+ public void startDrag(DragGestureEvent dragGestureEvent) {
+ // if there is already a floating panel, don't float this one
+ if (Floating.isFloating()) {
+ return;
}
- boolean isModal = modalityType == ModalityType.TOOLKIT_MODAL || modalityType == ModalityType.APPLICATION_MODAL;
-
- // change overlays and bring frames to front if we move over a new frame
- if (frame != currentTargetWindow && !isModal) {
- currentTargetWindow = frame;
- currentTopWindow = frame;
-
- changeFrameOverlays(frame);
+ if (!allowDrag(dragGestureEvent)) {
+ return;
}
- Dockable dockable = DockingComponentUtils.findDockableAtScreenPos(mousePosOnScreen, currentTopWindow);
-
- if (activeUtilsFrame != null) {
- activeUtilsFrame.setFloating(floatingPanel);
- activeUtilsFrame.setTargetDockable(dockable);
- activeUtilsFrame.update(mousePosOnScreen);
+ try {
+ dragSource.startDrag(dragGestureEvent, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), new StringSelection(""), this);
+ }
+ catch (InvalidDnDOperationException ignored) {
+ // someone beat us to it
+ return;
}
- CustomTabbedPane tabbedPane = DockingComponentUtils.findTabbedPaneAtPos(mousePosOnScreen, currentTopWindow);
-
- if (activeUtilsFrame != null) {
- boolean overTab = dockable == null && tabbedPane != null && floatingPanel instanceof DisplayPanel;
-
- if (overTab) {
- int targetTabIndex = tabbedPane.getTargetTabIndex(mousePosOnScreen, true);
-
- Rectangle boundsAt;
- boolean last = false;
-
- if (targetTabIndex != -1) {
- boundsAt = tabbedPane.getBoundsAt(targetTabIndex);
-
- Point p = new Point(boundsAt.x, boundsAt.y);
- SwingUtilities.convertPointToScreen(p, tabbedPane);
- SwingUtilities.convertPointFromScreen(p, activeUtilsFrame);
- boundsAt.x = p.x;
- boundsAt.y = p.y;
+ currentWindow = null;
- boundsAt.width /=2;
- }
- else {
- boundsAt = tabbedPane.getBoundsAt(tabbedPane.getTabCount() - 1);
+ dragStarted(dragGestureEvent.getDragOrigin());
- Point tabPoint = new Point(tabbedPane.getX(), tabbedPane.getY());
- SwingUtilities.convertPointToScreen(tabPoint, tabbedPane.getParent());
+ Floating.setFloating(true);
- Point boundsPoint = new Point(boundsAt.x, boundsAt.y);
- SwingUtilities.convertPointToScreen(boundsPoint, tabbedPane);
+ Point mouseOnScreen = new Point(dragGestureEvent.getDragOrigin());
+ SwingUtilities.convertPointToScreen(mouseOnScreen, dragGestureEvent.getComponent());
- int widthToAdd = boundsAt.width;
+ SwingUtilities.invokeLater(() -> updateFramePosition(mouseOnScreen));
+ }
- if (boundsPoint.x + (boundsAt.width * 2) >= tabPoint.x + tabbedPane.getWidth()) {
- boundsAt.width = Math.abs((tabPoint.x + tabbedPane.getWidth()) - (boundsPoint.x + boundsAt.width));
- }
+ private void dragStarted(Point dragOrigin) {
+ dragComponentDragOffset = new Point(dragOrigin);
- SwingUtilities.convertPointFromScreen(boundsPoint, activeUtilsFrame);
+ // force the drag offset to be inset from the edge slightly
+ dragComponentDragOffset.y = Math.max(5, dragComponentDragOffset.y);
+ dragComponentDragOffset.x = Math.max(5, dragComponentDragOffset.x);
- boundsAt.x = boundsPoint.x + widthToAdd;
- boundsAt.y = boundsPoint.y;
+ originalWindow = getOriginalWindow();
- last = true;
- }
+ originalWindowLayout = docking.getDockingState().getWindowLayout(originalWindow);
- activeUtilsFrame.setOverTab(true, boundsAt, last);
- activeUtilsFrame.update(mousePosOnScreen);
- floatingFrame.setVisible(false);
- }
- else if (isOverTab) {
- activeUtilsFrame.setOverTab(false, null, false);
- floatingFrame.setVisible(true);
+ floatingFrame = createFloatingFrame();
- reorderWindows();
- }
+ undock();
- isOverTab = overTab;
- }
+ DockingComponentUtils.removeIllegalFloats(docking, originalWindow);
- if (tabbedPane != null && tabbedPane.getSelectedComponent() instanceof DisplayPanel) {
- DisplayPanel panel = (DisplayPanel) tabbedPane.getSelectedComponent();
+ RootDockingPanelAPI currentRoot = DockingComponentUtils.rootForWindow(docking, originalWindow);
- if (activeUtilsFrame != null) {
- activeUtilsFrame.setFloating(floatingPanel);
- activeUtilsFrame.setTargetDockable(panel.getWrapper().getDockable());
- activeUtilsFrame.update(mousePosOnScreen);
- }
+ if (currentRoot.isEmpty()) {
+ originalWindow.setVisible(false);
}
}
- private void changeFrameOverlays(Window newWindow) {
- if (activeUtilsFrame != null) {
- activeUtilsFrame.setActive(false);
- activeUtilsFrame = null;
- }
-
- if (newWindow != null) {
- activeUtilsFrame = utilFrames.get(newWindow);
-
- if (currentTopWindow != null && floatingFrame != null && activeUtilsFrame != null) {
- Point mousePos = MouseInfo.getPointerInfo().getLocation();
-
- activeUtilsFrame.setFloating(floatingPanel);
- activeUtilsFrame.update(mousePos);
- activeUtilsFrame.setActive(true);
-
- reorderWindows();
- }
- }
+ public void removeListeners() {
+ dragSource.removeDragSourceMotionListener(this);
}
- private void reorderWindows() {
- // Set all of these as invokeLater to force the order they happen in
- SwingUtilities.invokeLater(() -> {
- // check that the current top frame still exists since we invoked later and time might have passed
- if (currentTopWindow != null) {
- currentTopWindow.toFront();
- }
- });
- SwingUtilities.invokeLater(() -> {
- // check that the floating frame still exists since we invoked later and time might have passed
- if (floatingFrame != null) {
- floatingFrame.toFront();
- }
- });
- SwingUtilities.invokeLater(() -> {
- // check that the utils frame still exists since we invoked later and time might have passed
- if (activeUtilsFrame != null) {
- activeUtilsFrame.toFront();
- }
- });
+ @Override
+ public void dragMouseMoved(DragSourceDragEvent event) {
+ if (!Floating.isFloating()) {
+ return;
+ }
+ SwingUtilities.invokeLater(() -> updateFramePosition(event.getLocation()));
}
- public void mouseDragStarted(Point point) {
- isFloating = true;
-
- dragOffset = point;
-
- // force the drag offset to be inset from the edge slightly
- dragOffset.y = Math.max(5, dragOffset.y);
- dragOffset.x = Math.max(5, dragOffset.x);
-
- currentTargetWindow = null;
-
- // make sure we are still using the mouse press point, not the current mouse position which might not be over the frame anymore
- Point mousePos = new Point(point);
- SwingUtilities.convertPointToScreen(mousePos, draggedObject);
+ private void updateFramePosition(Point mousePosOnScreen) {
+ // update the frames position to our mouse position
+ Point framePos = new Point(mousePosOnScreen.x - dragComponentDragOffset.x, mousePosOnScreen.y - dragComponentDragOffset.y);
+ floatingFrame.setLocation(framePos);
- if (source instanceof DisplayPanel) {
- originalWindow = ((DisplayPanel) source).getWrapper().getWindow();
- floatingPanel = source;
- }
- else {
- originalWindow = ((DockedTabbedPanel) source).getDockables().get(0).getWindow();
- }
- windowLayout = docking.getDockingState().getWindowLayout(originalWindow);
+ checkForFrameSwitch(mousePosOnScreen);
+ }
- RootDockingPanelAPI currentRoot = DockingComponentUtils.rootForWindow(docking, originalWindow);
+ private void checkForFrameSwitch(Point mousePosOnScreen) {
+ // find the frame at our current position
+ Window frame = DockingComponentUtils.findRootAtScreenPos(docking, mousePosOnScreen);
- if (floatingPanel instanceof DisplayPanel) {
- if (Settings.alwaysDisplayTabsMode(((DisplayPanel) floatingPanel).getWrapper().getDockable())) {
- floatingFrame = new TempFloatingFrame(Collections.singletonList(((DisplayPanel) floatingPanel).getWrapper()), 0, source, floatingPanel.getSize());
+ if (frame != currentWindow) {
+ if (currentUtilFrame != null) {
+ currentUtilFrame.deactivate();
}
- else {
- floatingFrame = new TempFloatingFrame(((DisplayPanel) floatingPanel).getWrapper(), source, floatingPanel.getSize());
- }
-
- docking.undock(((DisplayPanel) floatingPanel).getWrapper().getDockable());
- }
- else {
- DockedTabbedPanel tabs = (DockedTabbedPanel) floatingPanel;
- List wrappers = new ArrayList<>(tabs.getDockables());
+ currentWindow = frame;
- floatingFrame = new TempFloatingFrame(wrappers, tabs.getSelectedTabIndex(), source, floatingPanel.getSize());
+ currentUtilFrame = Floating.frameForWindow(currentWindow);
- for (DockableWrapper wrapper : wrappers) {
- docking.undock(wrapper.getDockable());
+ if (currentUtilFrame != null) {
+ currentUtilFrame.activate(this, floatingFrame, dragSource, mousePosOnScreen);
}
}
+ }
- DockingComponentUtils.removeIllegalFloats(docking, originalWindow);
-
- if (originalWindow != null && currentRoot != null && currentRoot.getPanel() == null && docking.canDisposeWindow(originalWindow)) {
- windowToDispose = originalWindow;
- windowToDispose.setVisible(false);
+ @Override
+ public void dragDropEnd(DragSourceDropEvent event) {
+ if (!Floating.isFloating()) {
+ return;
}
+ dropFloatingPanel(event.getLocation());
- if (originalWindow != windowToDispose) {
- currentTopWindow = originalWindow;
- currentTargetWindow = originalWindow;
- activeUtilsFrame = utilFrames.get(originalWindow);
- }
+ RootDockingPanelAPI currentRoot = DockingComponentUtils.rootForWindow(docking, originalWindow);
- if (activeUtilsFrame != null) {
- activeUtilsFrame.setFloating(floatingPanel);
- activeUtilsFrame.update(mousePos);
- activeUtilsFrame.setActive(true);
- activeUtilsFrame.toFront();
+ if (currentRoot.isEmpty() && docking.canDisposeWindow(originalWindow)) {
+ originalWindow.dispose();
}
- docking.getAppState().setPaused(true);
+ Floating.setFloating(false);
}
- private void dropFloatingPanel() {
- docking.getAppState().setPaused(false);
-
- Point mousePos = MouseInfo.getPointerInfo().getLocation();
-
- Point point = MouseInfo.getPointerInfo().getLocation();
-
- RootDockingPanelAPI root = currentTopWindow == null ? null : DockingComponentUtils.rootForWindow(docking, currentTopWindow);
-
- DockingPanel dockingPanel = DockingComponentUtils.findDockingPanelAtScreenPos(point, currentTopWindow);
- Dockable dockableAtPos = DockingComponentUtils.findDockableAtScreenPos(point, currentTopWindow);
-
-// Dockable dockableAtPos = activeUtilsFrame.getTargetDockable();
-
- DockingRegion region = activeUtilsFrame != null ? activeUtilsFrame.getRegion(mousePos) : DockingRegion.CENTER;
-
- if (floatingPanel instanceof DisplayPanel) {
- DockableWrapper floatingDockable = ((DisplayPanel) this.floatingPanel).getWrapper();
-
- if (activeUtilsFrame != null && activeUtilsFrame.isDockingToPin()) {
- docking.unpinDockable(floatingDockable.getDockable(), activeUtilsFrame.getToolbarLocation(), currentTopWindow, root);
- }
- else if (root != null && activeUtilsFrame != null && activeUtilsFrame.isDockingToRoot()) {
- docking.dock(floatingDockable.getDockable(), currentTopWindow, region, 0.25);
- }
- else if (floatingDockable.getDockable().isLimitedToRoot() && floatingDockable.getRoot() != root) {
- docking.getDockingState().restoreWindowLayout(originalWindow, windowLayout);
- }
- else if (dockableAtPos != null && currentTopWindow != null && dockingPanel != null && activeUtilsFrame != null && activeUtilsFrame.isDockingToDockable()) {
- docking.dock(floatingDockable.getDockable(), dockableAtPos, region);
- }
- else if (root != null && region != DockingRegion.CENTER && activeUtilsFrame == null) {
- docking.dock(floatingDockable.getDockable(), currentTopWindow, region);
- }
- else if (!floatingDockable.getDockable().isFloatingAllowed()) {
- docking.getDockingState().restoreWindowLayout(originalWindow, windowLayout);
- }
- else if (dockableAtPos == null && root != null) {
- // we're inserting at a specific position in a tabbed pane
- CustomTabbedPane tabbedPane = (CustomTabbedPane) DockingComponentUtils.findTabbedPaneAtPos(point, currentTopWindow);
-
- if (tabbedPane != null) {
- DockedTabbedPanel parent = (DockedTabbedPanel) tabbedPane.getParent();
-
- int targetTabIndex = tabbedPane.getTargetTabIndex(point, true);
-
- Rectangle boundsAt;
-
- if (targetTabIndex != -1) {
- boundsAt = tabbedPane.getBoundsAt(targetTabIndex);
-
- Point p = new Point(boundsAt.x, boundsAt.y);
- SwingUtilities.convertPointToScreen(p, tabbedPane);
- SwingUtilities.convertPointFromScreen(p, activeUtilsFrame);
- boundsAt.x = p.x;
- boundsAt.y = p.y;
-
- boundsAt.width /= 2;
- } else {
- boundsAt = tabbedPane.getBoundsAt(tabbedPane.getTabCount() - 1);
-
- Point p = new Point(boundsAt.x, boundsAt.y);
- SwingUtilities.convertPointToScreen(p, tabbedPane);
- SwingUtilities.convertPointFromScreen(p, activeUtilsFrame);
- boundsAt.x = p.x;
- boundsAt.y = p.y;
- boundsAt.x += boundsAt.width;
- }
-
- parent.dockAtIndex(floatingDockable.getDockable(), targetTabIndex);
- }
- }
- else {
- new FloatingFrame(docking, floatingDockable.getDockable(), floatingFrame);
- }
- }
- else {
- List dockables = new ArrayList<>(floatingFrame.getDockables());
-
- boolean first = true;
- Dockable firstDockable = null;
-
- for (DockableWrapper dockable : dockables) {
- if (first) {
- if (dockableAtPos != null && currentTopWindow != null && dockingPanel != null && activeUtilsFrame != null && activeUtilsFrame.isDockingToDockable()) {
- docking.dock(dockable.getDockable(), dockableAtPos, region);
- }
- else {
- new FloatingFrame(docking, dockable.getDockable(), floatingFrame);
- }
- firstDockable = dockable.getDockable();
- }
- else {
- docking.dock(dockable.getDockable(), firstDockable, DockingRegion.CENTER);
- }
- first = false;
- }
-
- docking.bringToFront(dockables.get(floatingFrame.getSelectedIndex()).getDockable());
+ private void dropFloatingPanel(Point mousePosOnScreen) {
+ if (!Floating.isFloating()) {
+ return;
}
- // auto persist the new layout to the file
- docking.getAppState().persist();
+ boolean docked = dropPanel(currentUtilFrame, floatingFrame, mousePosOnScreen);
- // remove the drag listener now that tab group has no dockables
- if (source instanceof DockedTabbedPanel && ((DockedTabbedPanel) source).getDockables().isEmpty()) {
- removeListeners();
+ if (currentUtilFrame != null) {
+ currentUtilFrame.deactivate();
}
- if (originalWindow instanceof JDialog) {
- ((JDialog) originalWindow).setModalityType(modalityType);
+ if (!docked) {
+ docking.getDockingState().restoreWindowLayout(originalWindow, originalWindowLayout);
}
- originalWindow = null;
-
- // if we're disposing the frame we started dragging from, dispose of it now
- if (windowToDispose != null) {
- docking.deregisterDockingPanel(windowToDispose);
- windowToDispose.dispose();
- windowToDispose = null;
- }
-
- // dispose of the temp floating frame now that we're done with it
floatingFrame.dispose();
floatingFrame = null;
-
- // hide the overlay frame if one is active
- if (activeUtilsFrame != null) {
- activeUtilsFrame.setTargetDockable(null);
- activeUtilsFrame.setFloating(null);
- activeUtilsFrame.setActive(false);
- activeUtilsFrame = null;
- }
}
- @Override
- public void dragDropEnd(DragSourceDropEvent dsde) {
- if (!isFloating) {
- return;
- }
- dropFloatingPanel();
+ protected abstract Window getOriginalWindow();
- isFloating = false;
- }
+ protected abstract void undock();
- @Override
- public void dragMouseMoved(DragSourceDragEvent dsde) {
- if (!isFloating) {
- return;
- }
- updateFramePosition(dsde.getLocation());
- }
-}
+ protected abstract JFrame createFloatingFrame();
+
+ protected abstract boolean dropPanel(FloatUtilsFrame utilsFrame, JFrame floatingFrame, Point mousePosOnScreen);
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/floating/FloatUtilsFrame.java b/docking-api/src/ModernDocking/floating/FloatUtilsFrame.java
new file mode 100644
index 00000000..0b42085a
--- /dev/null
+++ b/docking-api/src/ModernDocking/floating/FloatUtilsFrame.java
@@ -0,0 +1,341 @@
+/*
+Copyright (c) 2024 Andrew Auclair
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+package ModernDocking.floating;
+
+import ModernDocking.Dockable;
+import ModernDocking.DockingRegion;
+import ModernDocking.api.DockingAPI;
+import ModernDocking.api.RootDockingPanelAPI;
+import ModernDocking.internal.CustomTabbedPane;
+import ModernDocking.internal.DockingComponentUtils;
+import ModernDocking.ui.ToolbarLocation;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.DragSourceDragEvent;
+import java.awt.dnd.DragSourceMotionListener;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.image.BufferStrategy;
+
+public class FloatUtilsFrame extends JFrame implements DragSourceMotionListener, ComponentListener {
+ private final Window referenceDockingWindow;
+ private final RootDockingPanelAPI root;
+ private final RootDockingHandles rootHandles;
+ private final FloatingOverlay overlay;
+
+ private FloatListener floatListener;
+ private JFrame floatingFrame;
+ private DragSource dragSource;
+ private Dockable currentDockable;
+ private DockableHandles dockableHandles;
+
+ BufferStrategy bs; //create an strategy for multi-buffering.
+ JPanel renderPanel = new JPanel() {
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2 = (Graphics2D) g.create();
+ rootHandles.paint(g2);
+
+ if (dockableHandles != null) {
+ dockableHandles.paint(g2);
+ }
+
+ overlay.paint(g);
+ g2.dispose();
+ }
+ };
+
+ public FloatUtilsFrame(DockingAPI docking, Window referenceDockingWindow, RootDockingPanelAPI root) {
+ this.referenceDockingWindow = referenceDockingWindow;
+ this.root = root;
+ this.rootHandles = new RootDockingHandles(this, root);
+ this.overlay = new FloatingOverlay(docking, this);
+
+ this.referenceDockingWindow.addComponentListener(this);
+ SwingUtilities.invokeLater(this::setSizeAndLocation);
+
+ orderFrames();
+
+ setLayout(null); // don't use a layout manager for this custom painted frame
+ setUndecorated(true); // don't want to see a frame border
+ setType(Type.UTILITY); // hide this frame from the task bar
+
+ setBackground(new Color(0, 0, 0, 0)); // don't want a background for this frame
+ getRootPane().setBackground(new Color(0, 0, 0, 0)); // don't want a background for the root pane either. Workaround for a FlatLaf macOS issue.
+ getContentPane().setBackground(new Color(0, 0, 0, 0)); // don't want a background for the content frame either.
+
+ add(renderPanel);
+ renderPanel.setOpaque(false);
+
+ try {
+ if (getContentPane() instanceof JComponent) {
+ ((JComponent) getContentPane()).setOpaque(false);
+ }
+ }
+ catch (IllegalComponentStateException e) {
+ // TODO we need to handle platforms that don't support translucent display
+ // this exception indicates that the platform doesn't support changing the opacity
+ }
+ }
+
+ public void activate(FloatListener floatListener, JFrame floatingFrame, DragSource dragSource, Point mousePosOnScreen) {
+ this.floatListener = floatListener;
+ this.floatingFrame = floatingFrame;
+ this.dragSource = dragSource;
+ dragSource.addDragSourceMotionListener(this);
+
+ mouseMoved(mousePosOnScreen);
+
+ if (floatListener instanceof DisplayPanelFloatListener) {
+ Dockable floatingDockable = ((DisplayPanelFloatListener) floatListener).getDockable();
+ rootHandles.setFloatingDockable(floatingDockable);
+ }
+
+ setVisible(true);
+
+ createBufferStrategy(2);
+ bs = this.getBufferStrategy();
+ orderFrames();
+ }
+
+ public void deactivate() {
+ setVisible(false);
+
+ if (dragSource != null) {
+ dragSource.removeDragSourceMotionListener(this);
+ }
+ floatListener = null;
+ floatingFrame = null;
+ dragSource = null;
+ currentDockable = null;
+ dockableHandles = null;
+ }
+
+ @Override
+ public void dragMouseMoved(DragSourceDragEvent event) {
+ SwingUtilities.invokeLater(() -> mouseMoved(event.getLocation()));
+ }
+
+ private void mouseMoved(Point mousePosOnScreen) {
+ if (dragSource == null) {
+ return;
+ }
+
+ rootHandles.mouseMoved(mousePosOnScreen);
+
+ if (dockableHandles != null) {
+ dockableHandles.mouseMoved(mousePosOnScreen);
+ }
+
+ boolean prevVisible = overlay.isVisible();
+
+ // hide the overlay. it will be marked visible again if we update it
+ overlay.setVisible(false);
+
+ if (!referenceDockingWindow.getBounds().contains(mousePosOnScreen)) {
+ return;
+ }
+
+ Dockable dockable = DockingComponentUtils.findDockableAtScreenPos(mousePosOnScreen, referenceDockingWindow);
+
+ Dockable floatingDockable = null;
+
+ if (floatListener instanceof DisplayPanelFloatListener) {
+ floatingDockable = ((DisplayPanelFloatListener) floatListener).getDockable();
+ }
+
+ if (dockable != currentDockable) {
+ if (dockable == null) {
+ dockableHandles = null;
+ }
+ else if (floatListener instanceof DisplayPanelFloatListener) {
+ dockableHandles = new DockableHandles(this, dockable, floatingDockable);
+ }
+ else {
+ dockableHandles = new DockableHandles(this, dockable);
+ }
+ }
+ currentDockable = dockable;
+
+ if (rootHandles.isOverHandle()) {
+ if (!floatingFrame.isVisible()) {
+ changeVisibility(floatingFrame, true);
+ }
+ overlay.updateForRoot(root, rootHandles.getRegion());
+ }
+ else if (dockableHandles != null) {
+ if (!floatingFrame.isVisible()) {
+ changeVisibility(floatingFrame, true);
+ }
+ overlay.updateForDockable(currentDockable, floatingDockable, mousePosOnScreen, dockableHandles.getRegion());
+ }
+ else if (currentDockable == null && floatListener instanceof DisplayPanelFloatListener) {
+ CustomTabbedPane tabbedPane = DockingComponentUtils.findTabbedPaneAtPos(mousePosOnScreen, referenceDockingWindow);
+
+ changeVisibility(floatingFrame, tabbedPane == null);
+
+ if (tabbedPane != null) {
+ overlay.updateForTab(tabbedPane, mousePosOnScreen);
+ }
+ }
+ else if (!floatingFrame.isVisible()) {
+ changeVisibility(floatingFrame, true);
+ }
+ repaint();
+ if (overlay.requiresRedraw() || prevVisible != overlay.isVisible()) {
+// System.out.println("Redraw");
+// revalidate();
+ repaint();
+ overlay.clearRedraw();
+ }
+ }
+
+ private void changeVisibility(JFrame frame, boolean visible) {
+ if (frame.isVisible() != visible) {
+ System.out.println("Change visibility");
+ frame.setVisible(visible);
+
+ orderFrames();
+ }
+ }
+
+ private void orderFrames() {
+ SwingUtilities.invokeLater(referenceDockingWindow::toFront);
+ if (floatingFrame != null) {
+ SwingUtilities.invokeLater(floatingFrame::toFront);
+ }
+ SwingUtilities.invokeLater(this::toFront);
+ }
+
+// @Override
+// public void paint(Graphics gf) {
+//// super.paint(gf);
+////Graphics2D g2 = (Graphics2D) gf.create();
+//// rootHandles.paint(g2);
+////
+//// if (dockableHandles != null) {
+//// dockableHandles.paint(g2);
+//// }
+////
+//// overlay.paint(gf);
+//// g2.dispose();
+// if (bs == null) {
+// return;
+// }
+//
+// Graphics2D g2 = (Graphics2D) bs.getDrawGraphics();
+//
+// g2.setColor(new Color(0,0,0,0));
+// g2.clearRect(0, 0, getWidth(), getHeight());
+// g2.fillRect(0, 0, getWidth(), getHeight());
+//
+// rootHandles.paint(g2);
+// if (dockableHandles != null) {
+// dockableHandles.paint(g2);
+// }
+// overlay.paint(g2);
+// g2.dispose();
+// bs.show();
+// }
+
+ @Override
+ public void componentResized(ComponentEvent e) {
+ SwingUtilities.invokeLater(this::setSizeAndLocation);
+ }
+
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ SwingUtilities.invokeLater(this::setSizeAndLocation);
+ }
+
+ @Override
+ public void componentShown(ComponentEvent e) {
+ }
+
+ @Override
+ public void componentHidden(ComponentEvent e) {
+ }
+
+ public boolean isOverRootHandle() {
+ return rootHandles.isOverHandle();
+ }
+
+ public DockingRegion rootHandleRegion() {
+ return rootHandles.getRegion();
+ }
+
+ public boolean isOverPinHandle() {
+ return rootHandles.isOverPinHandle();
+ }
+
+ public ToolbarLocation pinRegion() {
+ return rootHandles.getPinRegion();
+ }
+
+ public boolean isOverDockableHandle() {
+ if (dockableHandles == null) {
+ return false;
+ }
+ return dockableHandles.getRegion() != null;
+ }
+
+ public boolean isOverTab() {
+ return overlay.isOverTab();
+ }
+
+ public DockingRegion dockableHandle() {
+ if (dockableHandles == null) {
+ return null;
+ }
+ return dockableHandles.getRegion();
+ }
+
+ public DockingRegion getDockableRegion(Dockable targetDockable, Dockable floatingDockable, Point mousePosOnScreen) {
+ return overlay.getRegion(targetDockable, floatingDockable, mousePosOnScreen);
+ }
+
+ private void setSizeAndLocation() {
+ int padding = (int) (DockingHandle.HANDLE_ICON_SIZE * 1.75);
+
+ Point location = new Point(referenceDockingWindow.getLocationOnScreen());
+ Dimension size = new Dimension(referenceDockingWindow.getSize());
+
+ location.x -= padding;
+ location.y -= padding;
+
+ size.width += padding * 2;
+ size.height += padding * 2;
+
+ // set location and size based on the reference docking frame
+ setLocation(location);
+ setSize(size);
+
+ renderPanel.setSize(size);
+
+ rootHandles.updateHandlePositions();
+
+ revalidate();
+ repaint();
+ }
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/floating/Floating.java b/docking-api/src/ModernDocking/floating/Floating.java
new file mode 100644
index 00000000..ccaaf997
--- /dev/null
+++ b/docking-api/src/ModernDocking/floating/Floating.java
@@ -0,0 +1,53 @@
+/*
+Copyright (c) 2024 Andrew Auclair
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+package ModernDocking.floating;
+
+import ModernDocking.api.DockingAPI;
+import ModernDocking.api.RootDockingPanelAPI;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Floating {
+ private static final Map utilFrames = new HashMap<>();
+ private static boolean isFloating = false;
+
+ public static void registerDockingWindow(DockingAPI docking, Window window, RootDockingPanelAPI root) {
+ SwingUtilities.invokeLater(() -> utilFrames.put(window, new FloatUtilsFrame(docking, window, root)));
+ }
+
+ public static void deregisterDockingWindow(Window window) {
+ utilFrames.remove(window);
+ }
+
+ public static FloatUtilsFrame frameForWindow(Window window) {
+ return utilFrames.get(window);
+ }
+
+ public static boolean isFloating() { return isFloating; }
+
+ static void setFloating(boolean floating) {
+ isFloating = floating;
+ }
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/floating/FloatingOverlay.java b/docking-api/src/ModernDocking/floating/FloatingOverlay.java
new file mode 100644
index 00000000..ffb058ca
--- /dev/null
+++ b/docking-api/src/ModernDocking/floating/FloatingOverlay.java
@@ -0,0 +1,279 @@
+/*
+Copyright (c) 2024 Andrew Auclair
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+package ModernDocking.floating;
+
+import ModernDocking.Dockable;
+import ModernDocking.DockableStyle;
+import ModernDocking.DockingRegion;
+import ModernDocking.api.DockingAPI;
+import ModernDocking.api.RootDockingPanelAPI;
+import ModernDocking.internal.CustomTabbedPane;
+import ModernDocking.internal.DisplayPanel;
+import ModernDocking.internal.DockingInternal;
+import ModernDocking.ui.DockingSettings;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class FloatingOverlay {
+ // determines how close to the edge the user has to drag the panel before they see an overlay other than CENTER
+ private static final double REGION_SENSITIVITY = 0.35;
+
+ private boolean visible = false;
+
+ // the top left location where the overlay starts
+ private Point location = new Point(0, 0);
+ // the total size of the overlay, used for drawing
+ private Dimension size = new Dimension(0, 0);
+ private Rectangle targetTab = null;
+
+ private final DockingAPI docking;
+ private final JFrame utilFrame;
+
+ private Point prevLocation = location;
+ private Dimension prevSize = size;
+
+ public FloatingOverlay(DockingAPI docking, JFrame utilFrame) {
+ this.docking = docking;
+ this.utilFrame = utilFrame;
+ }
+
+ public boolean requiresRedraw() {
+ return !location.equals(prevLocation) ||
+ !size.equals(prevSize);
+ }
+
+ public void clearRedraw() {
+ prevLocation = location;
+ prevSize = size;
+ }
+
+ public void updateForRoot(RootDockingPanelAPI rootPanel, DockingRegion region) {
+ setVisible(true);
+
+ targetTab = null;
+
+ Point point = rootPanel.getLocation();
+ Dimension size = rootPanel.getSize();
+
+ point = SwingUtilities.convertPoint(rootPanel.getParent(), point, utilFrame);
+
+ final double DROP_SIZE = 4;
+
+ prevLocation = new Point(this.location);
+ prevSize = new Dimension(this.size);
+
+ switch (region) {
+ case WEST: {
+ size = new Dimension((int) (size.width / DROP_SIZE), size.height);
+ break;
+ }
+ case NORTH: {
+ size = new Dimension(size.width, (int) (size.height / DROP_SIZE));
+ break;
+ }
+ case EAST: {
+ point.x += size.width - (size.width / DROP_SIZE);
+ size = new Dimension((int) (size.width / DROP_SIZE), size.height);
+ break;
+ }
+ case SOUTH: {
+ point.y += size.height - (size.height / DROP_SIZE);
+ size = new Dimension(size.width, (int) (size.height / DROP_SIZE));
+ break;
+ }
+ }
+
+ this.location = point;
+ this.size = size;
+ }
+
+ public void updateForDockable(Dockable targetDockable, Dockable floatingDockable, Point mousePosOnScreen, DockingRegion region) {
+ setVisible(true);
+
+ prevLocation = new Point(this.location);
+ prevSize = new Dimension(this.size);
+
+ targetTab = null;
+
+ if (region == null) {
+ region = getRegion(targetDockable, floatingDockable, mousePosOnScreen);
+ }
+
+ JComponent component = DockingInternal.get(docking).getWrapper(targetDockable).getDisplayPanel();
+
+ Point point = component.getLocation();
+ Dimension size = component.getSize();
+
+ point = SwingUtilities.convertPoint(component.getParent(), point, utilFrame);
+
+ final double DROP_SIZE = 2;
+
+ switch (region) {
+ case WEST: {
+ size = new Dimension((int) (size.width / DROP_SIZE), size.height);
+ break;
+ }
+ case NORTH: {
+ size = new Dimension(size.width, (int) (size.height / DROP_SIZE));
+ break;
+ }
+ case EAST: {
+ point.x += size.width / DROP_SIZE;
+ size = new Dimension((int) (size.width / DROP_SIZE), size.height);
+ break;
+ }
+ case SOUTH: {
+ point.y += size.height / DROP_SIZE;
+ size = new Dimension(size.width, (int) (size.height / DROP_SIZE));
+ break;
+ }
+ }
+
+ this.location = point;
+ this.size = size;
+ }
+
+ public void updateForTab(CustomTabbedPane tabbedPane, Point mousePosOnScreen) {
+ setVisible(true);
+
+ prevLocation = new Point(this.location);
+ prevSize = new Dimension(this.size);
+
+ Component componentAt = tabbedPane.getComponentAt(0);
+
+ location = componentAt.getLocation();
+ SwingUtilities.convertPointToScreen(location, tabbedPane);
+ SwingUtilities.convertPointFromScreen(location, utilFrame);
+
+ size = componentAt.getSize();
+
+ int targetTabIndex = tabbedPane.getTargetTabIndex(mousePosOnScreen, true);
+
+ if (targetTabIndex != -1) {
+ targetTab = tabbedPane.getBoundsAt(targetTabIndex);
+
+ Point p = new Point(targetTab.x, targetTab.y);
+ SwingUtilities.convertPointToScreen(p, tabbedPane);
+ SwingUtilities.convertPointFromScreen(p, utilFrame);
+
+ targetTab.x = p.x;
+ targetTab.y = p.y;
+
+ targetTab.width /= 2;
+ }
+ else {
+ targetTab = tabbedPane.getBoundsAt(tabbedPane.getTabCount() - 1);
+
+ Point tabPoint = new Point(tabbedPane.getX(), tabbedPane.getY());
+ SwingUtilities.convertPointToScreen(tabPoint, tabbedPane.getParent());
+
+ Point boundsPoint = new Point(targetTab.x, targetTab.y);
+ SwingUtilities.convertPointToScreen(boundsPoint, tabbedPane);
+
+ int widthToAdd = targetTab.width;
+
+ if (boundsPoint.x + (targetTab.width * 2) >= tabPoint.x + tabbedPane.getWidth()) {
+ targetTab.width = Math.abs((tabPoint.x + tabbedPane.getWidth()) - (boundsPoint.x + targetTab.width));
+ }
+
+ SwingUtilities.convertPointFromScreen(boundsPoint, utilFrame);
+
+ targetTab.x = boundsPoint.x + widthToAdd;
+ targetTab.y = boundsPoint.y;
+ }
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ }
+
+ public DockingRegion getRegion(Dockable targetDockable, Dockable floatingDockable, Point mousePosOnScreen) {
+ JComponent component = DockingInternal.get(docking).getWrapper(targetDockable).getDisplayPanel();
+
+ Point framePoint = new Point(mousePosOnScreen);
+ SwingUtilities.convertPointFromScreen(framePoint, utilFrame);
+
+ Point point = (component).getLocation();
+ Dimension size = component.getSize();
+
+ point = SwingUtilities.convertPoint(component.getParent(), point, utilFrame);
+
+ double horizontalPct = (framePoint.x - point.x) / (double) size.width;
+ double verticalPct = (framePoint.y - point.y) / (double) size.height;
+
+ double horizontalEdgeDist = horizontalPct > 0.5 ? 1.0 - horizontalPct : horizontalPct;
+ double verticalEdgeDist = verticalPct > 0.5 ? 1.0 - verticalPct : verticalPct;
+
+ if (horizontalEdgeDist < verticalEdgeDist) {
+ if (horizontalPct < REGION_SENSITIVITY && isRegionAllowed(targetDockable, DockingRegion.WEST) && isRegionAllowed(floatingDockable, DockingRegion.WEST)) {
+ return DockingRegion.WEST;
+ }
+ else if (horizontalPct > (1.0 - REGION_SENSITIVITY) && isRegionAllowed(targetDockable, DockingRegion.EAST) && isRegionAllowed(floatingDockable, DockingRegion.EAST)) {
+ return DockingRegion.EAST;
+ }
+ }
+ else {
+ if (verticalPct < REGION_SENSITIVITY && isRegionAllowed(targetDockable, DockingRegion.NORTH) && isRegionAllowed(floatingDockable, DockingRegion.NORTH)) {
+ return DockingRegion.NORTH;
+ }
+ else if (verticalPct > (1.0 - REGION_SENSITIVITY) && isRegionAllowed(targetDockable, DockingRegion.SOUTH) && isRegionAllowed(floatingDockable, DockingRegion.SOUTH)) {
+ return DockingRegion.SOUTH;
+ }
+ }
+ return DockingRegion.CENTER;
+ }
+
+ public boolean isOverTab() {
+ return targetTab != null;
+ }
+
+ public void paint(Graphics g) {
+ if (!visible) {
+ return;
+ }
+ g.setColor(DockingSettings.getOverlayBackground());
+ g.fillRect(location.x, location.y, size.width, size.height);
+
+ if (targetTab != null) {
+ g.fillRect(targetTab.x, targetTab.y, targetTab.width, targetTab.height);
+ }
+ }
+
+ // check if the floating dockable is allowed to dock to this region
+ private boolean isRegionAllowed(Dockable dockable, DockingRegion region) {
+ if (dockable == null) {
+ return true;
+ }
+ if (dockable.getStyle() == DockableStyle.BOTH) {
+ return true;
+ }
+ if (region == DockingRegion.NORTH || region == DockingRegion.SOUTH) {
+ return dockable.getStyle() == DockableStyle.HORIZONTAL;
+ }
+ return dockable.getStyle() == DockableStyle.VERTICAL;
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/floating/RootDockingHandles.java b/docking-api/src/ModernDocking/floating/RootDockingHandles.java
new file mode 100644
index 00000000..92828d11
--- /dev/null
+++ b/docking-api/src/ModernDocking/floating/RootDockingHandles.java
@@ -0,0 +1,211 @@
+/*
+Copyright (c) 2024 Andrew Auclair
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+package ModernDocking.floating;
+
+import ModernDocking.Dockable;
+import ModernDocking.DockableStyle;
+import ModernDocking.DockingRegion;
+import ModernDocking.api.RootDockingPanelAPI;
+import ModernDocking.ui.ToolbarLocation;
+
+import javax.swing.*;
+import java.awt.*;
+
+import static ModernDocking.floating.DockingHandle.HANDLE_ICON_SIZE;
+
+public class RootDockingHandles {
+ private final DockingHandle rootCenter = new DockingHandle(DockingRegion.CENTER, true);
+ private final DockingHandle rootWest = new DockingHandle(DockingRegion.WEST, true);
+ private final DockingHandle rootNorth = new DockingHandle(DockingRegion.NORTH, true);
+ private final DockingHandle rootEast = new DockingHandle(DockingRegion.EAST, true);
+ private final DockingHandle rootSouth = new DockingHandle(DockingRegion.SOUTH, true);
+
+ private final DockingHandle pinWest = new DockingHandle(DockingRegion.WEST);
+ private final DockingHandle pinEast = new DockingHandle(DockingRegion.EAST);
+ private final DockingHandle pinSouth = new DockingHandle(DockingRegion.SOUTH);
+
+ private final JFrame frame;
+ private final RootDockingPanelAPI rootPanel;
+
+ private DockingRegion mouseOverRegion = null;
+ private DockingRegion mouseOverPin = null;
+
+ public RootDockingHandles(JFrame frame, RootDockingPanelAPI rootPanel) {
+ this.frame = frame;
+ this.rootPanel = rootPanel;
+ setupHandle(frame, rootCenter);
+ setupHandle(frame, rootWest);
+ setupHandle(frame, rootNorth);
+ setupHandle(frame, rootEast);
+ setupHandle(frame, rootSouth);
+
+ setupHandle(frame, pinWest);
+ setupHandle(frame, pinEast);
+ setupHandle(frame, pinSouth);
+
+ rootCenter.setVisible(rootPanel.isEmpty());
+
+ // invoke later to wait for the root panel to have a parent
+ SwingUtilities.invokeLater(this::setRootHandleLocations);
+ }
+
+ public void updateHandlePositions() {
+ setRootHandleLocations();
+ }
+
+ public void setFloatingDockable(Dockable dockable) {
+ if (dockable == null) {
+ pinWest.setVisible(false);
+ pinEast.setVisible(false);
+ pinSouth.setVisible(false);
+ return;
+ }
+
+ pinWest.setVisible(dockable.isPinningAllowed() && (dockable.getPinningStyle() == DockableStyle.BOTH || dockable.getPinningStyle() == DockableStyle.VERTICAL));
+ pinEast.setVisible(dockable.isPinningAllowed() && (dockable.getPinningStyle() == DockableStyle.BOTH || dockable.getPinningStyle() == DockableStyle.VERTICAL));
+ pinSouth.setVisible(dockable.isPinningAllowed() && (dockable.getPinningStyle() == DockableStyle.BOTH || dockable.getPinningStyle() == DockableStyle.HORIZONTAL));
+ }
+
+ public void mouseMoved(Point mousePosOnScreen) {
+ Point framePoint = new Point(mousePosOnScreen);
+ SwingUtilities.convertPointFromScreen(framePoint, frame);
+
+ rootCenter.mouseMoved(framePoint);
+ rootWest.mouseMoved(framePoint);
+ rootNorth.mouseMoved(framePoint);
+ rootEast.mouseMoved(framePoint);
+ rootSouth.mouseMoved(framePoint);
+
+ mouseOverRegion = null;
+ if (rootCenter.isMouseOver()) mouseOverRegion = DockingRegion.CENTER;
+ if (rootWest.isMouseOver()) mouseOverRegion = DockingRegion.WEST;
+ if (rootNorth.isMouseOver()) mouseOverRegion = DockingRegion.NORTH;
+ if (rootEast.isMouseOver()) mouseOverRegion = DockingRegion.EAST;
+ if (rootSouth.isMouseOver()) mouseOverRegion = DockingRegion.SOUTH;
+
+ pinWest.mouseMoved(framePoint);
+ pinEast.mouseMoved(framePoint);
+ pinSouth.mouseMoved(framePoint);
+
+ mouseOverPin = null;
+ if (pinWest.isMouseOver()) mouseOverPin = DockingRegion.WEST;
+ if (pinEast.isMouseOver()) mouseOverPin = DockingRegion.EAST;
+ if (pinSouth.isMouseOver()) mouseOverPin = DockingRegion.SOUTH;
+ }
+
+ private void setupHandle(JFrame frame, DockingHandle label) {
+ label.setVisible(true);
+ frame.add(label);
+ }
+
+ private void setRootHandleLocations() {
+ Point location = rootPanel.getLocation();
+ Dimension size = rootPanel.getSize();
+ location.x += size.width / 2;
+ location.y += size.height / 2;
+
+ SwingUtilities.convertPointToScreen(location, rootPanel.getParent());
+ SwingUtilities.convertPointFromScreen(location, frame);
+
+ setLocation(rootCenter, location.x, location.y);
+ setLocation(rootWest, location.x - (size.width / 2) + rootHandleSpacing(rootWest), location.y);
+ setLocation(rootNorth, location.x, location.y - (size.height / 2) + rootHandleSpacing(rootNorth));
+ setLocation(rootEast, location.x + (size.width / 2) - rootHandleSpacing(rootEast), location.y);
+ setLocation(rootSouth, location.x, location.y + (size.height / 2) - rootHandleSpacing(rootSouth));
+
+ setLocation(pinWest, location.x - (size.width / 2) + rootHandleSpacing(pinWest), location.y - (size.height / 3));
+ setLocation(pinEast, location.x + (size.width / 2) - rootHandleSpacing(pinEast), location.y - (size.height / 3));
+ setLocation(pinSouth, location.x - (size.width / 3), location.y + (size.height / 2) - rootHandleSpacing(pinSouth));
+ }
+
+ private int rootHandleSpacing(JLabel handle) {
+ return handle.getWidth() + 16;
+ }
+
+ private void setLocation(Component component, int x, int y) {
+ component.setLocation(x - (HANDLE_ICON_SIZE / 2), y - (HANDLE_ICON_SIZE / 2));
+ }
+
+ /**
+ * Paint the handles
+ *
+ * @param g2 Graphics2D instance to use
+ */
+ public void paint(Graphics2D g2) {
+ // draw root handles
+ rootCenter.paintHandle(g2);
+ rootEast.paintHandle(g2);
+ rootWest.paintHandle(g2);
+ rootNorth.paintHandle(g2);
+ rootSouth.paintHandle(g2);
+
+ pinWest.paintHandle(g2);
+ pinEast.paintHandle(g2);
+ pinSouth.paintHandle(g2);
+ }
+
+ public boolean isOverHandle() {
+ return rootCenter.isMouseOver() ||
+ rootEast.isMouseOver() ||
+ rootWest.isMouseOver() ||
+ rootNorth.isMouseOver() ||
+ rootSouth.isMouseOver();
+ }
+
+ public boolean isOverPinHandle() {
+ return pinEast.isMouseOver() ||
+ pinSouth.isMouseOver() ||
+ pinWest.isMouseOver();
+ }
+
+ public DockingRegion getRegion() {
+ if (rootCenter.isMouseOver()) {
+ return DockingRegion.CENTER;
+ }
+ if (rootEast.isMouseOver()) {
+ return DockingRegion.EAST;
+ }
+ if (rootWest.isMouseOver()) {
+ return DockingRegion.WEST;
+ }
+ if (rootNorth.isMouseOver()) {
+ return DockingRegion.NORTH;
+ }
+ if (rootSouth.isMouseOver()) {
+ return DockingRegion.SOUTH;
+ }
+ return null;
+ }
+
+ public ToolbarLocation getPinRegion() {
+ if (pinEast.isMouseOver()) {
+ return ToolbarLocation.EAST;
+ }
+ else if (pinWest.isMouseOver()) {
+ return ToolbarLocation.WEST;
+ }
+ else if (pinSouth.isMouseOver()) {
+ return ToolbarLocation.SOUTH;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/docking-api/src/ModernDocking/internal/CustomTabbedPane.java b/docking-api/src/ModernDocking/internal/CustomTabbedPane.java
index 6da9dae8..f3e19b0f 100644
--- a/docking-api/src/ModernDocking/internal/CustomTabbedPane.java
+++ b/docking-api/src/ModernDocking/internal/CustomTabbedPane.java
@@ -84,7 +84,9 @@ public void actionPerformed(ActionEvent e) {
}
public int getTargetTabIndex(Point mousePosOnScreen, boolean ignoreY) {
- SwingUtilities.convertPointFromScreen(mousePosOnScreen, this);
+ // convert the screen mouse position to a position on the tabbed pane
+ Point newPoint = new Point(mousePosOnScreen);
+ SwingUtilities.convertPointFromScreen(newPoint, this);
Point d = isTopBottomTabPlacement(getTabPlacement()) ? new Point(1, 0) : new Point(0, 1);
@@ -93,10 +95,10 @@ public int getTargetTabIndex(Point mousePosOnScreen, boolean ignoreY) {
if (ignoreY) {
// we only care to check the x value
- mousePosOnScreen.y = tab.y;
+ newPoint.y = tab.y;
}
- if (tab.contains(mousePosOnScreen)) {
+ if (tab.contains(newPoint)) {
return i;
}
}
diff --git a/docking-api/src/ModernDocking/internal/DockableWrapper.java b/docking-api/src/ModernDocking/internal/DockableWrapper.java
index 368014ae..659748bf 100644
--- a/docking-api/src/ModernDocking/internal/DockableWrapper.java
+++ b/docking-api/src/ModernDocking/internal/DockableWrapper.java
@@ -25,6 +25,7 @@ of this software and associated documentation files (the "Software"), to deal
import ModernDocking.Property;
import ModernDocking.api.DockingAPI;
import ModernDocking.api.RootDockingPanelAPI;
+import ModernDocking.floating.DisplayPanelFloatListener;
import ModernDocking.floating.FloatListener;
import ModernDocking.layouts.DockingSimplePanelNode;
import ModernDocking.ui.DockingHeaderUI;
@@ -75,7 +76,7 @@ public DockableWrapper(DockingAPI docking, Dockable dockable) {
displayPanel = new DisplayPanel(this);
- floatListener = new FloatListener(docking, displayPanel);
+ floatListener = new DisplayPanelFloatListener(docking, displayPanel);
}
/**
@@ -116,6 +117,10 @@ public Dockable getDockable() {
return dockable;
}
+ public FloatListener getFloatListener() {
+ return floatListener;
+ }
+
/**
* Remove any listeners that this wrapper has added for the dockable
*/
diff --git a/docking-api/src/ModernDocking/internal/DockedTabbedPanel.java b/docking-api/src/ModernDocking/internal/DockedTabbedPanel.java
index 3e6b9615..64ef0a27 100644
--- a/docking-api/src/ModernDocking/internal/DockedTabbedPanel.java
+++ b/docking-api/src/ModernDocking/internal/DockedTabbedPanel.java
@@ -24,7 +24,9 @@ of this software and associated documentation files (the "Software"), to deal
import ModernDocking.Dockable;
import ModernDocking.DockingRegion;
import ModernDocking.api.DockingAPI;
+import ModernDocking.floating.DockedTabbedPanelFloatListener;
import ModernDocking.floating.FloatListener;
+import ModernDocking.floating.Floating;
import ModernDocking.settings.Settings;
import ModernDocking.ui.DockingSettings;
@@ -32,6 +34,7 @@ of this software and associated documentation files (the "Software"), to deal
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
+import java.awt.dnd.DragGestureListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
@@ -50,6 +53,7 @@ public class DockedTabbedPanel extends DockingPanel implements ChangeListener {
private final List panels = new ArrayList<>();
private FloatListener floatListener;
+ private List listeners = new ArrayList<>();
private final CustomTabbedPane tabs = new CustomTabbedPane();
private final DockingAPI docking;
@@ -160,13 +164,15 @@ public void addNotify() {
tabs.addChangeListener(this);
- floatListener = new FloatListener(docking, this, tabs);
+ floatListener = new DockedTabbedPanelFloatListener(docking, this, tabs);
}
@Override
public void removeNotify() {
tabs.removeChangeListener(this);
+ floatListener = null;
+
super.removeNotify();
}
@@ -179,6 +185,24 @@ public void addPanel(DockableWrapper dockable) {
panels.add(dockable);
tabs.add(dockable.getDockable().getTabText(), dockable.getDisplayPanel());
+ DragGestureListener draggingFromTabPanel = dge -> {
+ Point dragOrigin = new Point(dge.getDragOrigin());
+ SwingUtilities.convertPointToScreen(dragOrigin, dge.getComponent());
+
+ int targetTabIndex = tabs.getTargetTabIndex(dragOrigin, true);
+
+ if (targetTabIndex != -1) {
+ DockableWrapper dockableWrapper = panels.get(targetTabIndex);
+
+ if (dockableWrapper == dockable) {
+ dockableWrapper.getFloatListener().startDrag(dge);
+ }
+ }
+ };
+ listeners.add(draggingFromTabPanel);
+
+ dockable.getFloatListener().addAlternateDragSource(tabs, draggingFromTabPanel);
+
// if any of the dockables use top tab position, switch this tabbedpanel to top tabs
if (tabs.getTabPlacement() != SwingConstants.TOP && dockable.getDockable().getTabPosition() == SwingConstants.TOP) {
tabs.setTabPlacement(SwingConstants.TOP);
@@ -203,7 +227,10 @@ public void addPanel(DockableWrapper dockable) {
*/
public void removePanel(DockableWrapper dockable) {
if (panels.contains(dockable)) {
- panels.indexOf(dockable);
+ int index = panels.indexOf(dockable);
+
+ dockable.getFloatListener().removeAlternateDragSource(listeners.get(index));
+ listeners.remove(index);
tabs.remove(dockable.getDisplayPanel());
panels.remove(dockable);
@@ -295,11 +322,11 @@ public void dockAtIndex(Dockable dockable, int index) {
public void undock(Dockable dockable) {
removePanel(DockingInternal.get(docking).getWrapper(dockable));
- if (!Settings.alwaysDisplayTabsMode(dockable) && tabs.getTabCount() == 1 && parent != null) {
+ if (!Settings.alwaysDisplayTabsMode(dockable) && panels.size() == 1 && parent != null && panels.get(0).getDockable().getTabPosition() != SwingConstants.TOP) {
parent.replaceChild(this, new DockedSimplePanel(docking, panels.get(0)));
}
- if (tabs.getTabCount() == 0) {
+ if (panels.size() == 0) {
parent.removeChild(this);
}
}
@@ -368,7 +395,7 @@ public void stateChanged(ChangeEvent e) {
return;
}
- if (selectedTab != -1 && !FloatListener.isFloating()) {
+ if (selectedTab != -1 && !Floating.isFloating()) {
DockingListeners.fireHiddenEvent(panels.get(selectedTab).getDockable());
}
selectedTab = tabs.getSelectedIndex();
@@ -415,8 +442,17 @@ public void updateTabInfo(Dockable dockable) {
}
}
- public int getTargetTabIndex(Point point) {
- return tabs.getTargetTabIndex(point, false);
+ public int getTargetTabIndex(Point mousePosOnScreen) {
+ return tabs.getTargetTabIndex(mousePosOnScreen, false);
+ }
+
+ public int getIndexOfPanel(DisplayPanel displayPanel) {
+ for (int i = 0; i < panels.size(); i++) {
+ if (panels.get(i).getDisplayPanel() == displayPanel) {
+ return i;
+ }
+ }
+ return -1;
}
public boolean isDraggingFromTabGutter(Point point) {
diff --git a/docking-api/src/ModernDocking/internal/FloatingFrame.java b/docking-api/src/ModernDocking/internal/FloatingFrame.java
index e960203b..e9db9e23 100644
--- a/docking-api/src/ModernDocking/internal/FloatingFrame.java
+++ b/docking-api/src/ModernDocking/internal/FloatingFrame.java
@@ -74,7 +74,7 @@ public FloatingFrame(DockingAPI docking, Point location, Dimension size, int sta
}
// create a new floating frame. this is used when calling Docking.newWindow or when restoring the layout from a file
- public FloatingFrame(DockingAPI docking, Dockable dockable, Point location, Dimension size, int state) {
+ public FloatingFrame(DockingAPI docking, Dockable dockable, Point mousePosOnScreen, Dimension size, int state) {
this.docking = docking;
DisplayPanel displayPanel = DockingInternal.get(docking).getWrapper(dockable).getDisplayPanel();
@@ -82,6 +82,10 @@ public FloatingFrame(DockingAPI docking, Dockable dockable, Point location, Dime
Point point = displayPanel.getLocation();
SwingUtilities.convertPointToScreen(point, displayPanel.getParent());
+ Point location = new Point(mousePosOnScreen);
+ location.x -= mousePosOnScreen.x - point.x;
+ location.y -= mousePosOnScreen.y - point.y;
+
setLocation(location);
setSize(size);
setExtendedState(state);