diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java
index 75c9c33a0..12e04775b 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java
@@ -257,18 +257,39 @@ public interface FlatClientProperties
String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner";
/**
- * Specifies whether a component in an embedded menu bar should behave as caption
+ * Specifies whether a component shown in a window title bar area should behave as caption
* (left-click allows moving window, right-click shows window system menu).
- * The component does not receive mouse pressed/released/clicked/dragged events,
+ * The caption component does not receive mouse pressed/released/clicked/dragged events,
* but it gets mouse entered/exited/moved events.
*
+ * Since 3.4, this client property also supports using a function that can check
+ * whether a given location in the component should behave as caption.
+ * Useful for components that do not use mouse input on whole component bounds.
+ *
+ *
{@code
+ * myComponent.putClientProperty( "JComponent.titleBarCaption",
+ * (Function) pt -> {
+ * // parameter pt contains mouse location (in myComponent coordinates)
+ * // return true if the component is not interested in mouse input at the given location
+ * // return false if the component wants process mouse input at the given location
+ * // return null if the component children should be checked
+ * return ...; // check here
+ * } );
+ * }
+ * Warning:
+ *
+ * - This function is invoked often when mouse is moved over window title bar area
+ * and should therefore return quickly.
+ *
- This function is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
+ * while processing Windows messages.
+ * It must not change any component property or layout because this could cause a dead lock.
+ *
+ *
* Component {@link javax.swing.JComponent}
- * Value type {@link java.lang.Boolean}
+ * Value type {@link java.lang.Boolean} or {@link java.util.function.Function}<Point, Boolean>
*
* @since 2.5
- * @deprecated No longer used since FlatLaf 3.4. Retained for API compatibility.
*/
- @Deprecated
String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption";
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java
index 1f4e2d073..c634f493e 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java
@@ -219,13 +219,13 @@ public static void setHasCustomDecoration( Window window, boolean hasCustomDecor
}
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
- Predicate hitTestCallback, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
+ Predicate captionHitTestCallback, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Rectangle maximizeButtonBounds, Rectangle closeButtonBounds )
{
if( !isSupported() )
return;
- nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestCallback,
+ nativeProvider.updateTitleBarInfo( window, titleBarHeight, captionHitTestCallback,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
}
@@ -271,7 +271,7 @@ public interface Provider
{
boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
- void updateTitleBarInfo( Window window, int titleBarHeight, Predicate hitTestCallback,
+ void updateTitleBarInfo( Window window, int titleBarHeight, Predicate captionHitTestCallback,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds );
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSplitPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSplitPaneUI.java
index 273d1fbf1..335c11d54 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSplitPaneUI.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSplitPaneUI.java
@@ -84,7 +84,7 @@
*/
public class FlatSplitPaneUI
extends BasicSplitPaneUI
- implements StyleableUI
+ implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{
@Styleable protected String arrowType;
/** @since 3.3 */ @Styleable protected Color draggingColor;
@@ -227,6 +227,15 @@ private void paintDragDivider( Graphics g, int dividerLocation ) {
((FlatSplitPaneDivider)divider).paintStyle( g, x, y, width, height );
}
+ //---- interface FlatTitlePane.TitleBarCaptionHitTest ----
+
+ /** @since 3.4 */
+ @Override
+ public Boolean isTitleBarCaptionAt( int x, int y ) {
+ // necessary because BasicSplitPaneDivider adds some mouse listeners for dragging divider
+ return null; // check children
+ }
+
//---- class FlatSplitPaneDivider -----------------------------------------
protected class FlatSplitPaneDivider
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java
index 4a202f86b..dfff891e6 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java
@@ -182,7 +182,7 @@
*/
public class FlatTabbedPaneUI
extends BasicTabbedPaneUI
- implements StyleableUI
+ implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{
// tab type
/** @since 2 */ protected static final int TAB_TYPE_UNDERLINED = 0;
@@ -2300,6 +2300,17 @@ private int rectsTotalHeight() {
return (rects[last].y + rects[last].height) - rects[0].y;
}
+ //---- interface FlatTitlePane.TitleBarCaptionHitTest ----
+
+ /** @since 3.4 */
+ @Override
+ public Boolean isTitleBarCaptionAt( int x, int y ) {
+ if( tabForCoordinate( tabPane, x, y ) >= 0 )
+ return false;
+
+ return null; // check children
+ }
+
//---- class TabCloseButton -----------------------------------------------
private static class TabCloseButton
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java
index c9af7656b..01cb991f7 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java
@@ -49,6 +49,7 @@
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.Objects;
+import java.util.function.Function;
import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory;
import javax.swing.Box;
@@ -65,6 +66,7 @@
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder;
@@ -314,7 +316,7 @@ public void layoutContainer( Container target ) {
}
// clear hit-test cache
- lastHitTestTime = 0;
+ lastCaptionHitTestTime = 0;
}
} );
@@ -1004,10 +1006,10 @@ protected void updateNativeTitleBarHeightAndHitTestSpots() {
Rectangle closeButtonBounds = boundsInWindow( closeButton );
// clear hit-test cache
- lastHitTestTime = 0;
+ lastCaptionHitTestTime = 0;
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
- this::hitTest, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
+ this::captionHitTest, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
debugTitleBarHeight = titleBarHeight;
debugAppIconBounds = appIconBounds;
@@ -1024,18 +1026,8 @@ private Rectangle boundsInWindow( JComponent c ) {
: null;
}
- protected Rectangle getNativeHitTestSpot( JComponent c ) {
- Dimension size = c.getSize();
- if( size.width <= 0 || size.height <= 0 )
- return null;
-
- Point location = SwingUtilities.convertPoint( c, 0, 0, window );
- Rectangle r = new Rectangle( location, size );
- return r;
- }
-
/**
- * Returns wheter there is a component at the given location, that processes
+ * Returns whether there is a component at the given location, that processes
* mouse events. E.g. buttons, menus, etc.
*
* Note:
@@ -1046,12 +1038,12 @@ protected Rectangle getNativeHitTestSpot( JComponent c ) {
* while processing Windows messages.
*
*/
- private boolean hitTest( Point pt ) {
+ private boolean captionHitTest( Point pt ) {
// Windows invokes this method every ~200ms, even if the mouse has not moved
long time = System.currentTimeMillis();
- if( pt.x == lastHitTestX && pt.y == lastHitTestY && time < lastHitTestTime + 300 ) {
- lastHitTestTime = time;
- return lastHitTestResult;
+ if( pt.x == lastCaptionHitTestX && pt.y == lastCaptionHitTestY && time < lastCaptionHitTestTime + 300 ) {
+ lastCaptionHitTestTime = time;
+ return lastCaptionHitTestResult;
}
// convert pt from window coordinates to layeredPane coordinates
@@ -1063,35 +1055,70 @@ private boolean hitTest( Point pt ) {
y -= c.getY();
}
- lastHitTestX = pt.x;
- lastHitTestY = pt.y;
- lastHitTestTime = time;
- lastHitTestResult = isComponentWithMouseListenerAt( layeredPane, x, y );
- return lastHitTestResult;
+ lastCaptionHitTestX = pt.x;
+ lastCaptionHitTestY = pt.y;
+ lastCaptionHitTestTime = time;
+ lastCaptionHitTestResult = isTitleBarCaptionAt( layeredPane, x, y );
+ return lastCaptionHitTestResult;
}
- private boolean isComponentWithMouseListenerAt( Component c, int x, int y ) {
+ private boolean isTitleBarCaptionAt( Component c, int x, int y ) {
if( !c.isDisplayable() || !c.isVisible() || !c.contains( x, y ) || c == mouseLayer )
- return false;
+ return true; // continue checking with next component
- if( c.getMouseListeners().length > 0 ||
- c.getMouseMotionListeners().length > 0 ||
- c.getMouseWheelListeners().length > 0 )
- return true;
+ if( c.isEnabled() &&
+ (c.getMouseListeners().length > 0 ||
+ c.getMouseMotionListeners().length > 0) )
+ {
+ if( !(c instanceof JComponent) )
+ return false; // assume that this is not a caption because the component has mouse listeners
+
+ // check client property boolean value
+ Object caption = ((JComponent)c).getClientProperty( COMPONENT_TITLE_BAR_CAPTION );
+ if( caption instanceof Boolean )
+ return (boolean) caption;
+
+ // if component is not fully layouted, do not invoke function
+ // because it is too dangerous that the function tries to layout the component,
+ // which could cause a dead lock
+ if( !c.isValid() )
+ return false; // assume that this is not a caption because the component has mouse listeners
+
+ if( caption instanceof Function ) {
+ // check client property function value
+ @SuppressWarnings( "unchecked" )
+ Function hitTest = (Function) caption;
+ Boolean result = hitTest.apply( new Point( x, y ) );
+ if( result != null )
+ return result;
+ } else {
+ // check component UI
+ ComponentUI ui = JavaCompatibility2.getUI( (JComponent) c );
+ if( !(ui instanceof TitleBarCaptionHitTest) )
+ return false; // assume that this is not a caption because the component has mouse listeners
+
+ Boolean result = ((TitleBarCaptionHitTest)ui).isTitleBarCaptionAt( x, y );
+ if( result != null )
+ return result;
+ }
+ // else continue checking children
+ }
+
+ // check children
if( c instanceof Container ) {
for( Component child : ((Container)c).getComponents() ) {
- if( isComponentWithMouseListenerAt( child, x - child.getX(), y - child.getY() ) )
- return true;
+ if( !isTitleBarCaptionAt( child, x - child.getX(), y - child.getY() ) )
+ return false;
}
}
- return false;
+ return true;
}
- private int lastHitTestX;
- private int lastHitTestY;
- private long lastHitTestTime;
- private boolean lastHitTestResult;
+ private int lastCaptionHitTestX;
+ private int lastCaptionHitTestY;
+ private long lastCaptionHitTestTime;
+ private boolean lastCaptionHitTestResult;
private int debugTitleBarHeight;
private Rectangle debugAppIconBounds;
@@ -1490,4 +1517,27 @@ public void componentShown( ComponentEvent e ) {
@Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
}
+
+ //---- interface TitleBarCaptionHitTest -----------------------------------
+
+ /**
+ * For custom components use {@link FlatClientProperties#COMPONENT_TITLE_BAR_CAPTION}
+ * instead of this interface.
+ *
+ * @since 3.4
+ */
+ public interface TitleBarCaptionHitTest {
+ /**
+ * Invoked for a component that is enabled and has mouse listeners,
+ * to check whether it processes mouse input at the given x/y location.
+ * Useful for components that do not use mouse input on whole component bounds.
+ * E.g. a tabbed pane with a few tabs has some empty space beside the tabs
+ * that can be used to move the window.
+ *
+ * @return {@code true} if the component is not interested in mouse input at the given location
+ * {@code false} if the component wants process mouse input at the given location
+ * {@code null} if the component children should be checked
+ */
+ Boolean isTitleBarCaptionAt( int x, int y );
+ }
}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java
index c6d97f5d9..7bc13fb88 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java
@@ -82,7 +82,7 @@
*/
public class FlatToolBarUI
extends BasicToolBarUI
- implements StyleableUI
+ implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{
/** @since 1.4 */ @Styleable protected boolean focusableButtons;
/** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation;
@@ -453,6 +453,15 @@ private ButtonGroup getButtonGroup( AbstractButton b ) {
: null;
}
+ //---- interface FlatTitlePane.TitleBarCaptionHitTest ----
+
+ /** @since 3.4 */
+ @Override
+ public Boolean isTitleBarCaptionAt( int x, int y ) {
+ // necessary because BasicToolBarUI adds some mouse listeners for dragging when toolbar is floatable
+ return null; // check children
+ }
+
//---- class FlatToolBarFocusTraversalPolicy ------------------------------
/**
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java
index 64a671a96..dda40c281 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java
@@ -159,7 +159,7 @@ private void uninstall( Window window ) {
}
@Override
- public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate hitTestCallback,
+ public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate captionHitTestCallback,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds )
{
@@ -168,7 +168,7 @@ public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate hitTestCallback;
+ private Predicate captionHitTestCallback;
private Rectangle appIconBounds;
private Rectangle minimizeButtonBounds;
private Rectangle maximizeButtonBounds;
@@ -376,7 +376,7 @@ private int onNcHitTest( int x, int y, boolean isOnResizeBorder ) {
// that processes mouse events (e.g. buttons, menus, etc)
// - Windows ignores mouse events in this area
try {
- if( hitTestCallback != null && hitTestCallback.test( pt ) )
+ if( captionHitTestCallback != null && !captionHitTestCallback.test( pt ) )
return HTCLIENT;
} catch( Throwable ex ) {
// ignore
diff --git a/flatlaf-jide-oss/src/main/java/com/formdev/flatlaf/jideoss/ui/FlatJideTabbedPaneUI.java b/flatlaf-jide-oss/src/main/java/com/formdev/flatlaf/jideoss/ui/FlatJideTabbedPaneUI.java
index 60b5da361..f34589419 100644
--- a/flatlaf-jide-oss/src/main/java/com/formdev/flatlaf/jideoss/ui/FlatJideTabbedPaneUI.java
+++ b/flatlaf-jide-oss/src/main/java/com/formdev/flatlaf/jideoss/ui/FlatJideTabbedPaneUI.java
@@ -16,6 +16,7 @@
package com.formdev.flatlaf.jideoss.ui;
+import static com.formdev.flatlaf.FlatClientProperties.COMPONENT_TITLE_BAR_CAPTION;
import static com.formdev.flatlaf.FlatClientProperties.TABBED_PANE_HAS_FULL_BORDER;
import static com.formdev.flatlaf.FlatClientProperties.TABBED_PANE_SHOW_TAB_SEPARATORS;
import static com.formdev.flatlaf.FlatClientProperties.clientPropertyBoolean;
@@ -30,6 +31,7 @@
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
+import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseListener;
@@ -37,6 +39,7 @@
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
+import java.util.function.Function;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
@@ -100,6 +103,25 @@ public static ComponentUI createUI( JComponent c ) {
return new FlatJideTabbedPaneUI();
}
+ @Override
+ public void installUI( JComponent c ) {
+ super.installUI( c );
+
+ c.putClientProperty( COMPONENT_TITLE_BAR_CAPTION,
+ (Function) pt -> {
+ if( tabForCoordinate( _tabPane, pt.x, pt.y ) >= 0 )
+ return false;
+
+ return null; // check children
+ } );
+ }
+
+ @Override
+ public void uninstallUI( JComponent c ) {
+ super.uninstallUI( c );
+ c.putClientProperty( COMPONENT_TITLE_BAR_CAPTION, null );
+ }
+
@Override
protected void installDefaults() {
super.installDefaults();
diff --git a/flatlaf-natives/flatlaf-natives-jna/src/main/java/com/formdev/flatlaf/natives/jna/windows/FlatWindowsNativeWindowBorder.java b/flatlaf-natives/flatlaf-natives-jna/src/main/java/com/formdev/flatlaf/natives/jna/windows/FlatWindowsNativeWindowBorder.java
index 5c0d407c0..bdf31aa70 100644
--- a/flatlaf-natives/flatlaf-natives-jna/src/main/java/com/formdev/flatlaf/natives/jna/windows/FlatWindowsNativeWindowBorder.java
+++ b/flatlaf-natives/flatlaf-natives-jna/src/main/java/com/formdev/flatlaf/natives/jna/windows/FlatWindowsNativeWindowBorder.java
@@ -164,7 +164,7 @@ private void uninstall( Window window ) {
}
@Override
- public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate hitTestCallback,
+ public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate captionHitTestCallback,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds )
{
@@ -173,7 +173,7 @@ public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate hitTestCallback;
+ private Predicate captionHitTestCallback;
private Rectangle appIconBounds;
private Rectangle minimizeButtonBounds;
private Rectangle maximizeButtonBounds;
@@ -684,7 +684,7 @@ private LRESULT WmNcHitTest( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam )
// that processes mouse events (e.g. buttons, menus, etc)
// - Windows ignores mouse events in this area
try {
- if( hitTestCallback != null && hitTestCallback.test( pt ) )
+ if( captionHitTestCallback != null && !captionHitTestCallback.test( pt ) )
return new LRESULT( HTCLIENT );
} catch( Throwable ex ) {
// ignore