Native window decorations:

- API to check whether current platform supports window decorations `FlatLaf.supportsNativeWindowDecorations()`
- API to toggle window decorations of all windows `FlatLaf.setUseNativeWindowDecorations(boolean)`
- `FlatClientProperties.USE_WINDOW_DECORATIONS` can now used to toggle window decorations for single window
- cleaned-up/fixed/simplified window decorations "enabled" checking:
  1. if `FlatSystemProperties.USE_WINDOW_DECORATIONS` is set, its value is used
  2. if `FlatClientProperties.USE_WINDOW_DECORATIONS` is set, its value is used
  3. use value of UI default `TitlePane.useWindowDecorations`
This commit is contained in:
Karl Tauber
2021-04-03 11:13:57 +02:00
parent b47e0c88d6
commit 6addb5c4b4
10 changed files with 168 additions and 71 deletions

View File

@@ -3,6 +3,13 @@ FlatLaf Change Log
## 1.2-SNAPSHOT ## 1.2-SNAPSHOT
#### New features and improvements
- Native window decorations: Added API to check whether current platform
supports window decorations (`FlatLaf.supportsNativeWindowDecorations()`) and
to toggle window decorations of all windows
(`FlatLaf.setUseNativeWindowDecorations(boolean)`).
#### Fixed bugs #### Fixed bugs
- Native window decorations: Fixed loading of native library when using Java - Native window decorations: Fixed loading of native library when using Java

View File

@@ -233,10 +233,14 @@ public interface FlatClientProperties
/** /**
* Specifies whether FlatLaf native window decorations should be used * Specifies whether FlatLaf native window decorations should be used
* when creating {@code JFrame} or {@code JDialog}. * for {@code JFrame} or {@code JDialog}.
* <p> * <p>
* Setting this to {@code false} disables using FlatLaf native window decorations * Setting this enables/disables using FlatLaf native window decorations
* for the window that contains the root pane. Needs to be set before showing the window. * for the window that contains the root pane.
* <p>
* This client property has lower priority than system property
* {@link FlatSystemProperties#USE_WINDOW_DECORATIONS}, but higher priority
* than UI default {@code TitlePane.useWindowDecorations}.
* <p> * <p>
* (requires Window 10) * (requires Window 10)
* <p> * <p>

View File

@@ -46,6 +46,7 @@ import javax.swing.JDialog;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.PopupFactory; import javax.swing.PopupFactory;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIDefaults; import javax.swing.UIDefaults;
import javax.swing.UIDefaults.ActiveValue; import javax.swing.UIDefaults.ActiveValue;
@@ -59,6 +60,7 @@ import javax.swing.text.StyleContext;
import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder; import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.ui.FlatPopupFactory; import com.formdev.flatlaf.ui.FlatPopupFactory;
import com.formdev.flatlaf.ui.FlatRootPaneUI;
import com.formdev.flatlaf.util.GrayFilter; import com.formdev.flatlaf.util.GrayFilter;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.MultiResolutionImageSupport;
@@ -715,6 +717,79 @@ public abstract class FlatLaf
} ); } );
} }
/**
* Returns whether native window decorations are supported on current platform.
* <p>
* This requires Windows 10, but may be disabled if running in special environments
* (JetBrains Projector, Webswing or WinPE) or if loading native library fails.
* If system property {@link FlatSystemProperties#USE_WINDOW_DECORATIONS} is set to
* {@code false}, then this method also returns {@code false}.
*
* @since 1.1.2
*/
public static boolean supportsNativeWindowDecorations() {
return SystemInfo.isWindows_10_orLater && FlatNativeWindowBorder.isSupported();
}
/**
* Returns whether native window decorations are enabled.
*
* @since 1.1.2
*/
public static boolean isUseNativeWindowDecorations() {
return UIManager.getBoolean( "TitlePane.useWindowDecorations" );
}
/**
* Sets whether native window decorations are enabled.
* <p>
* Existing frames and dialogs will be updated.
*
* @since 1.1.2
*/
public static void setUseNativeWindowDecorations( boolean enabled ) {
UIManager.put( "TitlePane.useWindowDecorations", enabled );
if( !(UIManager.getLookAndFeel() instanceof FlatLaf) )
return;
// update existing frames and dialogs
for( Window w : Window.getWindows() ) {
if( isDisplayableFrameOrDialog( w ) )
FlatRootPaneUI.updateNativeWindowBorder( ((RootPaneContainer)w).getRootPane() );
}
}
/**
* Revalidate and repaint all displayable frames and dialogs.
*
* @since 1.1.2
*/
public static void revalidateAndRepaintAllFramesAndDialogs() {
for( Window w : Window.getWindows() ) {
if( isDisplayableFrameOrDialog( w ) ) {
w.revalidate();
w.repaint();
}
}
}
/**
* Repaint all displayable frames and dialogs.
*
* @since 1.1.2
*/
public static void repaintAllFramesAndDialogs() {
for( Window w : Window.getWindows() ) {
if( isDisplayableFrameOrDialog( w ) )
w.repaint();
}
}
private static boolean isDisplayableFrameOrDialog( Window w ) {
return w.isDisplayable() && (w instanceof JFrame || w instanceof JDialog);
}
public static boolean isShowMnemonics() { public static boolean isShowMnemonics() {
return MnemonicHandler.isShowMnemonics(); return MnemonicHandler.isShowMnemonics();
} }

View File

@@ -63,6 +63,10 @@ public interface FlatSystemProperties
* <p> * <p>
* Setting this to {@code false} disables using FlatLaf native window decorations. * Setting this to {@code false} disables using FlatLaf native window decorations.
* <p> * <p>
* This system property has higher priority than client property
* {@link FlatClientProperties#USE_WINDOW_DECORATIONS} and
* UI default {@code TitlePane.useWindowDecorations}.
* <p>
* (requires Window 10) * (requires Window 10)
* <p> * <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br> * <strong>Allowed Values</strong> {@code false} and {@code true}<br>

View File

@@ -99,18 +99,12 @@ public class FlatNativeWindowBorder
if( window instanceof JFrame ) { if( window instanceof JFrame ) {
JFrame frame = (JFrame) window; JFrame frame = (JFrame) window;
JRootPane rootPane = frame.getRootPane();
// check whether disabled via client property // check whether disabled via system property, client property or UI default
if( !FlatClientProperties.clientPropertyBoolean( frame.getRootPane(), FlatClientProperties.USE_WINDOW_DECORATIONS, true ) ) if( !useWindowDecorations( rootPane, systemPropertyKey ) )
return; return;
// do not enable native window border if JFrame should use system window decorations
// and if not forced to use FlatLaf/JBR native window decorations
if( !JFrame.isDefaultLookAndFeelDecorated() &&
!UIManager.getBoolean( "TitlePane.useWindowDecorations" ) &&
!FlatSystemProperties.getBoolean( systemPropertyKey, false ) )
return;
// do not enable native window border if frame is undecorated // do not enable native window border if frame is undecorated
if( frame.isUndecorated() ) if( frame.isUndecorated() )
return; return;
@@ -119,22 +113,16 @@ public class FlatNativeWindowBorder
setHasCustomDecoration( frame, true ); setHasCustomDecoration( frame, true );
// enable Swing window decoration // enable Swing window decoration
frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME ); rootPane.setWindowDecorationStyle( JRootPane.FRAME );
} else if( window instanceof JDialog ) { } else if( window instanceof JDialog ) {
JDialog dialog = (JDialog) window; JDialog dialog = (JDialog) window;
JRootPane rootPane = dialog.getRootPane();
// check whether disabled via client property // check whether disabled via system property, client property or UI default
if( !FlatClientProperties.clientPropertyBoolean( dialog.getRootPane(), FlatClientProperties.USE_WINDOW_DECORATIONS, true ) ) if( !useWindowDecorations( rootPane, systemPropertyKey ) )
return; return;
// do not enable native window border if JDialog should use system window decorations
// and if not forced to use FlatLaf/JBR native window decorations
if( !JDialog.isDefaultLookAndFeelDecorated() &&
!UIManager.getBoolean( "TitlePane.useWindowDecorations" ) &&
!FlatSystemProperties.getBoolean( systemPropertyKey, false ) )
return;
// do not enable native window border if dialog is undecorated // do not enable native window border if dialog is undecorated
if( dialog.isUndecorated() ) if( dialog.isUndecorated() )
return; return;
@@ -143,7 +131,7 @@ public class FlatNativeWindowBorder
setHasCustomDecoration( dialog, true ); setHasCustomDecoration( dialog, true );
// enable Swing window decoration // enable Swing window decoration
dialog.getRootPane().setWindowDecorationStyle( JRootPane.PLAIN_DIALOG ); rootPane.setWindowDecorationStyle( JRootPane.PLAIN_DIALOG );
} }
} }
@@ -153,12 +141,15 @@ public class FlatNativeWindowBorder
return; return;
} }
if( !isSupported() )
return;
// remove listener // remove listener
if( data instanceof PropertyChangeListener ) if( data instanceof PropertyChangeListener )
rootPane.removePropertyChangeListener( "ancestor", (PropertyChangeListener) data ); rootPane.removePropertyChangeListener( "ancestor", (PropertyChangeListener) data );
// do not uninstall when switching to another FlatLaf theme // do not uninstall when switching to another FlatLaf theme and if still enabled
if( UIManager.getLookAndFeel() instanceof FlatLaf ) if( UIManager.getLookAndFeel() instanceof FlatLaf && useWindowDecorations( rootPane, FlatSystemProperties.USE_WINDOW_DECORATIONS ) )
return; return;
// uninstall native window border // uninstall native window border
@@ -188,6 +179,20 @@ public class FlatNativeWindowBorder
} }
} }
private static boolean useWindowDecorations( JRootPane rootPane, String systemPropertyKey ) {
// check whether forced to enabled/disabled via system property
Boolean enabled = FlatSystemProperties.getBooleanStrict( systemPropertyKey, null );
if( enabled != null )
return enabled;
// check whether forced to enabled/disabled via client property
enabled = FlatClientProperties.clientPropertyBooleanStrict( rootPane, FlatClientProperties.USE_WINDOW_DECORATIONS, null );
if( enabled != null )
return enabled;
return UIManager.getBoolean( "TitlePane.useWindowDecorations" );
}
public static boolean hasCustomDecoration( Window window ) { public static boolean hasCustomDecoration( Window window ) {
if( canUseJBRCustomDecorations ) if( canUseJBRCustomDecorations )
return JBRCustomDecorations.hasCustomDecoration( window ); return JBRCustomDecorations.hasCustomDecoration( window );

View File

@@ -95,7 +95,7 @@ public class FlatRootPaneUI
else else
installBorder(); installBorder();
nativeWindowBorderData = FlatNativeWindowBorder.install( rootPane ); installNativeWindowBorder();
} }
protected void installBorder() { protected void installBorder() {
@@ -110,8 +110,7 @@ public class FlatRootPaneUI
public void uninstallUI( JComponent c ) { public void uninstallUI( JComponent c ) {
super.uninstallUI( c ); super.uninstallUI( c );
FlatNativeWindowBorder.uninstall( rootPane, nativeWindowBorderData ); uninstallNativeWindowBorder();
uninstallClientDecorations(); uninstallClientDecorations();
rootPane = null; rootPane = null;
} }
@@ -137,6 +136,34 @@ public class FlatRootPaneUI
c.putClientProperty( "jetbrains.awt.windowDarkAppearance", FlatLaf.isLafDark() ); c.putClientProperty( "jetbrains.awt.windowDarkAppearance", FlatLaf.isLafDark() );
} }
/**
* @since 1.1.2
*/
protected void installNativeWindowBorder() {
nativeWindowBorderData = FlatNativeWindowBorder.install( rootPane );
}
/**
* @since 1.1.2
*/
protected void uninstallNativeWindowBorder() {
FlatNativeWindowBorder.uninstall( rootPane, nativeWindowBorderData );
nativeWindowBorderData = null;
}
/**
* @since 1.1.2
*/
public static void updateNativeWindowBorder( JRootPane rootPane ) {
RootPaneUI rui = rootPane.getUI();
if( !(rui instanceof FlatRootPaneUI) )
return;
FlatRootPaneUI ui = (FlatRootPaneUI) rui;
ui.uninstallNativeWindowBorder();
ui.installNativeWindowBorder();
}
protected void installClientDecorations() { protected void installClientDecorations() {
boolean isNativeWindowBorderSupported = FlatNativeWindowBorder.isSupported(); boolean isNativeWindowBorderSupported = FlatNativeWindowBorder.isSupported();
@@ -218,6 +245,10 @@ public class FlatRootPaneUI
installBorder(); installBorder();
break; break;
case FlatClientProperties.USE_WINDOW_DECORATIONS:
updateNativeWindowBorder( rootPane );
break;
case FlatClientProperties.MENU_BAR_EMBEDDED: case FlatClientProperties.MENU_BAR_EMBEDDED:
if( titlePane != null ) { if( titlePane != null ) {
titlePane.menuBarChanged(); titlePane.menuBarChanged();

View File

@@ -24,7 +24,6 @@ import java.util.prefs.Preferences;
import javax.swing.*; import javax.swing.*;
import javax.swing.text.DefaultEditorKit; import javax.swing.text.DefaultEditorKit;
import javax.swing.text.StyleContext; import javax.swing.text.StyleContext;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.demo.HintManager.Hint; import com.formdev.flatlaf.demo.HintManager.Hint;
import com.formdev.flatlaf.demo.extras.*; import com.formdev.flatlaf.demo.extras.*;
@@ -35,7 +34,6 @@ import com.formdev.flatlaf.extras.FlatUIDefaultsInspector;
import com.formdev.flatlaf.extras.components.FlatButton; import com.formdev.flatlaf.extras.components.FlatButton;
import com.formdev.flatlaf.extras.components.FlatButton.ButtonType; import com.formdev.flatlaf.extras.components.FlatButton.ButtonType;
import com.formdev.flatlaf.extras.FlatSVGUtils; import com.formdev.flatlaf.extras.FlatSVGUtils;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.ui.JBRCustomDecorations; import com.formdev.flatlaf.ui.JBRCustomDecorations;
import net.miginfocom.layout.ConstraintParser; import net.miginfocom.layout.ConstraintParser;
import net.miginfocom.layout.LC; import net.miginfocom.layout.LC;
@@ -144,35 +142,21 @@ class DemoFrame
private void windowDecorationsChanged() { private void windowDecorationsChanged() {
boolean windowDecorations = windowDecorationsCheckBoxMenuItem.isSelected(); boolean windowDecorations = windowDecorationsCheckBoxMenuItem.isSelected();
// change window decoration of demo main frame // change window decoration of all frames and dialogs
if( FlatNativeWindowBorder.isSupported() ) { FlatLaf.setUseNativeWindowDecorations( windowDecorations );
FlatNativeWindowBorder.setHasCustomDecoration( this, windowDecorations );
getRootPane().setWindowDecorationStyle( windowDecorations ? JRootPane.FRAME : JRootPane.NONE );
} else {
dispose();
setUndecorated( windowDecorations );
getRootPane().setWindowDecorationStyle( windowDecorations ? JRootPane.FRAME : JRootPane.NONE );
setVisible( true );
}
menuBarEmbeddedCheckBoxMenuItem.setEnabled( windowDecorations );
// enable/disable window decoration for later created frames/dialogs menuBarEmbeddedCheckBoxMenuItem.setEnabled( windowDecorations );
UIManager.put( "TitlePane.useWindowDecorations", windowDecorations ); unifiedTitleBarMenuItem.setEnabled( windowDecorations );
} }
private void menuBarEmbeddedChanged() { private void menuBarEmbeddedChanged() {
getRootPane().putClientProperty( FlatClientProperties.MENU_BAR_EMBEDDED, UIManager.put( "TitlePane.menuBarEmbedded", menuBarEmbeddedCheckBoxMenuItem.isSelected() );
menuBarEmbeddedCheckBoxMenuItem.isSelected() ? null : false ); FlatLaf.revalidateAndRepaintAllFramesAndDialogs();
// alternative method for all frames and menu bars in an application
// UIManager.put( "TitlePane.menuBarEmbedded", menuBarEmbeddedCheckBoxMenuItem.isSelected() );
// revalidate();
// repaint();
} }
private void unifiedTitleBar() { private void unifiedTitleBar() {
UIManager.put( "TitlePane.unifiedBackground", unifiedTitleBarMenuItem.isSelected() ); UIManager.put( "TitlePane.unifiedBackground", unifiedTitleBarMenuItem.isSelected() );
repaint(); FlatLaf.repaintAllFramesAndDialogs();
} }
private void underlineMenuSelection() { private void underlineMenuSelection() {
@@ -748,8 +732,7 @@ class DemoFrame
copyMenuItem.addActionListener( new DefaultEditorKit.CopyAction() ); copyMenuItem.addActionListener( new DefaultEditorKit.CopyAction() );
pasteMenuItem.addActionListener( new DefaultEditorKit.PasteAction() ); pasteMenuItem.addActionListener( new DefaultEditorKit.PasteAction() );
boolean supportsWindowDecorations = UIManager.getLookAndFeel() boolean supportsWindowDecorations = FlatLaf.supportsNativeWindowDecorations();
.getSupportsWindowDecorations() || FlatNativeWindowBorder.isSupported();
// If the JetBrainsRuntime is used, it forces the use of it's own custom // If the JetBrainsRuntime is used, it forces the use of it's own custom
// window decoration, meaning we can't use our own. // window decoration, meaning we can't use our own.

View File

@@ -35,8 +35,8 @@ import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf; import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.extras.FlatInspector; import com.formdev.flatlaf.extras.FlatInspector;
import com.formdev.flatlaf.extras.components.FlatTriStateCheckBox;
import com.formdev.flatlaf.ui.FlatLineBorder; import com.formdev.flatlaf.ui.FlatLineBorder;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import net.miginfocom.swing.*; import net.miginfocom.swing.*;
@@ -304,13 +304,11 @@ public class FlatNativeWindowBorderTest
} }
private void nativeChanged() { private void nativeChanged() {
FlatNativeWindowBorder.setHasCustomDecoration( window, nativeCheckBox.isSelected() ); FlatLaf.setUseNativeWindowDecorations( nativeCheckBox.isSelected() );
} }
private void native2Changed() { private void native2Changed() {
((RootPaneContainer)window).getRootPane().putClientProperty( FlatClientProperties.USE_WINDOW_DECORATIONS, native2CheckBox.isSelected() ); ((RootPaneContainer)window).getRootPane().putClientProperty( FlatClientProperties.USE_WINDOW_DECORATIONS, native2CheckBox.getChecked() );
window.dispose();
window.setVisible( true );
} }
private void revalidateLayout() { private void revalidateLayout() {
@@ -382,7 +380,7 @@ public class FlatNativeWindowBorderTest
undecoratedCheckBox = new JCheckBox(); undecoratedCheckBox = new JCheckBox();
fullScreenCheckBox = new JCheckBox(); fullScreenCheckBox = new JCheckBox();
nativeCheckBox = new JCheckBox(); nativeCheckBox = new JCheckBox();
native2CheckBox = new JCheckBox(); native2CheckBox = new FlatTriStateCheckBox();
openDialogButton = new JButton(); openDialogButton = new JButton();
hideWindowButton = new JButton(); hideWindowButton = new JButton();
reopenButton = new JButton(); reopenButton = new JButton();
@@ -446,7 +444,6 @@ public class FlatNativeWindowBorderTest
//---- native2CheckBox ---- //---- native2CheckBox ----
native2CheckBox.setText("JRootPane.useWindowDecorations"); native2CheckBox.setText("JRootPane.useWindowDecorations");
native2CheckBox.setSelected(true);
native2CheckBox.addActionListener(e -> native2Changed()); native2CheckBox.addActionListener(e -> native2Changed());
add(native2CheckBox, "cell 0 3 3 1"); add(native2CheckBox, "cell 0 3 3 1");
@@ -507,7 +504,7 @@ public class FlatNativeWindowBorderTest
private JCheckBox undecoratedCheckBox; private JCheckBox undecoratedCheckBox;
private JCheckBox fullScreenCheckBox; private JCheckBox fullScreenCheckBox;
private JCheckBox nativeCheckBox; private JCheckBox nativeCheckBox;
private JCheckBox native2CheckBox; private FlatTriStateCheckBox native2CheckBox;
private JButton openDialogButton; private JButton openDialogButton;
private JButton hideWindowButton; private JButton hideWindowButton;
private JButton reopenButton; private JButton reopenButton;

View File

@@ -65,10 +65,9 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3 3 1" "value": "cell 0 3 3 1"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) {
name: "native2CheckBox" name: "native2CheckBox"
"text": "JRootPane.useWindowDecorations" "text": "JRootPane.useWindowDecorations"
"selected": true
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "native2Changed", false ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "native2Changed", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3 3 1" "value": "cell 0 3 3 1"

View File

@@ -35,17 +35,9 @@ public class FlatWindowDecorationsTest
{ {
public static void main( String[] args ) { public static void main( String[] args ) {
SwingUtilities.invokeLater( () -> { SwingUtilities.invokeLater( () -> {
// enable custom window decoration (if LaF supports it)
JFrame.setDefaultLookAndFeelDecorated( true );
JDialog.setDefaultLookAndFeelDecorated( true );
FlatTestFrame frame = FlatTestFrame.create( args, "FlatWindowDecorationsTest" ); FlatTestFrame frame = FlatTestFrame.create( args, "FlatWindowDecorationsTest" );
frame.applyComponentOrientationToFrame = true; frame.applyComponentOrientationToFrame = true;
// WARNING: Do not this in real-world programs.
// frame.setUndecorated( true );
// frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME );
Class<?> cls = FlatWindowDecorationsTest.class; Class<?> cls = FlatWindowDecorationsTest.class;
List<Image> images = Arrays.asList( List<Image> images = Arrays.asList(
new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test16.png" ) ).getImage(), new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test16.png" ) ).getImage(),