diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0bab4d76..424d6173 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ FlatLaf Change Log
#### New features and improvements
+- FlatLaf window decorations (Windows 10/11 and Linux): Support "full window
+ content" mode, which allows you to extend the content into the window title
+ bar. (PR #801)
- macOS: Support larger window title bar close/minimize/zoom buttons spacing in
[full window content](https://www.formdev.com/flatlaf/macos/#full_window_content)
mode and introduced "buttons placeholder". (PR #779)
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 f2fc9fea..12e04775 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java
@@ -257,13 +257,36 @@ 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
*/
@@ -274,7 +297,7 @@ public interface FlatClientProperties
/**
* Marks the panel as placeholder for the iconfify/maximize/close buttons
- * in fullWindowContent mode.
+ * in fullWindowContent mode. See {@link #FULL_WINDOW_CONTENT}.
*
* If fullWindowContent mode is enabled, the preferred size of the panel is equal
* to the size of the iconfify/maximize/close buttons. Otherwise is is {@code 0,0}.
@@ -462,6 +485,32 @@ public interface FlatClientProperties
*/
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
+ /**
+ * Specifies whether the content pane (and the glass pane) should be extended
+ * into the window title bar area
+ * (requires enabled window decorations). Default is {@code false}.
+ *
+ * On macOS, use client property {@code apple.awt.fullWindowContent}
+ * (see macOS Full window content).
+ *
+ * Setting this enables/disables full window content
+ * for the {@code JFrame} or {@code JDialog} that contains the root pane.
+ *
+ * If {@code true}, the content pane (and the glass pane) is extended into
+ * the title bar area. The window icon and title are hidden.
+ * Only the iconfify/maximize/close buttons stay visible in the upper right corner
+ * (and overlap the content pane).
+ *
+ * The user can left-click-and-drag on the title bar area to move the window,
+ * except when clicking on a component that processes mouse events (e.g. buttons or menus).
+ *
+ * Component {@link javax.swing.JRootPane}
+ * Value type {@link java.lang.Boolean}
+ *
+ * @since 3.4
+ */
+ String FULL_WINDOW_CONTENT = "FlatLaf.fullWindowContent";
+
/**
* Contains the current bounds of the iconfify/maximize/close buttons
* (in root pane coordinates) if fullWindowContent mode is enabled.
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 ded82a01..c634f493 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
@@ -21,11 +21,12 @@ import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
+import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.beans.PropertyChangeListener;
-import java.util.List;
+import java.util.function.Predicate;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JRootPane;
@@ -218,13 +219,13 @@ public class FlatNativeWindowBorder
}
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
- List hitTestSpots, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
+ Predicate captionHitTestCallback, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Rectangle maximizeButtonBounds, Rectangle closeButtonBounds )
{
if( !isSupported() )
return;
- nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots,
+ nativeProvider.updateTitleBarInfo( window, titleBarHeight, captionHitTestCallback,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
}
@@ -270,7 +271,7 @@ public class FlatNativeWindowBorder
{
boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
- void updateTitleBarInfo( Window window, int titleBarHeight, List hitTestSpots,
+ 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/FlatRootPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java
index 2edef9e9..a2dfab1c 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java
@@ -269,15 +269,28 @@ public class FlatRootPaneUI
// layer title pane under frame content layer to allow placing menu bar over title pane
protected final static Integer TITLE_PANE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 1;
+ private final static Integer TITLE_PANE_MOUSE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 2;
+
+ // for fullWindowContent mode, layer title pane over frame content layer to allow placing title bar buttons over content
+ /** @since 3.4 */
+ protected final static Integer TITLE_PANE_FULL_WINDOW_CONTENT_LAYER = JLayeredPane.FRAME_CONTENT_LAYER + 1;
+
+ private Integer getLayerForTitlePane() {
+ return isFullWindowContent( rootPane ) ? TITLE_PANE_FULL_WINDOW_CONTENT_LAYER : TITLE_PANE_LAYER;
+ }
protected void setTitlePane( FlatTitlePane newTitlePane ) {
JLayeredPane layeredPane = rootPane.getLayeredPane();
- if( titlePane != null )
+ if( titlePane != null ) {
layeredPane.remove( titlePane );
+ layeredPane.remove( titlePane.mouseLayer );
+ }
- if( newTitlePane != null )
- layeredPane.add( newTitlePane, TITLE_PANE_LAYER );
+ if( newTitlePane != null ) {
+ layeredPane.add( newTitlePane, getLayerForTitlePane() );
+ layeredPane.add( newTitlePane.mouseLayer, TITLE_PANE_MOUSE_LAYER );
+ }
titlePane = newTitlePane;
}
@@ -430,6 +443,17 @@ public class FlatRootPaneUI
titlePane.titleBarColorsChanged();
break;
+ case FlatClientProperties.FULL_WINDOW_CONTENT:
+ if( titlePane != null ) {
+ rootPane.getLayeredPane().setLayer( titlePane, getLayerForTitlePane() );
+ titlePane.updateIcon();
+ titlePane.updateVisibility();
+ titlePane.updateFullWindowContentButtonsBoundsProperty();
+ }
+ FullWindowContentSupport.revalidatePlaceholders( rootPane );
+ rootPane.revalidate();
+ break;
+
case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS:
FullWindowContentSupport.revalidatePlaceholders( rootPane );
break;
@@ -471,11 +495,14 @@ public class FlatRootPaneUI
}
}
+ /** @since 3.4 */
+ protected static boolean isFullWindowContent( JRootPane rootPane ) {
+ return FlatClientProperties.clientPropertyBoolean( rootPane, FlatClientProperties.FULL_WINDOW_CONTENT, false );
+ }
+
protected static boolean isMenuBarEmbedded( JRootPane rootPane ) {
- RootPaneUI ui = rootPane.getUI();
- return ui instanceof FlatRootPaneUI &&
- ((FlatRootPaneUI)ui).titlePane != null &&
- ((FlatRootPaneUI)ui).titlePane.isMenuBarEmbedded();
+ FlatTitlePane titlePane = getTitlePane( rootPane );
+ return titlePane != null && titlePane.isMenuBarEmbedded();
}
/** @since 2.4 */
@@ -511,23 +538,21 @@ public class FlatRootPaneUI
private Dimension computeLayoutSize( Container parent, Function getSizeFunc ) {
JRootPane rootPane = (JRootPane) parent;
- Dimension titlePaneSize = (titlePane != null)
- ? getSizeFunc.apply( titlePane )
- : new Dimension();
Dimension contentSize = (rootPane.getContentPane() != null)
? getSizeFunc.apply( rootPane.getContentPane() )
- : rootPane.getSize();
+ : rootPane.getSize(); // same as in JRootPane.RootLayout.preferredLayoutSize()
int width = contentSize.width; // title pane width is not considered here
- int height = titlePaneSize.height + contentSize.height;
+ int height = contentSize.height;
+ if( titlePane != null && !isFullWindowContent( rootPane ) )
+ height += getSizeFunc.apply( titlePane ).height;
if( titlePane == null || !titlePane.isMenuBarEmbedded() ) {
JMenuBar menuBar = rootPane.getJMenuBar();
- Dimension menuBarSize = (menuBar != null && menuBar.isVisible())
- ? getSizeFunc.apply( menuBar )
- : new Dimension();
-
- width = Math.max( width, menuBarSize.width );
- height += menuBarSize.height;
+ if( menuBar != null && menuBar.isVisible() ) {
+ Dimension menuBarSize = getSizeFunc.apply( menuBar );
+ width = Math.max( width, menuBarSize.width );
+ height += menuBarSize.height;
+ }
}
Insets insets = rootPane.getInsets();
@@ -552,12 +577,23 @@ public class FlatRootPaneUI
if( rootPane.getLayeredPane() != null )
rootPane.getLayeredPane().setBounds( x, y, width, height );
- // title pane
+ // title pane (is a child of layered pane)
int nextY = 0;
if( titlePane != null ) {
int prefHeight = !isFullScreen ? titlePane.getPreferredSize().height : 0;
- titlePane.setBounds( 0, 0, width, prefHeight );
- nextY += prefHeight;
+ boolean isFullWindowContent = isFullWindowContent( rootPane );
+ if( isFullWindowContent && !UIManager.getBoolean( FlatTitlePane.KEY_DEBUG_SHOW_RECTANGLES ) ) {
+ // place title bar into top-right corner
+ int tw = Math.min( titlePane.getPreferredSize().width, width );
+ int tx = titlePane.getComponentOrientation().isLeftToRight() ? width - tw : 0;
+ titlePane.setBounds( tx, 0, tw, prefHeight );
+ } else
+ titlePane.setBounds( 0, 0, width, prefHeight );
+
+ titlePane.mouseLayer.setBounds( 0, 0, width, prefHeight );
+
+ if( !isFullWindowContent )
+ nextY += prefHeight;
}
// glass pane
@@ -568,7 +604,7 @@ public class FlatRootPaneUI
rootPane.getGlassPane().setBounds( x, y + offset, width, height - offset );
}
- // menu bar
+ // menu bar (is a child of layered pane)
JMenuBar menuBar = rootPane.getJMenuBar();
if( menuBar != null && menuBar.isVisible() ) {
boolean embedded = !isFullScreen && titlePane != null && titlePane.isMenuBarEmbedded();
@@ -576,13 +612,23 @@ public class FlatRootPaneUI
titlePane.validate();
menuBar.setBounds( titlePane.getMenuBarBounds() );
} else {
+ int mx = 0;
+ int mw = width;
+ if( titlePane != null && isFullWindowContent( rootPane ) ) {
+ // make menu bar width smaller to avoid that it overlaps title bar buttons
+ int tw = Math.min( titlePane.getPreferredSize().width, width );
+ mw -= tw;
+ if( !titlePane.getComponentOrientation().isLeftToRight() )
+ mx = tw;
+ }
+
Dimension prefSize = menuBar.getPreferredSize();
- menuBar.setBounds( 0, nextY, width, prefSize.height );
+ menuBar.setBounds( mx, nextY, mw, prefSize.height );
nextY += prefSize.height;
}
}
- // content pane
+ // content pane (is a child of layered pane)
Container contentPane = rootPane.getContentPane();
if( contentPane != null )
contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) );
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 273d1fbf..335c11d5 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 @@ import com.formdev.flatlaf.util.UIScale;
*/
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 @@ public class FlatSplitPaneUI
((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 afa242a6..1411c9e3 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 @@ import com.formdev.flatlaf.util.UIScale;
*/
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 @@ debug*/
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 4beae363..82f5a22b 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
@@ -36,6 +36,7 @@ import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
@@ -46,9 +47,9 @@ import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
-import java.util.ArrayList;
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;
@@ -57,7 +58,6 @@ import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
-import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
@@ -66,6 +66,7 @@ import javax.swing.SwingUtilities;
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;
@@ -98,7 +99,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.showIconBesideTitle boolean
* @uiDefault TitlePane.menuBarTitleGap int
* @uiDefault TitlePane.menuBarTitleMinimumGap int
- * @uiDefault TitlePane.menuBarResizeHeight int
* @uiDefault TitlePane.closeIcon Icon
* @uiDefault TitlePane.iconifyIcon Icon
* @uiDefault TitlePane.maximizeIcon Icon
@@ -109,7 +109,7 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatTitlePane
extends JComponent
{
- private static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
+ static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
/** @since 2.5 */ protected final Font titleFont;
protected final Color activeBackground;
@@ -131,7 +131,6 @@ public class FlatTitlePane
/** @since 2.4 */ protected final boolean showIconBesideTitle;
protected final int menuBarTitleGap;
/** @since 2.4 */ protected final int menuBarTitleMinimumGap;
- /** @since 2.4 */ protected final int menuBarResizeHeight;
protected final JRootPane rootPane;
protected final String windowStyle;
@@ -150,6 +149,23 @@ public class FlatTitlePane
private final Handler handler;
+ /**
+ * This panel handles mouse events if FlatLaf window decorations are used
+ * without native window border. E.g. on Linux.
+ *
+ * This panel usually has same bounds as the title pane,
+ * except if fullWindowContent mode is enabled.
+ *
+ * This panel is not a child of the title pane.
+ * Instead it is added by FlatRootPaneUI to the layered pane at a layer
+ * under the title pane and under the frame content.
+ * The separation is necessary for fullWindowContent mode, where the title pane
+ * is layered over the frame content (for title pane buttons), but the mousePanel
+ * needs to be layered under the frame content so that components on content pane
+ * can receive mouse events when located in title area.
+ */
+ final JPanel mouseLayer;
+
public FlatTitlePane( JRootPane rootPane ) {
this.rootPane = rootPane;
@@ -178,7 +194,6 @@ public class FlatTitlePane
showIconBesideTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconBesideTitle", windowStyle, false );
menuBarTitleGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleGap", windowStyle, 40 );
menuBarTitleMinimumGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleMinimumGap", windowStyle, 12 );
- menuBarResizeHeight = FlatUIUtils.getSubUIInt( "TitlePane.menuBarResizeHeight", windowStyle, 4 );
handler = createHandler();
@@ -187,11 +202,10 @@ public class FlatTitlePane
addSubComponents();
activeChanged( true );
- addMouseListener( handler );
- addMouseMotionListener( handler );
-
- // necessary for closing window with double-click on icon
- iconLabel.addMouseListener( handler );
+ mouseLayer = new JPanel();
+ mouseLayer.setOpaque( false );
+ mouseLayer.addMouseListener( handler );
+ mouseLayer.addMouseMotionListener( handler );
applyComponentOrientation( rootPane.getComponentOrientation() );
}
@@ -234,6 +248,11 @@ public class FlatTitlePane
setLayout( new BorderLayout() {
@Override
public void layoutContainer( Container target ) {
+ if( isFullWindowContent() ) {
+ super.layoutContainer( target );
+ return;
+ }
+
// compute available bounds
Insets insets = target.getInsets();
int x = insets.left;
@@ -247,7 +266,7 @@ public class FlatTitlePane
int titleWidth = w - leftWidth - buttonsWidth;
int minTitleWidth = UIScale.scale( titleMinimumWidth );
- // increase minimum width if icon is show besides the title
+ // increase minimum width if icon is shown besides the title
Icon icon = titleLabel.getIcon();
if( icon != null ) {
Insets iconInsets = iconLabel.getInsets();
@@ -295,6 +314,9 @@ public class FlatTitlePane
horizontalGlue.getWidth(), titleLabel.getHeight() );
}
}
+
+ // clear hit-test cache
+ lastCaptionHitTestTime = 0;
}
} );
@@ -338,6 +360,13 @@ public class FlatTitlePane
buttonPanel.add( restoreButton );
}
buttonPanel.add( closeButton );
+
+ ComponentListener l = new ComponentAdapter() {
+ @Override public void componentResized( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
+ @Override public void componentMoved( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
+ };
+ buttonPanel.addComponentListener( l );
+ addComponentListener( l );
}
protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) {
@@ -417,7 +446,9 @@ public class FlatTitlePane
/** @since 3 */
protected void updateVisibility() {
- titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) );
+ boolean isFullWindowContent = isFullWindowContent();
+ leftPanel.setVisible( !isFullWindowContent );
+ titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) && !isFullWindowContent );
closeButton.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_CLOSE, true ) );
if( window instanceof Frame ) {
@@ -443,7 +474,7 @@ public class FlatTitlePane
// get window images
List images = null;
- if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) ) {
+ if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) && !isFullWindowContent() ) {
images = window.getIconImages();
if( images.isEmpty() ) {
// search in owners
@@ -468,6 +499,13 @@ public class FlatTitlePane
updateNativeTitleBarHeightAndHitTestSpotsLater();
}
+ void updateFullWindowContentButtonsBoundsProperty() {
+ Rectangle bounds = isFullWindowContent()
+ ? new Rectangle( SwingUtilities.convertPoint( buttonPanel, 0, 0, rootPane ), buttonPanel.getSize() )
+ : null;
+ rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds );
+ }
+
@Override
public void addNotify() {
super.addNotify();
@@ -522,6 +560,11 @@ public class FlatTitlePane
window.removeComponentListener( handler );
}
+ /** @since 3.4 */
+ protected boolean isFullWindowContent() {
+ return FlatRootPaneUI.isFullWindowContent( rootPane );
+ }
+
/**
* Returns whether this title pane currently has a visible and embedded menubar.
*/
@@ -533,6 +576,9 @@ public class FlatTitlePane
* Returns whether the menubar should be embedded into the title pane.
*/
protected boolean isMenuBarEmbedded() {
+ if( isFullWindowContent() )
+ return false;
+
// not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime
return FlatUIUtils.getBoolean( rootPane,
FlatSystemProperties.MENUBAR_EMBEDDED,
@@ -620,21 +666,45 @@ public class FlatTitlePane
return;
if( debugTitleBarHeight > 0 ) {
+ // title bar height is measured from window top edge
+ int y = SwingUtilities.convertPoint( window, 0, debugTitleBarHeight, this ).y;
g.setColor( Color.green );
- g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight );
+ g.drawLine( 0, y, getWidth(), y );
}
- if( debugHitTestSpots != null ) {
- for( Rectangle r : debugHitTestSpots )
- paintRect( g, Color.red, r );
- }
- paintRect( g, Color.cyan, debugCloseButtonBounds );
- paintRect( g, Color.blue, debugAppIconBounds );
- paintRect( g, Color.blue, debugMinimizeButtonBounds );
- paintRect( g, Color.magenta, debugMaximizeButtonBounds );
- paintRect( g, Color.cyan, debugCloseButtonBounds );
+
+ g.setColor( Color.red );
+ debugPaintComponentWithMouseListener( g, Color.red, rootPane.getLayeredPane(), 0, 0 );
+
+ debugPaintRect( g, Color.blue, debugAppIconBounds );
+ debugPaintRect( g, Color.blue, debugMinimizeButtonBounds );
+ debugPaintRect( g, Color.magenta, debugMaximizeButtonBounds );
+ debugPaintRect( g, Color.cyan, debugCloseButtonBounds );
}
- private void paintRect( Graphics g, Color color, Rectangle r ) {
+ private void debugPaintComponentWithMouseListener( Graphics g, Color color, Component c, int x, int y ) {
+ if( !c.isDisplayable() || !c.isVisible() || c == mouseLayer ||
+ c == iconifyButton || c == maximizeButton || c == restoreButton || c == closeButton )
+ return;
+
+ if( c.getMouseListeners().length > 0 ||
+ c.getMouseMotionListeners().length > 0 ||
+ c.getMouseWheelListeners().length > 0 )
+ {
+ g.drawRect( x, y, c.getWidth(), c.getHeight() );
+ return;
+ }
+
+ if( c instanceof Container ) {
+ Rectangle titlePaneBoundsOnWindow = SwingUtilities.convertRectangle( this, new Rectangle( getSize() ), window );
+ for( Component child : ((Container)c).getComponents() ) {
+ Rectangle compBoundsOnWindow = SwingUtilities.convertRectangle( c, new Rectangle( c.getSize() ), window );
+ if( compBoundsOnWindow.intersects( titlePaneBoundsOnWindow ) )
+ debugPaintComponentWithMouseListener( g, color, child, x + child.getX(), y + child.getY() );
+ }
+ }
+ }
+
+ private void debugPaintRect( Graphics g, Color color, Rectangle r ) {
if( r == null )
return;
@@ -645,6 +715,9 @@ public class FlatTitlePane
@Override
protected void paintComponent( Graphics g ) {
+ if( isFullWindowContent() )
+ return;
+
// not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime
g.setColor( (UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null)
@@ -866,11 +939,14 @@ public class FlatTitlePane
return;
int titleBarHeight = getHeight();
+ // title bar height must be measured from window top edge
+ // (when window is maximized, window y location is e.g. -11 and window top inset is 11)
+ for( Component c = this; c != window && c != null; c = c.getParent() )
+ titleBarHeight += c.getY();
// slightly reduce height so that component receives mouseExit events
if( titleBarHeight > 0 )
titleBarHeight--;
- List hitTestSpots = new ArrayList<>();
Rectangle appIconBounds = null;
if( !showIconBesideTitle && iconLabel.isVisible() ) {
@@ -928,71 +1004,17 @@ public class FlatTitlePane
}
}
- Rectangle r = getNativeHitTestSpot( buttonPanel );
- if( r != null )
- hitTestSpots.add( r );
-
- JMenuBar menuBar = rootPane.getJMenuBar();
- if( hasVisibleEmbeddedMenuBar( menuBar ) ) {
- r = getNativeHitTestSpot( menuBar );
- if( r != null ) {
- // if frame is resizable and not maximized, make menu bar hit test spot smaller at top
- // to have a small area above the menu bar to resize the window
- if( window instanceof Frame && ((Frame)window).isResizable() && !isWindowMaximized() ) {
- // limit to 8, because Windows does not use a larger height
- int resizeHeight = UIScale.scale( Math.min( menuBarResizeHeight, 8 ) );
- r.y += resizeHeight;
- r.height -= resizeHeight;
- }
-
- int count = menuBar.getComponentCount();
- for( int i = count - 1; i >= 0; i-- ) {
- Component c = menuBar.getComponent( i );
- if( c instanceof Box.Filler ||
- (c instanceof JComponent && clientPropertyBoolean( (JComponent) c, COMPONENT_TITLE_BAR_CAPTION, false ) ) )
- {
- // If menu bar is embedded and contains a horizontal glue or caption component,
- // then split the hit test spot so that
- // the glue/caption component area can be used to move the window.
-
- Point glueLocation = SwingUtilities.convertPoint( c, 0, 0, window );
- int x2 = glueLocation.x + c.getWidth();
- Rectangle r2;
- if( getComponentOrientation().isLeftToRight() ) {
- r2 = new Rectangle( x2, r.y, (r.x + r.width) - x2, r.height );
-
- r.width = glueLocation.x - r.x;
- } else {
- r2 = new Rectangle( r.x, r.y, glueLocation.x - r.x, r.height );
-
- r.width = (r.x + r.width) - x2;
- r.x = x2;
- }
- if( r2.width > 0 )
- hitTestSpots.add( r2 );
- }
- }
-
- hitTestSpots.add( r );
- }
- }
-
- // allow internal frames in layered pane to be moved/resized when placed over title bar
- for( Component c : rootPane.getLayeredPane().getComponents() ) {
- r = (c instanceof JInternalFrame) ? getNativeHitTestSpot( (JInternalFrame) c ) : null;
- if( r != null )
- hitTestSpots.add( r );
- }
-
Rectangle minimizeButtonBounds = boundsInWindow( iconifyButton );
Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton );
Rectangle closeButtonBounds = boundsInWindow( closeButton );
+ // clear hit-test cache
+ lastCaptionHitTestTime = 0;
+
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
- hitTestSpots, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
+ this::captionHitTest, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
debugTitleBarHeight = titleBarHeight;
- debugHitTestSpots = hitTestSpots;
debugAppIconBounds = appIconBounds;
debugMinimizeButtonBounds = minimizeButtonBounds;
debugMaximizeButtonBounds = maximizeButtonBounds;
@@ -1007,18 +1029,101 @@ public class FlatTitlePane
: null;
}
- protected Rectangle getNativeHitTestSpot( JComponent c ) {
- Dimension size = c.getSize();
- if( size.width <= 0 || size.height <= 0 )
- return null;
+ /**
+ * Returns whether there is a component at the given location, that processes
+ * mouse events. E.g. buttons, menus, etc.
+ *
+ * Note:
+ *
+ *
This method is invoked often when mouse is moved over title bar
+ * and should therefore return quickly.
+ *
This method is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
+ * while processing Windows messages.
+ *
+ */
+ 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 == lastCaptionHitTestX && pt.y == lastCaptionHitTestY && time < lastCaptionHitTestTime + 300 ) {
+ lastCaptionHitTestTime = time;
+ return lastCaptionHitTestResult;
+ }
- Point location = SwingUtilities.convertPoint( c, 0, 0, window );
- Rectangle r = new Rectangle( location, size );
- return r;
+ // convert pt from window coordinates to layeredPane coordinates
+ Component layeredPane = rootPane.getLayeredPane();
+ int x = pt.x;
+ int y = pt.y;
+ for( Component c = layeredPane; c != window && c != null; c = c.getParent() ) {
+ x -= c.getX();
+ y -= c.getY();
+ }
+
+ lastCaptionHitTestX = pt.x;
+ lastCaptionHitTestY = pt.y;
+ lastCaptionHitTestTime = time;
+ lastCaptionHitTestResult = isTitleBarCaptionAt( layeredPane, x, y );
+ return lastCaptionHitTestResult;
}
+ private boolean isTitleBarCaptionAt( Component c, int x, int y ) {
+ if( !c.isDisplayable() || !c.isVisible() || !c.contains( x, y ) || c == mouseLayer )
+ return true; // continue checking with next component
+
+ 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( !isTitleBarCaptionAt( child, x - child.getX(), y - child.getY() ) )
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private int lastCaptionHitTestX;
+ private int lastCaptionHitTestY;
+ private long lastCaptionHitTestTime;
+ private boolean lastCaptionHitTestResult;
+
private int debugTitleBarHeight;
- private List debugHitTestSpots;
private Rectangle debugAppIconBounds;
private Rectangle debugMinimizeButtonBounds;
private Rectangle debugMaximizeButtonBounds;
@@ -1116,7 +1221,7 @@ public class FlatTitlePane
}
}
- // compute icon width and gap (if icon is show besides the title)
+ // compute icon width and gap (if icon is shown besides the title)
int iconTextGap = 0;
int iconWidthAndGap = 0;
if( icon != null ) {
@@ -1125,7 +1230,7 @@ public class FlatTitlePane
iconWidthAndGap = icon.getIconWidth() + iconTextGap;
}
- // layout title and icon (if show besides the title)
+ // layout title and icon (if shown besides the title)
String clippedText = SwingUtilities.layoutCompoundLabel( label, fontMetrics, text, icon,
label.getVerticalAlignment(), label.getHorizontalAlignment(),
label.getVerticalTextPosition(), label.getHorizontalTextPosition(),
@@ -1275,7 +1380,7 @@ debug*/
}
if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) {
- if( e.getSource() == iconLabel ) {
+ if( SwingUtilities.getDeepestComponentAt( FlatTitlePane.this, e.getX(), e.getY() ) == iconLabel ) {
// double-click on icon closes window
close();
} else if( !hasNativeCustomDecoration() ) {
@@ -1302,7 +1407,7 @@ debug*/
if( !SwingUtilities.isLeftMouseButton( e ) )
return;
- dragOffset = SwingUtilities.convertPoint( FlatTitlePane.this, e.getPoint(), window );
+ dragOffset = SwingUtilities.convertPoint( mouseLayer, e.getPoint(), window );
linuxNativeMove = false;
// on Linux, move or maximize/restore window
@@ -1415,4 +1520,27 @@ debug*/
@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 c6d97f5d..7bc13fb8 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 @@ import com.formdev.flatlaf.util.UIScale;
*/
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 @@ public class FlatToolBarUI
: 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 2fed2618..dda40c28 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
@@ -29,8 +29,8 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
+import java.util.function.Predicate;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.Timer;
@@ -159,7 +159,7 @@ class FlatWindowsNativeWindowBorder
}
@Override
- public void updateTitleBarInfo( Window window, int titleBarHeight, List hitTestSpots,
+ public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate captionHitTestCallback,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds )
{
@@ -168,7 +168,7 @@ class FlatWindowsNativeWindowBorder
return;
wndProc.titleBarHeight = titleBarHeight;
- wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] );
+ wndProc.captionHitTestCallback = captionHitTestCallback;
wndProc.appIconBounds = cloneRectange( appIconBounds );
wndProc.minimizeButtonBounds = cloneRectange( minimizeButtonBounds );
wndProc.maximizeButtonBounds = cloneRectange( maximizeButtonBounds );
@@ -288,8 +288,8 @@ class FlatWindowsNativeWindowBorder
private final long hwnd;
// Swing coordinates/values may be scaled on a HiDPI screen
- private int titleBarHeight;
- private Rectangle[] hitTestSpots;
+ private int titleBarHeight; // measured from window top edge, which may be out-of-screen if maximized
+ private Predicate captionHitTestCallback;
private Rectangle appIconBounds;
private Rectangle minimizeButtonBounds;
private Rectangle maximizeButtonBounds;
@@ -340,50 +340,61 @@ class FlatWindowsNativeWindowBorder
private int onNcHitTest( int x, int y, boolean isOnResizeBorder ) {
// scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
Point pt = scaleDown( x, y );
- int sx = pt.x;
- int sy = pt.y;
// return HTSYSMENU if mouse is over application icon
// - left-click on HTSYSMENU area shows system menu
// - double-left-click sends WM_CLOSE
- if( contains( appIconBounds, sx, sy ) )
+ if( contains( appIconBounds, pt ) )
return HTSYSMENU;
// return HTMINBUTTON if mouse is over minimize button
// - hovering mouse over HTMINBUTTON area shows tooltip on Windows 10/11
- if( contains( minimizeButtonBounds, sx, sy ) )
+ if( contains( minimizeButtonBounds, pt ) )
return HTMINBUTTON;
// return HTMAXBUTTON if mouse is over maximize/restore button
// - hovering mouse over HTMAXBUTTON area shows tooltip on Windows 10
// - hovering mouse over HTMAXBUTTON area shows snap layouts menu on Windows 11
// https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu
- if( contains( maximizeButtonBounds, sx, sy ) )
+ if( contains( maximizeButtonBounds, pt ) )
return HTMAXBUTTON;
// return HTCLOSE if mouse is over close button
// - hovering mouse over HTCLOSE area shows tooltip on Windows 10/11
- if( contains( closeButtonBounds, sx, sy ) )
+ if( contains( closeButtonBounds, pt ) )
return HTCLOSE;
- boolean isOnTitleBar = (sy < titleBarHeight);
+ // return HTTOP if mouse is over top resize border
+ // - hovering mouse shows vertical resize cursor
+ // - left-click and drag vertically resizes window
+ if( isOnResizeBorder )
+ return HTTOP;
+ boolean isOnTitleBar = (pt.y < titleBarHeight);
if( isOnTitleBar ) {
- // use a second reference to the array to avoid that it can be changed
- // in another thread while processing the array
- Rectangle[] hitTestSpots2 = hitTestSpots;
- for( Rectangle spot : hitTestSpots2 ) {
- if( spot.contains( sx, sy ) )
+ // return HTCLIENT if mouse is over any Swing component in title bar
+ // that processes mouse events (e.g. buttons, menus, etc)
+ // - Windows ignores mouse events in this area
+ try {
+ if( captionHitTestCallback != null && !captionHitTestCallback.test( pt ) )
return HTCLIENT;
+ } catch( Throwable ex ) {
+ // ignore
}
- return isOnResizeBorder ? HTTOP : HTCAPTION;
+
+ // return HTCAPTION if mouse is over title bar
+ // - right-click shows system menu
+ // - double-left-click maximizes/restores window size
+ return HTCAPTION;
}
- return isOnResizeBorder ? HTTOP : HTCLIENT;
+ // return HTCLIENT
+ // - Windows ignores mouse events in this area
+ return HTCLIENT;
}
- private boolean contains( Rectangle rect, int x, int y ) {
- return (rect != null && rect.contains( x, y ) );
+ private boolean contains( Rectangle rect, Point pt ) {
+ return (rect != null && rect.contains( pt ) );
}
/**
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FullWindowContentSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FullWindowContentSupport.java
index 87950f32..d5e080b9 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FullWindowContentSupport.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FullWindowContentSupport.java
@@ -209,9 +209,11 @@ class FullWindowContentSupport
g.drawRect( r.x, r.y, r.width - 1, r.height - 1 );
// draw diagonal cross
+ int x2 = r.x + r.width - 1;
+ int y2 = r.y + r.height - 1;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
- g.drawLine( r.x, r.y, r.width - 1, r.height - 1 );
- g.drawLine( r.x, r.height - 1, r.width - 1, r.y );
+ g.drawLine( r.x, r.y, x2, y2 );
+ g.drawLine( r.x, y2, x2, r.y );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
}
diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
index 2d7c2a0b..1f23a13c 100644
--- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
+++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
@@ -831,7 +831,6 @@ TitlePane.centerTitleIfMenuBarEmbedded = true
TitlePane.showIconBesideTitle = false
TitlePane.menuBarTitleGap = 40
TitlePane.menuBarTitleMinimumGap = 12
-TitlePane.menuBarResizeHeight = 4
TitlePane.closeIcon = com.formdev.flatlaf.icons.FlatWindowCloseIcon
TitlePane.iconifyIcon = com.formdev.flatlaf.icons.FlatWindowIconifyIcon
TitlePane.maximizeIcon = com.formdev.flatlaf.icons.FlatWindowMaximizeIcon
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
index eef1cf09..e9a9171a 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
@@ -73,6 +73,7 @@ class DemoFrame
initComponents();
updateFontMenuItems();
initAccentColors();
+ initFullWindowContent();
controlBar.initialize( this, tabbedPane );
setIconImages( FlatSVGUtils.createWindowIconImages( "/com/formdev/flatlaf/demo/FlatLaf.svg" ) );
@@ -101,9 +102,6 @@ class DemoFrame
rootPane.putClientProperty( "apple.awt.windowTitleVisible", false );
else
setTitle( null );
-
- // uncomment this line to see title bar buttons placeholders in fullWindowContent mode
-// UIManager.put( "FlatLaf.debug.panel.showPlaceholders", true );
}
// enable full screen mode for this window (for Java 8 - 10; not necessary for Java 11+)
@@ -463,9 +461,37 @@ class DemoFrame
accentColorButtons[i].setVisible( isAccentColorSupported );
}
+ private void initFullWindowContent() {
+ if( !supportsFlatLafWindowDecorations() )
+ return;
+
+ // create fullWindowContent mode toggle button
+ Icon expandIcon = new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/expand.svg" );
+ Icon collapseIcon = new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/collapse.svg" );
+ JToggleButton fullWindowContentButton = new JToggleButton( expandIcon );
+ fullWindowContentButton.setToolTipText( "Toggle full window content" );
+ fullWindowContentButton.addActionListener( e -> {
+ boolean fullWindowContent = fullWindowContentButton.isSelected();
+ fullWindowContentButton.setIcon( fullWindowContent ? collapseIcon : expandIcon );
+ menuBar.setVisible( !fullWindowContent );
+ toolBar.setVisible( !fullWindowContent );
+ getRootPane().putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT, fullWindowContent );
+ } );
+
+ // add fullWindowContent mode toggle button to tabbed pane
+ JToolBar trailingToolBar = new JToolBar();
+ trailingToolBar.add( Box.createGlue() );
+ trailingToolBar.add( fullWindowContentButton );
+ tabbedPane.putClientProperty( FlatClientProperties.TABBED_PANE_TRAILING_COMPONENT, trailingToolBar );
+ }
+
+ private boolean supportsFlatLafWindowDecorations() {
+ return FlatLaf.supportsNativeWindowDecorations() || (SystemInfo.isLinux && JFrame.isDefaultLookAndFeelDecorated());
+ }
+
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
- JMenuBar menuBar1 = new JMenuBar();
+ menuBar = new JMenuBar();
JMenu fileMenu = new JMenu();
JMenuItem newMenuItem = new JMenuItem();
JMenuItem openMenuItem = new JMenuItem();
@@ -528,8 +554,10 @@ class DemoFrame
DataComponentsPanel dataComponentsPanel = new DataComponentsPanel();
TabsPanel tabsPanel = new TabsPanel();
OptionPanePanel optionPanePanel = new OptionPanePanel();
- ExtrasPanel extrasPanel1 = new ExtrasPanel();
+ ExtrasPanel extrasPanel = new ExtrasPanel();
controlBar = new ControlBar();
+ JPanel themesPanelPanel = new JPanel();
+ JPanel winFullWindowContentButtonsPlaceholder = new JPanel();
themesPanel = new IJThemesPanel();
//======== this ========
@@ -538,7 +566,7 @@ class DemoFrame
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
- //======== menuBar1 ========
+ //======== menuBar ========
{
//======== fileMenu ========
@@ -583,7 +611,7 @@ class DemoFrame
exitMenuItem.addActionListener(e -> exitActionPerformed());
fileMenu.add(exitMenuItem);
}
- menuBar1.add(fileMenu);
+ menuBar.add(fileMenu);
//======== editMenu ========
{
@@ -636,7 +664,7 @@ class DemoFrame
deleteMenuItem.addActionListener(e -> menuItemActionPerformed(e));
editMenu.add(deleteMenuItem);
}
- menuBar1.add(editMenu);
+ menuBar.add(editMenu);
//======== viewMenu ========
{
@@ -736,7 +764,7 @@ class DemoFrame
radioButtonMenuItem3.addActionListener(e -> menuItemActionPerformed(e));
viewMenu.add(radioButtonMenuItem3);
}
- menuBar1.add(viewMenu);
+ menuBar.add(viewMenu);
//======== fontMenu ========
{
@@ -760,7 +788,7 @@ class DemoFrame
decrFontMenuItem.addActionListener(e -> decrFont());
fontMenu.add(decrFontMenuItem);
}
- menuBar1.add(fontMenu);
+ menuBar.add(fontMenu);
//======== optionsMenu ========
{
@@ -812,7 +840,7 @@ class DemoFrame
showUIDefaultsInspectorMenuItem.addActionListener(e -> showUIDefaultsInspector());
optionsMenu.add(showUIDefaultsInspectorMenuItem);
}
- menuBar1.add(optionsMenu);
+ menuBar.add(optionsMenu);
//======== helpMenu ========
{
@@ -825,9 +853,9 @@ class DemoFrame
aboutMenuItem.addActionListener(e -> aboutActionPerformed());
helpMenu.add(aboutMenuItem);
}
- menuBar1.add(helpMenu);
+ menuBar.add(helpMenu);
}
- setJMenuBar(menuBar1);
+ setJMenuBar(menuBar);
//======== toolBarPanel ========
{
@@ -884,7 +912,7 @@ class DemoFrame
}
toolBarPanel.add(toolBar, BorderLayout.CENTER);
}
- contentPane.add(toolBarPanel, BorderLayout.NORTH);
+ contentPane.add(toolBarPanel, BorderLayout.PAGE_START);
//======== contentPanel ========
{
@@ -904,13 +932,25 @@ class DemoFrame
tabbedPane.addTab("Data Components", dataComponentsPanel);
tabbedPane.addTab("Tabs", tabsPanel);
tabbedPane.addTab("Option Pane", optionPanePanel);
- tabbedPane.addTab("Extras", extrasPanel1);
+ tabbedPane.addTab("Extras", extrasPanel);
}
contentPanel.add(tabbedPane, "cell 0 0");
}
contentPane.add(contentPanel, BorderLayout.CENTER);
- contentPane.add(controlBar, BorderLayout.SOUTH);
- contentPane.add(themesPanel, BorderLayout.EAST);
+ contentPane.add(controlBar, BorderLayout.PAGE_END);
+
+ //======== themesPanelPanel ========
+ {
+ themesPanelPanel.setLayout(new BorderLayout());
+
+ //======== winFullWindowContentButtonsPlaceholder ========
+ {
+ winFullWindowContentButtonsPlaceholder.setLayout(new FlowLayout());
+ }
+ themesPanelPanel.add(winFullWindowContentButtonsPlaceholder, BorderLayout.NORTH);
+ themesPanelPanel.add(themesPanel, BorderLayout.CENTER);
+ }
+ contentPane.add(themesPanelPanel, BorderLayout.LINE_END);
//---- buttonGroup1 ----
ButtonGroup buttonGroup1 = new ButtonGroup();
@@ -925,8 +965,8 @@ class DemoFrame
usersButton.setButtonType( ButtonType.toolBarButton );
usersButton.setFocusable( false );
usersButton.addActionListener( e -> JOptionPane.showMessageDialog( null, "Hello User! How are you?", "User", JOptionPane.INFORMATION_MESSAGE ) );
- menuBar1.add( Box.createGlue() );
- menuBar1.add( usersButton );
+ menuBar.add( Box.createGlue() );
+ menuBar.add( usersButton );
cutMenuItem.addActionListener( new DefaultEditorKit.CutAction() );
copyMenuItem.addActionListener( new DefaultEditorKit.CopyAction() );
@@ -938,7 +978,7 @@ class DemoFrame
for( int i = 1; i <= 100; i++ )
scrollingPopupMenu.add( "Item " + i );
- if( FlatLaf.supportsNativeWindowDecorations() || (SystemInfo.isLinux && JFrame.isDefaultLookAndFeelDecorated()) ) {
+ if( supportsFlatLafWindowDecorations() ) {
if( SystemInfo.isLinux )
unsupported( windowDecorationsCheckBoxMenuItem );
else
@@ -959,9 +999,17 @@ class DemoFrame
if( "false".equals( System.getProperty( "flatlaf.animatedLafChange" ) ) )
animatedLafChangeMenuItem.setSelected( false );
+
// on macOS, panel left to toolBar is a placeholder for title bar buttons in fullWindowContent mode
macFullWindowContentButtonsPlaceholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac zeroInFullScreen" );
+ // on Windows/Linux, panel above themesPanel is a placeholder for title bar buttons in fullWindowContent mode
+ winFullWindowContentButtonsPlaceholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "win" );
+
+ // uncomment this line to see title bar buttons placeholders in fullWindowContent mode
+// UIManager.put( "FlatLaf.debug.panel.showPlaceholders", true );
+
+
// remove contentPanel bottom insets
MigLayout layout = (MigLayout) contentPanel.getLayout();
LC lc = ConstraintParser.parseLayoutConstraint( (String) layout.getLayoutConstraints() );
@@ -982,6 +1030,7 @@ class DemoFrame
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
+ private JMenuBar menuBar;
private JMenuItem exitMenuItem;
private JMenu scrollingPopupMenu;
private JMenuItem htmlMenuItem;
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
index f37f92ca..08248c1d 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
@@ -74,7 +74,7 @@ new FormModel {
"value": "Center"
} )
}, new FormLayoutConstraints( class java.lang.String ) {
- "value": "North"
+ "value": "First"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets dialog,hidemode 3"
@@ -115,7 +115,7 @@ new FormModel {
"title": "Option Pane"
} )
add( new FormComponent( "com.formdev.flatlaf.demo.extras.ExtrasPanel" ) {
- name: "extrasPanel1"
+ name: "extrasPanel"
}, new FormLayoutConstraints( null ) {
"title": "Extras"
} )
@@ -131,19 +131,32 @@ new FormModel {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class java.lang.String ) {
- "value": "South"
+ "value": "Last"
} )
- add( new FormComponent( "com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel" ) {
- name: "themesPanel"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": false
- "JavaCodeGenerator.variableModifiers": 0
- }
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
+ name: "themesPanelPanel"
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) {
+ name: "winFullWindowContentButtonsPlaceholder"
+ }, new FormLayoutConstraints( class java.lang.String ) {
+ "value": "North"
+ } )
+ add( new FormComponent( "com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel" ) {
+ name: "themesPanel"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ "JavaCodeGenerator.variableModifiers": 0
+ }
+ }, new FormLayoutConstraints( class java.lang.String ) {
+ "value": "Center"
+ } )
}, new FormLayoutConstraints( class java.lang.String ) {
- "value": "East"
+ "value": "After"
} )
menuBar: new FormContainer( "javax.swing.JMenuBar", new FormLayoutManager( class javax.swing.JMenuBar ) ) {
- name: "menuBar1"
+ name: "menuBar"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
name: "fileMenu"
"text": "File"
diff --git a/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/collapse.svg b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/collapse.svg
new file mode 100644
index 00000000..dfef7365
--- /dev/null
+++ b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/collapse.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/expand.svg b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/expand.svg
new file mode 100644
index 00000000..02c4e5cc
--- /dev/null
+++ b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/expand.svg
@@ -0,0 +1,4 @@
+
+
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 60b5da36..f3458941 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.Graphics;
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.event.MouseMotionListener;
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 class FlatJideTabbedPaneUI
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 c7a5ec23..bdf31aa7 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
@@ -32,8 +32,8 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
+import java.util.function.Predicate;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.Timer;
@@ -164,7 +164,7 @@ public class FlatWindowsNativeWindowBorder
}
@Override
- public void updateTitleBarInfo( Window window, int titleBarHeight, List hitTestSpots,
+ public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate captionHitTestCallback,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds )
{
@@ -173,7 +173,7 @@ public class FlatWindowsNativeWindowBorder
return;
wndProc.titleBarHeight = titleBarHeight;
- wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] );
+ wndProc.captionHitTestCallback = captionHitTestCallback;
wndProc.appIconBounds = cloneRectange( appIconBounds );
wndProc.minimizeButtonBounds = cloneRectange( minimizeButtonBounds );
wndProc.maximizeButtonBounds = cloneRectange( maximizeButtonBounds );
@@ -351,7 +351,7 @@ public class FlatWindowsNativeWindowBorder
// Swing coordinates/values may be scaled on a HiDPI screen
private int titleBarHeight;
- private Rectangle[] hitTestSpots;
+ private Predicate captionHitTestCallback;
private Rectangle appIconBounds;
private Rectangle minimizeButtonBounds;
private Rectangle maximizeButtonBounds;
@@ -644,53 +644,65 @@ public class FlatWindowsNativeWindowBorder
// scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
Point pt = scaleDown( x, y );
- int sx = pt.x;
- int sy = pt.y;
// return HTSYSMENU if mouse is over application icon
// - left-click on HTSYSMENU area shows system menu
// - double-left-click sends WM_CLOSE
- if( contains( appIconBounds, sx, sy ) )
+ if( contains( appIconBounds, pt ) )
return new LRESULT( HTSYSMENU );
// return HTMINBUTTON if mouse is over minimize button
// - hovering mouse over HTMINBUTTON area shows tooltip on Windows 10/11
- if( contains( minimizeButtonBounds, sx, sy ) )
+ if( contains( minimizeButtonBounds, pt ) )
return new LRESULT( HTMINBUTTON );
// return HTMAXBUTTON if mouse is over maximize/restore button
// - hovering mouse over HTMAXBUTTON area shows tooltip on Windows 10
// - hovering mouse over HTMAXBUTTON area shows snap layouts menu on Windows 11
// https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu
- if( contains( maximizeButtonBounds, sx, sy ) )
+ if( contains( maximizeButtonBounds, pt ) )
return new LRESULT( HTMAXBUTTON );
// return HTCLOSE if mouse is over close button
// - hovering mouse over HTCLOSE area shows tooltip on Windows 10/11
- if( contains( closeButtonBounds, sx, sy ) )
+ if( contains( closeButtonBounds, pt ) )
return new LRESULT( HTCLOSE );
int resizeBorderHeight = getResizeHandleHeight();
boolean isOnResizeBorder = (y < resizeBorderHeight) &&
(User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0;
- boolean isOnTitleBar = (sy < titleBarHeight);
+ // return HTTOP if mouse is over top resize border
+ // - hovering mouse shows vertical resize cursor
+ // - left-click and drag vertically resizes window
+ if( isOnResizeBorder )
+ return new LRESULT( HTTOP );
+
+ boolean isOnTitleBar = (pt.y < titleBarHeight);
if( isOnTitleBar ) {
- // use a second reference to the array to avoid that it can be changed
- // in another thread while processing the array
- Rectangle[] hitTestSpots2 = hitTestSpots;
- for( Rectangle spot : hitTestSpots2 ) {
- if( spot.contains( sx, sy ) )
+ // return HTCLIENT if mouse is over any Swing component in title bar
+ // that processes mouse events (e.g. buttons, menus, etc)
+ // - Windows ignores mouse events in this area
+ try {
+ if( captionHitTestCallback != null && !captionHitTestCallback.test( pt ) )
return new LRESULT( HTCLIENT );
+ } catch( Throwable ex ) {
+ // ignore
}
- return new LRESULT( isOnResizeBorder ? HTTOP : HTCAPTION );
+
+ // return HTCAPTION if mouse is over title bar
+ // - right-click shows system menu
+ // - double-left-click maximizes/restores window size
+ return new LRESULT( HTCAPTION );
}
- return new LRESULT( isOnResizeBorder ? HTTOP : HTCLIENT );
+ // return HTCLIENT
+ // - Windows ignores mouse events in this area
+ return new LRESULT( HTCLIENT );
}
- private boolean contains( Rectangle rect, int x, int y ) {
- return (rect != null && rect.contains( x, y ) );
+ private boolean contains( Rectangle rect, Point pt ) {
+ return (rect != null && rect.contains( pt ) );
}
/**
diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
index 912bdee5..b0bbcf98 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
@@ -1266,7 +1266,6 @@ TitlePane.inactiveBackground #303234 HSL 210 4 20 javax.swing.plaf.Colo
TitlePane.inactiveForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI]
TitlePane.maximizeIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowMaximizeIcon [UI]
TitlePane.menuBarEmbedded true
-TitlePane.menuBarResizeHeight 4
TitlePane.menuBarTitleGap 40
TitlePane.menuBarTitleMinimumGap 12
TitlePane.noIconLeftGap 8
diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
index dd25a5bd..76120a29 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
@@ -1271,7 +1271,6 @@ TitlePane.inactiveBackground #ffffff HSL 0 0 100 javax.swing.plaf.Colo
TitlePane.inactiveForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI]
TitlePane.maximizeIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowMaximizeIcon [UI]
TitlePane.menuBarEmbedded true
-TitlePane.menuBarResizeHeight 4
TitlePane.menuBarTitleGap 40
TitlePane.menuBarTitleMinimumGap 12
TitlePane.noIconLeftGap 8
diff --git a/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt
index 257c6cfb..8a325ba3 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt
@@ -1276,7 +1276,6 @@ TitlePane.inactiveBackground #323232 HSL 0 0 20 javax.swing.plaf.Colo
TitlePane.inactiveForeground #9a9a9a HSL 0 0 60 javax.swing.plaf.ColorUIResource [UI]
TitlePane.maximizeIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowMaximizeIcon [UI]
TitlePane.menuBarEmbedded true
-TitlePane.menuBarResizeHeight 4
TitlePane.menuBarTitleGap 40
TitlePane.menuBarTitleMinimumGap 12
TitlePane.noIconLeftGap 8
diff --git a/flatlaf-testing/dumps/uidefaults/FlatMacLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatMacLightLaf_1.8.0.txt
index 32084040..eef398f1 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatMacLightLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatMacLightLaf_1.8.0.txt
@@ -1280,7 +1280,6 @@ TitlePane.inactiveBackground #ececec HSL 0 0 93 javax.swing.plaf.Colo
TitlePane.inactiveForeground #b6b6b6 HSL 0 0 71 javax.swing.plaf.ColorUIResource [UI]
TitlePane.maximizeIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowMaximizeIcon [UI]
TitlePane.menuBarEmbedded true
-TitlePane.menuBarResizeHeight 4
TitlePane.menuBarTitleGap 40
TitlePane.menuBarTitleMinimumGap 12
TitlePane.noIconLeftGap 8
diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
index cfab4c91..112f9388 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
@@ -1307,7 +1307,6 @@ TitlePane.inactiveBackground #008800 HSL 120 100 27 javax.swing.plaf.Colo
TitlePane.inactiveForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI]
TitlePane.maximizeIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowMaximizeIcon [UI]
TitlePane.menuBarEmbedded true
-TitlePane.menuBarResizeHeight 4
TitlePane.menuBarTitleGap 40
TitlePane.menuBarTitleMinimumGap 12
TitlePane.noIconLeftGap 8
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java
index 85a62fab..1748ff69 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java
@@ -41,6 +41,9 @@ import net.miginfocom.swing.*;
public class FlatWindowDecorationsTest
extends FlatTestPanel
{
+ // same as in FlatTitlePane
+ private static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
+
public static void main( String[] args ) {
SwingUtilities.invokeLater( () -> {
if( SystemInfo.isLinux ) {
@@ -51,7 +54,7 @@ public class FlatWindowDecorationsTest
FlatTestFrame frame = FlatTestFrame.create( args, "FlatWindowDecorationsTest" );
frame.applyComponentOrientationToFrame = true;
- UIManager.put( "FlatLaf.debug.titlebar.showRectangles", true );
+ UIManager.put( KEY_DEBUG_SHOW_RECTANGLES, true );
Class> cls = FlatWindowDecorationsTest.class;
List images = Arrays.asList(
@@ -117,6 +120,14 @@ public class FlatWindowDecorationsTest
rootPane.addPropertyChangeListener( "windowDecorationStyle", e -> {
updateDecorationStyleRadioButtons( rootPane );
} );
+ rootPane.addPropertyChangeListener( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, e -> {
+ Rectangle bounds = (Rectangle) e.getNewValue();
+ if( bounds != null ) {
+ fullWindowContentButtonsBoundsField.setText( bounds.width + ", " + bounds.height
+ + " @ " + bounds.x + ", " + bounds.y );
+ } else
+ fullWindowContentButtonsBoundsField.setText( "null" );
+ } );
}
}
@@ -309,12 +320,21 @@ debug*/
JLabel caption = new JLabel( "Caption" );
caption.setBackground( Color.green );
caption.setOpaque( true );
- caption.putClientProperty( FlatClientProperties.COMPONENT_TITLE_BAR_CAPTION, true );
menuBar.add( caption );
menuBar.revalidate();
}
+ private void addTextField() {
+ JTextField textField = new JTextField( "text", 10 );
+
+ JPanel panel = new JPanel( new GridBagLayout() );
+ panel.add( textField, new GridBagConstraints() );
+
+ menuBar.add( panel );
+ menuBar.revalidate();
+ }
+
private void removeMenu() {
int menuCount = menuBar.getMenuCount();
if( menuCount <= 0 )
@@ -515,13 +535,31 @@ debug*/
rootPane.putClientProperty( FlatClientProperties.TITLE_BAR_SHOW_CLOSE, showCloseCheckBox.isSelected() ? null : false );
}
+ private void fullWindowContentChanged() {
+ JRootPane rootPane = getWindowRootPane();
+ if( rootPane != null ) {
+ boolean selected = fullWindowContentCheckBox.isSelected();
+ rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT, selected ? true : null );
+
+ showIconCheckBox.setEnabled( !selected );
+ showTitleCheckBox.setEnabled( !selected );
+ }
+ }
+
private JRootPane getWindowRootPane() {
Window window = SwingUtilities.windowForComponent( this );
- if( window instanceof JFrame )
- return ((JFrame)window).getRootPane();
- else if( window instanceof JDialog )
- return ((JDialog)window).getRootPane();
- return null;
+ return (window instanceof RootPaneContainer)
+ ? ((RootPaneContainer)window).getRootPane()
+ : null;
+ }
+
+ private void showRectangles() {
+ JRootPane rootPane = getWindowRootPane();
+ if( rootPane != null ) {
+ UIManager.put( KEY_DEBUG_SHOW_RECTANGLES, showRectanglesCheckBox.isSelected() );
+ rootPane.revalidate();
+ rootPane.repaint();
+ }
}
private void initComponents() {
@@ -538,6 +576,9 @@ debug*/
showIconifyCheckBox = new JCheckBox();
showMaximizeCheckBox = new JCheckBox();
showCloseCheckBox = new JCheckBox();
+ fullWindowContentCheckBox = new JCheckBox();
+ JLabel fullWindowContentButtonsBoundsLabel = new JLabel();
+ fullWindowContentButtonsBoundsField = new JLabel();
JPanel panel6 = new JPanel();
menuBarCheckBox = new JCheckBox();
menuBarEmbeddedCheckBox = new JCheckBox();
@@ -548,6 +589,7 @@ debug*/
addMenuButton = new JButton();
addGlueButton = new JButton();
addCaptionButton = new JButton();
+ addTextFieldButton = new JButton();
removeMenuButton = new JButton();
changeMenuButton = new JButton();
changeTitleButton = new JButton();
@@ -578,6 +620,7 @@ debug*/
typeNormalRadioButton = new JRadioButton();
typeUtilityRadioButton = new JRadioButton();
typeSmallRadioButton = new JRadioButton();
+ showRectanglesCheckBox = new JCheckBox();
menuBar = new JMenuBar();
JMenu fileMenu = new JMenu();
JMenuItem newMenuItem = new JMenuItem();
@@ -616,6 +659,7 @@ debug*/
// rows
"[fill]" +
"[fill]" +
+ "[]" +
"[]"));
//======== panel7 ========
@@ -673,6 +717,8 @@ debug*/
"[]" +
"[]" +
"[]" +
+ "[]rel" +
+ "[]rel" +
"[]"));
//---- showIconCheckBox ----
@@ -703,6 +749,19 @@ debug*/
showCloseCheckBox.setSelected(true);
showCloseCheckBox.addActionListener(e -> showCloseChanged());
panel4.add(showCloseCheckBox, "cell 0 4");
+
+ //---- fullWindowContentCheckBox ----
+ fullWindowContentCheckBox.setText("full window content");
+ fullWindowContentCheckBox.addActionListener(e -> fullWindowContentChanged());
+ panel4.add(fullWindowContentCheckBox, "cell 0 5");
+
+ //---- fullWindowContentButtonsBoundsLabel ----
+ fullWindowContentButtonsBoundsLabel.setText("Buttons bounds:");
+ panel4.add(fullWindowContentButtonsBoundsLabel, "cell 0 6");
+
+ //---- fullWindowContentButtonsBoundsField ----
+ fullWindowContentButtonsBoundsField.setText("null");
+ panel4.add(fullWindowContentButtonsBoundsField, "cell 0 6");
}
add(panel4, "cell 1 0");
@@ -761,6 +820,7 @@ debug*/
"[]" +
"[]" +
"[]" +
+ "[]" +
"[]unrel" +
"[]"));
@@ -779,20 +839,25 @@ debug*/
addCaptionButton.addActionListener(e -> addCaption());
panel3.add(addCaptionButton, "cell 0 2");
+ //---- addTextFieldButton ----
+ addTextFieldButton.setText("Add text field");
+ addTextFieldButton.addActionListener(e -> addTextField());
+ panel3.add(addTextFieldButton, "cell 0 3");
+
//---- removeMenuButton ----
removeMenuButton.setText("Remove menu");
removeMenuButton.addActionListener(e -> removeMenu());
- panel3.add(removeMenuButton, "cell 0 3");
+ panel3.add(removeMenuButton, "cell 0 4");
//---- changeMenuButton ----
changeMenuButton.setText("Change menu");
changeMenuButton.addActionListener(e -> changeMenu());
- panel3.add(changeMenuButton, "cell 0 4");
+ panel3.add(changeMenuButton, "cell 0 5");
//---- changeTitleButton ----
changeTitleButton.setText("Change title");
changeTitleButton.addActionListener(e -> changeTitle());
- panel3.add(changeTitleButton, "cell 0 5");
+ panel3.add(changeTitleButton, "cell 0 6");
}
add(panel3, "cell 3 0 1 2,aligny top,growy 0");
@@ -969,6 +1034,12 @@ debug*/
typeSmallRadioButton.setText("Small");
add(typeSmallRadioButton, "cell 0 2 3 1");
+ //---- showRectanglesCheckBox ----
+ showRectanglesCheckBox.setText("Show debug title bar rectangles");
+ showRectanglesCheckBox.setSelected(true);
+ showRectanglesCheckBox.addActionListener(e -> showRectangles());
+ add(showRectanglesCheckBox, "cell 0 3");
+
//======== menuBar ========
{
@@ -1176,6 +1247,8 @@ debug*/
private JCheckBox showIconifyCheckBox;
private JCheckBox showMaximizeCheckBox;
private JCheckBox showCloseCheckBox;
+ private JCheckBox fullWindowContentCheckBox;
+ private JLabel fullWindowContentButtonsBoundsField;
private JCheckBox menuBarCheckBox;
private JCheckBox menuBarEmbeddedCheckBox;
private JCheckBox menuBarVisibleCheckBox;
@@ -1184,6 +1257,7 @@ debug*/
private JButton addMenuButton;
private JButton addGlueButton;
private JButton addCaptionButton;
+ private JButton addTextFieldButton;
private JButton removeMenuButton;
private JButton changeMenuButton;
private JButton changeTitleButton;
@@ -1209,6 +1283,7 @@ debug*/
private JRadioButton typeNormalRadioButton;
private JRadioButton typeUtilityRadioButton;
private JRadioButton typeSmallRadioButton;
+ private JCheckBox showRectanglesCheckBox;
private JMenuBar menuBar;
// JFormDesigner - End of variables declaration //GEN-END:variables
}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd
index 484c1fac..c3579666 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd
@@ -9,7 +9,7 @@ new FormModel {
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "ltr,insets dialog,hidemode 3"
"$columnConstraints": "[left][fill][fill][fill]"
- "$rowConstraints": "[fill][fill][]"
+ "$rowConstraints": "[fill][fill][][]"
} ) {
name: "this"
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
@@ -77,7 +77,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "ltr,hidemode 3,gap 0 0"
"$columnConstraints": "[grow,left]"
- "$rowConstraints": "[][][][][]"
+ "$rowConstraints": "[][][][][]rel[]rel[]"
} ) {
name: "panel4"
"border": new javax.swing.border.TitledBorder( "Title Bar" )
@@ -135,6 +135,31 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4"
} )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "fullWindowContentCheckBox"
+ "text": "full window content"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "fullWindowContentChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 5"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "fullWindowContentButtonsBoundsLabel"
+ "text": "Buttons bounds:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "fullWindowContentButtonsBoundsField"
+ "text": "null"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6"
+ } )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 0"
} )
@@ -204,7 +229,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3"
"$columnConstraints": "[fill]"
- "$rowConstraints": "[][][][][]unrel[]"
+ "$rowConstraints": "[][][][][][]unrel[]"
} ) {
name: "panel3"
add( new FormComponent( "javax.swing.JButton" ) {
@@ -237,6 +262,16 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "addTextFieldButton"
+ "text": "Add text field"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "addTextField", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
add( new FormComponent( "javax.swing.JButton" ) {
name: "removeMenuButton"
"text": "Remove menu"
@@ -245,7 +280,7 @@ new FormModel {
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "removeMenu", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 3"
+ "value": "cell 0 4"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "changeMenuButton"
@@ -255,7 +290,7 @@ new FormModel {
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "changeMenu", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 4"
+ "value": "cell 0 5"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "changeTitleButton"
@@ -265,7 +300,7 @@ new FormModel {
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "changeTitle", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 5"
+ "value": "cell 0 6"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 3 0 1 2,aligny top,growy 0"
@@ -552,6 +587,17 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2 3 1"
} )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "showRectanglesCheckBox"
+ "text": "Show debug title bar rectangles"
+ "selected": true
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showRectangles", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 960, 495 )
diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt
index 0d9d0aa1..928cdfa2 100644
--- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt
+++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt
@@ -1039,7 +1039,6 @@ TitlePane.inactiveBackground
TitlePane.inactiveForeground
TitlePane.maximizeIcon
TitlePane.menuBarEmbedded
-TitlePane.menuBarResizeHeight
TitlePane.menuBarTitleGap
TitlePane.menuBarTitleMinimumGap
TitlePane.noIconLeftGap
@@ -1071,7 +1070,6 @@ TitlePane.small.iconifyIcon
TitlePane.small.inactiveBackground
TitlePane.small.inactiveForeground
TitlePane.small.maximizeIcon
-TitlePane.small.menuBarResizeHeight
TitlePane.small.menuBarTitleGap
TitlePane.small.menuBarTitleMinimumGap
TitlePane.small.noIconLeftGap