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);