From 6addb5c4b4c5ba8da527966ee56acef563a04eef Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 3 Apr 2021 11:13:57 +0200 Subject: [PATCH] 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` --- CHANGELOG.md | 7 ++ .../formdev/flatlaf/FlatClientProperties.java | 10 ++- .../java/com/formdev/flatlaf/FlatLaf.java | 75 +++++++++++++++++++ .../formdev/flatlaf/FlatSystemProperties.java | 4 + .../flatlaf/ui/FlatNativeWindowBorder.java | 49 ++++++------ .../formdev/flatlaf/ui/FlatRootPaneUI.java | 37 ++++++++- .../com/formdev/flatlaf/demo/DemoFrame.java | 33 ++------ .../testing/FlatNativeWindowBorderTest.java | 13 ++-- .../testing/FlatNativeWindowBorderTest.jfd | 3 +- .../testing/FlatWindowDecorationsTest.java | 8 -- 10 files changed, 168 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95a308db..87da0078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ FlatLaf Change Log ## 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 - Native window decorations: Fixed loading of native library when using 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 6a82e2ac..221a069e 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -233,10 +233,14 @@ public interface FlatClientProperties /** * Specifies whether FlatLaf native window decorations should be used - * when creating {@code JFrame} or {@code JDialog}. + * for {@code JFrame} or {@code JDialog}. *

- * Setting this to {@code false} disables using FlatLaf native window decorations - * for the window that contains the root pane. Needs to be set before showing the window. + * Setting this enables/disables using FlatLaf native window decorations + * for the window that contains the root pane. + *

+ * This client property has lower priority than system property + * {@link FlatSystemProperties#USE_WINDOW_DECORATIONS}, but higher priority + * than UI default {@code TitlePane.useWindowDecorations}. *

* (requires Window 10) *

diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index 41db5a9b..9e865a41 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -46,6 +46,7 @@ import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.LookAndFeel; import javax.swing.PopupFactory; +import javax.swing.RootPaneContainer; import javax.swing.SwingUtilities; import javax.swing.UIDefaults; import javax.swing.UIDefaults.ActiveValue; @@ -59,6 +60,7 @@ import javax.swing.text.StyleContext; import javax.swing.text.html.HTMLEditorKit; import com.formdev.flatlaf.ui.FlatNativeWindowBorder; import com.formdev.flatlaf.ui.FlatPopupFactory; +import com.formdev.flatlaf.ui.FlatRootPaneUI; import com.formdev.flatlaf.util.GrayFilter; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.MultiResolutionImageSupport; @@ -715,6 +717,79 @@ public abstract class FlatLaf } ); } + /** + * Returns whether native window decorations are supported on current platform. + *

+ * 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. + *

+ * 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() { return MnemonicHandler.isShowMnemonics(); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java index 7ec39204..1a1040d5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java @@ -63,6 +63,10 @@ public interface FlatSystemProperties *

* Setting this to {@code false} disables using FlatLaf native window decorations. *

+ * This system property has higher priority than client property + * {@link FlatClientProperties#USE_WINDOW_DECORATIONS} and + * UI default {@code TitlePane.useWindowDecorations}. + *

* (requires Window 10) *

* Allowed Values {@code false} and {@code true}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java index 1d640898..49a0332f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java @@ -99,18 +99,12 @@ public class FlatNativeWindowBorder if( window instanceof JFrame ) { JFrame frame = (JFrame) window; + JRootPane rootPane = frame.getRootPane(); - // check whether disabled via client property - if( !FlatClientProperties.clientPropertyBoolean( frame.getRootPane(), FlatClientProperties.USE_WINDOW_DECORATIONS, true ) ) + // check whether disabled via system property, client property or UI default + if( !useWindowDecorations( rootPane, systemPropertyKey ) ) 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 if( frame.isUndecorated() ) return; @@ -119,22 +113,16 @@ public class FlatNativeWindowBorder setHasCustomDecoration( frame, true ); // enable Swing window decoration - frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME ); + rootPane.setWindowDecorationStyle( JRootPane.FRAME ); } else if( window instanceof JDialog ) { JDialog dialog = (JDialog) window; + JRootPane rootPane = dialog.getRootPane(); - // check whether disabled via client property - if( !FlatClientProperties.clientPropertyBoolean( dialog.getRootPane(), FlatClientProperties.USE_WINDOW_DECORATIONS, true ) ) + // check whether disabled via system property, client property or UI default + if( !useWindowDecorations( rootPane, systemPropertyKey ) ) 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 if( dialog.isUndecorated() ) return; @@ -143,7 +131,7 @@ public class FlatNativeWindowBorder setHasCustomDecoration( dialog, true ); // enable Swing window decoration - dialog.getRootPane().setWindowDecorationStyle( JRootPane.PLAIN_DIALOG ); + rootPane.setWindowDecorationStyle( JRootPane.PLAIN_DIALOG ); } } @@ -153,12 +141,15 @@ public class FlatNativeWindowBorder return; } + if( !isSupported() ) + return; + // remove listener if( data instanceof PropertyChangeListener ) rootPane.removePropertyChangeListener( "ancestor", (PropertyChangeListener) data ); - // do not uninstall when switching to another FlatLaf theme - if( UIManager.getLookAndFeel() instanceof FlatLaf ) + // do not uninstall when switching to another FlatLaf theme and if still enabled + if( UIManager.getLookAndFeel() instanceof FlatLaf && useWindowDecorations( rootPane, FlatSystemProperties.USE_WINDOW_DECORATIONS ) ) return; // 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 ) { if( canUseJBRCustomDecorations ) return JBRCustomDecorations.hasCustomDecoration( window ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java index 7dc26eff..a484b6cd 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 @@ -95,7 +95,7 @@ public class FlatRootPaneUI else installBorder(); - nativeWindowBorderData = FlatNativeWindowBorder.install( rootPane ); + installNativeWindowBorder(); } protected void installBorder() { @@ -110,8 +110,7 @@ public class FlatRootPaneUI public void uninstallUI( JComponent c ) { super.uninstallUI( c ); - FlatNativeWindowBorder.uninstall( rootPane, nativeWindowBorderData ); - + uninstallNativeWindowBorder(); uninstallClientDecorations(); rootPane = null; } @@ -137,6 +136,34 @@ public class FlatRootPaneUI 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() { boolean isNativeWindowBorderSupported = FlatNativeWindowBorder.isSupported(); @@ -218,6 +245,10 @@ public class FlatRootPaneUI installBorder(); break; + case FlatClientProperties.USE_WINDOW_DECORATIONS: + updateNativeWindowBorder( rootPane ); + break; + case FlatClientProperties.MENU_BAR_EMBEDDED: if( titlePane != null ) { titlePane.menuBarChanged(); 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 46d826f6..0cac1876 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 @@ -24,7 +24,6 @@ import java.util.prefs.Preferences; import javax.swing.*; import javax.swing.text.DefaultEditorKit; import javax.swing.text.StyleContext; -import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.demo.HintManager.Hint; 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.ButtonType; import com.formdev.flatlaf.extras.FlatSVGUtils; -import com.formdev.flatlaf.ui.FlatNativeWindowBorder; import com.formdev.flatlaf.ui.JBRCustomDecorations; import net.miginfocom.layout.ConstraintParser; import net.miginfocom.layout.LC; @@ -144,35 +142,21 @@ class DemoFrame private void windowDecorationsChanged() { boolean windowDecorations = windowDecorationsCheckBoxMenuItem.isSelected(); - // change window decoration of demo main frame - if( FlatNativeWindowBorder.isSupported() ) { - 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 ); + // change window decoration of all frames and dialogs + FlatLaf.setUseNativeWindowDecorations( windowDecorations ); - // enable/disable window decoration for later created frames/dialogs - UIManager.put( "TitlePane.useWindowDecorations", windowDecorations ); + menuBarEmbeddedCheckBoxMenuItem.setEnabled( windowDecorations ); + unifiedTitleBarMenuItem.setEnabled( windowDecorations ); } private void menuBarEmbeddedChanged() { - getRootPane().putClientProperty( FlatClientProperties.MENU_BAR_EMBEDDED, - menuBarEmbeddedCheckBoxMenuItem.isSelected() ? null : false ); - -// alternative method for all frames and menu bars in an application -// UIManager.put( "TitlePane.menuBarEmbedded", menuBarEmbeddedCheckBoxMenuItem.isSelected() ); -// revalidate(); -// repaint(); + UIManager.put( "TitlePane.menuBarEmbedded", menuBarEmbeddedCheckBoxMenuItem.isSelected() ); + FlatLaf.revalidateAndRepaintAllFramesAndDialogs(); } private void unifiedTitleBar() { UIManager.put( "TitlePane.unifiedBackground", unifiedTitleBarMenuItem.isSelected() ); - repaint(); + FlatLaf.repaintAllFramesAndDialogs(); } private void underlineMenuSelection() { @@ -748,8 +732,7 @@ class DemoFrame copyMenuItem.addActionListener( new DefaultEditorKit.CopyAction() ); pasteMenuItem.addActionListener( new DefaultEditorKit.PasteAction() ); - boolean supportsWindowDecorations = UIManager.getLookAndFeel() - .getSupportsWindowDecorations() || FlatNativeWindowBorder.isSupported(); + boolean supportsWindowDecorations = FlatLaf.supportsNativeWindowDecorations(); // If the JetBrainsRuntime is used, it forces the use of it's own custom // window decoration, meaning we can't use our own. diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java index 7ece92dd..9b121bd9 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java @@ -35,8 +35,8 @@ import com.formdev.flatlaf.FlatIntelliJLaf; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLightLaf; import com.formdev.flatlaf.extras.FlatInspector; +import com.formdev.flatlaf.extras.components.FlatTriStateCheckBox; import com.formdev.flatlaf.ui.FlatLineBorder; -import com.formdev.flatlaf.ui.FlatNativeWindowBorder; import com.formdev.flatlaf.util.SystemInfo; import net.miginfocom.swing.*; @@ -304,13 +304,11 @@ public class FlatNativeWindowBorderTest } private void nativeChanged() { - FlatNativeWindowBorder.setHasCustomDecoration( window, nativeCheckBox.isSelected() ); + FlatLaf.setUseNativeWindowDecorations( nativeCheckBox.isSelected() ); } private void native2Changed() { - ((RootPaneContainer)window).getRootPane().putClientProperty( FlatClientProperties.USE_WINDOW_DECORATIONS, native2CheckBox.isSelected() ); - window.dispose(); - window.setVisible( true ); + ((RootPaneContainer)window).getRootPane().putClientProperty( FlatClientProperties.USE_WINDOW_DECORATIONS, native2CheckBox.getChecked() ); } private void revalidateLayout() { @@ -382,7 +380,7 @@ public class FlatNativeWindowBorderTest undecoratedCheckBox = new JCheckBox(); fullScreenCheckBox = new JCheckBox(); nativeCheckBox = new JCheckBox(); - native2CheckBox = new JCheckBox(); + native2CheckBox = new FlatTriStateCheckBox(); openDialogButton = new JButton(); hideWindowButton = new JButton(); reopenButton = new JButton(); @@ -446,7 +444,6 @@ public class FlatNativeWindowBorderTest //---- native2CheckBox ---- native2CheckBox.setText("JRootPane.useWindowDecorations"); - native2CheckBox.setSelected(true); native2CheckBox.addActionListener(e -> native2Changed()); add(native2CheckBox, "cell 0 3 3 1"); @@ -507,7 +504,7 @@ public class FlatNativeWindowBorderTest private JCheckBox undecoratedCheckBox; private JCheckBox fullScreenCheckBox; private JCheckBox nativeCheckBox; - private JCheckBox native2CheckBox; + private FlatTriStateCheckBox native2CheckBox; private JButton openDialogButton; private JButton hideWindowButton; private JButton reopenButton; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.jfd index 56f5faa3..deefbc5f 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.jfd @@ -65,10 +65,9 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 3 3 1" } ) - add( new FormComponent( "javax.swing.JCheckBox" ) { + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTriStateCheckBox" ) { name: "native2CheckBox" "text": "JRootPane.useWindowDecorations" - "selected": true addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "native2Changed", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 3 3 1" diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java index 7df63667..856058ae 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java @@ -35,17 +35,9 @@ public class FlatWindowDecorationsTest { public static void main( String[] args ) { SwingUtilities.invokeLater( () -> { - // enable custom window decoration (if LaF supports it) - JFrame.setDefaultLookAndFeelDecorated( true ); - JDialog.setDefaultLookAndFeelDecorated( true ); - FlatTestFrame frame = FlatTestFrame.create( args, "FlatWindowDecorationsTest" ); frame.applyComponentOrientationToFrame = true; - // WARNING: Do not this in real-world programs. -// frame.setUndecorated( true ); -// frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME ); - Class cls = FlatWindowDecorationsTest.class; List images = Arrays.asList( new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test16.png" ) ).getImage(),