Merge PR #779: macOS: window title bar close/minimize/zoom buttons spacing

This commit is contained in:
Karl Tauber
2024-01-25 14:06:38 +01:00
16 changed files with 1481 additions and 100 deletions

View File

@@ -5,6 +5,9 @@ FlatLaf Change Log
#### New features and improvements #### 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 - Native libraries: System property `flatlaf.nativeLibraryPath` now supports
loading native libraries named the same as on Maven central. Improved log loading native libraries named the same as on Maven central. Improved log
messages for loading fails. messages for loading fails.

View File

@@ -270,6 +270,80 @@ public interface FlatClientProperties
String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"; String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption";
//---- Panel --------------------------------------------------------------
/**
* Marks the panel as placeholder for the iconfify/maximize/close buttons
* in fullWindowContent mode.
* <p>
* 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}.
* <p>
* 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.
* <p>
* Syntax of the value string is: {@code "win|mac [horizontal|vertical] [zeroInFullScreen] [leftToRight|rightToLeft]"}.
* <p>
* 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}.
* <p>
* Optionally, you can append following options to the value string (separated by space characters):
* <ul>
* <li>{@code "horizontal"} - preferred height is zero
* <li>{@code "vertical"} - preferred width is zero
* <li>{@code "zeroInFullScreen"} - in full-screen mode on macOS, preferred size is {@code 0,0}
* <li>{@code "leftToRight"} - in right-to-left component orientation, preferred size is {@code 0,0}
* <li>{@code "rightToLeft"} - in left-to-right component orientation, preferred size is {@code 0,0}
* </ul>
*
* Example for adding placeholder to top-left corner on macOS:
* <pre>{@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 );
* }</pre>
*
* Or add placeholder as first item to the tool bar:
* <pre>{@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 );
* }</pre>
*
* If a tabbed pane is located at the top, you can add the placeholder
* as leading component to that tabbed pane:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* tabbedPane.putClientProperty( FlatClientProperties.TABBED_PANE_LEADING_COMPONENT, placeholder );
* }</pre>
* <p>
* <strong>Component</strong> {@link javax.swing.JPanel}<br>
* <strong>Value type</strong> {@link java.lang.String}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER = "FlatLaf.fullWindowContent.buttonsPlaceholder";
//---- Popup -------------------------------------------------------------- //---- Popup --------------------------------------------------------------
/** /**
@@ -388,6 +462,20 @@ public interface FlatClientProperties
*/ */
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"; 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}.
* <p>
* <b>Note</b>: Do not set this client property. It is set by FlatLaf.
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@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 * Specifies whether the window icon should be shown in the window title bar
* (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}. * (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}.
@@ -1263,6 +1351,44 @@ public interface FlatClientProperties
String TREE_PAINT_SELECTION = "JTree.paintSelection"; String TREE_PAINT_SELECTION = "JTree.paintSelection";
//---- macOS --------------------------------------------------------------
/**
* Specifies the spacing around the macOS window close/minimize/zoom buttons.
* Useful if <a href="https://www.formdev.com/flatlaf/macos/#full_window_content">full window content</a>
* is enabled.
* <p>
* (requires macOS 10.14+ for "medium" spacing and macOS 11+ for "large" spacing, requires Java 17+)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@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.
* <p>
* (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 ----------------------------------------------------- //---- helper methods -----------------------------------------------------
/** /**

View File

@@ -16,7 +16,9 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Rectangle;
import java.awt.Window; import java.awt.Window;
import com.formdev.flatlaf.util.SystemInfo;
/** /**
* Native methods for macOS. * Native methods for macOS.
@@ -49,8 +51,19 @@ public class FlatNativeMacLibrary
* method of this class. Otherwise, the native library may not be loaded. * method of this class. Otherwise, the native library may not be loaded.
*/ */
public static boolean isLoaded() { 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 ); 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 );
} }

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
@@ -23,6 +24,7 @@ import java.beans.PropertyChangeListener;
import java.util.Map; import java.util.Map;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI; import javax.swing.plaf.basic.BasicPanelUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
@@ -69,6 +71,8 @@ public class FlatPanelUI
super.installUI( c ); super.installUI( c );
c.addPropertyChangeListener( this ); c.addPropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.registerPlaceholder( c );
installStyle( (JPanel) c ); installStyle( (JPanel) c );
} }
@@ -78,10 +82,20 @@ public class FlatPanelUI
super.uninstallUI( c ); super.uninstallUI( c );
c.removePropertyChangeListener( this ); c.removePropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.unregisterPlaceholder( c );
oldStyleValues = null; 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 */ /** @since 2.0.1 */
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
@@ -98,6 +112,17 @@ public class FlatPanelUI
c.revalidate(); c.revalidate();
c.repaint(); c.repaint();
break; 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 ); 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 );
}
} }

View File

@@ -89,6 +89,7 @@ public class FlatRootPaneUI
private LayoutManager oldLayout; private LayoutManager oldLayout;
private PropertyChangeListener ancestorListener; private PropertyChangeListener ancestorListener;
private ComponentListener componentListener; private ComponentListener componentListener;
private ComponentListener macFullWindowContentListener;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatRootPaneUI(); return new FlatRootPaneUI();
@@ -207,6 +208,9 @@ public class FlatRootPaneUI
}; };
root.addPropertyChangeListener( "ancestor", ancestorListener ); root.addPropertyChangeListener( "ancestor", ancestorListener );
} }
if( SystemInfo.isMacFullWindowContentSupported )
macFullWindowContentListener = FullWindowContentSupport.macInstallListeners( root );
} }
@Override @Override
@@ -223,6 +227,11 @@ public class FlatRootPaneUI
root.removePropertyChangeListener( "ancestor", ancestorListener ); root.removePropertyChangeListener( "ancestor", ancestorListener );
ancestorListener = null; ancestorListener = null;
} }
if( SystemInfo.isMacFullWindowContentSupported ) {
FullWindowContentSupport.macUninstallListeners( root, macFullWindowContentListener );
macFullWindowContentListener = null;
}
} }
/** @since 1.1.2 */ /** @since 1.1.2 */
@@ -359,6 +368,10 @@ public class FlatRootPaneUI
titlePane.titleBarColorsChanged(); titlePane.titleBarColorsChanged();
break; break;
case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS:
FullWindowContentSupport.revalidatePlaceholders( rootPane );
break;
case FlatClientProperties.GLASS_PANE_FULL_HEIGHT: case FlatClientProperties.GLASS_PANE_FULL_HEIGHT:
rootPane.revalidate(); rootPane.revalidate();
break; break;
@@ -367,6 +380,50 @@ public class FlatRootPaneUI
if( rootPane.isDisplayable() ) if( rootPane.isDisplayable() )
throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." ); throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." );
break; 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;
} }
} }

View File

@@ -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<WeakReference<JComponent>> 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<WeakReference<JComponent>> it = placeholders.iterator(); it.hasNext(); ) {
WeakReference<JComponent> 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 );
}
}

View File

@@ -46,6 +46,7 @@ import com.formdev.flatlaf.icons.FlatAbstractIcon;
import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.formdev.flatlaf.extras.FlatSVGUtils; import com.formdev.flatlaf.extras.FlatSVGUtils;
import com.formdev.flatlaf.ui.FlatNativeMacLibrary;
import com.formdev.flatlaf.util.ColorFunctions; import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.FontUtils; import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.LoggingFacade; 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 // do not use HTML text in menu items because this is not supported in macOS screen menu
htmlMenuItem.setText( "some text" ); htmlMenuItem.setText( "some text" );
JRootPane rootPane = getRootPane();
if( SystemInfo.isMacFullWindowContentSupported ) { if( SystemInfo.isMacFullWindowContentSupported ) {
// expand window content into window title bar and make title bar transparent // expand window content into window title bar and make title bar transparent
getRootPane().putClientProperty( "apple.awt.fullWindowContent", true ); rootPane.putClientProperty( "apple.awt.fullWindowContent", true );
getRootPane().putClientProperty( "apple.awt.transparentTitleBar", true ); rootPane.putClientProperty( "apple.awt.transparentTitleBar", true );
rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE );
// hide window title // hide window title
if( SystemInfo.isJava_17_orLater ) if( SystemInfo.isJava_17_orLater )
getRootPane().putClientProperty( "apple.awt.windowTitleVisible", false ); rootPane.putClientProperty( "apple.awt.windowTitleVisible", false );
else else
setTitle( null ); setTitle( null );
// add gap to left side of toolbar // uncomment this line to see title bar buttons placeholders in fullWindowContent mode
toolBar.add( Box.createHorizontalStrut( 70 ), 0 ); // UIManager.put( "FlatLaf.debug.panel.showPlaceholders", true );
} }
// enable full screen mode for this window (for Java 8 - 10; not necessary for Java 11+) // enable full screen mode for this window (for Java 8 - 10; not necessary for Java 11+)
if( !SystemInfo.isJava_11_orLater ) if( !SystemInfo.isJava_11_orLater )
getRootPane().putClientProperty( "apple.awt.fullscreenable", true ); rootPane.putClientProperty( "apple.awt.fullscreenable", true );
} }
// integrate into macOS screen menu // integrate into macOS screen menu
@@ -509,6 +512,8 @@ class DemoFrame
JMenuItem showUIDefaultsInspectorMenuItem = new JMenuItem(); JMenuItem showUIDefaultsInspectorMenuItem = new JMenuItem();
JMenu helpMenu = new JMenu(); JMenu helpMenu = new JMenu();
aboutMenuItem = new JMenuItem(); aboutMenuItem = new JMenuItem();
JPanel toolBarPanel = new JPanel();
JPanel macFullWindowContentButtonsPlaceholder = new JPanel();
toolBar = new JToolBar(); toolBar = new JToolBar();
JButton backButton = new JButton(); JButton backButton = new JButton();
JButton forwardButton = new JButton(); JButton forwardButton = new JButton();
@@ -825,50 +830,62 @@ class DemoFrame
} }
setJMenuBar(menuBar1); setJMenuBar(menuBar1);
//======== toolBar ======== //======== toolBarPanel ========
{ {
toolBar.setMargin(new Insets(3, 3, 3, 3)); toolBarPanel.setLayout(new BorderLayout());
//---- backButton ---- //======== macFullWindowContentButtonsPlaceholder ========
backButton.setToolTipText("Back"); {
backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg")); macFullWindowContentButtonsPlaceholder.setLayout(new FlowLayout());
toolBar.add(backButton); }
toolBarPanel.add(macFullWindowContentButtonsPlaceholder, BorderLayout.WEST);
//---- forwardButton ---- //======== toolBar ========
forwardButton.setToolTipText("Forward"); {
forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg")); toolBar.setMargin(new Insets(3, 3, 3, 3));
toolBar.add(forwardButton);
toolBar.addSeparator();
//---- cutButton ---- //---- backButton ----
cutButton.setToolTipText("Cut"); backButton.setToolTipText("Back");
cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg")); backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg"));
toolBar.add(cutButton); toolBar.add(backButton);
//---- copyButton ---- //---- forwardButton ----
copyButton.setToolTipText("Copy"); forwardButton.setToolTipText("Forward");
copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg")); forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg"));
toolBar.add(copyButton); toolBar.add(forwardButton);
toolBar.addSeparator();
//---- pasteButton ---- //---- cutButton ----
pasteButton.setToolTipText("Paste"); cutButton.setToolTipText("Cut");
pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg")); cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg"));
toolBar.add(pasteButton); toolBar.add(cutButton);
toolBar.addSeparator();
//---- refreshButton ---- //---- copyButton ----
refreshButton.setToolTipText("Refresh"); copyButton.setToolTipText("Copy");
refreshButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/refresh.svg")); copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg"));
toolBar.add(refreshButton); toolBar.add(copyButton);
toolBar.addSeparator();
//---- showToggleButton ---- //---- pasteButton ----
showToggleButton.setSelected(true); pasteButton.setToolTipText("Paste");
showToggleButton.setToolTipText("Show Details"); pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg"));
showToggleButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg")); toolBar.add(pasteButton);
toolBar.add(showToggleButton); 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 ======== //======== contentPanel ========
{ {
@@ -903,6 +920,70 @@ class DemoFrame
buttonGroup1.add(radioButtonMenuItem3); buttonGroup1.add(radioButtonMenuItem3);
// JFormDesigner - End of component initialization //GEN-END:initComponents // 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 // add "Users" button to menubar
FlatButton usersButton = new FlatButton(); FlatButton usersButton = new FlatButton();
usersButton.setIcon( new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/users.svg" ) ); usersButton.setIcon( new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/users.svg" ) );
@@ -943,6 +1024,9 @@ class DemoFrame
if( "false".equals( System.getProperty( "flatlaf.animatedLafChange" ) ) ) if( "false".equals( System.getProperty( "flatlaf.animatedLafChange" ) ) )
animatedLafChangeMenuItem.setSelected( false ); 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 // remove contentPanel bottom insets
MigLayout layout = (MigLayout) contentPanel.getLayout(); MigLayout layout = (MigLayout) contentPanel.getLayout();
LC lc = ConstraintParser.parseLayoutConstraint( (String) layout.getLayoutConstraints() ); LC lc = ConstraintParser.parseLayoutConstraint( (String) layout.getLayoutConstraints() );

View File

@@ -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 { new FormModel {
contentType: "form/swing" contentType: "form/swing"
@@ -12,56 +12,66 @@ new FormModel {
"defaultCloseOperation": 2 "defaultCloseOperation": 2
"$locationPolicy": 2 "$locationPolicy": 2
"$sizePolicy": 2 "$sizePolicy": 2
add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "toolBar" name: "toolBarPanel"
"margin": new java.awt.Insets( 3, 3, 3, 3 ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) {
auxiliary() { name: "macFullWindowContentButtonsPlaceholder"
"JavaCodeGenerator.variableLocal": false }, new FormLayoutConstraints( class java.lang.String ) {
} "value": "West"
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" ) { add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) {
name: "forwardButton" name: "toolBar"
"toolTipText": "Forward" "margin": new java.awt.Insets( 3, 3, 3, 3 )
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/forward.svg" ) auxiliary() {
} ) "JavaCodeGenerator.variableLocal": false
add( new FormComponent( "javax.swing.JToolBar$Separator" ) { }
name: "separator5" add( new FormComponent( "javax.swing.JButton" ) {
} ) name: "backButton"
add( new FormComponent( "javax.swing.JButton" ) { "toolTipText": "Back"
name: "cutButton" "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/back.svg" )
"toolTipText": "Cut" } )
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-cut.svg" ) add( new FormComponent( "javax.swing.JButton" ) {
} ) name: "forwardButton"
add( new FormComponent( "javax.swing.JButton" ) { "toolTipText": "Forward"
name: "copyButton" "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/forward.svg" )
"toolTipText": "Copy" } )
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/copy.svg" ) add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
} ) name: "separator5"
add( new FormComponent( "javax.swing.JButton" ) { } )
name: "pasteButton" add( new FormComponent( "javax.swing.JButton" ) {
"toolTipText": "Paste" name: "cutButton"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-paste.svg" ) "toolTipText": "Cut"
} ) "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-cut.svg" )
add( new FormComponent( "javax.swing.JToolBar$Separator" ) { } )
name: "separator6" add( new FormComponent( "javax.swing.JButton" ) {
} ) name: "copyButton"
add( new FormComponent( "javax.swing.JButton" ) { "toolTipText": "Copy"
name: "refreshButton" "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/copy.svg" )
"toolTipText": "Refresh" } )
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/refresh.svg" ) add( new FormComponent( "javax.swing.JButton" ) {
} ) name: "pasteButton"
add( new FormComponent( "javax.swing.JToolBar$Separator" ) { "toolTipText": "Paste"
name: "separator7" "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-paste.svg" )
} ) } )
add( new FormComponent( "javax.swing.JToggleButton" ) { add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
name: "showToggleButton" name: "separator6"
"selected": true } )
"toolTipText": "Show Details" add( new FormComponent( "javax.swing.JButton" ) {
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/show.svg" ) 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 ) { }, new FormLayoutConstraints( class java.lang.String ) {
"value": "North" "value": "North"

View File

@@ -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 );

View File

@@ -7,6 +7,12 @@
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #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 * Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
* Method: setWindowRoundedBorder * Method: setWindowRoundedBorder
@@ -15,6 +21,38 @@ extern "C" {
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowRoundedBorder JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowRoundedBorder
(JNIEnv *, jclass, jobject, jfloat, jfloat, jint); (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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -21,8 +21,8 @@
* @author Karl Tauber * @author Karl Tauber
*/ */
jfieldID getFieldID( JNIEnv *env, const char* className, const char* fieldName, const char* fieldSignature ) { jclass findClass( JNIEnv *env, const char* className, bool globalRef ) {
// NSLog( @"getFieldID %s %s %s", className, fieldName, fieldSignature ); // NSLog( @"findClass %s", className );
jclass cls = env->FindClass( className ); jclass cls = env->FindClass( className );
if( cls == NULL ) { if( cls == NULL ) {
@@ -32,7 +32,22 @@ jfieldID getFieldID( JNIEnv *env, const char* className, const char* fieldName,
return NULL; return NULL;
} }
jfieldID fieldID = env->GetFieldID( cls, fieldName, fieldSignature ); if( globalRef )
cls = reinterpret_cast<jclass>( 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 ) { if( fieldID == NULL ) {
NSLog( @"FlatLaf: failed to lookup field '%s' of type '%s' in class '%s'", fieldName, fieldSignature, className ); NSLog( @"FlatLaf: failed to lookup field '%s' of type '%s' in class '%s'", fieldName, fieldSignature, className );
env->ExceptionDescribe(); // print stack trace env->ExceptionDescribe(); // print stack trace
@@ -42,3 +57,22 @@ jfieldID getFieldID( JNIEnv *env, const char* className, const char* fieldName,
return fieldID; 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;
}

View File

@@ -15,6 +15,7 @@
*/ */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#import <jni.h> #import <jni.h>
#import "JNIUtils.h" #import "JNIUtils.h"
#import "JNFRunLoop.h" #import "JNFRunLoop.h"
@@ -24,14 +25,37 @@
* @author Karl Tauber * @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 ) { NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) {
if( window == NULL ) if( window == NULL )
return NULL; return NULL;
// initialize field IDs (done only once because fields are static) // initialize field IDs (done only once because variables are static)
static jfieldID peerID = getFieldID( env, "java/awt/Component", "peer", "Ljava/awt/peer/ComponentPeer;" ); static jfieldID peerID = getFieldID( env, "java/awt/Component", "peer", "Ljava/awt/peer/ComponentPeer;", false );
static jfieldID platformWindowID = getFieldID( env, "sun/lwawt/LWWindowPeer", "platformWindow", "Lsun/lwawt/PlatformWindow;" ); static jfieldID platformWindowID = getFieldID( env, "sun/lwawt/LWWindowPeer", "platformWindow", "Lsun/lwawt/PlatformWindow;", false );
static jfieldID ptrID = getFieldID( env, "sun/lwawt/macosx/CFRetainedResource", "ptr", "J" ); static jfieldID ptrID = getFieldID( env, "sun/lwawt/macosx/CFRetainedResource", "ptr", "J", false );
if( peerID == NULL || platformWindowID == NULL || ptrID == NULL ) if( peerID == NULL || platformWindowID == NULL || ptrID == NULL )
return NULL; return NULL;
@@ -49,6 +73,16 @@ NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) {
return (NSWindow *) jlong_to_ptr( env->GetLongField( platformWindow, ptrID ) ); 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" extern "C"
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowRoundedBorder JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setWindowRoundedBorder
( JNIEnv* env, jclass cls, jobject window, jfloat radius, jfloat borderWidth, jint borderColor ) ( JNIEnv* env, jclass cls, jobject window, jfloat radius, jfloat borderWidth, jint borderColor )
@@ -87,3 +121,259 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW
JNI_COCOA_EXIT() JNI_COCOA_EXIT()
return FALSE; 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, "<init>", "(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;
}

View File

@@ -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
}

View File

@@ -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 )
} )
}
}