From f40baed65e6f192a906ce63d979bcf0eb5becc41 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 10 Dec 2023 13:34:41 +0100 Subject: [PATCH 01/10] macOS large title bar: add NSToolbar to NSWindow --- .../flatlaf/ui/FlatNativeMacLibrary.java | 2 + .../com/formdev/flatlaf/demo/DemoFrame.java | 50 ++++++++++++++++++- .../com/formdev/flatlaf/demo/FlatLafDemo.java | 5 ++ ..._formdev_flatlaf_ui_FlatNativeMacLibrary.h | 8 +++ .../src/main/objcpp/MacWindow.mm | 24 +++++++++ 5 files changed, 88 insertions(+), 1 deletion(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java index 3799bfd9f..6e4e7ba3f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java @@ -53,4 +53,6 @@ public static boolean isLoaded() { } public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor ); + + public native static void setWindowToolbar( Window window ); } 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 1a5d3da39..e5e9cbde9 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 @@ -101,7 +101,7 @@ class DemoFrame setTitle( null ); // add gap to left side of toolbar - toolBar.add( Box.createHorizontalStrut( 70 ), 0 ); + toolBar.add( Box.createHorizontalStrut( 80 ), 0 ); } // enable full screen mode for this window (for Java 8 - 10; not necessary for Java 11+) @@ -903,6 +903,54 @@ private void initComponents() { buttonGroup1.add(radioButtonMenuItem3); // JFormDesigner - End of component initialization //GEN-END:initComponents + backButton.addActionListener( e -> System.out.println( e ) ); + backButton.addMouseListener( new MouseListener() { + + @Override + public void mouseReleased( MouseEvent e ) { + // TODO Auto-generated method stub + System.out.println( "m release" ); + } + + @Override + public void mousePressed( MouseEvent e ) { + // TODO Auto-generated method stub + System.out.println( "m press" ); + } + + @Override + public void mouseExited( MouseEvent e ) { + // TODO Auto-generated method stub + System.out.println( "m exit" ); + } + + @Override + public void mouseEntered( MouseEvent e ) { + // TODO Auto-generated method stub + System.out.println( "m ent" ); + } + + @Override + public void mouseClicked( MouseEvent e ) { + // TODO Auto-generated method stub + System.out.println( "m click" ); + } + } ); + backButton.addMouseMotionListener( new MouseMotionListener() { + + @Override + public void mouseMoved( MouseEvent e ) { + // TODO Auto-generated method stub + System.out.println( "m moved" ); + } + + @Override + public void mouseDragged( MouseEvent e ) { + // TODO Auto-generated method stub + System.out.println( "m drag" ); + } + } ); + // add "Users" button to menubar FlatButton usersButton = new FlatButton(); usersButton.setIcon( new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/users.svg" ) ); diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java index 4a55f459c..03bdcc014 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java @@ -27,6 +27,7 @@ import com.formdev.flatlaf.fonts.jetbrains_mono.FlatJetBrainsMonoFont; import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont; import com.formdev.flatlaf.fonts.roboto_mono.FlatRobotoMonoFont; +import com.formdev.flatlaf.ui.FlatNativeMacLibrary; import com.formdev.flatlaf.util.SystemInfo; /** @@ -117,6 +118,10 @@ public static void main( String[] args ) { // show frame frame.pack(); frame.setLocationRelativeTo( null ); + if( SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() ) { + // TODO use client property + FlatNativeMacLibrary.setWindowToolbar( frame ); + } frame.setVisible( true ); } ); } diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h index f5bcd35ba..6fd71690f 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h @@ -15,6 +15,14 @@ extern "C" { JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowRoundedBorder (JNIEnv *, jclass, jobject, jfloat, jfloat, jint); +/* + * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary + * Method: setWindowToolbar + * Signature: (Ljava/awt/Window;)V + */ +JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar + (JNIEnv *, jclass, jobject); + #ifdef __cplusplus } #endif diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm index 5d81a51cd..40f2190a0 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm @@ -87,3 +87,27 @@ JNI_COCOA_EXIT() return FALSE; } + +extern "C" +JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar + ( JNIEnv* env, jclass cls, jobject window ) +{ + JNI_COCOA_ENTER() + + NSWindow* nsWindow = getNSWindow( env, cls, window ); + if( nsWindow == NULL ) + return; + + [FlatJNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ + NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); + + NSToolbar* toolbar = [NSToolbar new]; + nsWindow.toolbar = toolbar; + + // TODO handle fullscreen + + NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); + }]; + + JNI_COCOA_EXIT() +} From ea2447dcb795f2e0fb985092bac1f16a63c234d0 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 11 Dec 2023 15:05:37 +0100 Subject: [PATCH 02/10] macOS large title bar: main `JToolBar` automatically: - uses height of macOS window title bar - adds left insets for close/minimize/zoom buttons (except if full screen, where those buttons are hidden) --- .../flatlaf/ui/FlatNativeMacLibrary.java | 5 +- .../formdev/flatlaf/ui/FlatToolBarBorder.java | 17 ++ .../com/formdev/flatlaf/ui/FlatToolBarUI.java | 149 ++++++++++++++++++ .../com/formdev/flatlaf/demo/DemoFrame.java | 17 +- .../com/formdev/flatlaf/demo/FlatLafDemo.java | 2 +- ..._formdev_flatlaf_ui_FlatNativeMacLibrary.h | 26 ++- .../src/main/objcpp/MacWindow.mm | 87 +++++++++- 7 files changed, 292 insertions(+), 11 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java index 6e4e7ba3f..65df59cd0 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java @@ -54,5 +54,8 @@ public static boolean isLoaded() { public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor ); - public native static void setWindowToolbar( Window window ); + public native static void setWindowToolbar( Window window, boolean hasToolbar ); + public native static int getWindowButtonAreaWidth( Window window ); + public native static int getWindowTitleBarHeight( Window window ); + public native static boolean isWindowFullScreen( Window window ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java index 68b9325db..4d66a4a12 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java @@ -25,6 +25,7 @@ import java.util.function.Function; import javax.swing.JToolBar; import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.ToolBarUI; import com.formdev.flatlaf.util.UIScale; @@ -114,6 +115,22 @@ public Insets getBorderInsets( Component c, Insets insets ) { insets.top += gripInset; } + // on macOS, add some extra space to left side for close/minimize/zoom buttons (if necessary) + if( c instanceof JToolBar && + FlatToolBarUI.isMacOSMainToolbar( (JToolBar) c ) && + (!FlatNativeMacLibrary.isLoaded() || + !FlatNativeMacLibrary.isWindowFullScreen( SwingUtilities.windowForComponent( c ) )) ) + { + // get button area width from macOS + int buttonBarWidth = FlatNativeMacLibrary.isLoaded() + ? FlatNativeMacLibrary.getWindowButtonAreaWidth( SwingUtilities.windowForComponent( c ) ) + : -1; + if( buttonBarWidth < 0 ) + buttonBarWidth = 68; // default width if NSWindow does not have a toolbar + + insets.left += buttonBarWidth; + } + return insets; } 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..c90926b69 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 @@ -19,35 +19,45 @@ import java.awt.Color; import java.awt.Component; import java.awt.Container; +import java.awt.Dimension; import java.awt.FocusTraversalPolicy; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.LayoutManager2; import java.awt.Rectangle; +import java.awt.Window; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; +import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Enumeration; import java.util.Map; import javax.swing.AbstractButton; +import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.ButtonModel; import javax.swing.DefaultButtonModel; import javax.swing.InputMap; import javax.swing.JComboBox; import javax.swing.JComponent; +import javax.swing.JRootPane; import javax.swing.JToolBar; import javax.swing.LayoutFocusTraversalPolicy; import javax.swing.RootPaneContainer; +import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicToolBarUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; +import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; /** @@ -148,6 +158,12 @@ protected void installDefaults() { toolBar.setFloatable( false ); } else oldFloatable = null; + + // layout manager + LayoutManager layout = createLayout(); + toolBar.setLayout( layout ); + if( layout instanceof PropertyChangeListener ) + toolBar.addPropertyChangeListener( (PropertyChangeListener) layout ); } @Override @@ -160,6 +176,8 @@ protected void uninstallDefaults() { toolBar.setFloatable( oldFloatable ); oldFloatable = null; } + + toolBar.setLayout( null ); } @Override @@ -453,6 +471,137 @@ private ButtonGroup getButtonGroup( AbstractButton b ) { : null; } + /** @since 3.3 */ + protected LayoutManager createLayout() { + return new FlatToolBarLayoutManager(); + } + + /** + * Returns whether the given toolbar is used in window titlebar on macOS. + *

+ * Returns {@code true} if: + *

+ * + * @since 3.3 + */ + public static boolean isMacOSMainToolbar( JToolBar toolBar ) { + if( !SystemInfo.isMacFullWindowContentSupported || + toolBar.getOrientation() != JToolBar.HORIZONTAL || + toolBar.getX() != 0 || + toolBar.getY() != 0 ) + return false; + + JRootPane rootPane = SwingUtilities.getRootPane( toolBar ); + if( rootPane == null ) + return false; + + if( !FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) + return false; + + for( Component p = toolBar.getParent(); p != null && !(p instanceof Window); p = p.getParent() ) { + if( p.getX() != 0 || p.getY() != 0 ) + return false; + } + + return true; + } + + //---- class FlatToolBarLayoutManager ------------------------------------- + + /** + * @since 3.3 + */ + protected class FlatToolBarLayoutManager + implements LayoutManager2, PropertyChangeListener, UIResource + { + private BoxLayout delegate; + + FlatToolBarLayoutManager() { + initBoxLayout(); + } + + private void initBoxLayout() { + delegate = new BoxLayout( toolBar, (toolBar.getOrientation() == JToolBar.HORIZONTAL) + ? BoxLayout.LINE_AXIS : BoxLayout.PAGE_AXIS ); + } + + @Override + public void addLayoutComponent( Component comp, Object constraints ) { + delegate.addLayoutComponent( comp, constraints ); + } + + @Override + public void addLayoutComponent( String name, Component comp ) { + delegate.addLayoutComponent( name, comp ); + } + + @Override + public void removeLayoutComponent( Component comp ) { + delegate.removeLayoutComponent( comp ); + } + + @Override + public Dimension preferredLayoutSize( Container parent ) { + return minimumHeightOnMacOS( delegate.preferredLayoutSize( parent ) ); + } + + @Override + public Dimension minimumLayoutSize( Container parent ) { + return minimumHeightOnMacOS( delegate.minimumLayoutSize( parent ) ); + } + + @Override + public Dimension maximumLayoutSize( Container target ) { + return minimumHeightOnMacOS( delegate.maximumLayoutSize( target ) ); + } + + private Dimension minimumHeightOnMacOS( Dimension size ) { + if( isMacOSMainToolbar( toolBar ) ) { + // get title bar height from macOS + int titleBarHeight = FlatNativeMacLibrary.isLoaded() + ? FlatNativeMacLibrary.getWindowTitleBarHeight( SwingUtilities.windowForComponent( toolBar ) ) + : -1; + if( titleBarHeight < 0 ) + titleBarHeight = 28; // default height if NSWindow does not have a toolbar + + size.height = Math.max( size.height, titleBarHeight ); + } + return size; + } + + @Override + public void layoutContainer( Container parent ) { + delegate.layoutContainer( parent ); + } + + @Override + public void invalidateLayout( Container target ) { + delegate.invalidateLayout( target ); + } + + @Override + public float getLayoutAlignmentX( Container target ) { + return delegate.getLayoutAlignmentX( target ); + } + + @Override + public float getLayoutAlignmentY( Container target ) { + return delegate.getLayoutAlignmentY( target ); + } + + @Override + public void propertyChange( PropertyChangeEvent e ) { + if( "orientation".equals( e.getPropertyName() ) ) + initBoxLayout(); + } + } + //---- class FlatToolBarFocusTraversalPolicy ------------------------------ /** 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 e5e9cbde9..191268c19 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 @@ -46,6 +46,7 @@ import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.formdev.flatlaf.extras.FlatSVGUtils; +import com.formdev.flatlaf.ui.FlatNativeMacLibrary; import com.formdev.flatlaf.util.ColorFunctions; import com.formdev.flatlaf.util.FontUtils; import com.formdev.flatlaf.util.LoggingFacade; @@ -99,9 +100,6 @@ class DemoFrame getRootPane().putClientProperty( "apple.awt.windowTitleVisible", false ); else setTitle( null ); - - // add gap to left side of toolbar - toolBar.add( Box.createHorizontalStrut( 80 ), 0 ); } // enable full screen mode for this window (for Java 8 - 10; not necessary for Java 11+) @@ -903,8 +901,15 @@ private void initComponents() { buttonGroup1.add(radioButtonMenuItem3); // JFormDesigner - End of component initialization //GEN-END:initComponents - backButton.addActionListener( e -> System.out.println( e ) ); - backButton.addMouseListener( new MouseListener() { + backButton.addActionListener( e -> { + FlatNativeMacLibrary.setWindowToolbar( this, true ); + }); + forwardButton.addActionListener( e -> { + FlatNativeMacLibrary.setWindowToolbar( this, false ); + }); + + cutButton.addActionListener( e -> System.out.println( e ) ); + cutButton.addMouseListener( new MouseListener() { @Override public void mouseReleased( MouseEvent e ) { @@ -936,7 +941,7 @@ public void mouseClicked( MouseEvent e ) { System.out.println( "m click" ); } } ); - backButton.addMouseMotionListener( new MouseMotionListener() { + cutButton.addMouseMotionListener( new MouseMotionListener() { @Override public void mouseMoved( MouseEvent e ) { diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java index 03bdcc014..cfc226b84 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java @@ -120,7 +120,7 @@ public static void main( String[] args ) { frame.setLocationRelativeTo( null ); if( SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() ) { // TODO use client property - FlatNativeMacLibrary.setWindowToolbar( frame ); + FlatNativeMacLibrary.setWindowToolbar( frame, true ); } frame.setVisible( true ); } ); diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h index 6fd71690f..7964d7f20 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h @@ -18,9 +18,33 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary * Method: setWindowToolbar - * Signature: (Ljava/awt/Window;)V + * Signature: (Ljava/awt/Window;Z)V */ JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar + (JNIEnv *, jclass, jobject, jboolean); + +/* + * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary + * Method: getWindowButtonAreaWidth + * Signature: (Ljava/awt/Window;)I + */ +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonAreaWidth + (JNIEnv *, jclass, jobject); + +/* + * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary + * Method: getWindowTitleBarHeight + * Signature: (Ljava/awt/Window;)I + */ +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowTitleBarHeight + (JNIEnv *, jclass, jobject); + +/* + * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary + * Method: isWindowFullScreen + * Signature: (Ljava/awt/Window;)Z + */ +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWindowFullScreen (JNIEnv *, jclass, jobject); #ifdef __cplusplus diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm index 40f2190a0..f15264e33 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm @@ -90,7 +90,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar - ( JNIEnv* env, jclass cls, jobject window ) + ( JNIEnv* env, jclass cls, jobject window, jboolean hasToolbar ) { JNI_COCOA_ENTER() @@ -101,7 +101,11 @@ [FlatJNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); - NSToolbar* toolbar = [NSToolbar new]; + NSToolbar* toolbar = NULL; + if( hasToolbar ) { + toolbar = [NSToolbar new]; + toolbar.showsBaselineSeparator = NO; // necessary for older macOS versions + } nsWindow.toolbar = toolbar; // TODO handle fullscreen @@ -111,3 +115,82 @@ JNI_COCOA_EXIT() } + +extern "C" +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonAreaWidth + ( JNIEnv* env, jclass cls, jobject window ) +{ + JNI_COCOA_ENTER() + + NSWindow* nsWindow = getNSWindow( env, cls, window ); + if( nsWindow == NULL ) + return -1; + + // get buttons + NSView* buttons[3] = { + [nsWindow standardWindowButton:NSWindowCloseButton], + [nsWindow standardWindowButton:NSWindowMiniaturizeButton], + [nsWindow standardWindowButton:NSWindowZoomButton] + }; + + // get most left and right coordinates + int left = -1; + int right = -1; + for( int i = 0; i < 3; i++ ) { + NSView* button = buttons[i]; + if( button == NULL ) + continue; + + int x = [button convertRect: [button bounds] toView:button.superview].origin.x; + int width = button.bounds.size.width; + if( left == -1 || x < left ) + left = x; + if( right == -1 || x + width > right ) + right = x + width; + } + + if( left == -1 || right == -1 ) + return -1; + + // 'right' is the actual button area width (from left window edge) + // adding 'left' to add same empty space on right side as on left side + return right + left; + + JNI_COCOA_EXIT() +} + +extern "C" +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowTitleBarHeight + ( JNIEnv* env, jclass cls, jobject window ) +{ + JNI_COCOA_ENTER() + + NSWindow* nsWindow = getNSWindow( env, cls, window ); + if( nsWindow == NULL ) + return -1; + + NSView* closeButton = [nsWindow standardWindowButton:NSWindowCloseButton]; + if( closeButton == NULL ) + return -1; + + NSView* titlebar = closeButton.superview; + return titlebar.bounds.size.height; + + JNI_COCOA_EXIT() +} + +extern "C" +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWindowFullScreen + ( JNIEnv* env, jclass cls, jobject window ) +{ + JNI_COCOA_ENTER() + + NSWindow* nsWindow = getNSWindow( env, cls, window ); + if( nsWindow == NULL ) + return FALSE; + + return (jboolean) (([nsWindow styleMask] & NSWindowStyleMaskFullScreen) != 0); + + JNI_COCOA_EXIT() +} + From 241fe855ccabe410f2019ad75365ab24e0c345c3 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 12 Dec 2023 17:22:18 +0100 Subject: [PATCH 03/10] macOS large title bar: hide NSToolbar of window title bar when window becomes full screen --- .../src/main/objcpp/MacWindow.mm | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm index f15264e33..62c41c7ac 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm @@ -15,6 +15,7 @@ */ #import +#import #import #import "JNIUtils.h" #import "JNFRunLoop.h" @@ -98,9 +99,13 @@ if( nsWindow == NULL ) return; + if( hasToolbar == (nsWindow.toolbar != NULL) ) + return; + [FlatJNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); + // add/remove toolbar NSToolbar* toolbar = NULL; if( hasToolbar ) { toolbar = [NSToolbar new]; @@ -108,9 +113,42 @@ } nsWindow.toolbar = toolbar; - // TODO handle fullscreen - NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); + + // when window becomes full screen, it is necessary to hide the toolbar + // because it otherwise is shown non-transparent and hides Swing components + static char enterObserverKey; + static char exitObserverKey; + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + if( hasToolbar ) { + NSLog( @"add observers %@", nsWindow ); + id enterObserver = [center addObserverForName:NSWindowWillEnterFullScreenNotification + object:nsWindow queue:nil usingBlock:^(NSNotification *note) { + NSLog( @"enter full screen %@", nsWindow ); + if( nsWindow.toolbar != NULL ) + nsWindow.toolbar.visible = NO; + }]; + id exitObserver = [center addObserverForName:NSWindowDidExitFullScreenNotification + object:nsWindow queue:nil usingBlock:^(NSNotification *note) { + NSLog( @"exit full screen %@", nsWindow ); + if( nsWindow.toolbar != NULL ) + nsWindow.toolbar.visible = YES; + }]; + objc_setAssociatedObject( nsWindow, &enterObserverKey, enterObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); + objc_setAssociatedObject( nsWindow, &exitObserverKey, exitObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); + } else { + NSLog( @"remove observers %@", nsWindow ); + id enterObserver = objc_getAssociatedObject( nsWindow, &enterObserverKey ); + id exitObserver = objc_getAssociatedObject( nsWindow, &exitObserverKey ); + if( enterObserver != NULL ) { + [center removeObserver:enterObserver]; + objc_setAssociatedObject( nsWindow, &enterObserverKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); + } + if( exitObserver != NULL ) { + [center removeObserver:exitObserver]; + objc_setAssociatedObject( nsWindow, &exitObserverKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); + } + } }]; JNI_COCOA_EXIT() From f3be3f2d1cafc1d6d073efcf33d671e4d941cbdc Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 12 Dec 2023 18:03:38 +0100 Subject: [PATCH 04/10] macOS large title bar: added client property `FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR` (or `"FlatLaf.macOS.largeWindowTitleBar"`) --- .../com/formdev/flatlaf/FlatClientProperties.java | 15 +++++++++++++++ .../com/formdev/flatlaf/ui/FlatRootPaneUI.java | 15 +++++++++++++++ .../java/com/formdev/flatlaf/demo/DemoFrame.java | 15 ++++++++------- .../com/formdev/flatlaf/demo/FlatLafDemo.java | 5 ----- 4 files changed, 38 insertions(+), 12 deletions(-) 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 e36b4fb4c..180d90c8f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -527,6 +527,21 @@ public interface FlatClientProperties */ String WINDOW_STYLE_SMALL = "small"; + /** + * Specifies whether the window should have a large title bar. + * This adds extra space around the close/minimize/zoom buttons. + * Useful if full window content + * is enabled. + *

+ * (requires macOS 10.14+, Java 17+ and client property {@code apple.awt.fullWindowContent} set to {@code true}) + *

+ * Component {@link javax.swing.JRootPane}
+ * Value type {@link java.lang.Boolean} + * + * @since 3.3 + */ + String MACOS_LARGE_WINDOW_TITLE_BAR = "FlatLaf.macOS.largeWindowTitleBar"; + //---- JScrollBar / JScrollPane ------------------------------------------- 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 3b6ee4981..756310812 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 @@ -367,6 +367,21 @@ public void propertyChange( PropertyChangeEvent e ) { if( rootPane.isDisplayable() ) throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." ); break; + + case FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR: + case "ancestor": + if( SystemInfo.isMacFullWindowContentSupported && + SystemInfo.isJava_17_orLater && + rootPane.isDisplayable() && + FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) && + FlatNativeMacLibrary.isLoaded() ) + { + Window window = SwingUtilities.windowForComponent( rootPane ); + boolean enabled = FlatClientProperties.clientPropertyBoolean( rootPane, + FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR, false ); + FlatNativeMacLibrary.setWindowToolbar( window, enabled ); + } + break; } } 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 191268c19..08adb0e13 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 @@ -46,7 +46,6 @@ import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.formdev.flatlaf.extras.FlatSVGUtils; -import com.formdev.flatlaf.ui.FlatNativeMacLibrary; import com.formdev.flatlaf.util.ColorFunctions; import com.formdev.flatlaf.util.FontUtils; import com.formdev.flatlaf.util.LoggingFacade; @@ -90,21 +89,23 @@ class DemoFrame // do not use HTML text in menu items because this is not supported in macOS screen menu htmlMenuItem.setText( "some text" ); + JRootPane rootPane = getRootPane(); if( SystemInfo.isMacFullWindowContentSupported ) { // expand window content into window title bar and make title bar transparent - getRootPane().putClientProperty( "apple.awt.fullWindowContent", true ); - getRootPane().putClientProperty( "apple.awt.transparentTitleBar", true ); + rootPane.putClientProperty( "apple.awt.fullWindowContent", true ); + rootPane.putClientProperty( "apple.awt.transparentTitleBar", true ); + rootPane.putClientProperty( FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR, true ); // hide window title if( SystemInfo.isJava_17_orLater ) - getRootPane().putClientProperty( "apple.awt.windowTitleVisible", false ); + rootPane.putClientProperty( "apple.awt.windowTitleVisible", false ); else setTitle( null ); } // enable full screen mode for this window (for Java 8 - 10; not necessary for Java 11+) if( !SystemInfo.isJava_11_orLater ) - getRootPane().putClientProperty( "apple.awt.fullscreenable", true ); + rootPane.putClientProperty( "apple.awt.fullscreenable", true ); } // integrate into macOS screen menu @@ -902,10 +903,10 @@ private void initComponents() { // JFormDesigner - End of component initialization //GEN-END:initComponents backButton.addActionListener( e -> { - FlatNativeMacLibrary.setWindowToolbar( this, true ); + rootPane.putClientProperty( FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR, true ); }); forwardButton.addActionListener( e -> { - FlatNativeMacLibrary.setWindowToolbar( this, false ); + rootPane.putClientProperty( FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR, null ); }); cutButton.addActionListener( e -> System.out.println( e ) ); diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java index cfc226b84..4a55f459c 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java @@ -27,7 +27,6 @@ import com.formdev.flatlaf.fonts.jetbrains_mono.FlatJetBrainsMonoFont; import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont; import com.formdev.flatlaf.fonts.roboto_mono.FlatRobotoMonoFont; -import com.formdev.flatlaf.ui.FlatNativeMacLibrary; import com.formdev.flatlaf.util.SystemInfo; /** @@ -118,10 +117,6 @@ public static void main( String[] args ) { // show frame frame.pack(); frame.setLocationRelativeTo( null ); - if( SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() ) { - // TODO use client property - FlatNativeMacLibrary.setWindowToolbar( frame, true ); - } frame.setVisible( true ); } ); } From 13528b49cbbda760763c75d5d5f5462f95c9cc5a Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 14 Dec 2023 18:51:03 +0100 Subject: [PATCH 05/10] macOS large title bar: fixed wrong "main" JToolBar height and left inset after leaving full screen --- .../flatlaf/ui/FlatNativeMacLibrary.java | 2 +- .../formdev/flatlaf/ui/FlatToolBarBorder.java | 6 +- .../natives/libflatlaf-macos-arm64.dylib | Bin 72640 -> 77024 bytes .../natives/libflatlaf-macos-x86_64.dylib | Bin 55408 -> 58784 bytes ..._formdev_flatlaf_ui_FlatNativeMacLibrary.h | 4 +- .../src/main/objcpp/MacWindow.mm | 126 ++++++++++++++---- 6 files changed, 101 insertions(+), 37 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java index 65df59cd0..059dedb09 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java @@ -54,7 +54,7 @@ public static boolean isLoaded() { public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor ); - public native static void setWindowToolbar( Window window, boolean hasToolbar ); + public native static boolean setWindowToolbar( Window window, boolean hasToolbar ); public native static int getWindowButtonAreaWidth( Window window ); public native static int getWindowTitleBarHeight( Window window ); public native static boolean isWindowFullScreen( Window window ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java index 4d66a4a12..f6c178738 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java @@ -116,11 +116,7 @@ public Insets getBorderInsets( Component c, Insets insets ) { } // on macOS, add some extra space to left side for close/minimize/zoom buttons (if necessary) - if( c instanceof JToolBar && - FlatToolBarUI.isMacOSMainToolbar( (JToolBar) c ) && - (!FlatNativeMacLibrary.isLoaded() || - !FlatNativeMacLibrary.isWindowFullScreen( SwingUtilities.windowForComponent( c ) )) ) - { + if( c instanceof JToolBar && FlatToolBarUI.isMacOSMainToolbar( (JToolBar) c ) ) { // get button area width from macOS int buttonBarWidth = FlatNativeMacLibrary.isLoaded() ? FlatNativeMacLibrary.getWindowButtonAreaWidth( SwingUtilities.windowForComponent( c ) ) diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib index e29df3b1766ed568b08d1bd2d7a94d97effcf663..61b6e9cdacf1e11192ca70b5681c3db7d51207f9 100755 GIT binary patch delta 15554 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 PYT8-vorce%pGE%#y!sFV diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib index bb1c221c9e60306dfa601e29fd6fb0f7f8c286c6..084b58cafbdf27e95ff6d6bb2e9cfb8bf7ac591d 100755 GIT binary patch delta 15274 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 PrmT|v`)XRcf9m=_6qORK delta 6925 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 diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h index 7964d7f20..f7669308d 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h @@ -18,9 +18,9 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary * Method: setWindowToolbar - * Signature: (Ljava/awt/Window;Z)V + * Signature: (Ljava/awt/Window;Z)Z */ -JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar (JNIEnv *, jclass, jobject, jboolean); /* diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm index 62c41c7ac..cc04908a0 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm @@ -25,6 +25,27 @@ * @author Karl Tauber */ +@interface WindowData : NSObject + // used when window is full screen + @property (nonatomic) int lastWindowButtonAreaWidth; + @property (nonatomic) int lastWindowTitleBarHeight; + + // full screen observers + @property (nonatomic) id willEnterFullScreenObserver; + @property (nonatomic) id didExitFullScreenObserver; +@end + +@implementation WindowData +@end + +// declare internal methods +NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ); +WindowData* getWindowData( NSWindow* nsWindow, bool allocate ); +int getWindowButtonAreaWidth( NSWindow* nsWindow ); +int getWindowTitleBarHeight( NSWindow* nsWindow ); +bool isWindowFullScreen( NSWindow* nsWindow ); + + NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) { if( window == NULL ) return NULL; @@ -50,6 +71,16 @@ return (NSWindow *) jlong_to_ptr( env->GetLongField( platformWindow, ptrID ) ); } +WindowData* getWindowData( NSWindow* nsWindow, bool allocate ) { + static char key; + WindowData* windowData = objc_getAssociatedObject( nsWindow, &key ); + if( windowData == NULL && allocate ) { + windowData = [WindowData new]; + objc_setAssociatedObject( nsWindow, &key, windowData, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); + } + return windowData; +} + extern "C" JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowRoundedBorder ( JNIEnv* env, jclass cls, jobject window, jfloat radius, jfloat borderWidth, jint borderColor ) @@ -90,20 +121,22 @@ } extern "C" -JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar ( JNIEnv* env, jclass cls, jobject window, jboolean hasToolbar ) { JNI_COCOA_ENTER() NSWindow* nsWindow = getNSWindow( env, cls, window ); if( nsWindow == NULL ) - return; + return FALSE; if( hasToolbar == (nsWindow.toolbar != NULL) ) - return; + return TRUE; + + WindowData* windowData = getWindowData( nsWindow, true ); [FlatJNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ - NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); +// NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); // add/remove toolbar NSToolbar* toolbar = NULL; @@ -113,45 +146,51 @@ } nsWindow.toolbar = toolbar; - NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); +// NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); // when window becomes full screen, it is necessary to hide the toolbar // because it otherwise is shown non-transparent and hides Swing components - static char enterObserverKey; - static char exitObserverKey; NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; if( hasToolbar ) { - NSLog( @"add observers %@", nsWindow ); - id enterObserver = [center addObserverForName:NSWindowWillEnterFullScreenNotification +// NSLog( @"add observers %@", nsWindow ); + windowData.willEnterFullScreenObserver = [center addObserverForName:NSWindowWillEnterFullScreenNotification object:nsWindow queue:nil usingBlock:^(NSNotification *note) { - NSLog( @"enter full screen %@", nsWindow ); - if( nsWindow.toolbar != NULL ) +// NSLog( @"enter full screen %@", nsWindow ); + if( nsWindow.toolbar != NULL ) { + // remember button area width, which is used later when window exits full screen + // remembar title bar height so that "main" JToolBar keeps its height in full screen + windowData.lastWindowButtonAreaWidth = getWindowButtonAreaWidth( nsWindow ); + windowData.lastWindowTitleBarHeight = getWindowTitleBarHeight( nsWindow ); + nsWindow.toolbar.visible = NO; + } }]; - id exitObserver = [center addObserverForName:NSWindowDidExitFullScreenNotification + windowData.didExitFullScreenObserver = [center addObserverForName:NSWindowDidExitFullScreenNotification object:nsWindow queue:nil usingBlock:^(NSNotification *note) { - NSLog( @"exit full screen %@", nsWindow ); +// NSLog( @"exit full screen %@", nsWindow ); if( nsWindow.toolbar != NULL ) nsWindow.toolbar.visible = YES; + + windowData.lastWindowButtonAreaWidth = 0; + windowData.lastWindowTitleBarHeight = 0; }]; - objc_setAssociatedObject( nsWindow, &enterObserverKey, enterObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); - objc_setAssociatedObject( nsWindow, &exitObserverKey, exitObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); } else { - NSLog( @"remove observers %@", nsWindow ); - id enterObserver = objc_getAssociatedObject( nsWindow, &enterObserverKey ); - id exitObserver = objc_getAssociatedObject( nsWindow, &exitObserverKey ); - if( enterObserver != NULL ) { - [center removeObserver:enterObserver]; - objc_setAssociatedObject( nsWindow, &enterObserverKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); +// NSLog( @"remove observers %@", nsWindow ); + if( windowData.willEnterFullScreenObserver != NULL ) { + [center removeObserver:windowData.willEnterFullScreenObserver]; + windowData.willEnterFullScreenObserver = nil; } - if( exitObserver != NULL ) { - [center removeObserver:exitObserver]; - objc_setAssociatedObject( nsWindow, &exitObserverKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC ); + if( windowData.didExitFullScreenObserver != NULL ) { + [center removeObserver:windowData.didExitFullScreenObserver]; + windowData.didExitFullScreenObserver = nil; } } }]; + return TRUE; + JNI_COCOA_EXIT() + return FALSE; } extern "C" @@ -164,6 +203,23 @@ if( nsWindow == NULL ) return -1; + // return zero if window is full screen because close/minimize/zoom buttons are hidden + if( isWindowFullScreen( nsWindow ) ) + return 0; + + // use remembered value if window is in transition from full screen to non-full screen + // because NSToolbar is not yet visible + WindowData* windowData = getWindowData( nsWindow, false ); + if( windowData != NULL && windowData.lastWindowButtonAreaWidth > 0 ) + return windowData.lastWindowButtonAreaWidth; + + return getWindowButtonAreaWidth( nsWindow ); + + JNI_COCOA_EXIT() + return -1; +} + +int getWindowButtonAreaWidth( NSWindow* nsWindow ) { // get buttons NSView* buttons[3] = { [nsWindow standardWindowButton:NSWindowCloseButton], @@ -193,8 +249,6 @@ // 'right' is the actual button area width (from left window edge) // adding 'left' to add same empty space on right side as on left side return right + left; - - JNI_COCOA_EXIT() } extern "C" @@ -207,14 +261,24 @@ if( nsWindow == NULL ) return -1; + // use remembered value if window is full screen because NSToolbar is hidden + WindowData* windowData = getWindowData( nsWindow, false ); + if( windowData != NULL && windowData.lastWindowTitleBarHeight > 0 ) + return windowData.lastWindowTitleBarHeight; + + return getWindowTitleBarHeight( nsWindow ); + + JNI_COCOA_EXIT() + return -1; +} + +int getWindowTitleBarHeight( NSWindow* nsWindow ) { NSView* closeButton = [nsWindow standardWindowButton:NSWindowCloseButton]; if( closeButton == NULL ) return -1; NSView* titlebar = closeButton.superview; return titlebar.bounds.size.height; - - JNI_COCOA_EXIT() } extern "C" @@ -227,8 +291,12 @@ if( nsWindow == NULL ) return FALSE; - return (jboolean) (([nsWindow styleMask] & NSWindowStyleMaskFullScreen) != 0); + return (jboolean) isWindowFullScreen( nsWindow ); JNI_COCOA_EXIT() + return FALSE; } +bool isWindowFullScreen( NSWindow* nsWindow ) { + return ((nsWindow.styleMask & NSWindowStyleMaskFullScreen) != 0); +} From a1adde088850d32a94389198829ced754187f2ba Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 15 Dec 2023 18:21:35 +0100 Subject: [PATCH 06/10] macOS window button style: support `NSWindowToolbarStyleUnified` (availaible since macOS 11+; standard in macOS Finder, etc) to allow even larger space around close/minimize/zoom buttons --- .../formdev/flatlaf/FlatClientProperties.java | 55 ++++++++++++++----- .../flatlaf/ui/FlatNativeMacLibrary.java | 7 ++- .../formdev/flatlaf/ui/FlatRootPaneUI.java | 19 +++++-- .../com/formdev/flatlaf/demo/DemoFrame.java | 16 ++++-- ..._formdev_flatlaf_ui_FlatNativeMacLibrary.h | 14 +++-- .../src/main/objcpp/MacWindow.mm | 36 ++++++++++-- 6 files changed, 113 insertions(+), 34 deletions(-) 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 180d90c8f..00838b21d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -527,21 +527,6 @@ public interface FlatClientProperties */ String WINDOW_STYLE_SMALL = "small"; - /** - * Specifies whether the window should have a large title bar. - * This adds extra space around the close/minimize/zoom buttons. - * Useful if full window content - * is enabled. - *

- * (requires macOS 10.14+, Java 17+ and client property {@code apple.awt.fullWindowContent} set to {@code true}) - *

- * Component {@link javax.swing.JRootPane}
- * Value type {@link java.lang.Boolean} - * - * @since 3.3 - */ - String MACOS_LARGE_WINDOW_TITLE_BAR = "FlatLaf.macOS.largeWindowTitleBar"; - //---- JScrollBar / JScrollPane ------------------------------------------- @@ -1278,6 +1263,46 @@ public interface FlatClientProperties String TREE_PAINT_SELECTION = "JTree.paintSelection"; + //---- macOS -------------------------------------------------------------- + + /** + * Specifies the style of macOS window close/minimize/zoom buttons. + * This does not change visual appearance but adds extra space around the buttons. + * Useful if full window content + * is enabled. + *

+ * (requires macOS 10.14+ or 11+ for style 'large', Java 17+ and client property {@code apple.awt.fullWindowContent} set to {@code true}) + *

+ * Component {@link javax.swing.JRootPane}
+ * Value type {@link java.lang.String} or {@link java.lang.Boolean}
+ * Allowed Values + * {@link #MACOS_WINDOW_TITLE_BAR_STYLE_MEDIUM}, + * {@link #MACOS_WINDOW_TITLE_BAR_STYLE_LARGE} (requires macOS 11+) or + * {@code true} (equal to 'large') + * + * @since 3.3 + */ + String MACOS_WINDOW_BUTTON_STYLE = "FlatLaf.macOS.windowButtonStyle"; + + /** + * Add medium space around the macOS window close/minimize/zoom buttons. + * + * @see #MACOS_WINDOW_BUTTON_STYLE + * @since 3.3 + */ + String MACOS_WINDOW_BUTTON_STYLE_MEDIUM = "medium"; + + /** + * Add large space around the macOS window close/minimize/zoom buttons. + *

+ * (requires macOS 11+; 'medium' is used on older systems) + * + * @see #MACOS_WINDOW_BUTTON_STYLE + * @since 3.3 + */ + String MACOS_WINDOW_BUTTON_STYLE_LARGE = "large"; + + //---- helper methods ----------------------------------------------------- /** diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java index 059dedb09..8f9acd5ff 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java @@ -54,7 +54,12 @@ public static boolean isLoaded() { public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor ); - public native static boolean setWindowToolbar( Window window, boolean hasToolbar ); + public static final int + BUTTON_STYLE_DEFAULT = 0, + BUTTON_STYLE_MEDIUM = 1, + BUTTON_STYLE_LARGE = 2; + + public native static boolean setWindowButtonStyle( Window window, int buttonStyle ); public native static int getWindowButtonAreaWidth( Window window ); public native static int getWindowTitleBarHeight( Window window ); public native static boolean isWindowFullScreen( Window window ); 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 756310812..4f7bea464 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 @@ -368,7 +368,7 @@ public void propertyChange( PropertyChangeEvent e ) { throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." ); break; - case FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR: + case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE: case "ancestor": if( SystemInfo.isMacFullWindowContentSupported && SystemInfo.isJava_17_orLater && @@ -376,10 +376,21 @@ public void propertyChange( PropertyChangeEvent e ) { FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) && FlatNativeMacLibrary.isLoaded() ) { + int buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_DEFAULT; + Object value = rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE ); + switch( String.valueOf( value ) ) { + case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_MEDIUM: + buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_MEDIUM; + break; + + case "true": + case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_LARGE: + buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_LARGE; + break; + } + Window window = SwingUtilities.windowForComponent( rootPane ); - boolean enabled = FlatClientProperties.clientPropertyBoolean( rootPane, - FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR, false ); - FlatNativeMacLibrary.setWindowToolbar( window, enabled ); + FlatNativeMacLibrary.setWindowButtonStyle( window, buttonStyle ); } break; } 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 08adb0e13..a2ee98213 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 @@ -94,7 +94,7 @@ class DemoFrame // expand window content into window title bar and make title bar transparent rootPane.putClientProperty( "apple.awt.fullWindowContent", true ); rootPane.putClientProperty( "apple.awt.transparentTitleBar", true ); - rootPane.putClientProperty( FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR, true ); + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE, true ); // hide window title if( SystemInfo.isJava_17_orLater ) @@ -902,15 +902,19 @@ private void initComponents() { buttonGroup1.add(radioButtonMenuItem3); // JFormDesigner - End of component initialization //GEN-END:initComponents + //TODO remove backButton.addActionListener( e -> { - rootPane.putClientProperty( FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR, true ); + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE, FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_LARGE ); }); forwardButton.addActionListener( e -> { - rootPane.putClientProperty( FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR, null ); + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE, FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_MEDIUM ); + }); + cutButton.addActionListener( e -> { + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE, null ); }); - cutButton.addActionListener( e -> System.out.println( e ) ); - cutButton.addMouseListener( new MouseListener() { + copyButton.addActionListener( e -> System.out.println( e ) ); + copyButton.addMouseListener( new MouseListener() { @Override public void mouseReleased( MouseEvent e ) { @@ -942,7 +946,7 @@ public void mouseClicked( MouseEvent e ) { System.out.println( "m click" ); } } ); - cutButton.addMouseMotionListener( new MouseMotionListener() { + copyButton.addMouseMotionListener( new MouseMotionListener() { @Override public void mouseMoved( MouseEvent e ) { diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h index f7669308d..bb7611916 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h @@ -7,6 +7,12 @@ #ifdef __cplusplus extern "C" { #endif +#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_DEFAULT +#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_DEFAULT 0L +#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_MEDIUM +#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_MEDIUM 1L +#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_LARGE +#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_LARGE 2L /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary * Method: setWindowRoundedBorder @@ -17,11 +23,11 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary - * Method: setWindowToolbar - * Signature: (Ljava/awt/Window;Z)Z + * Method: setWindowButtonStyle + * Signature: (Ljava/awt/Window;I)Z */ -JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar - (JNIEnv *, jclass, jobject, jboolean); +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowButtonStyle + (JNIEnv *, jclass, jobject, jint); /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm index cc04908a0..730f64abf 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm @@ -50,7 +50,7 @@ @implementation WindowData if( window == NULL ) return NULL; - // initialize field IDs (done only once because fields are static) + // initialize field IDs (done only once because variables are static) static jfieldID peerID = getFieldID( env, "java/awt/Component", "peer", "Ljava/awt/peer/ComponentPeer;" ); static jfieldID platformWindowID = getFieldID( env, "sun/lwawt/LWWindowPeer", "platformWindow", "Lsun/lwawt/PlatformWindow;" ); static jfieldID ptrID = getFieldID( env, "sun/lwawt/macosx/CFRetainedResource", "ptr", "J" ); @@ -121,8 +121,8 @@ @implementation WindowData } extern "C" -JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowToolbar - ( JNIEnv* env, jclass cls, jobject window, jboolean hasToolbar ) +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowButtonStyle + ( JNIEnv* env, jclass cls, jobject window, jint buttonStyle ) { JNI_COCOA_ENTER() @@ -130,7 +130,20 @@ @implementation WindowData if( nsWindow == NULL ) return FALSE; - if( hasToolbar == (nsWindow.toolbar != NULL) ) + #define STYLE_DEFAULT com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_DEFAULT + #define STYLE_MEDIUM com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_MEDIUM + #define STYLE_LARGE com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_LARGE + + bool isMacOS_11_orLater = @available( macOS 11, * ); + if( !isMacOS_11_orLater && buttonStyle == STYLE_LARGE ) + buttonStyle = STYLE_MEDIUM; + int oldButtonStyle = (nsWindow.toolbar != NULL) + ? ((isMacOS_11_orLater && nsWindow.toolbarStyle == NSWindowToolbarStyleUnified) + ? STYLE_LARGE + : STYLE_MEDIUM) + : STYLE_DEFAULT; + + if( buttonStyle == oldButtonStyle ) return TRUE; WindowData* windowData = getWindowData( nsWindow, true ); @@ -140,12 +153,26 @@ @implementation WindowData // add/remove toolbar NSToolbar* toolbar = NULL; + bool hasToolbar = (buttonStyle != STYLE_DEFAULT); if( hasToolbar ) { toolbar = [NSToolbar new]; toolbar.showsBaselineSeparator = NO; // necessary for older macOS versions + if( isWindowFullScreen( nsWindow ) ) + toolbar.visible = NO; } nsWindow.toolbar = toolbar; + if( isMacOS_11_orLater ) { + nsWindow.toolbarStyle = (buttonStyle == STYLE_LARGE) + ? NSWindowToolbarStyleUnified + : (buttonStyle == STYLE_MEDIUM) + ? NSWindowToolbarStyleUnifiedCompact + : NSWindowToolbarStyleAutomatic; + } + + windowData.lastWindowButtonAreaWidth = 0; + windowData.lastWindowTitleBarHeight = 0; + // NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); // when window becomes full screen, it is necessary to hide the toolbar @@ -161,6 +188,7 @@ @implementation WindowData // remembar title bar height so that "main" JToolBar keeps its height in full screen windowData.lastWindowButtonAreaWidth = getWindowButtonAreaWidth( nsWindow ); windowData.lastWindowTitleBarHeight = getWindowTitleBarHeight( nsWindow ); +// NSLog(@"%d %d",windowData.lastWindowButtonAreaWidth,windowData.lastWindowTitleBarHeight); nsWindow.toolbar.visible = NO; } From 93d424cfe148145a904ad0419dcbb3a21f2d9d50 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 16 Dec 2023 11:58:27 +0100 Subject: [PATCH 07/10] macOS native: added `FlatNativeMacLibrary.windowToggleFullScreen()` for easier testing --- .../flatlaf/ui/FlatNativeMacLibrary.java | 1 + .../natives/libflatlaf-macos-arm64.dylib | Bin 77024 -> 77504 bytes .../natives/libflatlaf-macos-x86_64.dylib | Bin 58784 -> 60592 bytes .../com/formdev/flatlaf/demo/DemoFrame.java | 6 ++++++ ..._formdev_flatlaf_ui_FlatNativeMacLibrary.h | 8 +++++++ .../src/main/objcpp/MacWindow.mm | 20 ++++++++++++++++++ 6 files changed, 35 insertions(+) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java index 8f9acd5ff..dad23882c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java @@ -63,4 +63,5 @@ public static boolean isLoaded() { public native static int getWindowButtonAreaWidth( Window window ); public native static int getWindowTitleBarHeight( Window window ); public native static boolean isWindowFullScreen( Window window ); + public native static boolean windowToggleFullScreen( Window window ); } diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib index 61b6e9cdacf1e11192ca70b5681c3db7d51207f9..34d85495b5248b70b6dfc7d5b167d274eeed2994 100755 GIT binary patch delta 11022 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 ZUQ}jxWl6#GQI*!6D}RCi&Gd`;{{U~5h8X|= diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib index 084b58cafbdf27e95ff6d6bb2e9cfb8bf7ac591d..25d9db4f90c9af7819c09c7bd8e46c473cac4324 100755 GIT binary patch delta 16282 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 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 a2ee98213..c48dc887e 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 @@ -46,6 +46,7 @@ import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.formdev.flatlaf.extras.FlatSVGUtils; +import com.formdev.flatlaf.ui.FlatNativeMacLibrary; import com.formdev.flatlaf.util.ColorFunctions; import com.formdev.flatlaf.util.FontUtils; import com.formdev.flatlaf.util.LoggingFacade; @@ -960,6 +961,11 @@ public void mouseDragged( MouseEvent e ) { System.out.println( "m drag" ); } } ); + if( SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() ) { + showToggleButton.addActionListener( e -> { + FlatNativeMacLibrary.windowToggleFullScreen( this ); + } ); + } // add "Users" button to menubar FlatButton usersButton = new FlatButton(); diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h index bb7611916..f962f645d 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h @@ -53,6 +53,14 @@ JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindo JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWindowFullScreen (JNIEnv *, jclass, jobject); +/* + * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary + * Method: windowToggleFullScreen + * Signature: (Ljava/awt/Window;)Z + */ +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_windowToggleFullScreen + (JNIEnv *, jclass, jobject); + #ifdef __cplusplus } #endif diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm index 730f64abf..5fc8a285c 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm @@ -328,3 +328,23 @@ int getWindowTitleBarHeight( NSWindow* nsWindow ) { bool isWindowFullScreen( NSWindow* nsWindow ) { return ((nsWindow.styleMask & NSWindowStyleMaskFullScreen) != 0); } + +extern "C" +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_windowToggleFullScreen + ( JNIEnv* env, jclass cls, jobject window ) +{ + JNI_COCOA_ENTER() + + NSWindow* nsWindow = getNSWindow( env, cls, window ); + if( nsWindow == NULL ) + return FALSE; + + [FlatJNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ + [nsWindow toggleFullScreen:nil]; + }]; + + return TRUE; + + JNI_COCOA_EXIT() + return FALSE; +} From f68a871dd6ddd6dce1614eb4f9772a295b6462db Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 16 Dec 2023 12:46:02 +0100 Subject: [PATCH 08/10] macOS window button style: fixed javadoc --- .../main/java/com/formdev/flatlaf/FlatClientProperties.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 00838b21d..afd33b8e2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -1276,8 +1276,8 @@ public interface FlatClientProperties * Component {@link javax.swing.JRootPane}
* Value type {@link java.lang.String} or {@link java.lang.Boolean}
* Allowed Values - * {@link #MACOS_WINDOW_TITLE_BAR_STYLE_MEDIUM}, - * {@link #MACOS_WINDOW_TITLE_BAR_STYLE_LARGE} (requires macOS 11+) or + * {@link #MACOS_WINDOW_BUTTON_STYLE_MEDIUM}, + * {@link #MACOS_WINDOW_BUTTON_STYLE_LARGE} (requires macOS 11+) or * {@code true} (equal to 'large') * * @since 3.3 From 28278a75a7e7c0b53141702dbdb43a2cf767ae23 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 20 Jan 2024 19:54:26 +0100 Subject: [PATCH 09/10] macOS fullWindowContent mode: - added title bar buttons placeholder - added client property to root pane that contains title bar buttons bounds - undone toolbar extensions from commit ea2447dcb795f2e0fb985092bac1f16a63c234d0 --- .../formdev/flatlaf/FlatClientProperties.java | 84 ++++++++ .../flatlaf/ui/FlatNativeMacLibrary.java | 11 +- .../com/formdev/flatlaf/ui/FlatPanelUI.java | 40 ++++ .../formdev/flatlaf/ui/FlatRootPaneUI.java | 49 +++-- .../formdev/flatlaf/ui/FlatToolBarBorder.java | 13 -- .../com/formdev/flatlaf/ui/FlatToolBarUI.java | 149 -------------- .../flatlaf/ui/FullWindowContentSupport.java | 183 ++++++++++++++++++ .../com/formdev/flatlaf/demo/DemoFrame.java | 106 ++++++---- .../com/formdev/flatlaf/demo/DemoFrame.jfd | 110 ++++++----- .../src/main/headers/JNIUtils.h | 4 +- ..._formdev_flatlaf_ui_FlatNativeMacLibrary.h | 18 +- .../src/main/objcpp/JNIUtils.mm | 40 +++- .../src/main/objcpp/MacWindow.mm | 74 ++++--- 13 files changed, 550 insertions(+), 331 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FullWindowContentSupport.java 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 afd33b8e2..ebc393cdb 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -270,6 +270,76 @@ public interface FlatClientProperties String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"; + //---- Panel -------------------------------------------------------------- + + /** + * Marks the panel as placeholder for the iconfify/maximize/close buttons + * in fullWindowContent mode. + *

+ * 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}. + *

+ * You're responsible to layout that panel at the top-left or top-right corner, + * depending on platform, where the iconfify/maximize/close buttons are located. + *

+ * Syntax of the value string is: {@code "win|mac [horizontal|vertical]"}. + *

+ * The string must start with {@code "win"} (for Windows or Linux) or + * with {@code "mac"} (for macOS) and specifies the platform where the placeholder + * should be used. On macOS, you need the placeholder in the top-left corner, + * but on Windows/Linux you need it in the top-right corner. So if fullWindowContent mode + * is supported on both platforms, you can add two placeholders to your layout + * and FlatLaf automatically uses only one of them. The other gets size {@code 0,0}. + *

+ * Optionally, you can append {@code " horizontal"} or {@code " vertical"} to the value string + * to specify that the placeholder preferred size should be limited to one orientation. + * E.g. {@code "win horizontal"} means that the placeholder preferred width is + * equal to iconfify/maximize/close buttons width, but preferred height is zero. + *

+ * Example for adding placeholder to top-left corner on macOS: + *

{@code
+	 * JPanel placeholder = new JPanel();
+	 * placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
+     *
+     * JToolBar toolBar = new JToolBar();
+     * // add tool bar items
+     *
+	 * JPanel toolBarPanel = new JPanel( new BorderLayout() );
+	 * toolBarPanel.add( placeholder, BorderLayout.WEST );
+	 * toolBarPanel.add( toolBar, BorderLayout.CENTER );
+	 *
+	 * frame.getContentPane().add( toolBarPanel, BorderLayout.NORTH );
+	 * }
+ * + * Or add placeholder as first item to the tool bar: + *
{@code
+	 * JPanel placeholder = new JPanel();
+	 * placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
+	 *
+	 * JToolBar toolBar = new JToolBar();
+	 * toolBar.add( placeholder );
+	 * // add tool bar items
+	 *
+	 * frame.getContentPane().add( toolBar, BorderLayout.NORTH );
+	 * }
+ * + * If a tabbed pane is located at the top, you can add the placeholder + * as leading component to that tabbed pane: + *
{@code
+	 * JPanel placeholder = new JPanel();
+	 * placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
+	 *
+	 * tabbedPane.putClientProperty( FlatClientProperties.TABBED_PANE_LEADING_COMPONENT, placeholder );
+	 * }
+ *

+ * Component {@link javax.swing.JPanel}
+ * Value type {@link java.lang.String} + * + * @since 3.4 + */ + String FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER = "FlatLaf.fullWindowContent.buttonsPlaceholder"; + + //---- Popup -------------------------------------------------------------- /** @@ -388,6 +458,20 @@ public interface FlatClientProperties */ String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"; + /** + * Contains the current bounds of the iconfify/maximize/close buttons + * (in root pane coordinates) if fullWindowContent mode is enabled. + * Otherwise its value is {@code null}. + *

+ * Note: Do not set this client property. It is set by FlatLaf. + *

+ * Component {@link javax.swing.JRootPane}
+ * Value type {@link java.awt.Rectangle} + * + * @since 3.4 + */ + String FULL_WINDOW_CONTENT_BUTTONS_BOUNDS = "FlatLaf.fullWindowContent.buttonsBounds"; + /** * Specifies whether the window icon should be shown in the window title bar * (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}. diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java index dad23882c..6a7858b24 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.ui; +import java.awt.Rectangle; import java.awt.Window; /** @@ -54,14 +55,14 @@ public static boolean isLoaded() { public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor ); + /** @since 3.4 */ public static final int BUTTON_STYLE_DEFAULT = 0, BUTTON_STYLE_MEDIUM = 1, BUTTON_STYLE_LARGE = 2; - public native static boolean setWindowButtonStyle( Window window, int buttonStyle ); - public native static int getWindowButtonAreaWidth( Window window ); - public native static int getWindowTitleBarHeight( Window window ); - public native static boolean isWindowFullScreen( Window window ); - public native static boolean windowToggleFullScreen( Window window ); + /** @since 3.4 */ public native static boolean setWindowButtonStyle( Window window, int buttonStyle ); + /** @since 3.4 */ public native static Rectangle getWindowButtonsBounds( Window window ); + /** @since 3.4 */ public native static boolean isWindowFullScreen( Window window ); + /** @since 3.4 */ public native static boolean toggleWindowFullScreen( Window window ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java index c9db1fa28..f023faecb 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.ui; +import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.beans.PropertyChangeEvent; @@ -23,6 +24,7 @@ import java.util.Map; import javax.swing.JComponent; import javax.swing.JPanel; +import javax.swing.LookAndFeel; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicPanelUI; import com.formdev.flatlaf.FlatClientProperties; @@ -69,6 +71,8 @@ public void installUI( JComponent c ) { super.installUI( c ); c.addPropertyChangeListener( this ); + if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null ) + FullWindowContentSupport.registerPlaceholder( c ); installStyle( (JPanel) c ); } @@ -78,10 +82,20 @@ public void uninstallUI( JComponent c ) { super.uninstallUI( c ); c.removePropertyChangeListener( this ); + if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null ) + FullWindowContentSupport.unregisterPlaceholder( c ); oldStyleValues = null; } + @Override + protected void installDefaults( JPanel p ) { + super.installDefaults( p ); + + if( p.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null ) + LookAndFeel.installProperty( p, "opaque", false ); + } + /** @since 2.0.1 */ @Override public void propertyChange( PropertyChangeEvent e ) { @@ -98,6 +112,17 @@ public void propertyChange( PropertyChangeEvent e ) { c.revalidate(); c.repaint(); break; + + case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER: + JPanel p = (JPanel) e.getSource(); + if( e.getOldValue() != null ) + FullWindowContentSupport.unregisterPlaceholder( p ); + if( e.getNewValue() != null ) + FullWindowContentSupport.registerPlaceholder( p ); + + // make panel non-opaque for placeholders + LookAndFeel.installProperty( p, "opaque", e.getNewValue() == null ); + break; } } @@ -162,4 +187,19 @@ public void update( Graphics g, JComponent c ) { paint( g, c ); } + + @Override + public Dimension getPreferredSize( JComponent c ) { + Object value = c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ); + if( value != null ) + return FullWindowContentSupport.getPlaceholderPreferredSize( c, (String) value ); + + return super.getPreferredSize( c ); + } + + @Override + public void paint( Graphics g, JComponent c ) { + if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null ) + FullWindowContentSupport.debugPaint( g, c ); + } } 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 4f7bea464..c4bc00016 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 @@ -89,6 +89,7 @@ public class FlatRootPaneUI private LayoutManager oldLayout; private PropertyChangeListener ancestorListener; private ComponentListener componentListener; + private ComponentListener macFullWindowContentListener; public static ComponentUI createUI( JComponent c ) { return new FlatRootPaneUI(); @@ -207,6 +208,9 @@ public void componentShown( ComponentEvent e ) { }; root.addPropertyChangeListener( "ancestor", ancestorListener ); } + + if( SystemInfo.isMacFullWindowContentSupported ) + macFullWindowContentListener = FullWindowContentSupport.macInstallListeners( root ); } @Override @@ -223,6 +227,11 @@ protected void uninstallListeners( JRootPane root ) { root.removePropertyChangeListener( "ancestor", ancestorListener ); ancestorListener = null; } + + if( SystemInfo.isMacFullWindowContentSupported ) { + FullWindowContentSupport.macUninstallListeners( root, macFullWindowContentListener ); + macFullWindowContentListener = null; + } } /** @since 1.1.2 */ @@ -359,6 +368,10 @@ public void propertyChange( PropertyChangeEvent e ) { titlePane.titleBarColorsChanged(); break; + case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS: + FullWindowContentSupport.revalidatePlaceholders( rootPane ); + break; + case FlatClientProperties.GLASS_PANE_FULL_HEIGHT: rootPane.revalidate(); break; @@ -371,26 +384,30 @@ public void propertyChange( PropertyChangeEvent e ) { case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE: case "ancestor": if( SystemInfo.isMacFullWindowContentSupported && - SystemInfo.isJava_17_orLater && rootPane.isDisplayable() && - FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) && - FlatNativeMacLibrary.isLoaded() ) + FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) { - int buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_DEFAULT; - Object value = rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE ); - switch( String.valueOf( value ) ) { - case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_MEDIUM: - buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_MEDIUM; - break; - - case "true": - case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_LARGE: - buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_LARGE; - break; + // set window button style + if( SystemInfo.isJava_17_orLater && FlatNativeMacLibrary.isLoaded() ) { + int buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_DEFAULT; + Object value = rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE ); + switch( String.valueOf( value ) ) { + case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_MEDIUM: + buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_MEDIUM; + break; + + case "true": + case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_LARGE: + buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_LARGE; + break; + } + + Window window = SwingUtilities.windowForComponent( rootPane ); + FlatNativeMacLibrary.setWindowButtonStyle( window, buttonStyle ); } - Window window = SwingUtilities.windowForComponent( rootPane ); - FlatNativeMacLibrary.setWindowButtonStyle( window, buttonStyle ); + // update buttons bounds client property + FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane ); } break; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java index f6c178738..68b9325db 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java @@ -25,7 +25,6 @@ import java.util.function.Function; import javax.swing.JToolBar; import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.ToolBarUI; import com.formdev.flatlaf.util.UIScale; @@ -115,18 +114,6 @@ public Insets getBorderInsets( Component c, Insets insets ) { insets.top += gripInset; } - // on macOS, add some extra space to left side for close/minimize/zoom buttons (if necessary) - if( c instanceof JToolBar && FlatToolBarUI.isMacOSMainToolbar( (JToolBar) c ) ) { - // get button area width from macOS - int buttonBarWidth = FlatNativeMacLibrary.isLoaded() - ? FlatNativeMacLibrary.getWindowButtonAreaWidth( SwingUtilities.windowForComponent( c ) ) - : -1; - if( buttonBarWidth < 0 ) - buttonBarWidth = 68; // default width if NSWindow does not have a toolbar - - insets.left += buttonBarWidth; - } - return insets; } 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 c90926b69..c6d97f5d9 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 @@ -19,45 +19,35 @@ import java.awt.Color; import java.awt.Component; import java.awt.Container; -import java.awt.Dimension; import java.awt.FocusTraversalPolicy; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; -import java.awt.LayoutManager; -import java.awt.LayoutManager2; import java.awt.Rectangle; -import java.awt.Window; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; -import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Enumeration; import java.util.Map; import javax.swing.AbstractButton; -import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.ButtonModel; import javax.swing.DefaultButtonModel; import javax.swing.InputMap; import javax.swing.JComboBox; import javax.swing.JComponent; -import javax.swing.JRootPane; import javax.swing.JToolBar; import javax.swing.LayoutFocusTraversalPolicy; import javax.swing.RootPaneContainer; -import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; -import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicToolBarUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; -import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; /** @@ -158,12 +148,6 @@ protected void installDefaults() { toolBar.setFloatable( false ); } else oldFloatable = null; - - // layout manager - LayoutManager layout = createLayout(); - toolBar.setLayout( layout ); - if( layout instanceof PropertyChangeListener ) - toolBar.addPropertyChangeListener( (PropertyChangeListener) layout ); } @Override @@ -176,8 +160,6 @@ protected void uninstallDefaults() { toolBar.setFloatable( oldFloatable ); oldFloatable = null; } - - toolBar.setLayout( null ); } @Override @@ -471,137 +453,6 @@ private ButtonGroup getButtonGroup( AbstractButton b ) { : null; } - /** @since 3.3 */ - protected LayoutManager createLayout() { - return new FlatToolBarLayoutManager(); - } - - /** - * Returns whether the given toolbar is used in window titlebar on macOS. - *

- * Returns {@code true} if: - *

    - *
  • running on macOS - *
  • Java supports "full window content" - *
  • "full window content" is enabled for window - *
  • toolbar orientation is horizontal - *
  • toolbar is located at {@code 0,0} in window - *
- * - * @since 3.3 - */ - public static boolean isMacOSMainToolbar( JToolBar toolBar ) { - if( !SystemInfo.isMacFullWindowContentSupported || - toolBar.getOrientation() != JToolBar.HORIZONTAL || - toolBar.getX() != 0 || - toolBar.getY() != 0 ) - return false; - - JRootPane rootPane = SwingUtilities.getRootPane( toolBar ); - if( rootPane == null ) - return false; - - if( !FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) - return false; - - for( Component p = toolBar.getParent(); p != null && !(p instanceof Window); p = p.getParent() ) { - if( p.getX() != 0 || p.getY() != 0 ) - return false; - } - - return true; - } - - //---- class FlatToolBarLayoutManager ------------------------------------- - - /** - * @since 3.3 - */ - protected class FlatToolBarLayoutManager - implements LayoutManager2, PropertyChangeListener, UIResource - { - private BoxLayout delegate; - - FlatToolBarLayoutManager() { - initBoxLayout(); - } - - private void initBoxLayout() { - delegate = new BoxLayout( toolBar, (toolBar.getOrientation() == JToolBar.HORIZONTAL) - ? BoxLayout.LINE_AXIS : BoxLayout.PAGE_AXIS ); - } - - @Override - public void addLayoutComponent( Component comp, Object constraints ) { - delegate.addLayoutComponent( comp, constraints ); - } - - @Override - public void addLayoutComponent( String name, Component comp ) { - delegate.addLayoutComponent( name, comp ); - } - - @Override - public void removeLayoutComponent( Component comp ) { - delegate.removeLayoutComponent( comp ); - } - - @Override - public Dimension preferredLayoutSize( Container parent ) { - return minimumHeightOnMacOS( delegate.preferredLayoutSize( parent ) ); - } - - @Override - public Dimension minimumLayoutSize( Container parent ) { - return minimumHeightOnMacOS( delegate.minimumLayoutSize( parent ) ); - } - - @Override - public Dimension maximumLayoutSize( Container target ) { - return minimumHeightOnMacOS( delegate.maximumLayoutSize( target ) ); - } - - private Dimension minimumHeightOnMacOS( Dimension size ) { - if( isMacOSMainToolbar( toolBar ) ) { - // get title bar height from macOS - int titleBarHeight = FlatNativeMacLibrary.isLoaded() - ? FlatNativeMacLibrary.getWindowTitleBarHeight( SwingUtilities.windowForComponent( toolBar ) ) - : -1; - if( titleBarHeight < 0 ) - titleBarHeight = 28; // default height if NSWindow does not have a toolbar - - size.height = Math.max( size.height, titleBarHeight ); - } - return size; - } - - @Override - public void layoutContainer( Container parent ) { - delegate.layoutContainer( parent ); - } - - @Override - public void invalidateLayout( Container target ) { - delegate.invalidateLayout( target ); - } - - @Override - public float getLayoutAlignmentX( Container target ) { - return delegate.getLayoutAlignmentX( target ); - } - - @Override - public float getLayoutAlignmentY( Container target ) { - return delegate.getLayoutAlignmentY( target ); - } - - @Override - public void propertyChange( PropertyChangeEvent e ) { - if( "orientation".equals( e.getPropertyName() ) ) - initBoxLayout(); - } - } - //---- class FlatToolBarFocusTraversalPolicy ------------------------------ /** 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 new file mode 100644 index 000000000..65bfe8b93 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FullWindowContentSupport.java @@ -0,0 +1,183 @@ +/* + * Copyright 2024 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.ui; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import javax.swing.JComponent; +import javax.swing.JRootPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.util.SystemInfo; + +/** + * @author Karl Tauber + */ +class FullWindowContentSupport +{ + private static final String KEY_DEBUG_SHOW_PLACEHOLDERS = "FlatLaf.debug.panel.showPlaceholders"; + + private static ArrayList> placeholders = new ArrayList<>(); + + static Dimension getPlaceholderPreferredSize( JComponent c, String options ) { + JRootPane rootPane; + Rectangle bounds; + + if( options.startsWith( SystemInfo.isMacOS ? "mac" : "win" ) && + c.isDisplayable() && + (rootPane = SwingUtilities.getRootPane( c )) != null && + (bounds = (Rectangle) rootPane.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS )) != null ) + { + // On macOS, the client property is updated very late when toggling full screen, + // which results in "jumping" layout after full screen toggle finished. + // To avoid that, get up-to-date buttons bounds from macOS. + if( SystemInfo.isMacFullWindowContentSupported && FlatNativeMacLibrary.isLoaded() ) { + Rectangle r = FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( c ) ); + if( r != null ) + bounds = r; + } + + if( options.length() > 3 ) { + if( options.contains( "horizontal" ) ) + return new Dimension( bounds.width, 0 ); + if( options.contains( "vertical" ) ) + return new Dimension( 0, bounds.height ); + } + + return bounds.getSize(); + } + + // default to 0,0 + return new Dimension(); + } + + static void registerPlaceholder( JComponent c ) { + synchronized( placeholders ) { + if( indexOfPlaceholder( c ) < 0 ) + placeholders.add( new WeakReference<>( c ) ); + } + } + + static void unregisterPlaceholder( JComponent c ) { + synchronized( placeholders ) { + int index = indexOfPlaceholder( c ); + if( index >= 0 ) + placeholders.remove( index ); + } + } + + private static int indexOfPlaceholder( JComponent c ) { + int size = placeholders.size(); + for( int i = 0; i < size; i++ ) { + if( placeholders.get( i ).get() == c ) + return i; + } + return -1; + } + + static void revalidatePlaceholders( Component container ) { + synchronized( placeholders ) { + if( placeholders.isEmpty() ) + return; + + for( Iterator> it = placeholders.iterator(); it.hasNext(); ) { + WeakReference ref = it.next(); + JComponent c = ref.get(); + + // remove already released placeholder + if( c == null ) { + it.remove(); + continue; + } + + // revalidate placeholder if is in given container + if( SwingUtilities.isDescendingFrom( c, container ) ) + c.revalidate(); + } + } + } + + static ComponentListener macInstallListeners( JRootPane rootPane ) { + ComponentListener l = new ComponentAdapter() { + boolean lastFullScreen; + + @Override + public void componentResized( ComponentEvent e ) { + Window window = SwingUtilities.windowForComponent( rootPane ); + if( window == null ) + return; + + boolean fullScreen = FlatNativeMacLibrary.isLoaded() && FlatNativeMacLibrary.isWindowFullScreen( window ); + if( fullScreen == lastFullScreen ) + return; + + lastFullScreen = fullScreen; + macUpdateFullWindowContentButtonsBoundsProperty( rootPane ); + } + }; + + rootPane.addComponentListener( l ); + return l; + } + + static void macUninstallListeners( JRootPane rootPane, ComponentListener l ) { + if( l != null ) + rootPane.removeComponentListener( l ); + } + + static void macUpdateFullWindowContentButtonsBoundsProperty( JRootPane rootPane ) { + if( !SystemInfo.isMacFullWindowContentSupported || + !rootPane.isDisplayable() || + !FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) + return; + + Rectangle bounds = FlatNativeMacLibrary.isLoaded() + ? FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( rootPane ) ) + : new Rectangle( 68, 28 ); // default size + rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds ); + } + + static void debugPaint( Graphics g, JComponent c ) { + if( !UIManager.getBoolean( KEY_DEBUG_SHOW_PLACEHOLDERS ) ) + return; + + int width = c.getWidth() - 1; + int height = c.getHeight() - 1; + if( width <= 0 || height <= 0 ) + return; + + g.setColor( Color.red ); + g.drawRect( 0, 0, width, height ); + + // draw diagonal cross + Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g ); + g.drawLine( 0, 0, width, height ); + g.drawLine( 0, height, width, 0 ); + FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); + } +} 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 c48dc887e..c982a2de7 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 @@ -102,6 +102,9 @@ 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+) @@ -509,6 +512,8 @@ private void initComponents() { JMenuItem showUIDefaultsInspectorMenuItem = new JMenuItem(); JMenu helpMenu = new JMenu(); aboutMenuItem = new JMenuItem(); + JPanel toolBarPanel = new JPanel(); + JPanel macFullWindowContentButtonsPlaceholder = new JPanel(); toolBar = new JToolBar(); JButton backButton = new JButton(); JButton forwardButton = new JButton(); @@ -825,50 +830,62 @@ private void initComponents() { } setJMenuBar(menuBar1); - //======== toolBar ======== + //======== toolBarPanel ======== { - toolBar.setMargin(new Insets(3, 3, 3, 3)); - - //---- backButton ---- - backButton.setToolTipText("Back"); - backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg")); - toolBar.add(backButton); - - //---- forwardButton ---- - forwardButton.setToolTipText("Forward"); - forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg")); - toolBar.add(forwardButton); - toolBar.addSeparator(); - - //---- cutButton ---- - cutButton.setToolTipText("Cut"); - cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg")); - toolBar.add(cutButton); - - //---- copyButton ---- - copyButton.setToolTipText("Copy"); - copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg")); - toolBar.add(copyButton); - - //---- pasteButton ---- - pasteButton.setToolTipText("Paste"); - pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg")); - toolBar.add(pasteButton); - toolBar.addSeparator(); - - //---- refreshButton ---- - refreshButton.setToolTipText("Refresh"); - refreshButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/refresh.svg")); - toolBar.add(refreshButton); - toolBar.addSeparator(); - - //---- showToggleButton ---- - showToggleButton.setSelected(true); - showToggleButton.setToolTipText("Show Details"); - showToggleButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg")); - toolBar.add(showToggleButton); + toolBarPanel.setLayout(new BorderLayout()); + + //======== macFullWindowContentButtonsPlaceholder ======== + { + macFullWindowContentButtonsPlaceholder.setLayout(new FlowLayout()); + } + toolBarPanel.add(macFullWindowContentButtonsPlaceholder, BorderLayout.WEST); + + //======== toolBar ======== + { + toolBar.setMargin(new Insets(3, 3, 3, 3)); + + //---- backButton ---- + backButton.setToolTipText("Back"); + backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg")); + toolBar.add(backButton); + + //---- forwardButton ---- + forwardButton.setToolTipText("Forward"); + forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg")); + toolBar.add(forwardButton); + toolBar.addSeparator(); + + //---- cutButton ---- + cutButton.setToolTipText("Cut"); + cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg")); + toolBar.add(cutButton); + + //---- copyButton ---- + copyButton.setToolTipText("Copy"); + copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg")); + toolBar.add(copyButton); + + //---- pasteButton ---- + pasteButton.setToolTipText("Paste"); + pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg")); + toolBar.add(pasteButton); + toolBar.addSeparator(); + + //---- refreshButton ---- + refreshButton.setToolTipText("Refresh"); + refreshButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/refresh.svg")); + toolBar.add(refreshButton); + toolBar.addSeparator(); + + //---- showToggleButton ---- + showToggleButton.setSelected(true); + showToggleButton.setToolTipText("Show Details"); + showToggleButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg")); + toolBar.add(showToggleButton); + } + toolBarPanel.add(toolBar, BorderLayout.CENTER); } - contentPane.add(toolBar, BorderLayout.NORTH); + contentPane.add(toolBarPanel, BorderLayout.NORTH); //======== contentPanel ======== { @@ -963,7 +980,7 @@ public void mouseDragged( MouseEvent e ) { } ); if( SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() ) { showToggleButton.addActionListener( e -> { - FlatNativeMacLibrary.windowToggleFullScreen( this ); + FlatNativeMacLibrary.toggleWindowFullScreen( this ); } ); } @@ -1007,6 +1024,9 @@ public void mouseDragged( MouseEvent e ) { 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" ); + // remove contentPanel bottom insets MigLayout layout = (MigLayout) contentPanel.getLayout(); LC lc = ConstraintParser.parseLayoutConstraint( (String) layout.getLayoutConstraints() ); 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 ba3f2d4fb..f37f92ca4 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 @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "8.1.0.0.283" Java: "19.0.2" encoding: "UTF-8" +JFDML JFormDesigner: "8.2.1.0.348" Java: "21.0.1" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -12,56 +12,66 @@ new FormModel { "defaultCloseOperation": 2 "$locationPolicy": 2 "$sizePolicy": 2 - add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) { - name: "toolBar" - "margin": new java.awt.Insets( 3, 3, 3, 3 ) - auxiliary() { - "JavaCodeGenerator.variableLocal": false - } - add( new FormComponent( "javax.swing.JButton" ) { - name: "backButton" - "toolTipText": "Back" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/back.svg" ) - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "forwardButton" - "toolTipText": "Forward" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/forward.svg" ) - } ) - add( new FormComponent( "javax.swing.JToolBar$Separator" ) { - name: "separator5" - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "cutButton" - "toolTipText": "Cut" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-cut.svg" ) - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "copyButton" - "toolTipText": "Copy" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/copy.svg" ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) { + name: "toolBarPanel" + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) { + name: "macFullWindowContentButtonsPlaceholder" + }, new FormLayoutConstraints( class java.lang.String ) { + "value": "West" } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "pasteButton" - "toolTipText": "Paste" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-paste.svg" ) - } ) - add( new FormComponent( "javax.swing.JToolBar$Separator" ) { - name: "separator6" - } ) - add( new FormComponent( "javax.swing.JButton" ) { - name: "refreshButton" - "toolTipText": "Refresh" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/refresh.svg" ) - } ) - add( new FormComponent( "javax.swing.JToolBar$Separator" ) { - name: "separator7" - } ) - add( new FormComponent( "javax.swing.JToggleButton" ) { - name: "showToggleButton" - "selected": true - "toolTipText": "Show Details" - "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/show.svg" ) + add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) { + name: "toolBar" + "margin": new java.awt.Insets( 3, 3, 3, 3 ) + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + add( new FormComponent( "javax.swing.JButton" ) { + name: "backButton" + "toolTipText": "Back" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/back.svg" ) + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "forwardButton" + "toolTipText": "Forward" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/forward.svg" ) + } ) + add( new FormComponent( "javax.swing.JToolBar$Separator" ) { + name: "separator5" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "cutButton" + "toolTipText": "Cut" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-cut.svg" ) + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "copyButton" + "toolTipText": "Copy" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/copy.svg" ) + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "pasteButton" + "toolTipText": "Paste" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-paste.svg" ) + } ) + add( new FormComponent( "javax.swing.JToolBar$Separator" ) { + name: "separator6" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "refreshButton" + "toolTipText": "Refresh" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/refresh.svg" ) + } ) + add( new FormComponent( "javax.swing.JToolBar$Separator" ) { + name: "separator7" + } ) + add( new FormComponent( "javax.swing.JToggleButton" ) { + name: "showToggleButton" + "selected": true + "toolTipText": "Show Details" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/show.svg" ) + } ) + }, new FormLayoutConstraints( class java.lang.String ) { + "value": "Center" } ) }, new FormLayoutConstraints( class java.lang.String ) { "value": "North" diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h index 2de809560..667810cfb 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/JNIUtils.h @@ -41,4 +41,6 @@ } -jfieldID getFieldID( JNIEnv *env, const char* className, const char* fieldName, const char* fieldSignature ); +jclass findClass( JNIEnv *env, const char* className, bool globalRef ); +jfieldID getFieldID( JNIEnv *env, const char* className, const char* fieldName, const char* fieldSignature, bool staticField ); +jmethodID getMethodID( JNIEnv *env, jclass cls, const char* methodName, const char* methodSignature, bool staticMethod ); diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h index f962f645d..0e39445cd 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h @@ -31,18 +31,10 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary - * Method: getWindowButtonAreaWidth - * Signature: (Ljava/awt/Window;)I + * Method: getWindowButtonsBounds + * Signature: (Ljava/awt/Window;)Ljava/awt/Rectangle; */ -JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonAreaWidth - (JNIEnv *, jclass, jobject); - -/* - * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary - * Method: getWindowTitleBarHeight - * Signature: (Ljava/awt/Window;)I - */ -JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowTitleBarHeight +JNIEXPORT jobject JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonsBounds (JNIEnv *, jclass, jobject); /* @@ -55,10 +47,10 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWi /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary - * Method: windowToggleFullScreen + * Method: toggleWindowFullScreen * Signature: (Ljava/awt/Window;)Z */ -JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_windowToggleFullScreen +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen (JNIEnv *, jclass, jobject); #ifdef __cplusplus diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm index 7c4e798d8..089881fe4 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm @@ -21,8 +21,8 @@ * @author Karl Tauber */ -jfieldID getFieldID( JNIEnv *env, const char* className, const char* fieldName, const char* fieldSignature ) { -// NSLog( @"getFieldID %s %s %s", className, fieldName, fieldSignature ); +jclass findClass( JNIEnv *env, const char* className, bool globalRef ) { +// NSLog( @"findClass %s", className ); jclass cls = env->FindClass( className ); if( cls == NULL ) { @@ -32,7 +32,22 @@ jfieldID getFieldID( JNIEnv *env, const char* className, const char* fieldName, return NULL; } - jfieldID fieldID = env->GetFieldID( cls, fieldName, fieldSignature ); + if( globalRef ) + cls = reinterpret_cast( env->NewGlobalRef( cls ) ); + + return cls; +} + +jfieldID getFieldID( JNIEnv *env, const char* className, const char* fieldName, const char* fieldSignature, bool staticField ) { +// NSLog( @"getFieldID %s %s %s", className, fieldName, fieldSignature ); + + jclass cls = findClass( env, className, false ); + if( cls == NULL ) + return NULL; + + jfieldID fieldID = staticField + ? env->GetStaticFieldID( cls, fieldName, fieldSignature ) + : env->GetFieldID( cls, fieldName, fieldSignature ); if( fieldID == NULL ) { NSLog( @"FlatLaf: failed to lookup field '%s' of type '%s' in class '%s'", fieldName, fieldSignature, className ); env->ExceptionDescribe(); // print stack trace @@ -42,3 +57,22 @@ jfieldID getFieldID( JNIEnv *env, const char* className, const char* fieldName, return fieldID; } + +jmethodID getMethodID( JNIEnv *env, jclass cls, const char* methodName, const char* methodSignature, bool staticMethod ) { +// NSLog( @"getMethodID %s %s", methodName, methodSignature ); + + if( cls == NULL ) + return NULL; + + jmethodID methodID = staticMethod + ? env->GetStaticMethodID( cls, methodName, methodSignature ) + : env->GetMethodID( cls, methodName, methodSignature ); + if( methodID == NULL ) { + NSLog( @"FlatLaf: failed to lookup method '%s' of type '%s'", methodName, methodSignature ); + env->ExceptionDescribe(); // print stack trace + env->ExceptionClear(); + return NULL; + } + + return methodID; +} diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm index 5fc8a285c..2edd4cc66 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm @@ -51,9 +51,9 @@ @implementation WindowData return NULL; // initialize field IDs (done only once because variables are static) - static jfieldID peerID = getFieldID( env, "java/awt/Component", "peer", "Ljava/awt/peer/ComponentPeer;" ); - static jfieldID platformWindowID = getFieldID( env, "sun/lwawt/LWWindowPeer", "platformWindow", "Lsun/lwawt/PlatformWindow;" ); - static jfieldID ptrID = getFieldID( env, "sun/lwawt/macosx/CFRetainedResource", "ptr", "J" ); + static jfieldID peerID = getFieldID( env, "java/awt/Component", "peer", "Ljava/awt/peer/ComponentPeer;", false ); + static jfieldID platformWindowID = getFieldID( env, "sun/lwawt/LWWindowPeer", "platformWindow", "Lsun/lwawt/PlatformWindow;", false ); + static jfieldID ptrID = getFieldID( env, "sun/lwawt/macosx/CFRetainedResource", "ptr", "J", false ); if( peerID == NULL || platformWindowID == NULL || ptrID == NULL ) return NULL; @@ -148,7 +148,7 @@ @implementation WindowData WindowData* windowData = getWindowData( nsWindow, true ); - [FlatJNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ + [FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ // NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); // add/remove toolbar @@ -222,29 +222,48 @@ @implementation WindowData } extern "C" -JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonAreaWidth +JNIEXPORT jobject JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonsBounds ( JNIEnv* env, jclass cls, jobject window ) { JNI_COCOA_ENTER() NSWindow* nsWindow = getNSWindow( env, cls, window ); if( nsWindow == NULL ) - return -1; - - // return zero if window is full screen because close/minimize/zoom buttons are hidden - if( isWindowFullScreen( nsWindow ) ) - return 0; + return NULL; - // use remembered value if window is in transition from full screen to non-full screen - // because NSToolbar is not yet visible WindowData* windowData = getWindowData( nsWindow, false ); - if( windowData != NULL && windowData.lastWindowButtonAreaWidth > 0 ) - return windowData.lastWindowButtonAreaWidth; + int width = 0; + int height = 0; + + // get width + if( isWindowFullScreen( nsWindow ) ) { + // use zero if window is full screen because close/minimize/zoom buttons are hidden + width = 0; + } else if( windowData != NULL && windowData.lastWindowButtonAreaWidth > 0 ) { + // use remembered value if window is in transition from full screen to non-full screen + // because NSToolbar is not yet visible + width = windowData.lastWindowButtonAreaWidth; + } else + width = getWindowButtonAreaWidth( nsWindow ); + + // get height + if( windowData != NULL && windowData.lastWindowTitleBarHeight > 0 ) { + // use remembered value if window is full screen because NSToolbar is hidden + height = windowData.lastWindowTitleBarHeight; + } else + height = getWindowTitleBarHeight( nsWindow ); + + // initialize class and method ID (done only once because variables are static) + static jclass cls = findClass( env, "java/awt/Rectangle", true ); + static jmethodID methodID = getMethodID( env, cls, "", "(IIII)V", false ); + if( cls == NULL || methodID == NULL ) + return NULL; - return getWindowButtonAreaWidth( nsWindow ); + // create and return Rectangle + return env->NewObject( cls, methodID, 0, 0, width, height ); JNI_COCOA_EXIT() - return -1; + return NULL; } int getWindowButtonAreaWidth( NSWindow* nsWindow ) { @@ -279,27 +298,6 @@ int getWindowButtonAreaWidth( NSWindow* nsWindow ) { return right + left; } -extern "C" -JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowTitleBarHeight - ( JNIEnv* env, jclass cls, jobject window ) -{ - JNI_COCOA_ENTER() - - NSWindow* nsWindow = getNSWindow( env, cls, window ); - if( nsWindow == NULL ) - return -1; - - // use remembered value if window is full screen because NSToolbar is hidden - WindowData* windowData = getWindowData( nsWindow, false ); - if( windowData != NULL && windowData.lastWindowTitleBarHeight > 0 ) - return windowData.lastWindowTitleBarHeight; - - return getWindowTitleBarHeight( nsWindow ); - - JNI_COCOA_EXIT() - return -1; -} - int getWindowTitleBarHeight( NSWindow* nsWindow ) { NSView* closeButton = [nsWindow standardWindowButton:NSWindowCloseButton]; if( closeButton == NULL ) @@ -330,7 +328,7 @@ bool isWindowFullScreen( NSWindow* nsWindow ) { } extern "C" -JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_windowToggleFullScreen +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen ( JNIEnv* env, jclass cls, jobject window ) { JNI_COCOA_ENTER() From 3465fa68b42bc140f2ef5598663ab139ac3ac297 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 22 Jan 2024 00:27:42 +0100 Subject: [PATCH 10/10] macOS window buttons spacing: - renamed client property `MACOS_WINDOW_BUTTON_STYLE` to `MACOS_WINDOW_BUTTONS_SPACING` - no longer allow value `true` for that client property - enable using `MACOS_WINDOW_BUTTONS_SPACING` without `apple.awt.fullWindowContent` - remove client property `FULL_WINDOW_CONTENT_BUTTONS_BOUNDS` when `apple.awt.fullWindowContent` is set to false or null - added placeholder options `zeroInFullScreen`, `leftToRight` and `rightToLeft` - hide close/min/max buttons during the transition from full-screen to non-full-screen to avoid that they "jump" when the nsToolbar is made visible - fixed: full-screen listeners where added multiple times - updated macOS native libraries - added `FlatMacOSTest` --- .../formdev/flatlaf/FlatClientProperties.java | 54 ++-- .../flatlaf/ui/FlatNativeMacLibrary.java | 11 +- .../formdev/flatlaf/ui/FlatRootPaneUI.java | 52 ++-- .../flatlaf/ui/FullWindowContentSupport.java | 97 +++--- .../natives/libflatlaf-macos-arm64.dylib | Bin 77504 -> 78304 bytes .../natives/libflatlaf-macos-x86_64.dylib | Bin 60592 -> 61696 bytes .../com/formdev/flatlaf/demo/DemoFrame.java | 10 +- ..._formdev_flatlaf_ui_FlatNativeMacLibrary.h | 16 +- .../src/main/objcpp/MacWindow.mm | 71 +++-- .../flatlaf/testing/FlatMacOSTest.java | 283 ++++++++++++++++++ .../formdev/flatlaf/testing/FlatMacOSTest.jfd | 191 ++++++++++++ 11 files changed, 667 insertions(+), 118 deletions(-) create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSTest.java create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSTest.jfd 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 ebc393cdb..f2fc9fea3 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -282,20 +282,24 @@ public interface FlatClientProperties * You're responsible to layout that panel at the top-left or top-right corner, * depending on platform, where the iconfify/maximize/close buttons are located. *

- * Syntax of the value string is: {@code "win|mac [horizontal|vertical]"}. + * Syntax of the value string is: {@code "win|mac [horizontal|vertical] [zeroInFullScreen] [leftToRight|rightToLeft]"}. *

* The string must start with {@code "win"} (for Windows or Linux) or * with {@code "mac"} (for macOS) and specifies the platform where the placeholder * should be used. On macOS, you need the placeholder in the top-left corner, - * but on Windows/Linux you need it in the top-right corner. So if fullWindowContent mode - * is supported on both platforms, you can add two placeholders to your layout + * but on Windows/Linux you need it in the top-right corner. So if your application supports + * fullWindowContent mode on both platforms, you can add two placeholders to your layout * and FlatLaf automatically uses only one of them. The other gets size {@code 0,0}. *

- * Optionally, you can append {@code " horizontal"} or {@code " vertical"} to the value string - * to specify that the placeholder preferred size should be limited to one orientation. - * E.g. {@code "win horizontal"} means that the placeholder preferred width is - * equal to iconfify/maximize/close buttons width, but preferred height is zero. - *

+ * Optionally, you can append following options to the value string (separated by space characters): + *

    + *
  • {@code "horizontal"} - preferred height is zero + *
  • {@code "vertical"} - preferred width is zero + *
  • {@code "zeroInFullScreen"} - in full-screen mode on macOS, preferred size is {@code 0,0} + *
  • {@code "leftToRight"} - in right-to-left component orientation, preferred size is {@code 0,0} + *
  • {@code "rightToLeft"} - in left-to-right component orientation, preferred size is {@code 0,0} + *
+ * * Example for adding placeholder to top-left corner on macOS: *
{@code
 	 * JPanel placeholder = new JPanel();
@@ -1350,41 +1354,39 @@ public interface FlatClientProperties
 	//---- macOS --------------------------------------------------------------
 
 	/**
-	 * Specifies the style of macOS window close/minimize/zoom buttons.
-	 * This does not change visual appearance but adds extra space around the buttons.
+	 * Specifies the spacing around the macOS window close/minimize/zoom buttons.
 	 * Useful if full window content
 	 * is enabled.
 	 * 

- * (requires macOS 10.14+ or 11+ for style 'large', Java 17+ and client property {@code apple.awt.fullWindowContent} set to {@code true}) + * (requires macOS 10.14+ for "medium" spacing and macOS 11+ for "large" spacing, requires Java 17+) *

* Component {@link javax.swing.JRootPane}
- * Value type {@link java.lang.String} or {@link java.lang.Boolean}
+ * Value type {@link java.lang.String}
* Allowed Values - * {@link #MACOS_WINDOW_BUTTON_STYLE_MEDIUM}, - * {@link #MACOS_WINDOW_BUTTON_STYLE_LARGE} (requires macOS 11+) or - * {@code true} (equal to 'large') + * {@link #MACOS_WINDOW_BUTTONS_SPACING_MEDIUM} or + * {@link #MACOS_WINDOW_BUTTONS_SPACING_LARGE} (requires macOS 11+) * - * @since 3.3 + * @since 3.4 */ - String MACOS_WINDOW_BUTTON_STYLE = "FlatLaf.macOS.windowButtonStyle"; + String MACOS_WINDOW_BUTTONS_SPACING = "FlatLaf.macOS.windowButtonsSpacing"; /** - * Add medium space around the macOS window close/minimize/zoom buttons. + * Add medium spacing around the macOS window close/minimize/zoom buttons. * - * @see #MACOS_WINDOW_BUTTON_STYLE - * @since 3.3 + * @see #MACOS_WINDOW_BUTTONS_SPACING + * @since 3.4 */ - String MACOS_WINDOW_BUTTON_STYLE_MEDIUM = "medium"; + String MACOS_WINDOW_BUTTONS_SPACING_MEDIUM = "medium"; /** - * Add large space around the macOS window close/minimize/zoom buttons. + * Add large spacing around the macOS window close/minimize/zoom buttons. *

- * (requires macOS 11+; 'medium' is used on older systems) + * (requires macOS 11+; "medium" is used on older systems) * - * @see #MACOS_WINDOW_BUTTON_STYLE - * @since 3.3 + * @see #MACOS_WINDOW_BUTTONS_SPACING + * @since 3.4 */ - String MACOS_WINDOW_BUTTON_STYLE_LARGE = "large"; + String MACOS_WINDOW_BUTTONS_SPACING_LARGE = "large"; //---- helper methods ----------------------------------------------------- diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java index 6a7858b24..0ef75f50d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java @@ -18,6 +18,7 @@ import java.awt.Rectangle; import java.awt.Window; +import com.formdev.flatlaf.util.SystemInfo; /** * Native methods for macOS. @@ -50,18 +51,18 @@ public class FlatNativeMacLibrary * method of this class. Otherwise, the native library may not be loaded. */ public static boolean isLoaded() { - return FlatNativeLibrary.isLoaded(); + return SystemInfo.isMacOS && FlatNativeLibrary.isLoaded(); } public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor ); /** @since 3.4 */ public static final int - BUTTON_STYLE_DEFAULT = 0, - BUTTON_STYLE_MEDIUM = 1, - BUTTON_STYLE_LARGE = 2; + BUTTONS_SPACING_DEFAULT = 0, + BUTTONS_SPACING_MEDIUM = 1, + BUTTONS_SPACING_LARGE = 2; - /** @since 3.4 */ public native static boolean setWindowButtonStyle( Window window, int buttonStyle ); + /** @since 3.4 */ public native static boolean setWindowButtonsSpacing( Window window, int buttonsSpacing ); /** @since 3.4 */ public native static Rectangle getWindowButtonsBounds( Window window ); /** @since 3.4 */ public native static boolean isWindowFullScreen( Window window ); /** @since 3.4 */ public native static boolean toggleWindowFullScreen( Window window ); 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 c4bc00016..5664f8c71 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 @@ -381,35 +381,49 @@ public void propertyChange( PropertyChangeEvent e ) { throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." ); break; - case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE: case "ancestor": - if( SystemInfo.isMacFullWindowContentSupported && - rootPane.isDisplayable() && - FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) - { - // set window button style - if( SystemInfo.isJava_17_orLater && FlatNativeMacLibrary.isLoaded() ) { - int buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_DEFAULT; - Object value = rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE ); - switch( String.valueOf( value ) ) { - case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_MEDIUM: - buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_MEDIUM; - break; - - case "true": - case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_LARGE: - buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_LARGE; - break; + // FlatNativeMacLibrary.setWindowButtonsSpacing() and + // FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty() + // require a native window, but setting the client properties + // "apple.awt.fullWindowContent" or FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING + // is usually done before the native window is created + // --> try again when native window is created + if( !SystemInfo.isMacOS || e.getNewValue() == null ) + break; + + // fall through + + case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING: + if( SystemInfo.isMacOS ) { + // set window buttons spacing + if( SystemInfo.isJava_17_orLater && rootPane.isDisplayable() && FlatNativeMacLibrary.isLoaded() ) { + int buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_DEFAULT; + String value = (String) rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING ); + if( value != null ) { + switch( value ) { + case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM: + buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_MEDIUM; + break; + + case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE: + buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_LARGE; + break; + } } Window window = SwingUtilities.windowForComponent( rootPane ); - FlatNativeMacLibrary.setWindowButtonStyle( window, buttonStyle ); + FlatNativeMacLibrary.setWindowButtonsSpacing( window, buttonsSpacing ); } // update buttons bounds client property FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane ); } break; + + case "apple.awt.fullWindowContent": + if( SystemInfo.isMacOS ) + FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane ); + break; } } 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 65bfe8b93..5665ce5c2 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 @@ -48,32 +48,41 @@ static Dimension getPlaceholderPreferredSize( JComponent c, String options ) { JRootPane rootPane; Rectangle bounds; - if( options.startsWith( SystemInfo.isMacOS ? "mac" : "win" ) && - c.isDisplayable() && - (rootPane = SwingUtilities.getRootPane( c )) != null && - (bounds = (Rectangle) rootPane.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS )) != null ) - { - // On macOS, the client property is updated very late when toggling full screen, - // which results in "jumping" layout after full screen toggle finished. - // To avoid that, get up-to-date buttons bounds from macOS. - if( SystemInfo.isMacFullWindowContentSupported && FlatNativeMacLibrary.isLoaded() ) { - Rectangle r = FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( c ) ); - if( r != null ) - bounds = r; - } + if( !options.startsWith( SystemInfo.isMacOS ? "mac" : "win" ) || + !c.isDisplayable() || + (rootPane = SwingUtilities.getRootPane( c )) == null || + (bounds = (Rectangle) rootPane.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS )) == null ) + return new Dimension( 0, 0 ); + + if( options.length() > 3 ) { + if( (options.contains( "leftToRight" ) && !c.getComponentOrientation().isLeftToRight()) || + (options.contains( "rightToLeft" ) && c.getComponentOrientation().isLeftToRight()) ) + return new Dimension( 0, 0 ); + } - if( options.length() > 3 ) { - if( options.contains( "horizontal" ) ) - return new Dimension( bounds.width, 0 ); - if( options.contains( "vertical" ) ) - return new Dimension( 0, bounds.height ); - } + // On macOS, the client property is updated very late when toggling full screen, + // which results in "jumping" layout after full screen toggle finished. + // To avoid that, get up-to-date buttons bounds from macOS. + if( SystemInfo.isMacFullWindowContentSupported && FlatNativeMacLibrary.isLoaded() ) { + Rectangle r = FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( c ) ); + if( r != null ) + bounds = r; + } - return bounds.getSize(); + int width = bounds.width; + int height = bounds.height; + + if( options.length() > 3 ) { + if( width == 0 && options.contains( "zeroInFullScreen" ) ) + height = 0; + + if( options.contains( "horizontal" ) ) + height = 0; + if( options.contains( "vertical" ) ) + width = 0; } - // default to 0,0 - return new Dimension(); + return new Dimension( width, height ); } static void registerPlaceholder( JComponent c ) { @@ -151,14 +160,15 @@ static void macUninstallListeners( JRootPane rootPane, ComponentListener l ) { } static void macUpdateFullWindowContentButtonsBoundsProperty( JRootPane rootPane ) { - if( !SystemInfo.isMacFullWindowContentSupported || - !rootPane.isDisplayable() || - !FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) - return; - - Rectangle bounds = FlatNativeMacLibrary.isLoaded() - ? FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( rootPane ) ) - : new Rectangle( 68, 28 ); // default size + if( !SystemInfo.isMacFullWindowContentSupported || !rootPane.isDisplayable() ) + return; + + Rectangle bounds = null; + if( FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) { + bounds = FlatNativeMacLibrary.isLoaded() + ? FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( rootPane ) ) + : new Rectangle( 68, 28 ); // default size + } rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds ); } @@ -166,18 +176,35 @@ static void debugPaint( Graphics g, JComponent c ) { if( !UIManager.getBoolean( KEY_DEBUG_SHOW_PLACEHOLDERS ) ) return; - int width = c.getWidth() - 1; - int height = c.getHeight() - 1; + int width = c.getWidth(); + int height = c.getHeight(); if( width <= 0 || height <= 0 ) return; + // draw red figure g.setColor( Color.red ); - g.drawRect( 0, 0, width, height ); + debugPaintRect( g, new Rectangle( width, height ) ); + + // draw magenta figure if buttons bounds are not equal to placeholder bounds + JRootPane rootPane; + Rectangle bounds; + if( (rootPane = SwingUtilities.getRootPane( c )) != null && + (bounds = (Rectangle) rootPane.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS )) != null && + (bounds.width != width || bounds.height != height) ) + { + g.setColor( Color.magenta ); + debugPaintRect( g, SwingUtilities.convertRectangle( rootPane, bounds, c ) ); + } + } + + private static void debugPaintRect( Graphics g, Rectangle r ) { + // draw rectangle + g.drawRect( r.x, r.y, r.width - 1, r.height - 1 ); // draw diagonal cross Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g ); - g.drawLine( 0, 0, width, height ); - g.drawLine( 0, height, width, 0 ); + g.drawLine( r.x, r.y, r.width - 1, r.height - 1 ); + g.drawLine( r.x, r.height - 1, r.width - 1, r.y ); FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); } } diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib index 34d85495b5248b70b6dfc7d5b167d274eeed2994..cf1677cb6d276554c84e006daf803cd49331ee89 100755 GIT binary patch delta 14004 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 J-3+Ph`adr03UB}b delta 11939 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 ZeDb1$WmaC7ti#iMi{D&>pM>^%{Xa%|zdir} delta 14260 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 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 c982a2de7..8f701a66e 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 @@ -95,7 +95,7 @@ class DemoFrame // expand window content into window title bar and make title bar transparent rootPane.putClientProperty( "apple.awt.fullWindowContent", true ); rootPane.putClientProperty( "apple.awt.transparentTitleBar", true ); - rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE, true ); + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE ); // hide window title if( SystemInfo.isJava_17_orLater ) @@ -922,13 +922,13 @@ private void initComponents() { //TODO remove backButton.addActionListener( e -> { - rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE, FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_LARGE ); + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE ); }); forwardButton.addActionListener( e -> { - rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE, FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_MEDIUM ); + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM ); }); cutButton.addActionListener( e -> { - rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE, null ); + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, null ); }); copyButton.addActionListener( e -> System.out.println( e ) ); @@ -1025,7 +1025,7 @@ public void mouseDragged( MouseEvent e ) { 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" ); + macFullWindowContentButtonsPlaceholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac zeroInFullScreen" ); // remove contentPanel bottom insets MigLayout layout = (MigLayout) contentPanel.getLayout(); diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h index 0e39445cd..f99549e5a 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h @@ -7,12 +7,12 @@ #ifdef __cplusplus extern "C" { #endif -#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_DEFAULT -#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_DEFAULT 0L -#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_MEDIUM -#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_MEDIUM 1L -#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_LARGE -#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_LARGE 2L +#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_DEFAULT +#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_DEFAULT 0L +#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_MEDIUM +#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_MEDIUM 1L +#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE +#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE 2L /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary * Method: setWindowRoundedBorder @@ -23,10 +23,10 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary - * Method: setWindowButtonStyle + * Method: setWindowButtonsSpacing * Signature: (Ljava/awt/Window;I)Z */ -JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowButtonStyle +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowButtonsSpacing (JNIEnv *, jclass, jobject, jint); /* diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm index 2edd4cc66..919c4ec8a 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm @@ -32,6 +32,7 @@ @interface WindowData : NSObject // full screen observers @property (nonatomic) id willEnterFullScreenObserver; + @property (nonatomic) id willExitFullScreenObserver; @property (nonatomic) id didExitFullScreenObserver; @end @@ -41,6 +42,7 @@ @implementation WindowData // declare internal methods NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ); WindowData* getWindowData( NSWindow* nsWindow, bool allocate ); +void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden ); int getWindowButtonAreaWidth( NSWindow* nsWindow ); int getWindowTitleBarHeight( NSWindow* nsWindow ); bool isWindowFullScreen( NSWindow* nsWindow ); @@ -121,8 +123,8 @@ @implementation WindowData } extern "C" -JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowButtonStyle - ( JNIEnv* env, jclass cls, jobject window, jint buttonStyle ) +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowButtonsSpacing + ( JNIEnv* env, jclass cls, jobject window, jint buttonsSpacing ) { JNI_COCOA_ENTER() @@ -130,20 +132,20 @@ @implementation WindowData if( nsWindow == NULL ) return FALSE; - #define STYLE_DEFAULT com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_DEFAULT - #define STYLE_MEDIUM com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_MEDIUM - #define STYLE_LARGE com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTON_STYLE_LARGE + #define SPACING_DEFAULT com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_DEFAULT + #define SPACING_MEDIUM com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_MEDIUM + #define SPACING_LARGE com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE bool isMacOS_11_orLater = @available( macOS 11, * ); - if( !isMacOS_11_orLater && buttonStyle == STYLE_LARGE ) - buttonStyle = STYLE_MEDIUM; - int oldButtonStyle = (nsWindow.toolbar != NULL) + if( !isMacOS_11_orLater && buttonsSpacing == SPACING_LARGE ) + buttonsSpacing = SPACING_MEDIUM; + int oldButtonsSpacing = (nsWindow.toolbar != NULL) ? ((isMacOS_11_orLater && nsWindow.toolbarStyle == NSWindowToolbarStyleUnified) - ? STYLE_LARGE - : STYLE_MEDIUM) - : STYLE_DEFAULT; + ? SPACING_LARGE + : SPACING_MEDIUM) + : SPACING_DEFAULT; - if( buttonStyle == oldButtonStyle ) + if( buttonsSpacing == oldButtonsSpacing ) return TRUE; WindowData* windowData = getWindowData( nsWindow, true ); @@ -153,8 +155,8 @@ @implementation WindowData // add/remove toolbar NSToolbar* toolbar = NULL; - bool hasToolbar = (buttonStyle != STYLE_DEFAULT); - if( hasToolbar ) { + bool needsToolbar = (buttonsSpacing != SPACING_DEFAULT); + if( needsToolbar ) { toolbar = [NSToolbar new]; toolbar.showsBaselineSeparator = NO; // necessary for older macOS versions if( isWindowFullScreen( nsWindow ) ) @@ -163,9 +165,9 @@ @implementation WindowData nsWindow.toolbar = toolbar; if( isMacOS_11_orLater ) { - nsWindow.toolbarStyle = (buttonStyle == STYLE_LARGE) + nsWindow.toolbarStyle = (buttonsSpacing == SPACING_LARGE) ? NSWindowToolbarStyleUnified - : (buttonStyle == STYLE_MEDIUM) + : (buttonsSpacing == SPACING_MEDIUM) ? NSWindowToolbarStyleUnifiedCompact : NSWindowToolbarStyleAutomatic; } @@ -178,7 +180,7 @@ @implementation WindowData // when window becomes full screen, it is necessary to hide the toolbar // because it otherwise is shown non-transparent and hides Swing components NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; - if( hasToolbar ) { + if( needsToolbar && windowData.willEnterFullScreenObserver == NULL ) { // NSLog( @"add observers %@", nsWindow ); windowData.willEnterFullScreenObserver = [center addObserverForName:NSWindowWillEnterFullScreenNotification object:nsWindow queue:nil usingBlock:^(NSNotification *note) { @@ -188,26 +190,40 @@ @implementation WindowData // remembar title bar height so that "main" JToolBar keeps its height in full screen windowData.lastWindowButtonAreaWidth = getWindowButtonAreaWidth( nsWindow ); windowData.lastWindowTitleBarHeight = getWindowTitleBarHeight( nsWindow ); -// NSLog(@"%d %d",windowData.lastWindowButtonAreaWidth,windowData.lastWindowTitleBarHeight); +// NSLog( @"%d %d", windowData.lastWindowButtonAreaWidth, windowData.lastWindowTitleBarHeight ); nsWindow.toolbar.visible = NO; } }]; + + windowData.willExitFullScreenObserver = [center addObserverForName:NSWindowWillExitFullScreenNotification + object:nsWindow queue:nil usingBlock:^(NSNotification *note) { +// NSLog( @"will exit full screen %@", nsWindow ); + if( nsWindow.toolbar != NULL ) + setWindowButtonsHidden( nsWindow, true ); + }]; + windowData.didExitFullScreenObserver = [center addObserverForName:NSWindowDidExitFullScreenNotification object:nsWindow queue:nil usingBlock:^(NSNotification *note) { // NSLog( @"exit full screen %@", nsWindow ); - if( nsWindow.toolbar != NULL ) + if( nsWindow.toolbar != NULL ) { + setWindowButtonsHidden( nsWindow, false ); nsWindow.toolbar.visible = YES; + } windowData.lastWindowButtonAreaWidth = 0; windowData.lastWindowTitleBarHeight = 0; }]; - } else { + } else if( !needsToolbar ) { // NSLog( @"remove observers %@", nsWindow ); if( windowData.willEnterFullScreenObserver != NULL ) { [center removeObserver:windowData.willEnterFullScreenObserver]; windowData.willEnterFullScreenObserver = nil; } + if( windowData.willExitFullScreenObserver != NULL ) { + [center removeObserver:windowData.willExitFullScreenObserver]; + windowData.willExitFullScreenObserver = nil; + } if( windowData.didExitFullScreenObserver != NULL ) { [center removeObserver:windowData.didExitFullScreenObserver]; windowData.didExitFullScreenObserver = nil; @@ -221,6 +237,21 @@ @implementation WindowData return FALSE; } +void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden ) { + // get buttons + NSView* buttons[3] = { + [nsWindow standardWindowButton:NSWindowCloseButton], + [nsWindow standardWindowButton:NSWindowMiniaturizeButton], + [nsWindow standardWindowButton:NSWindowZoomButton] + }; + + for( int i = 0; i < 3; i++ ) { + NSView* button = buttons[i]; + if( button != NULL ) + button.hidden = hidden; + } +} + extern "C" JNIEXPORT jobject JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonsBounds ( JNIEnv* env, jclass cls, jobject window ) diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSTest.java new file mode 100644 index 000000000..226392d03 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSTest.java @@ -0,0 +1,283 @@ +/* + * Copyright 2024 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.testing; + +import java.awt.*; +import javax.swing.*; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatNativeMacLibrary; +import com.formdev.flatlaf.util.SystemInfo; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +public class FlatMacOSTest + extends FlatTestPanel +{ + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + FlatTestFrame frame = FlatTestFrame.create( args, FlatMacOSTest.class.getSimpleName() ); + frame.applyComponentOrientationToFrame = true; + + JRootPane rootPane = frame.getRootPane(); + rootPane.putClientProperty( "apple.awt.fullWindowContent", true ); + rootPane.putClientProperty( "apple.awt.transparentTitleBar", true ); + rootPane.putClientProperty( "apple.awt.windowTitleVisible", false ); + + frame.showFrame( FlatMacOSTest::new ); + } ); + } + + FlatMacOSTest() { + initComponents(); + + if( SystemInfo.isMacFullWindowContentSupported ) { + fullWindowContentHint.setVisible( false ); + transparentTitleBarHint.setVisible( false ); + } + if( SystemInfo.isJava_17_orLater ) { + windowTitleVisibleHint.setVisible( false ); + buttonsSpacingHint.setVisible( false ); + } + + placeholderPanel.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac zeroInFullScreen" ); + UIManager.put( "FlatLaf.debug.panel.showPlaceholders", true ); + } + + @Override + public void addNotify() { + super.addNotify(); + + JRootPane rootPane = getRootPane(); + fullWindowContentCheckBox.setSelected( FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ); + transparentTitleBarCheckBox.setSelected( FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.transparentTitleBar", false ) ); + windowTitleVisibleCheckBox.setSelected( FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.windowTitleVisible", true ) ); + + rootPane.addPropertyChangeListener( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, e -> { + Rectangle bounds = (Rectangle) e.getNewValue(); + fullWindowContentButtonsBoundsField.setText( bounds2string( bounds ) ); + } ); + updateNativeButtonBounds(); + } + + private void fullWindowContentChanged() { + getRootPane().putClientProperty( "apple.awt.fullWindowContent", fullWindowContentCheckBox.isSelected() ); + } + + private void transparentTitleBarChanged() { + getRootPane().putClientProperty( "apple.awt.transparentTitleBar", transparentTitleBarCheckBox.isSelected() ); + } + + private void windowTitleVisibleChanged() { + getRootPane().putClientProperty( "apple.awt.windowTitleVisible", windowTitleVisibleCheckBox.isSelected() ); + } + + private void buttonsSpacingChanged() { + String buttonsSpacing = null; + if( buttonsSpacingMediumRadioButton.isSelected() ) + buttonsSpacing = FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM; + else if( buttonsSpacingLargeRadioButton.isSelected() ) + buttonsSpacing = FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE; + + getRootPane().putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, buttonsSpacing ); + + updateNativeButtonBounds(); + } + + private void updateNativeButtonBounds() { + if( !FlatNativeMacLibrary.isLoaded() ) + return; + + Window window = SwingUtilities.windowForComponent( this ); + Rectangle bounds = FlatNativeMacLibrary.getWindowButtonsBounds( window ); + nativeButtonsBoundsField.setText( bounds2string( bounds ) ); + } + + private String bounds2string( Rectangle bounds ) { + return (bounds != null) + ? bounds.width + ", " + bounds.height + " @ " + bounds.x + ", " + bounds.y + : "null"; + } + + private void toggleFullScreen() { + if( !FlatNativeMacLibrary.isLoaded() ) + return; + + Window window = SwingUtilities.windowForComponent( this ); + FlatNativeMacLibrary.toggleWindowFullScreen( window ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + JPanel panel1 = new JPanel(); + placeholderPanel = new JPanel(); + JPanel panel2 = new JPanel(); + fullWindowContentCheckBox = new JCheckBox(); + fullWindowContentHint = new JLabel(); + transparentTitleBarCheckBox = new JCheckBox(); + transparentTitleBarHint = new JLabel(); + windowTitleVisibleCheckBox = new JCheckBox(); + windowTitleVisibleHint = new JLabel(); + JLabel buttonsSpacingLabel = new JLabel(); + buttonsSpacingDefaultRadioButton = new JRadioButton(); + buttonsSpacingMediumRadioButton = new JRadioButton(); + buttonsSpacingLargeRadioButton = new JRadioButton(); + buttonsSpacingHint = new JLabel(); + JLabel fullWindowContentButtonsBoundsLabel = new JLabel(); + fullWindowContentButtonsBoundsField = new JLabel(); + JLabel nativeButtonsBoundsLabel = new JLabel(); + nativeButtonsBoundsField = new JLabel(); + JButton toggleFullScreenButton = new JButton(); + + //======== this ======== + setLayout(new BorderLayout()); + + //======== panel1 ======== + { + panel1.setLayout(new BorderLayout()); + + //======== placeholderPanel ======== + { + placeholderPanel.setBackground(Color.green); + placeholderPanel.setLayout(new FlowLayout()); + } + panel1.add(placeholderPanel, BorderLayout.WEST); + } + add(panel1, BorderLayout.PAGE_START); + + //======== panel2 ======== + { + panel2.setLayout(new MigLayout( + "ltr,insets dialog,hidemode 3", + // columns + "[left]" + + "[left]" + + "[left]" + + "[left]para" + + "[fill]", + // rows + "[]" + + "[]" + + "[]" + + "[fill]" + + "[]" + + "[]para" + + "[]")); + + //---- fullWindowContentCheckBox ---- + fullWindowContentCheckBox.setText("fullWindowContent"); + fullWindowContentCheckBox.addActionListener(e -> fullWindowContentChanged()); + panel2.add(fullWindowContentCheckBox, "cell 0 0"); + + //---- fullWindowContentHint ---- + fullWindowContentHint.setText("requires Java 12, 11.0.8 or 8u292"); + fullWindowContentHint.setForeground(Color.red); + panel2.add(fullWindowContentHint, "cell 4 0"); + + //---- transparentTitleBarCheckBox ---- + transparentTitleBarCheckBox.setText("transparentTitleBar"); + transparentTitleBarCheckBox.addActionListener(e -> transparentTitleBarChanged()); + panel2.add(transparentTitleBarCheckBox, "cell 0 1"); + + //---- transparentTitleBarHint ---- + transparentTitleBarHint.setText("requires Java 12, 11.0.8 or 8u292"); + transparentTitleBarHint.setForeground(Color.red); + panel2.add(transparentTitleBarHint, "cell 4 1"); + + //---- windowTitleVisibleCheckBox ---- + windowTitleVisibleCheckBox.setText("windowTitleVisible"); + windowTitleVisibleCheckBox.addActionListener(e -> windowTitleVisibleChanged()); + panel2.add(windowTitleVisibleCheckBox, "cell 0 2"); + + //---- windowTitleVisibleHint ---- + windowTitleVisibleHint.setText("requires Java 17"); + windowTitleVisibleHint.setForeground(Color.red); + panel2.add(windowTitleVisibleHint, "cell 4 2"); + + //---- buttonsSpacingLabel ---- + buttonsSpacingLabel.setText("Buttons spacing:"); + panel2.add(buttonsSpacingLabel, "cell 0 3"); + + //---- buttonsSpacingDefaultRadioButton ---- + buttonsSpacingDefaultRadioButton.setText("Default"); + buttonsSpacingDefaultRadioButton.setSelected(true); + buttonsSpacingDefaultRadioButton.addActionListener(e -> buttonsSpacingChanged()); + panel2.add(buttonsSpacingDefaultRadioButton, "cell 1 3"); + + //---- buttonsSpacingMediumRadioButton ---- + buttonsSpacingMediumRadioButton.setText("Medium"); + buttonsSpacingMediumRadioButton.addActionListener(e -> buttonsSpacingChanged()); + panel2.add(buttonsSpacingMediumRadioButton, "cell 2 3"); + + //---- buttonsSpacingLargeRadioButton ---- + buttonsSpacingLargeRadioButton.setText("Large"); + buttonsSpacingLargeRadioButton.addActionListener(e -> buttonsSpacingChanged()); + panel2.add(buttonsSpacingLargeRadioButton, "cell 3 3"); + + //---- buttonsSpacingHint ---- + buttonsSpacingHint.setText("requires Java 17"); + buttonsSpacingHint.setForeground(Color.red); + panel2.add(buttonsSpacingHint, "cell 4 3"); + + //---- fullWindowContentButtonsBoundsLabel ---- + fullWindowContentButtonsBoundsLabel.setText("Buttons bounds:"); + panel2.add(fullWindowContentButtonsBoundsLabel, "cell 0 4"); + + //---- fullWindowContentButtonsBoundsField ---- + fullWindowContentButtonsBoundsField.setText("null"); + panel2.add(fullWindowContentButtonsBoundsField, "cell 1 4 3 1"); + + //---- nativeButtonsBoundsLabel ---- + nativeButtonsBoundsLabel.setText("Native buttons bounds:"); + panel2.add(nativeButtonsBoundsLabel, "cell 0 5"); + + //---- nativeButtonsBoundsField ---- + nativeButtonsBoundsField.setText("null"); + panel2.add(nativeButtonsBoundsField, "cell 1 5 3 1"); + + //---- toggleFullScreenButton ---- + toggleFullScreenButton.setText("Toggle Full Screen"); + toggleFullScreenButton.addActionListener(e -> toggleFullScreen()); + panel2.add(toggleFullScreenButton, "cell 0 6"); + } + add(panel2, BorderLayout.CENTER); + + //---- buttonsSpacingButtonGroup ---- + ButtonGroup buttonsSpacingButtonGroup = new ButtonGroup(); + buttonsSpacingButtonGroup.add(buttonsSpacingDefaultRadioButton); + buttonsSpacingButtonGroup.add(buttonsSpacingMediumRadioButton); + buttonsSpacingButtonGroup.add(buttonsSpacingLargeRadioButton); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JPanel placeholderPanel; + private JCheckBox fullWindowContentCheckBox; + private JLabel fullWindowContentHint; + private JCheckBox transparentTitleBarCheckBox; + private JLabel transparentTitleBarHint; + private JCheckBox windowTitleVisibleCheckBox; + private JLabel windowTitleVisibleHint; + private JRadioButton buttonsSpacingDefaultRadioButton; + private JRadioButton buttonsSpacingMediumRadioButton; + private JRadioButton buttonsSpacingLargeRadioButton; + private JLabel buttonsSpacingHint; + private JLabel fullWindowContentButtonsBoundsField; + private JLabel nativeButtonsBoundsField; + // JFormDesigner - End of variables declaration //GEN-END:variables +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSTest.jfd new file mode 100644 index 000000000..f580097b3 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMacOSTest.jfd @@ -0,0 +1,191 @@ +JFDML JFormDesigner: "8.2.1.0.348" Java: "21.0.1" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + auxiliary() { + "JavaCodeGenerator.defaultVariableLocal": true + } + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) { + name: "this" + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) { + name: "panel1" + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) { + name: "placeholderPanel" + "background": sfield java.awt.Color green + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class java.lang.String ) { + "value": "West" + } ) + }, new FormLayoutConstraints( class java.lang.String ) { + "value": "First" + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "ltr,insets dialog,hidemode 3" + "$columnConstraints": "[left][left][left][left]para[fill]" + "$rowConstraints": "[][][][fill][][]para[]" + } ) { + name: "panel2" + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "fullWindowContentCheckBox" + "text": "fullWindowContent" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "fullWindowContentChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fullWindowContentHint" + "text": "requires Java 12, 11.0.8 or 8u292" + "foreground": sfield java.awt.Color red + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 4 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "transparentTitleBarCheckBox" + "text": "transparentTitleBar" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "transparentTitleBarChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "transparentTitleBarHint" + "text": "requires Java 12, 11.0.8 or 8u292" + "foreground": sfield java.awt.Color red + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 4 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "windowTitleVisibleCheckBox" + "text": "windowTitleVisible" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "windowTitleVisibleChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "windowTitleVisibleHint" + "text": "requires Java 17" + "foreground": sfield java.awt.Color red + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 4 2" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "buttonsSpacingLabel" + "text": "Buttons spacing:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "buttonsSpacingDefaultRadioButton" + "text": "Default" + "$buttonGroup": new FormReference( "buttonsSpacingButtonGroup" ) + "selected": true + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "buttonsSpacingChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 3" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "buttonsSpacingMediumRadioButton" + "text": "Medium" + "$buttonGroup": new FormReference( "buttonsSpacingButtonGroup" ) + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "buttonsSpacingChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 3" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "buttonsSpacingLargeRadioButton" + "text": "Large" + "$buttonGroup": new FormReference( "buttonsSpacingButtonGroup" ) + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "buttonsSpacingChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 3 3" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "buttonsSpacingHint" + "text": "requires Java 17" + "foreground": sfield java.awt.Color red + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 4 3" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fullWindowContentButtonsBoundsLabel" + "text": "Buttons bounds:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "fullWindowContentButtonsBoundsField" + "text": "null" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 4 3 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "nativeButtonsBoundsLabel" + "text": "Native buttons bounds:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 5" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "nativeButtonsBoundsField" + "text": "null" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 5 3 1" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "toggleFullScreenButton" + "text": "Toggle Full Screen" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "toggleFullScreen", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6" + } ) + }, new FormLayoutConstraints( class java.lang.String ) { + "value": "Center" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 725, 350 ) + } ) + add( new FormNonVisual( "javax.swing.ButtonGroup" ) { + name: "buttonsSpacingButtonGroup" + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 360 ) + } ) + } +}