Dockable interface methods should be constant after the creation of the dockable component.
-Certain methods can be dynamic and the framework provides methods to update their values within the framework. Any method that doesn't specify such
-a method can lead to unexpected behavior if changed after the dockable component is created.
-
-Generally, if you wish to modify the return values after creating the dockable component, do so while the dockable is not docked. This can be checked with Docking.isDocked.
-getPersistentID provides a unique ID that the framework can use to refer to the dockable. This is the main piece of information that the application and the framework share in order to refer to specific dockables.
getTabText provides the text that should be displayed on a tab when this dockable is in a JTabbedPane.
getType provides an int to Modern Docking. This represents a unique type category for the dockable. Modern Docking will use this value when docking to determine which dockables to dock to.
Default value is 0
getTitleText provides the text that Modern Docking should display on the header. This string can be different than getTabText
Default value is getTabText
Used by the framework to get the text to display as a tooltip on JTabbedPane tabs.
Default value is null
Used by the framework to get the icon for the dockable to use in a JTabbedPane tab.
Default value is null
Tells Modern Docking if the dockable is allowed to be opened in its own window
-Default value is true
Allows the application to limit the dockable to the window it was initially docked in.
-Default value is false
The docking style of the dockable which can be DockableStyle.VERTICAL, DockableStyle.HORIZONTAL, DockableStyle.BOTH or DockableStyle.CENTER_ONLY. Modern Docking will use this to determine which docking regions to allow when docking this dockable. Docking handles that do not match this style will be hidden.
Default value is DockableStyle.BOTH
Determines which toolbars this dockable can be displayed on. Uses the same values as getStyle. DockableStyle.VERTICAL will allow the dockable on the east and west auto hide toolbars. DockableStyle.HORIZONTAL will allow the dockable on the south auto hide toolbar. DockableStyle.CENTER_ONLY is invalid for this method.
Default value is DockableStyle.BOTH
Indicates to the docking framework whether the Dockable component can be closed and undocked.
-Default value is true
Called by Modern Docking when the dockable is in the process of closing due to undock. This allows the application to stop the dockable from closing. For example, maybe the user has unsaved changes and the application wishes to confirm closing of the dockable.
Default value is true
Determines if the dockable can be set to the auto hide toolbars.
-Default value is false
Determines if the dockable can be maximized so that it takes up all the space in the window.
-Default value is false
Allows the application to specify whether the docking framework should automatically wrap the Dockable component in a JScrollPane.
Default value is false
Flag that tells Modern Docking that this dockable has more menu items it wishes to add to the context menu. If this method returns true then Modern Docking will call addMoreOptions
Default value is false
Gives the dockables preferred tab location when in a JTabbedPane
Default value is DockableTabPreference.NONE
Adds this dockables menu items to the context menu
-Creates the header UI for this dockable. The default implementation will create the default Modern Docking header.
-Default value is DockingInternal.createDefaultHeaderUI(headerController, headerModel);
Modern Docking will call this method after setting the values of any fields annotated with DockingProperty. If there are no fields with that annotation then this method is not called
-The dock methods of the Docking class are used to dock dockables. There are several variations of this method that allow docking directly to a window or to specific regions of other dockables. There are variations that allow specifying the divider proportions to use for JSplitPanes and each form of dock allows using the persistentID directly or an instance of Dockable
-
dock(String persistentID, Window window) | Allows docking the dockable with persistentID to a given Window. This will only work if the root docking panel of the window is empty. |
dock(Dockable dockable, Window window) | Allows docking the dockable to a given Window. This will only work if the root docking panel of the window is empty. |
dock(String persistentID, Window window, DockingRegion region) | Docks the dockable with persistentID to the specified root region of the window. The divider proportion is set to .25. |
dock(Dockable dockable, Window window, DockingRegion region) | Docks the dockable with persistentID to the specified root region of the window. The divider proportion is set to .25. |
dock(String persistentID, Window window, DockingRegion, double dividerProportion) | |
dock(Dockable dockable, Window window, DockingRegion, double dividerProportion) | |
dock(String sourcePersistentID, String targetPersistentID, DockingRegion region) | |
dock(String sourcePersistentID, Dockable target, DockingRegion region) | |
dock(Dockable source, String targetPersistentID, DockingRegion region) | |
dock(Dockable source, Dockable target, DockingRegion region) | |
dock(String sourcePersistentID, String targetPersistentID, DockingRegion region, double dividerProportion) | |
dock(Dockable source, Dockable target, DockingRegion region, double dividerProportion) |
undock(String persistentID) | Undocks the dockable with the persistentID. Nothing is done if the dockable is not docked |
undock(Dockable dockable | Undocks dockable. Nothing is done if the dockable is not docked |
newWindow(Dockable dockable) | Opens the dockable in a new `FloatingFrame` instance |
newWindow(String persistentID, Point location, Dimension size) | Opens the dockable with persistentID in a new FloatingFrame at the given location with the given size |
newWindow(Dockable dockable, Point location, Dimension size) | Opens the dockable in a new FloatingFrame at the given location with the given size |
JFrame::toFront function.
-
-A NotDockedException exception is thrown if the dockable is not docked.
-|
- |
-
-Call with an existing Dockable instance
- |
-
|
- |
-
-Call with the persistent ID of the dockable. Modern Docking will lookup the dockable. If not found, a DockableNotFoundException is thrown.
- |
-
This method is a combination of dock and bringToFront. If the dockable is not docked it will be docked and then brought to the front
display(Dockable dockable) | |
display(String persistentID) |
Checks if a dockable is already docked
-Checks if a dockable is currently maximized
-This event is fired when dockables are docked, undocked, shown, hidden, pinned or unpinned. Shown and hidden are used when a dockable is in a JTabbedPane and the active tab changes. Pinned and Unpinned are used when the dockable is added to a toolbar or removed from a toolbar. Shown and hidden will also be fired when a pinned dockable is shown and hidden.
Fired when layouts are added to or removed from DockingLayouts and when layouts are restored or persisted to a file
Thrown when a dockable is not found when restoring a DockingLayout
-DockingState.restoreStateThrown when a dockable with the persistentID has already been registered
Docking.registerDockableThis exception is thrown when there is an issue saving or loading a layout file. The exception provides the file that failed and the failure type
-AppState.restore, LayoutPersistence.saveLayoutToFile and LayoutPersistence.loadApplicationLayoutFromFileThis exception is thrown when Modern Docking attempts to use a dockable that should be docked but isn't. Thrown when the target dockable when docking is not docked or when attempting to bring a dockable to front that isn't already docked
-Docking.dock and Docking.bringToFrontThrown when the root for a window is not found
-Docking.configurePinning, Docking.dock and DockingComponentUtils.rootForWindowThrown when Modern Docking fails to register a RootDockingPanel because one is already registered for the window
Docing.registerDockingPanelThe ActiveDockableHighlighter is responsible for drawing a border around the dockable that the mouse is currently over. Using an AWT event listener on the AWT Toolkit lets us listen for all mouse events in the entire application.
Used to call AppState.persist whenever a Window instance resizes, moves or changes state.
Listen for when layouts are added to or removed from DockingLayouts and when layouts are restored or saved to a file
Listen for any changes to active dockables. This includes docking, undocking, shown, hidden, auto hide
-Listen for maximize events triggered by Docking.maximize
Listen for the creation of new floating frames. This allows the application to set icons, titles, menu bars, etc. on the new frame.
-This setting controls the color used for the background of Docking Handles.
-UIManager property used for the background color on Docking Handles. This property can be modified by calling DockingSettings.setHandleBackgroundProperty
Modern Docking property: ModernDocking.handleBackground
Default UIManager property: TableHeader.background
This setting controls the color used for the foreground of Docking Handles. The foreground color is used both for the borders and the mouse over color.
-UIManager property used for the foreground color on Docking Handles. This property can be modified by calling DockingSettings.setHandleForegroundProperty
Modern Docking property: ModernDocking.handleForeground
Default UIManager property: TableHeader.foreground
This setting controls the color used for the background of the default dockable header provided by Modern Docking.
-This property can be modified by calling DockingSettings.setHeaderBackgroundProperty
Modern Docking property: ModernDocking.headerBackground
Default UIManager property: TableHeader.background
This setting controls the color used for the foreground of the default dockable header provided by Modern Docking.
-This property can be modified by calling DockingSettings.setHeaderForegroundProperty
Modern Docking property: ModernDocking.headerForeground
Default UIManager property: TableHeader.foreground
This setting controls the color used for the background of the docking overlay. An alpha value less than 100% is typically used for this color.
-This property can be modified by calling DockingSettings.setOverlayBackgroundProperty
Modern Docking property: ModernDocking.overlayBackground
This setting controls the color used for the border color when the mouse is over a dockable and the active dockable highlighter is enabled.
-This property can be modified by calling DockingSettings.setHighlighterSelectedBorderProperty
Modern Docking property: ModernDocking.highlighterSelectedBorder
Default UIManager property: Component.focusColor
This setting controls the color used to reset to the default border when a dockable is no longer under the mouse. Modern Docking will use this color when the active dockable highlighter is active. The color should be set to the theme's default border color.
-This property can be modified by calling DockingSettings.setHighlighterNotSelectedBorderProperty
Modern Docking property: ModernDocking.highlighterNotSelectedBorder
Default UIManager property: Component.borderColor
Displays a list of all layouts that the layout manager knows about. These are automatically added and removed. When clicked the menu item will load that layout.
-Menu item specific to one layout. Simply displays the name and when clicked loads the layout with Docking.restore()
Displays a single dockable. Shows a checkmark if the dockable is docked. If the dockable is not docked, docks it, if it is docked, it displays it.
-- * http://www.apache.org/licenses/LICENSE-2.0 - *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - *
- * Copyright 2012-2015 the original author or authors. - */ -package exception; - -import java.lang.ref.WeakReference; -import java.util.Objects; -import javax.swing.JComponent; -import javax.swing.RepaintManager; - -import static javax.swing.SwingUtilities.isEventDispatchThread; - -/** - *
- * This class is used to detect Event Dispatch Thread rule violations
- * See How to Use Threads for more info
- *
- * This is a modification of original idea of Scott Delap.
- *
- * ...
- */
-abstract class CheckThreadViolationRepaintManager extends RepaintManager {
- private final boolean completeCheck;
-
- private WeakReference
- * Fails a test when a Event Dispatch Thread rule violation is detected. See How to Use Threads for more information.
- *
- * Creates a new {@link FailOnThreadViolationRepaintManager} and sets it as the current repaint manager.
- *
- * On Sun JVMs, this method will install the new repaint manager the first time only. Once installed, subsequent calls
- * to this method will not install new repaint managers. This optimization may not work on non-Sun JVMs, since we use
- * reflection to check if a {@code CheckThreadViolationRepaintManager.java} is already installed.
- *
- * Tries to restore the repaint manager before installing the {@link FailOnThreadViolationRepaintManager} via
- * {@link #install()}.
- * Each instance owns N child panels (N >= 2) and N-1 divider bars laid out
+ * in a single top-down pass. Every divider position is stored as a fraction of
+ * this panel's own rect, so dragging one divider changes exactly one
+ * stored value and leaves all sibling dividers at their absolute pixel positions.
+ *
+ * The old JSplitPane nesting problem is eliminated because there is no
+ * recursive proportion chain: every proportion is relative to the same rect.
*/
-public class DockedSplitPanel extends DockingPanel implements MouseListener, PropertyChangeListener {
- /**
- * Panel in the left/top part of the split
- */
- private DockingPanel left = null;
- /**
- * Panel in the right/bottom part of the split
- */
- private DockingPanel right = null;
-
- /**
- * The split pane we're controlling
- */
- private final JSplitPane splitPane = new JSplitPane();
- /**
- * The parent panel of this split panel
- */
- private DockingPanel dockedParent;
- /**
- * The docking instance this panel belongs to
- */
- private final DockingAPI docking;
- /**
- * The window this panel is in
- */
- private final Window window;
- /**
- * The anchor this panel belongs to, if any
- */
- private String anchor = "";
-
- /**
- * the last divider proportion that setDividerLocation was called with
- */
- private double lastRequestedDividerProportion;
-
- /**
- * Create a new DockedSplitPanel
- *
- * @param docking The docking instance
- * @param window The window this panel is in. Used to tell the child DockableWrappers what Window they are a part of
- * @param anchor The anchor associated with this docking panel
- */
- public DockedSplitPanel(DockingAPI docking, Window window, String anchor) {
- this.docking = docking;
- this.window = window;
- this.anchor = anchor;
-
- setLayout(new BorderLayout());
-
- splitPane.setContinuousLayout(true);
- splitPane.setResizeWeight(0.5);
- splitPane.setBorder(null);
- splitPane.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, this);
-
- setDividerLocation(splitPane.getResizeWeight());
-
- lastRequestedDividerProportion = splitPane.getResizeWeight();
-
- if (splitPane.getUI() instanceof BasicSplitPaneUI) {
- ((BasicSplitPaneUI) splitPane.getUI()).getDivider().addMouseListener(this);
- }
-
- add(splitPane, BorderLayout.CENTER);
- }
-
- /**
- * Retrieve the last double that we used to set the divider proportion with. This helps us properly
- * restore divider locations when restoring from a layout.
- *
- * @return Requested divider proportion
- */
- public double getLastRequestedDividerProportion() {
- return lastRequestedDividerProportion;
- }
-
- /**
- * Set the divider location of the splitpane. Tricks are needed to properly set the position.
- * The position cannot be set until the JSplitPane is displayed on screen.
- *
- * @param proportion The new proportion of the splitpane
- */
- public void setDividerLocation(final double proportion) {
- lastRequestedDividerProportion = proportion;
-
- // calling setDividerLocation on a JSplitPane that isn't visible does nothing, so we need to check if it is showing first
- if (splitPane.isShowing()) {
- if (splitPane.getWidth() > 0 && splitPane.getHeight() > 0) {
- splitPane.setDividerLocation(proportion);
- }
- else {
- // split hasn't been completely calculated yet, wait until componentResize
- splitPane.addComponentListener(new ComponentAdapter() {
- @Override
- public void componentResized(ComponentEvent e) {
- // remove this listener, it's a one off
- splitPane.removeComponentListener(this);
- // call the function again, this time it should actually set the divider location
- setDividerLocation(proportion);
- }
- });
- }
- }
- else {
- // split hasn't been shown yet, wait until it's showing
- splitPane.addHierarchyListener(new HierarchyListener() {
- @Override
- public void hierarchyChanged(HierarchyEvent e) {
- boolean isShowingChangeEvent = (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0;
-
- if (isShowingChangeEvent && splitPane.isShowing()) {
- // remove this listener, it's a one off
- splitPane.removeHierarchyListener(this);
- // call the function again, this time it might set the size or wait for componentResize
- setDividerLocation(proportion);
- }
- }
- });
- }
- }
-
- /**
- * Used to set the real divider location, which is set as an int, not a double.
- *
- * @param location The new proportion of the splitpane
- */
- public void setDividerLocation(final int location) {
- if (splitPane.isShowing()) {
- if ((splitPane.getWidth() > 0) && (splitPane.getHeight() > 0)) {
- splitPane.setDividerLocation(location);
-
- docking.getAppState().persist();
- }
- else {
- // split hasn't been completely calculated yet, wait until componentResize
- splitPane.addComponentListener(new ComponentAdapter() {
- @Override
- public void componentResized(ComponentEvent e) {
- // remove this listener, it's a one off
- splitPane.removeComponentListener(this);
- // call the function again, this time it should actually set the divider location
- setDividerLocation(location);
- }
- });
- }
- }
- else {
- // split hasn't been shown yet, wait until it's showing
- splitPane.addHierarchyListener(new HierarchyListener() {
- @Override
- public void hierarchyChanged(HierarchyEvent e) {
- boolean isShowingChangeEvent = (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0;
-
- if (isShowingChangeEvent && splitPane.isShowing()) {
- // remove this listener, it's a one off
- splitPane.removeHierarchyListener(this);
- // call the function again, this time it might set the size or wait for componentResize
- setDividerLocation(location);
- }
- }
- });
- }
- }
-
- /**
- * Access to the underlying JSplitPane. This is required so that we can pull a bunch of values for saving layouts.
- *
- * @return The JSplitPane used by this DockedSplitPanel
- */
- public JSplitPane getSplitPane() {
- return splitPane;
- }
-
- /**
- * Get the left/top component of the split
- *
- * @return Left/top component in the split
- */
- public DockingPanel getLeft() {
- return left;
- }
-
- /**
- * Set the panel in the left/top of split
- *
- * @param panel New left/top panel
- */
- public void setLeft(DockingPanel panel) {
- left = panel;
- left.setParent(this);
-// left.setAnchor(anchor);
-
- // remember where the divider was and put it back
- int dividerLocation = splitPane.getDividerLocation();
-
- splitPane.setLeftComponent(panel);
-
- splitPane.setDividerLocation(dividerLocation);
- }
-
- /**
- * Get the right/bottom component of the split
- *
- * @return Right/bottom component in the split
- */
- public DockingPanel getRight() {
- return right;
- }
-
- /**
- * Set the right panel of the split
- *
- * @param panel New right panel
- */
- public void setRight(DockingPanel panel) {
- right = panel;
- right.setParent(this);
-// right.setAnchor(anchor);
-
- // remember where the divider was and put it back
- int dividerLocation = splitPane.getDividerLocation();
-
- splitPane.setRightComponent(panel);
-
- splitPane.setDividerLocation(dividerLocation);
- }
-
- /**
- * Set the orientation of the split pane
- *
- * @param orientation New orientation of the split pane
- */
- public void setOrientation(int orientation) {
- splitPane.setOrientation(orientation);
-
- if (splitPane.getUI() instanceof BasicSplitPaneUI) {
- // grab the divider from the UI and remove the border from it
- BasicSplitPaneDivider divider = ((BasicSplitPaneUI) splitPane.getUI())
- .getDivider();
-
- if (divider != null && divider.getBorder() != null) {
- divider.setBorder(null);
- }
- }
- }
-
- @Override
- public String getAnchor() {
- return anchor;
- }
-
- @Override
- public void setAnchor(String anchor) {
- this.anchor = anchor;
- }
-
- @Override
- public void setParent(DockingPanel parent) {
- this.dockedParent = parent;
- }
-
- @Override
- public void dock(Dockable dockable, DockingRegion region, double dividerProportion) {
- DockableWrapper wrapper = DockingInternal.get(docking).getWrapper(dockable);
-
- // docking to the center of a split isn't something we allow
- // wouldn't be difficult to support, but isn't a complication we want in this framework
- if (region == DockingRegion.CENTER) {
- region = splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT ? DockingRegion.WEST : DockingRegion.NORTH;
- }
-
- wrapper.setWindow(window);
-
- DockedSplitPanel split = new DockedSplitPanel(docking, window, anchor);
- dockedParent.replaceChild(this, split);
-
- DockingPanel newPanel;
-
- if (Settings.alwaysDisplayTabsMode()) {
- newPanel = new DockedTabbedPanel(docking, wrapper, anchor);
- }
- else {
- newPanel = new DockedSimplePanel(docking, wrapper, anchor);
- }
-
- if (region == DockingRegion.EAST || region == DockingRegion.SOUTH) {
- split.setLeft(this);
- split.setRight(newPanel);
- dividerProportion = 1.0 - dividerProportion;
- }
- else {
- split.setLeft(newPanel);
- split.setRight(this);
- }
-
- if (region == DockingRegion.EAST || region == DockingRegion.WEST) {
- split.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
- }
- else {
- split.setOrientation(JSplitPane.VERTICAL_SPLIT);
- }
-
- split.setDividerLocation(dividerProportion);
- }
-
- @Override
- public void undock(Dockable dockable) {
- }
-
- @Override
- public void replaceChild(DockingPanel child, DockingPanel newChild) {
- if (left == child) {
- setLeft(newChild);
- }
- else if (right == child) {
- setRight(newChild);
- }
- }
-
- @Override
- public void removeChild(DockingPanel child) {
- // safety against partially configured layout restorations
- if (dockedParent == null) {
- return;
- }
-
- if (left == child) {
- dockedParent.replaceChild(this, right);
- }
- else if (right == child) {
- dockedParent.replaceChild(this, left);
- }
- }
-
- public List
+ * {@link FloatUtilsFrame} renders the docking handles and highlight overlay on a transparent
+ * top-level window. {@code FloatUtilsLayer} renders the same content on a {@code JPanel} added to
+ * the host window's {@code JLayeredPane}, which works on platforms that do not support per-pixel
+ * window translucency.
+ */
+public interface FloatUtils {
+
+ /**
+ * Activate the overlay for a new drag operation.
+ *
+ * @param floatListener The listener that started the drag
+ * @param floatingFrame The temporary frame containing the floating dockable
+ * @param dragSource The drag source for the operation
+ * @param mousePosOnScreen Initial mouse position in screen coordinates
+ */
+ void activate(FloatListener floatListener, JFrame floatingFrame, DragSource dragSource, Point mousePosOnScreen);
+
+ /**
+ * Deactivate the overlay when dragging ends.
+ */
+ void deactivate();
+
+ /**
+ * Check whether the mouse is currently over a root docking handle.
+ *
+ * @return true if over a root handle
+ */
+ boolean isOverRootHandle();
+
+ /**
+ * Get the root docking region currently indicated by the mouse.
+ *
+ * @return Root region, or null if not over a root handle
+ */
+ DockingRegion rootHandleRegion();
+
+ /**
+ * Check whether the mouse is currently over an auto-hide (pin) handle.
+ *
+ * @return true if over a pin handle
+ */
+ boolean isOverPinHandle();
+
+ /**
+ * Get the auto-hide toolbar location currently indicated by the mouse.
+ *
+ * @return Pin region, or null if not over a pin handle
+ */
+ ToolbarLocation pinRegion();
+
+ /**
+ * Check whether the mouse is currently over a dockable region handle.
+ *
+ * @return true if over a dockable handle
+ */
+ boolean isOverDockableHandle();
+
+ /**
+ * Check whether the mouse is currently positioned for a tab drop.
+ *
+ * @return true if over a tab target
+ */
+ boolean isOverTab();
+
+ /**
+ * Get the dockable region handle currently indicated by the mouse.
+ *
+ * @return Region of the target dockable, or null
+ */
+ DockingRegion dockableHandle();
+
+ /**
+ * Determine the docking region for a free mouse position over a target dockable.
+ *
+ * @param targetDockable The dockable the mouse is over
+ * @param floatingDockable The dockable being dragged
+ * @param mousePosOnScreen Mouse position in screen coordinates
+ *
+ * @return The docking region
+ */
+ DockingRegion getDockableRegion(Dockable targetDockable, Dockable floatingDockable, Point mousePosOnScreen);
+
+ /**
+ * Release all resources associated with this overlay.
+ * Called when the host window is deregistered.
+ */
+ void dispose();
+}
diff --git a/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/FloatUtilsFrame.java b/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/FloatUtilsFrame.java
index a6d20253..1319913a 100644
--- a/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/FloatUtilsFrame.java
+++ b/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/FloatUtilsFrame.java
@@ -48,7 +48,7 @@ of this software and associated documentation files (the "Software"), to deal
/**
* A special invisible frame that's used to provide docking handles and overlays
*/
-public class FloatUtilsFrame extends JFrame implements DragSourceMotionListener, ComponentListener, WindowListener {
+public class FloatUtilsFrame extends JFrame implements FloatUtils, DragSourceMotionListener, ComponentListener, WindowListener {
/**
* The window this utility frame is tied to
*/
@@ -501,7 +501,6 @@ public void windowDeiconified(WindowEvent e) {
@Override
public void windowActivated(WindowEvent e) {
-// System.out.println("windowActivated " + ((JFrame) e.getWindow()).getTitle());
windowStack.remove(e.getWindow());
windowStack.add(e.getWindow());
@@ -513,26 +512,21 @@ public void windowActivated(WindowEvent e) {
if (windowStack.size() == 3 && windowStack.get(0) == referenceDockingWindow &&
windowStack.get(1) == floatingFrame && windowStack.get(2) == this) {
// perfect
-// System.out.println("Windows are in proper order 1");
}
else if (windowStack.size() == 2 && windowStack.get(0) == referenceDockingWindow &&
windowStack.get(1) == this) {
// perfect
-// System.out.println("Windows are in proper order 2");
}
else if (windowStack.get(0) != referenceDockingWindow) {
// reference window isn't at the bottom, figure out which frame is out of order
if (windowStack.get(0) == floatingFrame) {
-// System.out.println("first frame is floating frame, bring to front");
SwingUtilities.invokeLater(floatingFrame::toFront);
}
else if (windowStack.get(0) == this) {
-// System.out.println("First frame is utils frame, bring to front");
SwingUtilities.invokeLater(this::toFront);
}
}
else if (windowStack.size() > 1 && windowStack.get(1) != floatingFrame) {
-// System.out.println("Second frame is not floating frame, bring utils to front");
SwingUtilities.invokeLater(this::toFront);
}
}
diff --git a/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/FloatUtilsLayer.java b/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/FloatUtilsLayer.java
new file mode 100644
index 00000000..b6bee73d
--- /dev/null
+++ b/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/FloatUtilsLayer.java
@@ -0,0 +1,357 @@
+/*
+Copyright (c) 2026 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 io.github.andrewauclair.moderndocking.internal.floating;
+
+import io.github.andrewauclair.moderndocking.Dockable;
+import io.github.andrewauclair.moderndocking.DockingRegion;
+import io.github.andrewauclair.moderndocking.api.DockingAPI;
+import io.github.andrewauclair.moderndocking.internal.CustomTabbedPane;
+import io.github.andrewauclair.moderndocking.internal.DockingComponentUtils;
+import io.github.andrewauclair.moderndocking.internal.InternalRootDockingPanel;
+import io.github.andrewauclair.moderndocking.ui.ToolbarLocation;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Window;
+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 javax.swing.JFrame;
+import javax.swing.JLayeredPane;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+/**
+ * A {@link FloatUtils} implementation that renders docking handles and the drop overlay on a
+ * transparent {@link JPanel} added to the host window's {@link JLayeredPane}.
+ *
+ * This approach is used on platforms that do not support per-pixel window translucency,
+ * where {@link FloatUtilsFrame} cannot make its background transparent.
+ * Because the panel lives inside the host window there is no separate overlay
+ * window and no need for z-order management.
+ */
+class FloatUtilsLayer implements FloatUtils, DragSourceMotionListener, ComponentListener {
+
+ private final Window referenceDockingWindow;
+ private final InternalRootDockingPanel root;
+ private final JLayeredPane layeredPane;
+ private final RootDockingHandles rootHandles;
+ private final FloatingOverlay overlay;
+
+ private FloatListener floatListener;
+ private JFrame floatingFrame;
+ private DragSource dragSource;
+ private Dockable currentDockable;
+ private DockableHandles dockableHandles;
+
+ /**
+ * Proxy panel that hosts the floating dockable's content while the drag is inside the host
+ * window. Sits below {@link #renderPanel} in the layered pane so the docking handles and
+ * drop overlay render on top. Its {@code contains()} always returns {@code false} so that
+ * {@code SwingUtilities.getDeepestComponentAt} passes through to the underlying dockables.
+ */
+ private final JPanel floatingProxy = new JPanel(new BorderLayout()) {
+ @Override
+ public boolean contains(int x, int y) {
+ return false;
+ }
+ };
+
+ /**
+ * Transparent panel covering the layered pane. Handles are added as children so that
+ * their bounds are expressed in this panel's coordinate space, making hit-testing and
+ * coordinate conversion consistent.
+ */
+ private final JPanel renderPanel = new JPanel() {
+ @Override
+ public boolean contains(int x, int y) {
+ // Pass through for SwingUtilities.getDeepestComponentAt so dockables
+ // underneath the overlay remain discoverable during hit-testing.
+ return false;
+ }
+
+ @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();
+ }
+ };
+
+ /**
+ * Create a new layer-based overlay tied to the given host window.
+ *
+ * @param docking The docking instance
+ * @param referenceDockingWindow The host window; must be a {@link JFrame}
+ * @param root The internal root panel within the host window
+ */
+ FloatUtilsLayer(DockingAPI docking, JFrame referenceDockingWindow, InternalRootDockingPanel root) {
+ this.referenceDockingWindow = referenceDockingWindow;
+ this.root = root;
+ this.layeredPane = referenceDockingWindow.getLayeredPane();
+
+ floatingProxy.setVisible(false);
+ layeredPane.add(floatingProxy, JLayeredPane.POPUP_LAYER);
+
+ renderPanel.setOpaque(false);
+ renderPanel.setLayout(null);
+
+ this.rootHandles = new RootDockingHandles(renderPanel, root);
+ this.overlay = new FloatingOverlay(docking, renderPanel);
+
+ referenceDockingWindow.addComponentListener(this);
+ SwingUtilities.invokeLater(this::updatePanelBounds);
+
+ layeredPane.add(renderPanel, JLayeredPane.DRAG_LAYER);
+ renderPanel.setVisible(false);
+ }
+
+ @Override
+ public void activate(FloatListener floatListener, JFrame floatingFrame, DragSource dragSource, Point mousePosOnScreen) {
+ this.floatListener = floatListener;
+ this.floatingFrame = floatingFrame;
+ this.dragSource = dragSource;
+ dragSource.addDragSourceMotionListener(this);
+
+ // Make the panel visible before any coordinate conversion: renderPanel is a JPanel,
+ // so getLocationOnScreen() (used internally by SwingUtilities.convertPointFromScreen)
+ // requires isShowing() == true. Unlike FloatUtilsFrame (a JFrame whose screen
+ // location is valid even before setVisible), renderPanel must be in a visible
+ // hierarchy first.
+ renderPanel.setVisible(true);
+ updatePanelBounds();
+
+ // Reparent the floating dockable's content into the layered pane proxy so that the
+ // actual dockable renders inside the host window rather than as a separate OS window.
+ // A Swing component can only have one parent, so we move it out of the JFrame.
+ Container contentPane = floatingFrame.getContentPane();
+ if (contentPane.getComponentCount() > 0) {
+ floatingProxy.add(contentPane.getComponent(0), BorderLayout.CENTER);
+ }
+ floatingFrame.setVisible(false);
+ updateFloatingProxyBounds();
+ floatingProxy.setVisible(true);
+
+ if (floatListener instanceof DisplayPanelFloatListener) {
+ Dockable floatingDockable = ((DisplayPanelFloatListener) floatListener).getDockable();
+ rootHandles.setFloatingDockable(floatingDockable);
+ }
+
+ mouseMoved(mousePosOnScreen);
+ renderPanel.repaint();
+ }
+
+ @Override
+ public void deactivate() {
+ renderPanel.setVisible(false);
+
+ if (dragSource != null) {
+ dragSource.removeDragSourceMotionListener(this);
+ }
+ floatListener = null;
+
+ // Move the floating dockable content back into its JFrame before making it visible,
+ // so the user sees the dockable ghost while dragging outside the host window.
+ if (floatingFrame != null) {
+ if (floatingProxy.getComponentCount() > 0) {
+ floatingFrame.getContentPane().add(floatingProxy.getComponent(0), BorderLayout.CENTER);
+ }
+ floatingFrame.setVisible(true);
+ }
+ floatingProxy.setVisible(false);
+ floatingProxy.removeAll();
+
+ 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;
+ }
+
+ updateFloatingProxyBounds();
+
+ rootHandles.mouseMoved(mousePosOnScreen);
+
+ if (dockableHandles != null) {
+ dockableHandles.mouseMoved(mousePosOnScreen);
+ }
+
+ boolean prevVisible = overlay.isVisible();
+
+ // Hide the overlay; it will be set visible again if we find a target
+ 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(renderPanel, dockable, floatingDockable);
+ }
+ else {
+ dockableHandles = new DockableHandles(renderPanel, dockable);
+ }
+ }
+ currentDockable = dockable;
+
+ if (rootHandles.isOverHandle()) {
+ overlay.updateForRoot(root, rootHandles.getRegion());
+ }
+ else if (dockableHandles != null) {
+ overlay.updateForDockable(currentDockable, floatingDockable, mousePosOnScreen, dockableHandles.getRegion());
+ }
+ else if (currentDockable == null && floatListener instanceof DisplayPanelFloatListener) {
+ CustomTabbedPane tabbedPane = DockingComponentUtils.findTabbedPaneAtPos(mousePosOnScreen, referenceDockingWindow);
+ if (tabbedPane != null) {
+ overlay.updateForTab(tabbedPane, mousePosOnScreen);
+ }
+ }
+
+ renderPanel.repaint();
+ if (overlay.requiresRedraw() || prevVisible != overlay.isVisible()) {
+ renderPanel.repaint();
+ overlay.clearRedraw();
+ }
+ }
+
+ @Override
+ public boolean isOverRootHandle() {
+ return rootHandles.isOverHandle();
+ }
+
+ @Override
+ public DockingRegion rootHandleRegion() {
+ return rootHandles.getRegion();
+ }
+
+ @Override
+ public boolean isOverPinHandle() {
+ return rootHandles.isOverPinHandle();
+ }
+
+ @Override
+ public ToolbarLocation pinRegion() {
+ return rootHandles.getPinRegion();
+ }
+
+ @Override
+ public boolean isOverDockableHandle() {
+ return dockableHandles != null && dockableHandles.getRegion() != null;
+ }
+
+ @Override
+ public boolean isOverTab() {
+ return overlay.isOverTab();
+ }
+
+ @Override
+ public DockingRegion dockableHandle() {
+ return dockableHandles == null ? null : dockableHandles.getRegion();
+ }
+
+ @Override
+ public DockingRegion getDockableRegion(Dockable targetDockable, Dockable floatingDockable, Point mousePosOnScreen) {
+ return overlay.getRegion(targetDockable, floatingDockable, mousePosOnScreen);
+ }
+
+ @Override
+ public void dispose() {
+ referenceDockingWindow.removeComponentListener(this);
+ layeredPane.remove(renderPanel);
+ layeredPane.remove(floatingProxy);
+ }
+
+ // -- ComponentListener --
+
+ @Override
+ public void componentResized(ComponentEvent e) {
+ SwingUtilities.invokeLater(this::updatePanelBounds);
+ }
+
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ // Panel position is relative to the layered pane; it doesn't need updating on window move
+ }
+
+ @Override
+ public void componentShown(ComponentEvent e) {
+ // Panel position is relative to the layered pane; it doesn't need updating on window shown
+ }
+
+ @Override
+ public void componentHidden(ComponentEvent e) {
+ // Panel position is relative to the layered pane; it doesn't need updating on window hidden
+ }
+
+ private void updatePanelBounds() {
+ renderPanel.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight());
+ // updateHandlePositions calls SwingUtilities.convertPointFromScreen against renderPanel,
+ // which requires renderPanel.isShowing() == true. Skip it here when the panel is hidden
+ // (e.g. the invokeLater from the constructor); activate() calls updatePanelBounds again
+ // after making the panel visible.
+ if (renderPanel.isShowing()) {
+ rootHandles.updateHandlePositions();
+ }
+ renderPanel.revalidate();
+ renderPanel.repaint();
+ }
+
+ private void updateFloatingProxyBounds() {
+ if (floatingFrame == null) {
+ return;
+ }
+ Point pos = new Point(floatingFrame.getLocation());
+ SwingUtilities.convertPointFromScreen(pos, layeredPane);
+ floatingProxy.setBounds(pos.x, pos.y, floatingFrame.getWidth(), floatingFrame.getHeight());
+ }
+}
diff --git a/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/Floating.java b/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/Floating.java
index e4f2a7c8..b4e703ad 100644
--- a/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/Floating.java
+++ b/docking-api/src/io/github/andrewauclair/moderndocking/internal/floating/Floating.java
@@ -23,16 +23,18 @@ of this software and associated documentation files (the "Software"), to deal
import io.github.andrewauclair.moderndocking.api.DockingAPI;
import io.github.andrewauclair.moderndocking.internal.InternalRootDockingPanel;
+import io.github.andrewauclair.moderndocking.ui.DockingSettings;
import java.awt.Window;
import java.util.HashMap;
import java.util.Map;
+import javax.swing.JFrame;
import javax.swing.SwingUtilities;
/**
* Small utility class for floating feature
*/
public class Floating {
- private static final Map
+ * When transparency is not supported, {@link Floating} uses a JLayeredPane-based
+ * overlay instead of a transparent JFrame.
+ */
+class TransparencyProbe {
+ /** Reference background color used by the Robot-based check. Chosen to be visually
+ * distinctive (magenta) and unlikely to match typical desktop content. */
+ private static final Color PROBE_COLOR = new Color(255, 0, 255);
+ /** Sentinel color painted on the probe frame before switching to transparent.
+ * Allows Robot polling to confirm the probe frame is actually on screen. */
+ private static final Color SENTINEL_COLOR = new Color(0, 255, 255);
+ /** Per-channel tolerance for the Robot color comparison. */
+ private static final int COLOR_TOLERANCE = 30;
+ /** How long to poll for the sentinel/transparent frame to appear (ms). */
+ private static final int POLL_TIMEOUT_MS = 2000;
+ /** Polling interval while waiting for a frame to appear (ms). */
+ private static final int POLL_INTERVAL_MS = 20;
+
+ private static volatile Boolean cachedResult = null;
+
+ private TransparencyProbe() {
+ }
+
+ /**
+ * Returns whether per-pixel window translucency is supported on this platform.
+ * Runs the probe on the first call; subsequent calls return the cached result.
+ *
+ * Should be triggered early (e.g., in {@link Floating#registerDockingWindow}) so the
+ * result is available before any drag operation begins.
+ *
+ * @return true if transparent overlay windows will work correctly
+ */
+ static boolean isTransparencySupported() {
+ if (cachedResult == null) {
+ cachedResult = probe();
+ }
+ return cachedResult;
+ }
+
+ private static boolean probe() {
+ // Over SSH X11 forwarding every Robot.getPixelColor() call is a round-trip
+ // to the remote X server, making the empirical check extremely slow.
+ // Transparency is also unreliable in that environment, so return false
+ // immediately and let Floating fall back to the JLayeredPane overlay.
+ if (isRemoteX11Display()) {
+ return false;
+ }
+
+ GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
+
+ // Fast path: platform declares per-pixel translucency unsupported
+ if (!gd.isWindowTranslucencySupported(GraphicsDevice.WindowTranslucency.PERPIXEL_TRANSLUCENT)) {
+ return false;
+ }
+
+ return probeOffEDT(gd);
+ }
+
+ /**
+ * Returns true when running inside an SSH session with X11 forwarding.
+ * Detected by the presence of {@code SSH_CONNECTION} or {@code SSH_CLIENT}
+ * environment variables together with a non-null {@code DISPLAY}.
+ */
+ private static boolean isRemoteX11Display() {
+ if (System.getenv("DISPLAY") == null) {
+ return false;
+ }
+ return System.getenv("SSH_CONNECTION") != null || System.getenv("SSH_CLIENT") != null;
+ }
+
+ /**
+ * Full empirical check for when the probe runs off the EDT.
+ *
+ * Phase 1: shows the probe frame as an opaque sentinel color (cyan) and polls
+ * Robot until that color appears at the sample point, confirming the probe is
+ * composited on screen and on top of the magenta reference frame.
+ *
+ * Phase 2: switches the probe frame to fully transparent and samples again.
+ * If the underlying magenta reference frame shows through, per-pixel translucency
+ * works. If the pixel stays black (or the sentinel color), it does not.
+ */
+ private static boolean probeOffEDT(GraphicsDevice gd) {
+ Rectangle screen = gd.getDefaultConfiguration().getBounds();
+ int sampleX = screen.x + 4;
+ int sampleY = screen.y + 4;
+
+ // holder[0] = reference window, holder[1] = probe window (null if setup failed)
+ JWindow[] holder = { null, null };
+
+ // --- Phase 1: create reference (magenta) + probe (opaque sentinel cyan) ---
+ // JWindow is used instead of JFrame so neither window appears in the taskbar.
+ try {
+ SwingUtilities.invokeAndWait(() -> {
+ JWindow reference = new JWindow();
+ reference.setType(Window.Type.UTILITY);
+ reference.getContentPane().setBackground(PROBE_COLOR);
+ reference.setSize(8, 8);
+ reference.setLocation(screen.x, screen.y);
+ reference.setVisible(true);
+ holder[0] = reference;
+
+ JWindow probe = new JWindow();
+ probe.setType(Window.Type.UTILITY);
+ // Start opaque with sentinel color so we can confirm it is on screen
+ // before switching to transparent.
+ probe.getContentPane().setBackground(SENTINEL_COLOR);
+ probe.setSize(8, 8);
+ probe.setLocation(screen.x, screen.y);
+ probe.setVisible(true);
+ holder[1] = probe;
+ });
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ catch (InvocationTargetException e) {
+ return false;
+ }
+
+ Robot robot;
+ try {
+ robot = new Robot(gd);
+ // Drain the event queue once after frame creation; subsequent polls use
+ // only Thread.sleep to avoid hammering the EDT every 20 ms.
+ robot.waitForIdle();
+ }
+ catch (AWTException e) {
+ SwingUtilities.invokeLater(() -> {
+ if (holder[0] != null) holder[0].dispose();
+ if (holder[1] != null) holder[1].dispose();
+ });
+ return false;
+ }
+
+ // Poll until the sentinel color is visible, confirming the probe frame is rendered
+ // and on top before we attempt the transparency switch.
+ boolean sentinelSeen = pollForColor(robot, sampleX, sampleY, SENTINEL_COLOR);
+
+ if (!sentinelSeen) {
+ // Can't confirm the probe frame appeared — conservative fallback
+ SwingUtilities.invokeLater(() -> {
+ if (holder[0] != null) holder[0].dispose();
+ if (holder[1] != null) holder[1].dispose();
+ });
+ return false;
+ }
+
+ // --- Phase 2: switch probe to transparent, then check for magenta reference ---
+ try {
+ SwingUtilities.invokeAndWait(() -> {
+ JWindow probe = holder[1];
+ try {
+ probe.setBackground(new Color(0, 0, 0, 0));
+ probe.getRootPane().setBackground(new Color(0, 0, 0, 0));
+ if (probe.getContentPane() instanceof JComponent) {
+ ((JComponent) probe.getContentPane()).setOpaque(false);
+ }
+ probe.repaint();
+ }
+ catch (IllegalComponentStateException e) {
+ // Transparency setup failed — signal via null
+ holder[1] = null;
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ SwingUtilities.invokeLater(() -> {
+ if (holder[0] != null) holder[0].dispose();
+ });
+ return false;
+ }
+ catch (InvocationTargetException e) {
+ SwingUtilities.invokeLater(() -> {
+ if (holder[0] != null) holder[0].dispose();
+ });
+ return false;
+ }
+
+ if (holder[1] == null) {
+ SwingUtilities.invokeLater(() -> {
+ if (holder[0] != null) holder[0].dispose();
+ });
+ return false;
+ }
+
+ // Drain the EDT once after the transparency switch before polling.
+ robot.waitForIdle();
+
+ // Poll for the magenta reference to become visible through the now-transparent probe
+ boolean result;
+ try {
+ result = pollForColor(robot, sampleX, sampleY, PROBE_COLOR);
+ }
+ finally {
+ JWindow ref = holder[0];
+ JWindow prob = holder[1];
+ SwingUtilities.invokeLater(() -> {
+ if (ref != null) ref.dispose();
+ if (prob != null) prob.dispose();
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Polls {@link Robot#getPixelColor} at (x, y) until the sampled color is within
+ * {@link #COLOR_TOLERANCE} of {@code target}, or {@link #POLL_TIMEOUT_MS} elapses.
+ *
+ * @return true if the target color was observed within the timeout
+ */
+ private static boolean pollForColor(Robot robot, int x, int y, Color target) {
+ long deadline = System.currentTimeMillis() + POLL_TIMEOUT_MS;
+ while (System.currentTimeMillis() < deadline) {
+ if (isCloseTo(robot.getPixelColor(x, y), target)) {
+ return true;
+ }
+ try {
+ Thread.sleep(POLL_INTERVAL_MS);
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isCloseTo(Color c, Color target) {
+ return Math.abs(c.getRed() - target.getRed()) < COLOR_TOLERANCE
+ && Math.abs(c.getGreen() - target.getGreen()) < COLOR_TOLERANCE
+ && Math.abs(c.getBlue() - target.getBlue()) < COLOR_TOLERANCE;
+ }
+}
\ No newline at end of file
diff --git a/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingAnchorPanelNode.java b/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingAnchorPanelNode.java
index 4d5aa83c..44f4fce1 100644
--- a/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingAnchorPanelNode.java
+++ b/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingAnchorPanelNode.java
@@ -21,11 +21,15 @@ of this software and associated documentation files (the "Software"), to deal
*/
package io.github.andrewauclair.moderndocking.layouts;
+import io.github.andrewauclair.moderndocking.Dockable;
import io.github.andrewauclair.moderndocking.DockingRegion;
import io.github.andrewauclair.moderndocking.Property;
import io.github.andrewauclair.moderndocking.api.DockingAPI;
+import io.github.andrewauclair.moderndocking.internal.DockingInternal;
+import io.github.andrewauclair.moderndocking.settings.Settings;
import java.util.HashMap;
import java.util.Map;
+import javax.swing.JSplitPane;
/**
* Layout node representing a docking anchor for Window and Application layouts
@@ -59,9 +63,48 @@ public DockingLayoutNode findNode(String persistentID) {
return null;
}
+ // does not match DockingAnchorPanel on purpose. The below approach (supporting region) allows for more flexibility
+ // when building layouts
@Override
public void dock(String persistentID, DockingRegion region, double dividerProportion) {
- // TODO I think we need to handle something here
+ Dockable newDockable = DockingInternal.get(docking).getDockable(persistentID);
+ String newClassName = newDockable.getClass().getTypeName();
+ String newTitleText = newDockable.getTitleText();
+ String newTabText = newDockable.getTabText();
+
+ int orientation = (region == DockingRegion.EAST || region == DockingRegion.WEST)
+ ? JSplitPane.HORIZONTAL_SPLIT : JSplitPane.VERTICAL_SPLIT;
+
+ DockingLayoutNode left;
+ DockingLayoutNode right;
+
+ if (Settings.alwaysDisplayTabsMode()) {
+ if (orientation == JSplitPane.HORIZONTAL_SPLIT) {
+ left = region == DockingRegion.EAST ? this : new DockingTabPanelNode(docking, persistentID, "", "", newTitleText, newTabText);
+ right = region == DockingRegion.EAST ? new DockingTabPanelNode(docking, persistentID, "", "", newTitleText, newTabText) : this;
+ }
+ else {
+ left = region == DockingRegion.SOUTH ? this : new DockingTabPanelNode(docking, persistentID, "", "", newTitleText, newTabText);
+ right = region == DockingRegion.SOUTH ? new DockingTabPanelNode(docking, persistentID, "", "", newTitleText, newTabText) : this;
+ }
+ } else {
+ if (orientation == JSplitPane.HORIZONTAL_SPLIT) {
+ left = region == DockingRegion.EAST ? this : new DockingSimplePanelNode(docking, persistentID, newClassName, "", newTitleText, newTabText);
+ right = region == DockingRegion.EAST ? new DockingSimplePanelNode(docking, persistentID, newClassName, "", newTitleText, newTabText) : this;
+ }
+ else {
+ left = region == DockingRegion.SOUTH ? this : new DockingSimplePanelNode(docking, persistentID, newClassName, "", newTitleText, newTabText);
+ right = region == DockingRegion.SOUTH ? new DockingSimplePanelNode(docking, persistentID, newClassName, "", newTitleText, newTabText) : this;
+ }
+ }
+
+ if (region == DockingRegion.EAST || region == DockingRegion.SOUTH) {
+ dividerProportion = 1.0 - dividerProportion;
+ }
+
+ DockingLayoutNode oldParent = parent;
+ DockingSplitPanelNode split = new DockingSplitPanelNode(docking, left, right, orientation, dividerProportion, "");
+ oldParent.replaceChild(this, split);
}
@Override
diff --git a/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingLayoutRootNode.java b/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingLayoutRootNode.java
index fd958a81..d6884d67 100644
--- a/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingLayoutRootNode.java
+++ b/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingLayoutRootNode.java
@@ -57,6 +57,7 @@ else if (DockingInternal.get(docking).hasAnchor(persistentID)) {
String className = DockingInternal.get(docking).getDockable(persistentID).getClass().getTypeName();
node = new DockingAnchorPanelNode(docking, persistentID, className);
+ node.setParent(this);
}
else if (Settings.alwaysDisplayTabsMode()) {
node = new DockingTabPanelNode(docking, persistentID, "", "", "", "");
diff --git a/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingLayouts.java b/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingLayouts.java
index ef388c06..a6481dca 100644
--- a/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingLayouts.java
+++ b/docking-api/src/io/github/andrewauclair/moderndocking/layouts/DockingLayouts.java
@@ -42,7 +42,6 @@ of this software and associated documentation files (the "Software"), to deal
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import javax.swing.JSplitPane;
/**
* Manage storage, persistence and restoration of application layouts
@@ -217,17 +216,11 @@ else if (panel == null) {
}
private static DockingLayoutNode splitPanelToNode(DockingAPI docking, DockedSplitPanel panel) {
- JSplitPane splitPane = panel.getSplitPane();
-
- int orientation = splitPane.getOrientation();
- int height = splitPane.getHeight();
- int dividerSize = splitPane.getDividerSize();
- int dividerLocation = splitPane.getDividerLocation();
- int width = splitPane.getWidth();
- double dividerProportion = orientation == JSplitPane.VERTICAL_SPLIT ? dividerLocation / (float) (height - dividerSize) :
- dividerLocation / (float) (width - dividerSize);
-
- return new DockingSplitPanelNode(docking, panelToNode(docking, panel.getLeft()), panelToNode(docking, panel.getRight()), splitPane.getOrientation(), dividerProportion, panel.getAnchor());
+ List
+ * When {@code true}, the layered-pane overlay is always used regardless of what the
+ * {@code TransparencyProbe} reports. When {@code false} (the default), the overlay
+ * strategy is chosen automatically based on platform transparency support.
+ *
+ * @param use true to force the layered-pane overlay
+ */
+ public static void setUseLayeredPaneOverlay(boolean use) {
+ useLayeredPaneOverlay = use;
+ }
+
+ /**
+ * Returns whether the JLayeredPane-based overlay has been explicitly requested.
+ *
+ * @return true if the layered-pane overlay is forced on
+ */
+ public static boolean isUseLayeredPaneOverlay() {
+ return useLayeredPaneOverlay;
+ }
+
/**
* Set a new property to use for the background color on Docking Handles
*
diff --git a/docking-api/src/io/github/andrewauclair/moderndocking/ui/HeaderController.java b/docking-api/src/io/github/andrewauclair/moderndocking/ui/HeaderController.java
index 60ab0438..aeb2532d 100644
--- a/docking-api/src/io/github/andrewauclair/moderndocking/ui/HeaderController.java
+++ b/docking-api/src/io/github/andrewauclair/moderndocking/ui/HeaderController.java
@@ -31,6 +31,7 @@ of this software and associated documentation files (the "Software"), to deal
/**
* Controller for the header of dockables. Responsible for controlling the state of all buttons on the header.
*/
+@SuppressWarnings("deprecation")
public class HeaderController implements MaximizeListener, DockingListener {
/**
* The dockable this header controller references
@@ -63,8 +64,8 @@ public HeaderController(Dockable dockable, DockingAPI docking, HeaderModel model
this.docking = docking;
this.model = model;
- DockingListeners.addMaximizeListener(this);
- DockingListeners.addDockingListener(this);
+ docking.getDockingListeners().addMaximizeListener(this);
+ docking.getDockingListeners().addDockingListener(this);
}
/**
@@ -80,8 +81,8 @@ public void setUI(DockingHeaderUI ui) {
* Remove the docking listeners that we've added
*/
public void removeListeners() {
- DockingListeners.removeMaximizeListener(this);
- DockingListeners.removeDockingListener(this);
+ docking.getDockingListeners().removeMaximizeListener(this);
+ docking.getDockingListeners().removeDockingListener(this);
}
/**
@@ -104,17 +105,33 @@ public void newWindow() {
}
/**
- * Minimize the dockable
+ * Exit focused mode for the dockable
*/
+ public void exitFocusedMode() {
+ docking.exitFocusedMode(dockable);
+ }
+
+ /**
+ * Enter focused mode for the dockable
+ */
+ public void enterFocusedMode() {
+ docking.enterFocusedMode(dockable);
+ }
+
+ /**
+ * @deprecated Use {@link #exitFocusedMode()} instead. Will be removed in 2.0.
+ */
+ @Deprecated(since = "1.5.0", forRemoval = true)
public void minimize() {
- docking.minimize(dockable);
+ exitFocusedMode();
}
/**
- * Maximize the dockable
+ * @deprecated Use {@link #enterFocusedMode()} instead. Will be removed in 2.0.
*/
+ @Deprecated(since = "1.5.0", forRemoval = true)
public void maximize() {
- docking.maximize(dockable);
+ enterFocusedMode();
}
/**
@@ -126,9 +143,16 @@ public void close() {
}
}
+ /**
+ * @deprecated {@link MaximizeListener} is deprecated. Focused mode changes are now delivered via
+ * {@link #dockingChange(DockingEvent)} using {@link DockingEvent.ID#FOCUSED_MODE_ENTERED} /
+ * {@link DockingEvent.ID#FOCUSED_MODE_EXITED}. This override is retained for binary compatibility.
+ * Will be removed in 2.0.
+ */
+ @Deprecated(since = "1.5.0", forRemoval = true)
@Override
public void maximized(Dockable dockable, boolean maximized) {
- ui.update();
+ // handled via dockingChange — no-op here to avoid double update
}
@Override
diff --git a/docking-api/src/io/github/andrewauclair/moderndocking/ui/HeaderModel.java b/docking-api/src/io/github/andrewauclair/moderndocking/ui/HeaderModel.java
index a40f687b..524b6376 100644
--- a/docking-api/src/io/github/andrewauclair/moderndocking/ui/HeaderModel.java
+++ b/docking-api/src/io/github/andrewauclair/moderndocking/ui/HeaderModel.java
@@ -88,21 +88,29 @@ public boolean isAutoHideEnabled() {
}
/**
- * helper function to determine if the header min/max option should be enabled
+ * helper function to determine if the header focused mode option should be enabled
*
- * @return Is min/max allowed for this dockable
+ * @return Is focused mode allowed for this dockable
*/
public boolean isMaximizeAllowed() {
return dockable.isMinMaxAllowed();
}
/**
- * Check if the dockable is currently maximized
+ * Check if the dockable is currently in focused mode
*
- * @return Is dockable maximized?
+ * @return Is dockable in focused mode?
*/
+ public boolean isInFocusedMode() {
+ return docking.inFocusedMode(dockable);
+ }
+
+ /**
+ * @deprecated Use {@link #isInFocusedMode()} instead. Will be removed in 2.0.
+ */
+ @Deprecated(since = "1.5.0", forRemoval = true)
public boolean isMaximized() {
- return docking.isMaximized(dockable);
+ return isInFocusedMode();
}
/**
diff --git a/docking-single-app/src/io/github/andrewauclair/moderndocking/app/AppState.java b/docking-single-app/src/io/github/andrewauclair/moderndocking/app/AppState.java
index d82213c2..2b7a081b 100644
--- a/docking-single-app/src/io/github/andrewauclair/moderndocking/app/AppState.java
+++ b/docking-single-app/src/io/github/andrewauclair/moderndocking/app/AppState.java
@@ -25,6 +25,8 @@ of this software and associated documentation files (the "Software"), to deal
import io.github.andrewauclair.moderndocking.Property;
import io.github.andrewauclair.moderndocking.api.AppStateAPI;
import io.github.andrewauclair.moderndocking.exception.DockingLayoutException;
+import io.github.andrewauclair.moderndocking.internal.DockableWrapper;
+import io.github.andrewauclair.moderndocking.internal.DockingInternal;
import io.github.andrewauclair.moderndocking.layouts.ApplicationLayout;
import java.io.File;
@@ -32,7 +34,9 @@ of this software and associated documentation files (the "Software"), to deal
* Handle persistence and restoration of Application layouts
*/
public class AppState {
- private static final AppStateAPI instance = new AppStateAPI(Docking.getSingleInstance()){};
+ private static AppStateAPI instance() {
+ return Docking.getSingleInstance().getAppState();
+ }
/**
* This class should not be instantiated
@@ -47,7 +51,7 @@ private AppState() {
* @param autoPersist Should the framework auto persist the application layout to a file?
*/
public static void setAutoPersist(boolean autoPersist) {
- instance.setAutoPersist(autoPersist);
+ instance().setAutoPersist(autoPersist);
}
/**
@@ -56,7 +60,7 @@ public static void setAutoPersist(boolean autoPersist) {
* @return True - we are auto persisting, False - we are not auto persisting
*/
public static boolean isAutoPersist() {
- return instance.isAutoPersist();
+ return instance().isAutoPersist();
}
/**
@@ -65,7 +69,7 @@ public static boolean isAutoPersist() {
* @param file File to persist layout to
*/
public static void setPersistFile(File file) {
- instance.setPersistFile(file);
+ instance().setPersistFile(file);
}
/**
@@ -74,7 +78,7 @@ public static void setPersistFile(File file) {
* @return The file we are currently persisting to
*/
public static File getPersistFile() {
- return instance.getPersistFile();
+ return instance().getPersistFile();
}
/**
@@ -83,7 +87,7 @@ public static File getPersistFile() {
* @param paused Whether auto persistence should be enabled
*/
public static void setPaused(boolean paused) {
- instance.setPaused(paused);
+ instance().setPaused(paused);
}
/**
@@ -92,7 +96,7 @@ public static void setPaused(boolean paused) {
* @return Whether auto persistence is enabled
*/
public static boolean isPaused() {
- return instance.isPaused();
+ return instance().isPaused();
}
/**
@@ -100,7 +104,7 @@ public static boolean isPaused() {
* This is a no-op if auto persistence is turned off, it's paused or there is no file
*/
public static void persist() {
- instance.persist();
+ instance().persist();
}
/**
@@ -110,7 +114,7 @@ public static void persist() {
* @throws DockingLayoutException Thrown for any issues with the layout file.
*/
public static boolean restore() throws DockingLayoutException {
- return instance.restore();
+ return instance().restore();
}
/**
@@ -120,7 +124,7 @@ public static boolean restore() throws DockingLayoutException {
* @param layout Default layout
*/
public static void setDefaultApplicationLayout(ApplicationLayout layout) {
- instance.setDefaultApplicationLayout(layout);
+ instance().setDefaultApplicationLayout(layout);
}
/**
@@ -132,7 +136,7 @@ public static void setDefaultApplicationLayout(ApplicationLayout layout) {
* @return The property instance of the dockable, or null if not found
*/
public static Property getProperty(Dockable dockable, String propertyName) {
- return instance.getProperty(dockable, propertyName);
+ return instance().getProperty(dockable, propertyName);
}
/**
@@ -143,7 +147,18 @@ public static Property getProperty(Dockable dockable, String propertyName) {
* @param value The value of the property
*/
public static void setProperty(Dockable dockable, String propertyName, String value) {
- instance.setProperty(dockable, propertyName, new Property.StringProperty(propertyName, value));
+ instance().setProperty(dockable, propertyName, new Property.StringProperty(propertyName, value));
+ }
+
+ /**
+ * Set the value of a property on the dockable. If the property does not exist, it will be created
+ *
+ * @param dockable The dockable to set a property for
+ * @param propertyName The name of the property we're setting
+ * @param value The value of the property
+ */
+ public static void setProperty(Dockable dockable, String propertyName, Property value) {
+ instance().setProperty(dockable, propertyName, value);
}
/**
@@ -153,6 +168,6 @@ public static void setProperty(Dockable dockable, String propertyName, String va
* @param propertyName The property to remove
*/
public static void removeProperty(Dockable dockable, String propertyName) {
- instance.removeProperty(dockable, propertyName);
+ instance().removeProperty(dockable, propertyName);
}
}
diff --git a/docking-single-app/src/io/github/andrewauclair/moderndocking/app/Docking.java b/docking-single-app/src/io/github/andrewauclair/moderndocking/app/Docking.java
index 45e49cbe..81b683bc 100644
--- a/docking-single-app/src/io/github/andrewauclair/moderndocking/app/Docking.java
+++ b/docking-single-app/src/io/github/andrewauclair/moderndocking/app/Docking.java
@@ -25,6 +25,7 @@ of this software and associated documentation files (the "Software"), to deal
import io.github.andrewauclair.moderndocking.DockingRegion;
import io.github.andrewauclair.moderndocking.api.DockingAPI;
import io.github.andrewauclair.moderndocking.api.RootDockingPanelAPI;
+import io.github.andrewauclair.moderndocking.event.DockingEvent;
import io.github.andrewauclair.moderndocking.event.DockingListener;
import io.github.andrewauclair.moderndocking.event.MaximizeListener;
import io.github.andrewauclair.moderndocking.event.NewFloatingFrameListener;
@@ -469,62 +470,86 @@ public static boolean canDisposeWindow(Window window) {
}
/**
- * checks if a dockable is currently maximized
+ * Checks if a dockable is currently in focused mode
*
* @param dockable The dockable to check
- * @return Whether the dockable is maximized
+ * @return Whether the dockable is in focused mode
*/
- public static boolean isMaximized(Dockable dockable) {
- return instance.isMaximized(dockable);
+ public static boolean inFocusedMode(Dockable dockable) {
+ return instance.inFocusedMode(dockable);
}
/**
- * maximizes a dockable
+ * Enter focused mode for a dockable, undocking all others in the same root
*
- * @param dockable Dockable to maximize
+ * @param dockable Dockable to enter focused mode
*/
- public static void maximize(Dockable dockable) {
- instance.maximize(dockable);
+ public static void enterFocusedMode(Dockable dockable) {
+ instance.enterFocusedMode(dockable);
}
/**
- * minimize a dockable if it is currently maximized
+ * Exit focused mode for a dockable, restoring the previous layout
*
- * @param dockable Dockable to minimize
+ * @param dockable Dockable to exit focused mode
+ */
+ public static void exitFocusedMode(Dockable dockable) {
+ instance.exitFocusedMode(dockable);
+ }
+
+ /**
+ * @deprecated Use {@link #inFocusedMode(Dockable)} instead. Will be removed in 2.0.
+ */
+ @Deprecated(since = "1.5.0", forRemoval = true)
+ public static boolean isMaximized(Dockable dockable) {
+ return instance.inFocusedMode(dockable);
+ }
+
+ /**
+ * @deprecated Use {@link #enterFocusedMode(Dockable)} instead. Will be removed in 2.0.
+ */
+ @Deprecated(since = "1.5.0", forRemoval = true)
+ public static void maximize(Dockable dockable) {
+ instance.enterFocusedMode(dockable);
+ }
+
+ /**
+ * @deprecated Use {@link #exitFocusedMode(Dockable)} instead. Will be removed in 2.0.
*/
+ @Deprecated(since = "1.5.0", forRemoval = true)
public static void minimize(Dockable dockable) {
- instance.minimize(dockable);
+ instance.exitFocusedMode(dockable);
}
- public void autoShowDockable(Dockable dockable) {
+ public static void autoShowDockable(Dockable dockable) {
instance.autoShowDockable(dockable);
}
- public void autoShowDockable(String persistentID) {
+ public static void autoShowDockable(String persistentID) {
instance.autoShowDockable(persistentID);
}
- public void autoHideDockable(Dockable dockable) {
+ public static void autoHideDockable(Dockable dockable) {
instance.autoHideDockable(dockable);
}
- public void autoHideDockable(String persistentID) {
+ public static void autoHideDockable(String persistentID) {
instance.autoHideDockable(persistentID);
}
- public void autoHideDockable(Dockable dockable, ToolbarLocation location) {
+ public static void autoHideDockable(Dockable dockable, ToolbarLocation location) {
instance.autoHideDockable(dockable, location);
}
- public void autoHideDockable(String persistentID, ToolbarLocation location) {
+ public static void autoHideDockable(String persistentID, ToolbarLocation location) {
instance.autoHideDockable(persistentID, location);
}
- public void autoHideDockable(Dockable dockable, ToolbarLocation location, Window window) {
+ public static void autoHideDockable(Dockable dockable, ToolbarLocation location, Window window) {
instance.autoHideDockable(dockable, location, window);
}
- public void autoHideDockable(String persistentID, ToolbarLocation location, Window window) {
+ public static void autoHideDockable(String persistentID, ToolbarLocation location, Window window) {
instance.autoHideDockable(persistentID, location, window);
}
@@ -568,19 +593,25 @@ public static void updateTabInfo(Dockable dockable) {
}
/**
- * Add a new maximize listener. Will be called when a dockable is maximized
+ * Add a new maximize listener. Will be called when a dockable enters or exits focused mode.
*
* @param listener Listener to add
+ * @deprecated Use {@link #addDockingListener(DockingListener)} and handle
+ * {@link DockingEvent.ID#FOCUSED_MODE_ENTERED} / {@link DockingEvent.ID#FOCUSED_MODE_EXITED} instead.
+ * Will be removed in 2.0.
*/
+ @Deprecated(since = "1.5.0", forRemoval = true)
public static void addMaximizeListener(MaximizeListener listener) {
instance.addMaximizeListener(listener);
}
/**
- * Remove a previously added maximize listener. No-op if the listener isn't in the list
+ * Remove a previously added maximize listener. No-op if the listener isn't in the list.
*
* @param listener Listener to remove
+ * @deprecated Will be removed in 2.0.
*/
+ @Deprecated(since = "1.5.0", forRemoval = true)
public static void removeMaximizeListener(MaximizeListener listener) {
instance.removeMaximizeListener(listener);
}
@@ -609,7 +640,7 @@ public static void removeDockingListener(DockingListener listener) {
* @param listener Listener to add
*/
public static void addNewFloatingFrameListener(NewFloatingFrameListener listener) {
- DockingListeners.addNewFloatingFrameListener(listener);
+ instance.getDockingListeners().addNewFloatingFrameListener(listener);
}
/**
@@ -618,7 +649,7 @@ public static void addNewFloatingFrameListener(NewFloatingFrameListener listener
* @param listener Listener to remove
*/
public static void removeNewFloatingFrameListener(NewFloatingFrameListener listener) {
- DockingListeners.removeNewFloatingFrameListener(listener);
+ instance.getDockingListeners().removeNewFloatingFrameListener(listener);
}
public static void setUserDynamicDockableCreationListener(DynamicDockableCreationListener listener) {
diff --git a/docking-single-app/src/io/github/andrewauclair/moderndocking/app/LayoutPersistence.java b/docking-single-app/src/io/github/andrewauclair/moderndocking/app/LayoutPersistence.java
index 08253733..1fdd4f84 100644
--- a/docking-single-app/src/io/github/andrewauclair/moderndocking/app/LayoutPersistence.java
+++ b/docking-single-app/src/io/github/andrewauclair/moderndocking/app/LayoutPersistence.java
@@ -31,7 +31,9 @@ of this software and associated documentation files (the "Software"), to deal
* Persist and restore Application and Window layouts to/from files
*/
public class LayoutPersistence {
- private static final LayoutPersistenceAPI instance = new LayoutPersistenceAPI(Docking.getSingleInstance()){};
+ private static LayoutPersistenceAPI instance() {
+ return Docking.getSingleInstance().getLayoutPersistence();
+ }
private LayoutPersistence() {
}
@@ -44,7 +46,7 @@ private LayoutPersistence() {
* @throws DockingLayoutException Thrown if we failed to save the layout to the file
*/
public static void saveLayoutToFile(File file, ApplicationLayout layout) throws DockingLayoutException {
- instance.saveLayoutToFile(file, layout);
+ instance().saveLayoutToFile(file, layout);
}
/**
@@ -55,11 +57,11 @@ public static void saveLayoutToFile(File file, ApplicationLayout layout) throws
* @throws DockingLayoutException Thrown if we failed to read from the file or something went wrong with loading the layout
*/
public static ApplicationLayout loadApplicationLayoutFromFile(File file) throws DockingLayoutException {
- return instance.loadApplicationLayoutFromFile(file);
+ return instance().loadApplicationLayoutFromFile(file);
}
public static boolean saveWindowLayoutToFile(File file, WindowLayout layout) {
- return instance.saveWindowLayoutToFile(file, layout);
+ return instance().saveWindowLayoutToFile(file, layout);
}
/**
@@ -69,6 +71,6 @@ public static boolean saveWindowLayoutToFile(File file, WindowLayout layout) {
* @return The loaded WindowLayout
*/
public static WindowLayout loadWindowLayoutFromFile(File file) {
- return instance.loadWindowLayoutFromFile(file);
+ return instance().loadWindowLayoutFromFile(file);
}
}
diff --git a/docking-ui/src/io/github/andrewauclair/moderndocking/ext/ui/FlatLafHeaderUI.java b/docking-ui/src/io/github/andrewauclair/moderndocking/ext/ui/FlatLafHeaderUI.java
index 1c6b8042..8736b79c 100644
--- a/docking-ui/src/io/github/andrewauclair/moderndocking/ext/ui/FlatLafHeaderUI.java
+++ b/docking-ui/src/io/github/andrewauclair/moderndocking/ext/ui/FlatLafHeaderUI.java
@@ -65,12 +65,8 @@ public FlatLafHeaderUI(HeaderController headerController, HeaderModel headerMode
settingsIcon.setColorFilter(new FlatSVGIcon.ColorFilter(color -> foreground));
closeIcon.setColorFilter(new FlatSVGIcon.ColorFilter(color -> foreground));
- }
-
- @Override
- protected void init() {
- super.init();
+ // Replace the PNG icons set by the parent constructor with sharper SVG icons
settings.setIcon(settingsIcon);
close.setIcon(closeIcon);
}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 9bbc975c..8bdaf60c 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2a84e188..c61a118f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index faf93008..ef07e016 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
#!/bin/sh
#
-# Copyright © 2015-2021 the original authors.
+# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
diff --git a/gradlew.bat b/gradlew.bat
index 9d21a218..db3a6ac2 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+set CLASSPATH=
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/settings.gradle b/settings.gradle
index c5bc4d48..ff903456 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,5 +4,6 @@ include ':docking-api'
include ':docking-ui'
include ':docking-single-app'
include ':docking-multi-app'
+include ':demo-common'
include ':demo-single-app'
include ':demo-multi-app'
\ No newline at end of file