From 3465fa68b42bc140f2ef5598663ab139ac3ac297 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 22 Jan 2024 00:27:42 +0100 Subject: [PATCH] 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 | 50 ++-- .../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, 666 insertions(+), 117 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 ebc393cd..f2fc9fea 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): + *

+ * * 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 6a7858b2..0ef75f50 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 @@ package com.formdev.flatlaf.ui; 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 c4bc0001..5664f8c7 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 class FlatRootPaneUI 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; + // 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; - case "true": - case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_LARGE: - buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_LARGE; - 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 65bfe8b9..5665ce5c 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 @@ class FullWindowContentSupport 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( "horizontal" ) ) - return new Dimension( bounds.width, 0 ); - if( options.contains( "vertical" ) ) - return new Dimension( 0, bounds.height ); - } - - return bounds.getSize(); + if( options.length() > 3 ) { + if( (options.contains( "leftToRight" ) && !c.getComponentOrientation().isLeftToRight()) || + (options.contains( "rightToLeft" ) && c.getComponentOrientation().isLeftToRight()) ) + return new Dimension( 0, 0 ); } - // default to 0,0 - return new Dimension(); + // 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; + } + + 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; + } + + return new Dimension( width, height ); } static void registerPlaceholder( JComponent c ) { @@ -151,14 +160,15 @@ class FullWindowContentSupport } static void macUpdateFullWindowContentButtonsBoundsProperty( JRootPane rootPane ) { - if( !SystemInfo.isMacFullWindowContentSupported || - !rootPane.isDisplayable() || - !FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) - return; + if( !SystemInfo.isMacFullWindowContentSupported || !rootPane.isDisplayable() ) + return; - Rectangle bounds = FlatNativeMacLibrary.isLoaded() - ? FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( rootPane ) ) - : new Rectangle( 68, 28 ); // default size + 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 @@ class FullWindowContentSupport 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 c982a2de..8f701a66 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 @@ class DemoFrame //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 @@ class DemoFrame 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 0e39445c..f99549e5 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 2edd4cc6..919c4ec8 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 @@ // full screen observers @property (nonatomic) id willEnterFullScreenObserver; + @property (nonatomic) id willExitFullScreenObserver; @property (nonatomic) id didExitFullScreenObserver; @end @@ -41,6 +42,7 @@ // 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW } 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW // 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW // 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 @@ 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); +// 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 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW 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 00000000..226392d0 --- /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 00000000..f580097b --- /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 ) + } ) + } +}