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 cac59091..a7b1c2a7 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -43,6 +43,8 @@ import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; import javax.swing.LookAndFeel; import javax.swing.PopupFactory; import javax.swing.SwingUtilities; @@ -57,6 +59,7 @@ import javax.swing.plaf.basic.BasicLookAndFeel; import javax.swing.text.StyleContext; import javax.swing.text.html.HTMLEditorKit; import com.formdev.flatlaf.ui.FlatPopupFactory; +import com.formdev.flatlaf.ui.JBRCustomDecorations; import com.formdev.flatlaf.util.GrayFilter; import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.SystemInfo; @@ -110,8 +113,32 @@ public abstract class FlatLaf public abstract boolean isDark(); + /** + * Returns whether FlatLaf supports custom window decorations. + *
+ * To use custom window decorations in your application, enable them with + * (before creating any frames or dialogs): + *
+ * JFrame.setDefaultLookAndFeelDecorated( true ); + * JDialog.setDefaultLookAndFeelDecorated( true ); + *+ *
+ * Returns {@code true} on Windows, {@code false} otherwise. + *
+ * Return also {@code false} if running on Windows 10 in
+ * JetBrains Runtime 11 (or later)
+ * (source code on github)
+ * and JBR supports custom window decorations. In this case, JBR custom decorations
+ * are enabled if {@link JFrame#isDefaultLookAndFeelDecorated()} or
+ * {@link JDialog#isDefaultLookAndFeelDecorated()} return {@code true}.
+ */
@Override
public boolean getSupportsWindowDecorations() {
+ if( SystemInfo.IS_JETBRAINS_JVM_11_OR_LATER &&
+ SystemInfo.IS_WINDOWS_10_OR_LATER &&
+ JBRCustomDecorations.isSupported() )
+ return false;
+
return SystemInfo.IS_WINDOWS;
}
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 6396890d..a1952205 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
@@ -30,6 +30,7 @@ import javax.swing.JMenuBar;
import javax.swing.JRootPane;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicRootPaneUI;
+import com.formdev.flatlaf.util.SystemInfo;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JRootPane}.
@@ -55,6 +56,9 @@ public class FlatRootPaneUI
if( rootPane.getWindowDecorationStyle() != JRootPane.NONE )
installClientDecorations();
+
+ if( SystemInfo.IS_JETBRAINS_JVM_11_OR_LATER && SystemInfo.IS_WINDOWS_10_OR_LATER )
+ JBRCustomDecorations.install( rootPane );
}
@Override
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java
index 1de1b986..45130581 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java
@@ -20,14 +20,18 @@ import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dialog;
import java.awt.Dimension;
+import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Insets;
+import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionListener;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
@@ -36,6 +40,7 @@ import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
import java.util.List;
import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory;
@@ -233,6 +238,8 @@ class FlatTitlePane
titleLabel.setText( getWindowTitle() );
installWindowListeners();
}
+
+ updateJBRHitTestSpotsAndTitleBarHeight();
}
@Override
@@ -258,6 +265,7 @@ class FlatTitlePane
window.addPropertyChangeListener( handler );
window.addWindowListener( handler );
window.addWindowStateListener( handler );
+ window.addComponentListener( handler );
}
private void uninstallWindowListeners() {
@@ -267,6 +275,7 @@ class FlatTitlePane
window.removePropertyChangeListener( handler );
window.removeWindowListener( handler );
window.removeWindowStateListener( handler );
+ window.removeComponentListener( handler );
}
@Override
@@ -283,8 +292,12 @@ class FlatTitlePane
}
private void maximize() {
- if( window instanceof Frame ) {
- Frame frame = (Frame) window;
+ if( !(window instanceof Frame) )
+ return;
+
+ Frame frame = (Frame) window;
+
+ if( !hasJBRCustomDecoration() ) {
GraphicsConfiguration gc = window.getGraphicsConfiguration();
// remember current maximized bounds
@@ -325,6 +338,11 @@ class FlatTitlePane
// restore old maximized bounds
frame.setMaximizedBounds( oldMaximizedBounds );
+ } else {
+ // not necessary to set maximized bounds when running in JBR
+
+ // maximize window
+ frame.setExtendedState( frame.getExtendedState() | Frame.MAXIMIZED_BOTH );
}
}
@@ -343,11 +361,45 @@ class FlatTitlePane
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
}
+ private boolean hasJBRCustomDecoration() {
+ return window != null && JBRCustomDecorations.hasCustomDecoration( window );
+ }
+
+ private void updateJBRHitTestSpotsAndTitleBarHeight() {
+ if( !isDisplayable() )
+ return;
+
+ if( !hasJBRCustomDecoration() )
+ return;
+
+ List
+ *
+ *
+ * @author Karl Tauber
+ */
+public class JBRCustomDecorations
+{
+ private static boolean initialized;
+ private static Method Window_hasCustomDecoration;
+ private static Method Window_setHasCustomDecoration;
+ private static Method WWindowPeer_setCustomDecorationHitTestSpots;
+ private static Method WWindowPeer_setCustomDecorationTitleBarHeight;
+ private static Method AWTAccessor_getComponentAccessor;
+ private static Method AWTAccessor_ComponentAccessor_getPeer;
+
+ public static boolean isSupported() {
+ initialize();
+ return Window_setHasCustomDecoration != null;
+ }
+
+ static void install( JRootPane rootPane ) {
+ boolean frameIsDefaultLookAndFeelDecorated = JFrame.isDefaultLookAndFeelDecorated();
+ boolean dialogIsDefaultLookAndFeelDecorated = JDialog.isDefaultLookAndFeelDecorated();
+ boolean lafSupportsWindowDecorations = UIManager.getLookAndFeel().getSupportsWindowDecorations();
+
+ // check whether decorations are enabled
+ if( !frameIsDefaultLookAndFeelDecorated && !dialogIsDefaultLookAndFeelDecorated )
+ return;
+
+ // do not enable JBR decorations if JFrame and JDialog will use LaF decorations
+ if( lafSupportsWindowDecorations &&
+ frameIsDefaultLookAndFeelDecorated &&
+ dialogIsDefaultLookAndFeelDecorated )
+ return;
+
+ if( !isSupported() )
+ return;
+
+ // use hierarchy listener to wait until the root pane is added to a window
+ HierarchyListener addListener = new HierarchyListener() {
+ @Override
+ public void hierarchyChanged( HierarchyEvent e ) {
+ if( (e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) == 0 )
+ return;
+
+ Container parent = e.getChangedParent();
+ if( parent instanceof JFrame ) {
+ JFrame frame = (JFrame) parent;
+
+ // do not enable JBR decorations if JFrame will use LaF decorations
+ if( lafSupportsWindowDecorations && frameIsDefaultLookAndFeelDecorated )
+ return;
+
+ // do not enable JBR decorations if frame is undecorated
+ if( frame.isUndecorated() )
+ return;
+
+ // enable JBR custom window decoration for window
+ setHasCustomDecoration( frame );
+
+ // enable Swing window decoration
+ rootPane.setWindowDecorationStyle( JRootPane.FRAME );
+
+ } else if( parent instanceof JDialog ) {
+ JDialog dialog = (JDialog)parent;
+
+ // do not enable JBR decorations if JDialog will use LaF decorations
+ if( lafSupportsWindowDecorations && dialogIsDefaultLookAndFeelDecorated )
+ return;
+
+ // do not enable JBR decorations if dialog is undecorated
+ if( dialog.isUndecorated() )
+ return;
+
+ // enable JBR custom window decoration for window
+ setHasCustomDecoration( dialog );
+
+ // enable Swing window decoration
+ rootPane.setWindowDecorationStyle( JRootPane.PLAIN_DIALOG );
+ }
+
+ // use invokeLater to remove listener to avoid that listener
+ // is removed while listener queue is processed
+ EventQueue.invokeLater( () -> {
+ rootPane.removeHierarchyListener( this );
+ } );
+ }
+ };
+ rootPane.addHierarchyListener( addListener );
+ }
+
+ static boolean hasCustomDecoration( Window window ) {
+ if( !isSupported() )
+ return false;
+
+ try {
+ return (Boolean) Window_hasCustomDecoration.invoke( window );
+ } catch( Exception ex ) {
+ Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
+ return false;
+ }
+ }
+
+ static void setHasCustomDecoration( Window window ) {
+ if( !isSupported() )
+ return;
+
+ try {
+ Window_setHasCustomDecoration.invoke( window );
+ } catch( Exception ex ) {
+ Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
+ }
+ }
+
+ static void setHitTestSpotsAndTitleBarHeight( Window window, List