diff --git a/CHANGELOG.md b/CHANGELOG.md index f86964e65..fc2e533d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ FlatLaf Change Log #### New features and improvements +- macOS: Support larger window title bar close/minimize/zoom buttons spacing in + [full window content](https://www.formdev.com/flatlaf/macos/#full_window_content) + mode and introduced "buttons placeholder". (PR #779) - Native libraries: System property `flatlaf.nativeLibraryPath` now supports loading native libraries named the same as on Maven central. Improved log messages for loading fails. 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..f2fc9fea3 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,80 @@ 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] [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 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 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();
+	 * 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 +462,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}. @@ -1263,6 +1351,44 @@ public interface FlatClientProperties String TREE_PAINT_SELECTION = "JTree.paintSelection"; + //---- macOS -------------------------------------------------------------- + + /** + * Specifies the spacing around the macOS window close/minimize/zoom buttons. + * Useful if full window content + * is enabled. + *

+ * (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}
+ * Allowed Values + * {@link #MACOS_WINDOW_BUTTONS_SPACING_MEDIUM} or + * {@link #MACOS_WINDOW_BUTTONS_SPACING_LARGE} (requires macOS 11+) + * + * @since 3.4 + */ + String MACOS_WINDOW_BUTTONS_SPACING = "FlatLaf.macOS.windowButtonsSpacing"; + + /** + * Add medium spacing around the macOS window close/minimize/zoom buttons. + * + * @see #MACOS_WINDOW_BUTTONS_SPACING + * @since 3.4 + */ + String MACOS_WINDOW_BUTTONS_SPACING_MEDIUM = "medium"; + + /** + * Add large spacing around the macOS window close/minimize/zoom buttons. + *

+ * (requires macOS 11+; "medium" is used on older systems) + * + * @see #MACOS_WINDOW_BUTTONS_SPACING + * @since 3.4 + */ + 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 3799bfd9f..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 @@ -16,7 +16,9 @@ package com.formdev.flatlaf.ui; +import java.awt.Rectangle; import java.awt.Window; +import com.formdev.flatlaf.util.SystemInfo; /** * Native methods for macOS. @@ -49,8 +51,19 @@ 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 + BUTTONS_SPACING_DEFAULT = 0, + BUTTONS_SPACING_MEDIUM = 1, + BUTTONS_SPACING_LARGE = 2; + + /** @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/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 3b6ee4981..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 @@ -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; @@ -367,6 +380,50 @@ 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 "ancestor": + // 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.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 new file mode 100644 index 000000000..5665ce5c2 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FullWindowContentSupport.java @@ -0,0 +1,210 @@ +/* + * 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 ) + 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 ); + } + + // 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 ) { + 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() ) + 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 ); + } + + static void debugPaint( Graphics g, JComponent c ) { + if( !UIManager.getBoolean( KEY_DEBUG_SHOW_PLACEHOLDERS ) ) + return; + + int width = c.getWidth(); + int height = c.getHeight(); + if( width <= 0 || height <= 0 ) + return; + + // draw red figure + g.setColor( Color.red ); + 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( 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 e29df3b17..cf1677cb6 100755 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-arm64.dylib differ 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 bb1c221c9..77cf3e036 100755 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-x86_64.dylib differ 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..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 @@ -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; @@ -89,24 +90,26 @@ 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_WINDOW_BUTTONS_SPACING, FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE ); // hide window title if( SystemInfo.isJava_17_orLater ) - getRootPane().putClientProperty( "apple.awt.windowTitleVisible", false ); + rootPane.putClientProperty( "apple.awt.windowTitleVisible", false ); else setTitle( null ); - // add gap to left side of toolbar - toolBar.add( Box.createHorizontalStrut( 70 ), 0 ); + // 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+) if( !SystemInfo.isJava_11_orLater ) - getRootPane().putClientProperty( "apple.awt.fullscreenable", true ); + rootPane.putClientProperty( "apple.awt.fullscreenable", true ); } // integrate into macOS screen menu @@ -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 ======== { @@ -903,6 +920,70 @@ private void initComponents() { buttonGroup1.add(radioButtonMenuItem3); // JFormDesigner - End of component initialization //GEN-END:initComponents + //TODO remove + backButton.addActionListener( e -> { + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE ); + }); + forwardButton.addActionListener( e -> { + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM ); + }); + cutButton.addActionListener( e -> { + rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, null ); + }); + + copyButton.addActionListener( e -> System.out.println( e ) ); + copyButton.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" ); + } + } ); + copyButton.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" ); + } + } ); + if( SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() ) { + showToggleButton.addActionListener( e -> { + FlatNativeMacLibrary.toggleWindowFullScreen( this ); + } ); + } + // add "Users" button to menubar FlatButton usersButton = new FlatButton(); usersButton.setIcon( new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/users.svg" ) ); @@ -943,6 +1024,9 @@ private void initComponents() { if( "false".equals( System.getProperty( "flatlaf.animatedLafChange" ) ) ) animatedLafChangeMenuItem.setSelected( false ); + // on macOS, panel left to toolBar is a placeholder for title bar buttons in fullWindowContent mode + macFullWindowContentButtonsPlaceholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac zeroInFullScreen" ); + // 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 f5bcd35ba..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,6 +7,12 @@ #ifdef __cplusplus extern "C" { #endif +#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 @@ -15,6 +21,38 @@ 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: setWindowButtonsSpacing + * Signature: (Ljava/awt/Window;I)Z + */ +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowButtonsSpacing + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary + * Method: getWindowButtonsBounds + * Signature: (Ljava/awt/Window;)Ljava/awt/Rectangle; + */ +JNIEXPORT jobject JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonsBounds + (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); + +/* + * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary + * Method: toggleWindowFullScreen + * Signature: (Ljava/awt/Window;)Z + */ +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen + (JNIEnv *, jclass, jobject); + #ifdef __cplusplus } #endif 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 5d81a51cd..919c4ec8a 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" @@ -24,14 +25,37 @@ * @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 willExitFullScreenObserver; + @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 ); +void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden ); +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; - // initialize field IDs (done only once because fields 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" ); + // initialize field IDs (done only once because variables are static) + 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; @@ -49,6 +73,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 ) @@ -87,3 +121,259 @@ JNI_COCOA_EXIT() return FALSE; } + +extern "C" +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowButtonsSpacing + ( JNIEnv* env, jclass cls, jobject window, jint buttonsSpacing ) +{ + JNI_COCOA_ENTER() + + NSWindow* nsWindow = getNSWindow( env, cls, window ); + if( nsWindow == NULL ) + return FALSE; + + #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 && buttonsSpacing == SPACING_LARGE ) + buttonsSpacing = SPACING_MEDIUM; + int oldButtonsSpacing = (nsWindow.toolbar != NULL) + ? ((isMacOS_11_orLater && nsWindow.toolbarStyle == NSWindowToolbarStyleUnified) + ? SPACING_LARGE + : SPACING_MEDIUM) + : SPACING_DEFAULT; + + if( buttonsSpacing == oldButtonsSpacing ) + return TRUE; + + WindowData* windowData = getWindowData( nsWindow, true ); + + [FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ +// NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); + + // add/remove toolbar + NSToolbar* toolbar = NULL; + bool needsToolbar = (buttonsSpacing != SPACING_DEFAULT); + if( needsToolbar ) { + 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 = (buttonsSpacing == SPACING_LARGE) + ? NSWindowToolbarStyleUnified + : (buttonsSpacing == SPACING_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 + // because it otherwise is shown non-transparent and hides Swing components + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + if( needsToolbar && windowData.willEnterFullScreenObserver == NULL ) { +// 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 ) { + // 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 ); +// 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 ) { + setWindowButtonsHidden( nsWindow, false ); + nsWindow.toolbar.visible = YES; + } + + windowData.lastWindowButtonAreaWidth = 0; + windowData.lastWindowTitleBarHeight = 0; + }]; + } 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; + } + } + }]; + + return TRUE; + + JNI_COCOA_EXIT() + 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 ) +{ + JNI_COCOA_ENTER() + + NSWindow* nsWindow = getNSWindow( env, cls, window ); + if( nsWindow == NULL ) + return NULL; + + WindowData* windowData = getWindowData( nsWindow, false ); + 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; + + // create and return Rectangle + return env->NewObject( cls, methodID, 0, 0, width, height ); + + JNI_COCOA_EXIT() + return NULL; +} + +int getWindowButtonAreaWidth( NSWindow* nsWindow ) { + // 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; +} + +int getWindowTitleBarHeight( NSWindow* nsWindow ) { + NSView* closeButton = [nsWindow standardWindowButton:NSWindowCloseButton]; + if( closeButton == NULL ) + return -1; + + NSView* titlebar = closeButton.superview; + return titlebar.bounds.size.height; +} + +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) isWindowFullScreen( nsWindow ); + + JNI_COCOA_EXIT() + return FALSE; +} + +bool isWindowFullScreen( NSWindow* nsWindow ) { + return ((nsWindow.styleMask & NSWindowStyleMaskFullScreen) != 0); +} + +extern "C" +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen + ( 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; +} 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 ) + } ) + } +}