From a1adde088850d32a94389198829ced754187f2ba Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 15 Dec 2023 18:21:35 +0100 Subject: [PATCH] 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 180d90c8..00838b21 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 059dedb0..8f9acd5f 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 class FlatNativeMacLibrary 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 75631081..4f7bea46 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 class FlatRootPaneUI 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 class FlatRootPaneUI 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 08adb0e1..a2ee9821 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 @@ class DemoFrame 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 @@ class DemoFrame 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 f7669308..bb761191 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 cc04908a..730f64ab 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 @@ NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) { 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW } 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW // 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW // 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; }