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
#### 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

View File

@@ -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}.
* <p>
* 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.
* <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>
* (requires Window 10)
* <p>

View File

@@ -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.
* <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() {
return MnemonicHandler.isShowMnemonics();
}

View File

@@ -63,6 +63,10 @@ public interface FlatSystemProperties
* <p>
* Setting this to {@code false} disables using FlatLaf native window decorations.
* <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)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Image> images = Arrays.asList(
new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test16.png" ) ).getImage(),