From 28278a75a7e7c0b53141702dbdb43a2cf767ae23 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 20 Jan 2024 19:54:26 +0100 Subject: [PATCH] macOS fullWindowContent mode: - added title bar buttons placeholder - added client property to root pane that contains title bar buttons bounds - undone toolbar extensions from commit ea2447dcb795f2e0fb985092bac1f16a63c234d0 --- .../formdev/flatlaf/FlatClientProperties.java | 84 ++++++++ .../flatlaf/ui/FlatNativeMacLibrary.java | 11 +- .../com/formdev/flatlaf/ui/FlatPanelUI.java | 40 ++++ .../formdev/flatlaf/ui/FlatRootPaneUI.java | 47 +++-- .../formdev/flatlaf/ui/FlatToolBarBorder.java | 13 -- .../com/formdev/flatlaf/ui/FlatToolBarUI.java | 149 -------------- .../flatlaf/ui/FullWindowContentSupport.java | 183 ++++++++++++++++++ .../com/formdev/flatlaf/demo/DemoFrame.java | 92 +++++---- .../com/formdev/flatlaf/demo/DemoFrame.jfd | 110 ++++++----- .../src/main/headers/JNIUtils.h | 4 +- ..._formdev_flatlaf_ui_FlatNativeMacLibrary.h | 18 +- .../src/main/objcpp/JNIUtils.mm | 40 +++- .../src/main/objcpp/MacWindow.mm | 74 ++++--- 13 files changed, 542 insertions(+), 323 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FullWindowContentSupport.java diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java index afd33b8e..ebc393cd 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -270,6 +270,76 @@ public interface FlatClientProperties String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"; + //---- Panel -------------------------------------------------------------- + + /** + * Marks the panel as placeholder for the iconfify/maximize/close buttons + * in fullWindowContent mode. + *

+ * If fullWindowContent mode is enabled, the preferred size of the panel is equal + * to the size of the iconfify/maximize/close buttons. Otherwise is is {@code 0,0}. + *

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

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

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

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

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

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

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

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

+ * Component {@link javax.swing.JRootPane}
+ * Value type {@link java.awt.Rectangle} + * + * @since 3.4 + */ + String FULL_WINDOW_CONTENT_BUTTONS_BOUNDS = "FlatLaf.fullWindowContent.buttonsBounds"; + /** * Specifies whether the window icon should be shown in the window title bar * (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}. diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java index dad23882..6a7858b2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeMacLibrary.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.ui; +import java.awt.Rectangle; import java.awt.Window; /** @@ -54,14 +55,14 @@ public class FlatNativeMacLibrary public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor ); + /** @since 3.4 */ public static final int BUTTON_STYLE_DEFAULT = 0, BUTTON_STYLE_MEDIUM = 1, BUTTON_STYLE_LARGE = 2; - public native static boolean setWindowButtonStyle( Window window, int buttonStyle ); - public native static int getWindowButtonAreaWidth( Window window ); - public native static int getWindowTitleBarHeight( Window window ); - public native static boolean isWindowFullScreen( Window window ); - public native static boolean windowToggleFullScreen( Window window ); + /** @since 3.4 */ public native static boolean setWindowButtonStyle( Window window, int buttonStyle ); + /** @since 3.4 */ public native static Rectangle getWindowButtonsBounds( Window window ); + /** @since 3.4 */ public native static boolean isWindowFullScreen( Window window ); + /** @since 3.4 */ public native static boolean toggleWindowFullScreen( Window window ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java index c9db1fa2..f023faec 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.beans.PropertyChangeListener; 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 class FlatPanelUI 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 class FlatPanelUI 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 class FlatPanelUI 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 class FlatPanelUI 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 4f7bea46..c4bc0001 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 class FlatRootPaneUI }; root.addPropertyChangeListener( "ancestor", ancestorListener ); } + + if( SystemInfo.isMacFullWindowContentSupported ) + macFullWindowContentListener = FullWindowContentSupport.macInstallListeners( root ); } @Override @@ -223,6 +227,11 @@ public class FlatRootPaneUI root.removePropertyChangeListener( "ancestor", ancestorListener ); ancestorListener = null; } + + if( SystemInfo.isMacFullWindowContentSupported ) { + FullWindowContentSupport.macUninstallListeners( root, macFullWindowContentListener ); + macFullWindowContentListener = null; + } } /** @since 1.1.2 */ @@ -359,6 +368,10 @@ public class FlatRootPaneUI titlePane.titleBarColorsChanged(); break; + case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS: + FullWindowContentSupport.revalidatePlaceholders( rootPane ); + break; + case FlatClientProperties.GLASS_PANE_FULL_HEIGHT: rootPane.revalidate(); break; @@ -371,26 +384,30 @@ public class FlatRootPaneUI case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE: case "ancestor": if( SystemInfo.isMacFullWindowContentSupported && - SystemInfo.isJava_17_orLater && rootPane.isDisplayable() && - FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) && - FlatNativeMacLibrary.isLoaded() ) + FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) { - int buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_DEFAULT; - Object value = rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE ); - switch( String.valueOf( value ) ) { - case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_MEDIUM: - buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_MEDIUM; - break; + // set window button style + if( SystemInfo.isJava_17_orLater && FlatNativeMacLibrary.isLoaded() ) { + int buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_DEFAULT; + Object value = rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE ); + switch( String.valueOf( value ) ) { + case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_MEDIUM: + buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_MEDIUM; + break; - case "true": - case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_LARGE: - buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_LARGE; - break; + case "true": + case FlatClientProperties.MACOS_WINDOW_BUTTON_STYLE_LARGE: + buttonStyle = FlatNativeMacLibrary.BUTTON_STYLE_LARGE; + break; + } + + Window window = SwingUtilities.windowForComponent( rootPane ); + FlatNativeMacLibrary.setWindowButtonStyle( window, buttonStyle ); } - Window window = SwingUtilities.windowForComponent( rootPane ); - FlatNativeMacLibrary.setWindowButtonStyle( window, buttonStyle ); + // update buttons bounds client property + FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane ); } break; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java index f6c17873..68b9325d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java @@ -25,7 +25,6 @@ import java.awt.Rectangle; import java.util.function.Function; import javax.swing.JToolBar; import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.ToolBarUI; import com.formdev.flatlaf.util.UIScale; @@ -115,18 +114,6 @@ public class FlatToolBarBorder insets.top += gripInset; } - // on macOS, add some extra space to left side for close/minimize/zoom buttons (if necessary) - if( c instanceof JToolBar && FlatToolBarUI.isMacOSMainToolbar( (JToolBar) c ) ) { - // get button area width from macOS - int buttonBarWidth = FlatNativeMacLibrary.isLoaded() - ? FlatNativeMacLibrary.getWindowButtonAreaWidth( SwingUtilities.windowForComponent( c ) ) - : -1; - if( buttonBarWidth < 0 ) - buttonBarWidth = 68; // default width if NSWindow does not have a toolbar - - insets.left += buttonBarWidth; - } - return insets; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java index c90926b6..c6d97f5d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java @@ -19,45 +19,35 @@ package com.formdev.flatlaf.ui; import java.awt.Color; import java.awt.Component; import java.awt.Container; -import java.awt.Dimension; import java.awt.FocusTraversalPolicy; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; -import java.awt.LayoutManager; -import java.awt.LayoutManager2; import java.awt.Rectangle; -import java.awt.Window; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; -import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Enumeration; import java.util.Map; import javax.swing.AbstractButton; -import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.ButtonModel; import javax.swing.DefaultButtonModel; import javax.swing.InputMap; import javax.swing.JComboBox; import javax.swing.JComponent; -import javax.swing.JRootPane; import javax.swing.JToolBar; import javax.swing.LayoutFocusTraversalPolicy; import javax.swing.RootPaneContainer; -import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; -import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicToolBarUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; -import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; /** @@ -158,12 +148,6 @@ public class FlatToolBarUI toolBar.setFloatable( false ); } else oldFloatable = null; - - // layout manager - LayoutManager layout = createLayout(); - toolBar.setLayout( layout ); - if( layout instanceof PropertyChangeListener ) - toolBar.addPropertyChangeListener( (PropertyChangeListener) layout ); } @Override @@ -176,8 +160,6 @@ public class FlatToolBarUI toolBar.setFloatable( oldFloatable ); oldFloatable = null; } - - toolBar.setLayout( null ); } @Override @@ -471,137 +453,6 @@ public class FlatToolBarUI : null; } - /** @since 3.3 */ - protected LayoutManager createLayout() { - return new FlatToolBarLayoutManager(); - } - - /** - * Returns whether the given toolbar is used in window titlebar on macOS. - *

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

- * - * @since 3.3 - */ - public static boolean isMacOSMainToolbar( JToolBar toolBar ) { - if( !SystemInfo.isMacFullWindowContentSupported || - toolBar.getOrientation() != JToolBar.HORIZONTAL || - toolBar.getX() != 0 || - toolBar.getY() != 0 ) - return false; - - JRootPane rootPane = SwingUtilities.getRootPane( toolBar ); - if( rootPane == null ) - return false; - - if( !FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) - return false; - - for( Component p = toolBar.getParent(); p != null && !(p instanceof Window); p = p.getParent() ) { - if( p.getX() != 0 || p.getY() != 0 ) - return false; - } - - return true; - } - - //---- class FlatToolBarLayoutManager ------------------------------------- - - /** - * @since 3.3 - */ - protected class FlatToolBarLayoutManager - implements LayoutManager2, PropertyChangeListener, UIResource - { - private BoxLayout delegate; - - FlatToolBarLayoutManager() { - initBoxLayout(); - } - - private void initBoxLayout() { - delegate = new BoxLayout( toolBar, (toolBar.getOrientation() == JToolBar.HORIZONTAL) - ? BoxLayout.LINE_AXIS : BoxLayout.PAGE_AXIS ); - } - - @Override - public void addLayoutComponent( Component comp, Object constraints ) { - delegate.addLayoutComponent( comp, constraints ); - } - - @Override - public void addLayoutComponent( String name, Component comp ) { - delegate.addLayoutComponent( name, comp ); - } - - @Override - public void removeLayoutComponent( Component comp ) { - delegate.removeLayoutComponent( comp ); - } - - @Override - public Dimension preferredLayoutSize( Container parent ) { - return minimumHeightOnMacOS( delegate.preferredLayoutSize( parent ) ); - } - - @Override - public Dimension minimumLayoutSize( Container parent ) { - return minimumHeightOnMacOS( delegate.minimumLayoutSize( parent ) ); - } - - @Override - public Dimension maximumLayoutSize( Container target ) { - return minimumHeightOnMacOS( delegate.maximumLayoutSize( target ) ); - } - - private Dimension minimumHeightOnMacOS( Dimension size ) { - if( isMacOSMainToolbar( toolBar ) ) { - // get title bar height from macOS - int titleBarHeight = FlatNativeMacLibrary.isLoaded() - ? FlatNativeMacLibrary.getWindowTitleBarHeight( SwingUtilities.windowForComponent( toolBar ) ) - : -1; - if( titleBarHeight < 0 ) - titleBarHeight = 28; // default height if NSWindow does not have a toolbar - - size.height = Math.max( size.height, titleBarHeight ); - } - return size; - } - - @Override - public void layoutContainer( Container parent ) { - delegate.layoutContainer( parent ); - } - - @Override - public void invalidateLayout( Container target ) { - delegate.invalidateLayout( target ); - } - - @Override - public float getLayoutAlignmentX( Container target ) { - return delegate.getLayoutAlignmentX( target ); - } - - @Override - public float getLayoutAlignmentY( Container target ) { - return delegate.getLayoutAlignmentY( target ); - } - - @Override - public void propertyChange( PropertyChangeEvent e ) { - if( "orientation".equals( e.getPropertyName() ) ) - initBoxLayout(); - } - } - //---- class FlatToolBarFocusTraversalPolicy ------------------------------ /** diff --git a/flatlaf-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 00000000..65bfe8b9 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FullWindowContentSupport.java @@ -0,0 +1,183 @@ +/* + * Copyright 2024 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.ui; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import javax.swing.JComponent; +import javax.swing.JRootPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.util.SystemInfo; + +/** + * @author Karl Tauber + */ +class FullWindowContentSupport +{ + private static final String KEY_DEBUG_SHOW_PLACEHOLDERS = "FlatLaf.debug.panel.showPlaceholders"; + + private static ArrayList> placeholders = new ArrayList<>(); + + static Dimension getPlaceholderPreferredSize( JComponent c, String options ) { + JRootPane rootPane; + Rectangle bounds; + + if( options.startsWith( SystemInfo.isMacOS ? "mac" : "win" ) && + c.isDisplayable() && + (rootPane = SwingUtilities.getRootPane( c )) != null && + (bounds = (Rectangle) rootPane.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS )) != null ) + { + // On macOS, the client property is updated very late when toggling full screen, + // which results in "jumping" layout after full screen toggle finished. + // To avoid that, get up-to-date buttons bounds from macOS. + if( SystemInfo.isMacFullWindowContentSupported && FlatNativeMacLibrary.isLoaded() ) { + Rectangle r = FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( c ) ); + if( r != null ) + bounds = r; + } + + if( options.length() > 3 ) { + if( options.contains( "horizontal" ) ) + return new Dimension( bounds.width, 0 ); + if( options.contains( "vertical" ) ) + return new Dimension( 0, bounds.height ); + } + + return bounds.getSize(); + } + + // default to 0,0 + return new Dimension(); + } + + static void registerPlaceholder( JComponent c ) { + synchronized( placeholders ) { + if( indexOfPlaceholder( c ) < 0 ) + placeholders.add( new WeakReference<>( c ) ); + } + } + + static void unregisterPlaceholder( JComponent c ) { + synchronized( placeholders ) { + int index = indexOfPlaceholder( c ); + if( index >= 0 ) + placeholders.remove( index ); + } + } + + private static int indexOfPlaceholder( JComponent c ) { + int size = placeholders.size(); + for( int i = 0; i < size; i++ ) { + if( placeholders.get( i ).get() == c ) + return i; + } + return -1; + } + + static void revalidatePlaceholders( Component container ) { + synchronized( placeholders ) { + if( placeholders.isEmpty() ) + return; + + for( Iterator> it = placeholders.iterator(); it.hasNext(); ) { + WeakReference ref = it.next(); + JComponent c = ref.get(); + + // remove already released placeholder + if( c == null ) { + it.remove(); + continue; + } + + // revalidate placeholder if is in given container + if( SwingUtilities.isDescendingFrom( c, container ) ) + c.revalidate(); + } + } + } + + static ComponentListener macInstallListeners( JRootPane rootPane ) { + ComponentListener l = new ComponentAdapter() { + boolean lastFullScreen; + + @Override + public void componentResized( ComponentEvent e ) { + Window window = SwingUtilities.windowForComponent( rootPane ); + if( window == null ) + return; + + boolean fullScreen = FlatNativeMacLibrary.isLoaded() && FlatNativeMacLibrary.isWindowFullScreen( window ); + if( fullScreen == lastFullScreen ) + return; + + lastFullScreen = fullScreen; + macUpdateFullWindowContentButtonsBoundsProperty( rootPane ); + } + }; + + rootPane.addComponentListener( l ); + return l; + } + + static void macUninstallListeners( JRootPane rootPane, ComponentListener l ) { + if( l != null ) + rootPane.removeComponentListener( l ); + } + + static void macUpdateFullWindowContentButtonsBoundsProperty( JRootPane rootPane ) { + if( !SystemInfo.isMacFullWindowContentSupported || + !rootPane.isDisplayable() || + !FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) + return; + + Rectangle bounds = FlatNativeMacLibrary.isLoaded() + ? FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( rootPane ) ) + : new Rectangle( 68, 28 ); // default size + rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds ); + } + + static void debugPaint( Graphics g, JComponent c ) { + if( !UIManager.getBoolean( KEY_DEBUG_SHOW_PLACEHOLDERS ) ) + return; + + int width = c.getWidth() - 1; + int height = c.getHeight() - 1; + if( width <= 0 || height <= 0 ) + return; + + g.setColor( Color.red ); + g.drawRect( 0, 0, width, height ); + + // draw diagonal cross + Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g ); + g.drawLine( 0, 0, width, height ); + g.drawLine( 0, height, width, 0 ); + FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); + } +} diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java index c48dc887..c982a2de 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java @@ -102,6 +102,9 @@ class DemoFrame rootPane.putClientProperty( "apple.awt.windowTitleVisible", false ); else setTitle( null ); + + // uncomment this line to see title bar buttons placeholders in fullWindowContent mode +// UIManager.put( "FlatLaf.debug.panel.showPlaceholders", true ); } // enable full screen mode for this window (for Java 8 - 10; not necessary for Java 11+) @@ -509,6 +512,8 @@ class DemoFrame 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 @@ class DemoFrame } setJMenuBar(menuBar1); - //======== toolBar ======== + //======== toolBarPanel ======== { - toolBar.setMargin(new Insets(3, 3, 3, 3)); + toolBarPanel.setLayout(new BorderLayout()); - //---- backButton ---- - backButton.setToolTipText("Back"); - backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg")); - toolBar.add(backButton); + //======== macFullWindowContentButtonsPlaceholder ======== + { + macFullWindowContentButtonsPlaceholder.setLayout(new FlowLayout()); + } + toolBarPanel.add(macFullWindowContentButtonsPlaceholder, BorderLayout.WEST); - //---- forwardButton ---- - forwardButton.setToolTipText("Forward"); - forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg")); - toolBar.add(forwardButton); - toolBar.addSeparator(); + //======== toolBar ======== + { + toolBar.setMargin(new Insets(3, 3, 3, 3)); - //---- cutButton ---- - cutButton.setToolTipText("Cut"); - cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg")); - toolBar.add(cutButton); + //---- backButton ---- + backButton.setToolTipText("Back"); + backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg")); + toolBar.add(backButton); - //---- copyButton ---- - copyButton.setToolTipText("Copy"); - copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg")); - toolBar.add(copyButton); + //---- forwardButton ---- + forwardButton.setToolTipText("Forward"); + forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg")); + toolBar.add(forwardButton); + toolBar.addSeparator(); - //---- pasteButton ---- - pasteButton.setToolTipText("Paste"); - pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg")); - toolBar.add(pasteButton); - toolBar.addSeparator(); + //---- cutButton ---- + cutButton.setToolTipText("Cut"); + cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg")); + toolBar.add(cutButton); - //---- refreshButton ---- - refreshButton.setToolTipText("Refresh"); - refreshButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/refresh.svg")); - toolBar.add(refreshButton); - toolBar.addSeparator(); + //---- copyButton ---- + copyButton.setToolTipText("Copy"); + copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg")); + toolBar.add(copyButton); - //---- showToggleButton ---- - showToggleButton.setSelected(true); - showToggleButton.setToolTipText("Show Details"); - showToggleButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg")); - toolBar.add(showToggleButton); + //---- pasteButton ---- + pasteButton.setToolTipText("Paste"); + pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg")); + toolBar.add(pasteButton); + toolBar.addSeparator(); + + //---- refreshButton ---- + refreshButton.setToolTipText("Refresh"); + refreshButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/refresh.svg")); + toolBar.add(refreshButton); + toolBar.addSeparator(); + + //---- showToggleButton ---- + showToggleButton.setSelected(true); + showToggleButton.setToolTipText("Show Details"); + showToggleButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg")); + toolBar.add(showToggleButton); + } + toolBarPanel.add(toolBar, BorderLayout.CENTER); } - contentPane.add(toolBar, BorderLayout.NORTH); + contentPane.add(toolBarPanel, BorderLayout.NORTH); //======== contentPanel ======== { @@ -963,7 +980,7 @@ class DemoFrame } ); if( SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() ) { showToggleButton.addActionListener( e -> { - FlatNativeMacLibrary.windowToggleFullScreen( this ); + FlatNativeMacLibrary.toggleWindowFullScreen( this ); } ); } @@ -1007,6 +1024,9 @@ class DemoFrame if( "false".equals( System.getProperty( "flatlaf.animatedLafChange" ) ) ) animatedLafChangeMenuItem.setSelected( false ); + // on macOS, panel left to toolBar is a placeholder for title bar buttons in fullWindowContent mode + macFullWindowContentButtonsPlaceholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" ); + // 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 ba3f2d4f..f37f92ca 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 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: "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" ) + 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 2de80956..667810cf 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 f962f645..0e39445c 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/headers/com_formdev_flatlaf_ui_FlatNativeMacLibrary.h @@ -31,18 +31,10 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary - * Method: getWindowButtonAreaWidth - * Signature: (Ljava/awt/Window;)I + * Method: getWindowButtonsBounds + * Signature: (Ljava/awt/Window;)Ljava/awt/Rectangle; */ -JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonAreaWidth - (JNIEnv *, jclass, jobject); - -/* - * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary - * Method: getWindowTitleBarHeight - * Signature: (Ljava/awt/Window;)I - */ -JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowTitleBarHeight +JNIEXPORT jobject JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonsBounds (JNIEnv *, jclass, jobject); /* @@ -55,10 +47,10 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWi /* * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary - * Method: windowToggleFullScreen + * Method: toggleWindowFullScreen * Signature: (Ljava/awt/Window;)Z */ -JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_windowToggleFullScreen +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen (JNIEnv *, jclass, jobject); #ifdef __cplusplus diff --git a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/JNIUtils.mm index 7c4e798d..089881fe 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 5fc8a285..2edd4cc6 100644 --- a/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm +++ b/flatlaf-natives/flatlaf-natives-macos/src/main/objcpp/MacWindow.mm @@ -51,9 +51,9 @@ NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) { return NULL; // initialize field IDs (done only once because variables are static) - static jfieldID peerID = getFieldID( env, "java/awt/Component", "peer", "Ljava/awt/peer/ComponentPeer;" ); - static jfieldID platformWindowID = getFieldID( env, "sun/lwawt/LWWindowPeer", "platformWindow", "Lsun/lwawt/PlatformWindow;" ); - static jfieldID ptrID = getFieldID( env, "sun/lwawt/macosx/CFRetainedResource", "ptr", "J" ); + static jfieldID peerID = getFieldID( env, "java/awt/Component", "peer", "Ljava/awt/peer/ComponentPeer;", false ); + static jfieldID platformWindowID = getFieldID( env, "sun/lwawt/LWWindowPeer", "platformWindow", "Lsun/lwawt/PlatformWindow;", false ); + static jfieldID ptrID = getFieldID( env, "sun/lwawt/macosx/CFRetainedResource", "ptr", "J", false ); if( peerID == NULL || platformWindowID == NULL || ptrID == NULL ) return NULL; @@ -148,7 +148,7 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW WindowData* windowData = getWindowData( nsWindow, true ); - [FlatJNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ + [FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){ // NSLog( @"\n%@\n\n", [nsWindow.contentView.superview _subtreeDescription] ); // add/remove toolbar @@ -222,29 +222,48 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW } extern "C" -JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonAreaWidth +JNIEXPORT jobject JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowButtonsBounds ( JNIEnv* env, jclass cls, jobject window ) { JNI_COCOA_ENTER() NSWindow* nsWindow = getNSWindow( env, cls, window ); if( nsWindow == NULL ) - return -1; + return NULL; - // return zero if window is full screen because close/minimize/zoom buttons are hidden - if( isWindowFullScreen( nsWindow ) ) - return 0; - - // use remembered value if window is in transition from full screen to non-full screen - // because NSToolbar is not yet visible WindowData* windowData = getWindowData( nsWindow, false ); - if( windowData != NULL && windowData.lastWindowButtonAreaWidth > 0 ) - return windowData.lastWindowButtonAreaWidth; + int width = 0; + int height = 0; - return getWindowButtonAreaWidth( nsWindow ); + // 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 -1; + return NULL; } int getWindowButtonAreaWidth( NSWindow* nsWindow ) { @@ -279,27 +298,6 @@ int getWindowButtonAreaWidth( NSWindow* nsWindow ) { return right + left; } -extern "C" -JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWindowTitleBarHeight - ( JNIEnv* env, jclass cls, jobject window ) -{ - JNI_COCOA_ENTER() - - NSWindow* nsWindow = getNSWindow( env, cls, window ); - if( nsWindow == NULL ) - return -1; - - // use remembered value if window is full screen because NSToolbar is hidden - WindowData* windowData = getWindowData( nsWindow, false ); - if( windowData != NULL && windowData.lastWindowTitleBarHeight > 0 ) - return windowData.lastWindowTitleBarHeight; - - return getWindowTitleBarHeight( nsWindow ); - - JNI_COCOA_EXIT() - return -1; -} - int getWindowTitleBarHeight( NSWindow* nsWindow ) { NSView* closeButton = [nsWindow standardWindowButton:NSWindowCloseButton]; if( closeButton == NULL ) @@ -330,7 +328,7 @@ bool isWindowFullScreen( NSWindow* nsWindow ) { } extern "C" -JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_windowToggleFullScreen +JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen ( JNIEnv* env, jclass cls, jobject window ) { JNI_COCOA_ENTER()