postInitialization;
+ private Boolean oldFrameWindowDecorated;
+ private Boolean oldDialogWindowDecorated;
+
public static boolean install( LookAndFeel newLookAndFeel ) {
try {
UIManager.setLookAndFeel( newLookAndFeel );
@@ -110,6 +116,45 @@ public abstract class FlatLaf
public abstract boolean isDark();
+ /**
+ * Checks whether the current look and feel is dark.
+ */
+ public static boolean isLafDark() {
+ LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
+ return lookAndFeel instanceof FlatLaf && ((FlatLaf)lookAndFeel).isDark();
+ }
+
+ /**
+ * Returns whether FlatLaf supports custom window decorations.
+ * This depends on the operating system and on the used Java runtime.
+ *
+ * To use custom window decorations in your application, enable them with
+ * following code (before creating any frames or dialogs). Then custom window
+ * decorations are only enabled if this method returns {@code true}.
+ *
+ * JFrame.setDefaultLookAndFeelDecorated( true );
+ * JDialog.setDefaultLookAndFeelDecorated( true );
+ *
+ *
+ * Returns {@code true} on Windows 10, {@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_10_OR_LATER;
+ }
+
@Override
public boolean isNativeLookAndFeel() {
return false;
@@ -200,6 +245,16 @@ public abstract class FlatLaf
String.format( "a { color: #%06x; }", linkColor.getRGB() & 0xffffff ) );
}
};
+
+ // enable/disable window decorations, but only if system property is either
+ // "true" or "false"; in other cases it is not changed
+ Boolean useWindowDecorations = FlatSystemProperties.getBooleanStrict( FlatSystemProperties.USE_WINDOW_DECORATIONS, null );
+ if( useWindowDecorations != null ) {
+ oldFrameWindowDecorated = JFrame.isDefaultLookAndFeelDecorated();
+ oldDialogWindowDecorated = JDialog.isDefaultLookAndFeelDecorated();
+ JFrame.setDefaultLookAndFeelDecorated( useWindowDecorations );
+ JDialog.setDefaultLookAndFeelDecorated( useWindowDecorations );
+ }
}
@Override
@@ -232,6 +287,14 @@ public abstract class FlatLaf
new HTMLEditorKit().getStyleSheet().addRule( "a { color: blue; }" );
postInitialization = null;
+ // restore enable/disable window decorations
+ if( oldFrameWindowDecorated != null ) {
+ JFrame.setDefaultLookAndFeelDecorated( oldFrameWindowDecorated );
+ JDialog.setDefaultLookAndFeelDecorated( oldDialogWindowDecorated );
+ oldFrameWindowDecorated = null;
+ oldDialogWindowDecorated = null;
+ }
+
super.uninitialize();
}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java
new file mode 100644
index 00000000..c8a6d858
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2020 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;
+
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+
+/**
+ * Defines/documents own system properties used in FlatLaf.
+ *
+ * @author Karl Tauber
+ */
+public interface FlatSystemProperties
+{
+ /**
+ * Specifies a custom scale factor used to scale the UI.
+ *
+ * If Java runtime scales (Java 9 or later), this scale factor is applied on top
+ * of the Java system scale factor. Java 8 does not scale and this scale factor
+ * replaces the user scale factor that FlatLaf computes based on the font.
+ * To replace the Java 9+ system scale factor, use system property "sun.java2d.uiScale",
+ * which has the same syntax as this one.
+ *
+ * Allowed Values e.g. {@code 1.5}, {@code 1.5x}, {@code 150%} or {@code 144dpi} (96dpi is 100%)
+ */
+ String UI_SCALE = "flatlaf.uiScale";
+
+ /**
+ * Specifies whether Ubuntu font should be used on Ubuntu Linux.
+ * By default, if not running in a JetBrains Runtime, the Liberation Sans font
+ * is used because there are rendering issues (in Java) with Ubuntu fonts.
+ *
+ * Allowed Values {@code false} and {@code true}
+ * Default {@code false}
+ */
+ String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont";
+
+ /**
+ * Specifies whether custom look and feel window decorations should be used
+ * when creating {@code JFrame} or {@code JDialog}.
+ *
+ * If this system property is set, FlatLaf invokes {@link JFrame#setDefaultLookAndFeelDecorated(boolean)}
+ * and {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} on LaF initialization.
+ *
+ * Allowed Values {@code false} and {@code true}
+ * Default none
+ */
+ String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations";
+
+ /**
+ * Specifies whether JetBrains Runtime custom window decorations should be used
+ * when creating {@code JFrame} or {@code JDialog}.
+ * Requires that the application runs in a
+ * JetBrains Runtime
+ * (based on OpenJDK).
+ *
+ * Setting this to {@code true} forces using JetBrains Runtime custom window decorations
+ * even if they are not enabled by the application.
+ *
+ * Allowed Values {@code false} and {@code true}
+ * Default {@code true}
+ */
+ String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations";
+
+ /**
+ * Specifies whether menubar is embedded into custom window decorations.
+ *
+ * Allowed Values {@code false} and {@code true}
+ * Default {@code true}
+ */
+ String MENUBAR_EMBEDDED = "flatlaf.menuBarEmbedded";
+
+ /**
+ * Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens.
+ *
+ * Allowed Values {@code false} and {@code true}
+ * Default {@code true}
+ */
+ String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection";
+
+ /**
+ * Checks whether a system property is set and returns {@code true} if its value
+ * is {@code "true"} (case-insensitive), otherwise it returns {@code false}.
+ * If the system property is not set, {@code defaultValue} is returned.
+ */
+ static boolean getBoolean( String key, boolean defaultValue ) {
+ String value = System.getProperty( key );
+ return (value != null) ? Boolean.parseBoolean( value ) : defaultValue;
+ }
+
+ /**
+ * Checks whether a system property is set and returns {@code Boolean.TRUE} if its value
+ * is {@code "true"} (case-insensitive) or returns {@code Boolean.FALSE} if its value
+ * is {@code "false"} (case-insensitive). Otherwise {@code defaultValue} is returned.
+ */
+ static Boolean getBooleanStrict( String key, Boolean defaultValue ) {
+ String value = System.getProperty( key );
+ if( "true".equalsIgnoreCase( value ) )
+ return Boolean.TRUE;
+ if( "false".equalsIgnoreCase( value ) )
+ return Boolean.FALSE;
+ return defaultValue;
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java
index bcb7ffc5..5397cc8a 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java
@@ -510,6 +510,10 @@ public class IntelliJTheme
// Slider
uiKeyMapping.put( "Slider.trackWidth", "" ); // ignore (used in Material Theme UI Lite)
+ // TitlePane
+ uiKeyMapping.put( "TitlePane.infoForeground", "TitlePane.foreground" );
+ uiKeyMapping.put( "TitlePane.inactiveInfoForeground", "TitlePane.inactiveForeground" );
+
for( Map.Entry e : uiKeyMapping.entrySet() )
uiKeyInverseMapping.put( e.getValue(), e.getKey() );
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java
index 1ba7e683..8642b4ff 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java
@@ -78,7 +78,7 @@ class LinuxFontPolicy
// --> use Liberation Sans font
if( family.startsWith( "Ubuntu" ) &&
!SystemInfo.IS_JETBRAINS_JVM &&
- !Boolean.parseBoolean( System.getProperty( "flatlaf.useUbuntuFont" ) ) )
+ !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_UBUNTU_FONT, false ) )
family = "Liberation Sans";
// scale font size
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowAbstractIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowAbstractIcon.java
new file mode 100644
index 00000000..15a8d6ab
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowAbstractIcon.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2020 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.icons;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import javax.swing.UIManager;
+import com.formdev.flatlaf.ui.FlatButtonUI;
+import com.formdev.flatlaf.ui.FlatUIUtils;
+import com.formdev.flatlaf.util.HiDPIUtils;
+
+/**
+ * Base class for window icons.
+ *
+ * @uiDefault TitlePane.buttonSize Dimension
+ * @uiDefault TitlePane.buttonHoverBackground Color
+ * @uiDefault TitlePane.buttonPressedBackground Color
+ *
+ * @author Karl Tauber
+ */
+public abstract class FlatWindowAbstractIcon
+ extends FlatAbstractIcon
+{
+ private final Color hoverBackground;
+ private final Color pressedBackground;
+
+ public FlatWindowAbstractIcon() {
+ this( UIManager.getDimension( "TitlePane.buttonSize" ),
+ UIManager.getColor( "TitlePane.buttonHoverBackground" ),
+ UIManager.getColor( "TitlePane.buttonPressedBackground" ) );
+ }
+
+ public FlatWindowAbstractIcon( Dimension size, Color hoverBackground, Color pressedBackground ) {
+ super( size.width, size.height, null );
+ this.hoverBackground = hoverBackground;
+ this.pressedBackground = pressedBackground;
+ }
+
+ @Override
+ protected void paintIcon( Component c, Graphics2D g ) {
+ paintBackground( c, g );
+
+ g.setColor( getForeground( c ) );
+ HiDPIUtils.paintAtScale1x( g, 0, 0, width, height, this::paintIconAt1x );
+ }
+
+ protected abstract void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor );
+
+ protected void paintBackground( Component c, Graphics2D g ) {
+ Color background = FlatButtonUI.buttonStateColor( c, null, null, null, hoverBackground, pressedBackground );
+ if( background != null ) {
+ g.setColor( FlatUIUtils.deriveColor( background, c.getBackground() ) );
+ g.fillRect( 0, 0, width, height );
+ }
+ }
+
+ protected Color getForeground( Component c ) {
+ return c.getForeground();
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowCloseIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowCloseIcon.java
new file mode 100644
index 00000000..f4bdc5ca
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowCloseIcon.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2020 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.icons;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import javax.swing.UIManager;
+import com.formdev.flatlaf.ui.FlatButtonUI;
+
+/**
+ * "close" icon for windows (frames and dialogs).
+ *
+ * @uiDefault TitlePane.closeHoverBackground Color
+ * @uiDefault TitlePane.closePressedBackground Color
+ * @uiDefault TitlePane.closeHoverForeground Color
+ * @uiDefault TitlePane.closePressedForeground Color
+ *
+ * @author Karl Tauber
+ */
+public class FlatWindowCloseIcon
+ extends FlatWindowAbstractIcon
+{
+ private final Color hoverForeground = UIManager.getColor( "TitlePane.closeHoverForeground" );
+ private final Color pressedForeground = UIManager.getColor( "TitlePane.closePressedForeground" );
+
+ public FlatWindowCloseIcon() {
+ super( UIManager.getDimension( "TitlePane.buttonSize" ),
+ UIManager.getColor( "TitlePane.closeHoverBackground" ),
+ UIManager.getColor( "TitlePane.closePressedBackground" ) );
+ }
+
+ @Override
+ protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
+ int iwh = (int) (10 * scaleFactor);
+ int ix = x + ((width - iwh) / 2);
+ int iy = y + ((height - iwh) / 2);
+ int ix2 = ix + iwh - 1;
+ int iy2 = iy + iwh - 1;
+ int thickness = (int) scaleFactor;
+
+ Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
+ path.append( new Line2D.Float( ix, iy, ix2, iy2 ), false );
+ path.append( new Line2D.Float( ix, iy2, ix2, iy ), false );
+ g.setStroke( new BasicStroke( thickness ) );
+ g.draw( path );
+ }
+
+ @Override
+ protected Color getForeground( Component c ) {
+ return FlatButtonUI.buttonStateColor( c, c.getForeground(), null, null, hoverForeground, pressedForeground );
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowIconifyIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowIconifyIcon.java
new file mode 100644
index 00000000..54cab7d7
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowIconifyIcon.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 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.icons;
+
+import java.awt.Graphics2D;
+
+/**
+ * "iconify" icon for windows (frames and dialogs).
+ *
+ * @author Karl Tauber
+ */
+public class FlatWindowIconifyIcon
+ extends FlatWindowAbstractIcon
+{
+ public FlatWindowIconifyIcon() {
+ }
+
+ @Override
+ protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
+ int iw = (int) (10 * scaleFactor);
+ int ih = (int) scaleFactor;
+ int ix = x + ((width - iw) / 2);
+ int iy = y + ((height - ih) / 2);
+
+ g.fillRect( ix, iy, iw, ih );
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowMaximizeIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowMaximizeIcon.java
new file mode 100644
index 00000000..5035d43d
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowMaximizeIcon.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 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.icons;
+
+import java.awt.Graphics2D;
+import com.formdev.flatlaf.ui.FlatUIUtils;
+
+/**
+ * "maximize" icon for windows (frames and dialogs).
+ *
+ * @author Karl Tauber
+ */
+public class FlatWindowMaximizeIcon
+ extends FlatWindowAbstractIcon
+{
+ public FlatWindowMaximizeIcon() {
+ }
+
+ @Override
+ protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
+ int iwh = (int) (10 * scaleFactor);
+ int ix = x + ((width - iwh) / 2);
+ int iy = y + ((height - iwh) / 2);
+ int thickness = (int) scaleFactor;
+
+ g.fill( FlatUIUtils.createRectangle( ix, iy, iwh, iwh, thickness ) );
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowRestoreIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowRestoreIcon.java
new file mode 100644
index 00000000..b62087ef
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowRestoreIcon.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2020 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.icons;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Area;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import com.formdev.flatlaf.ui.FlatUIUtils;
+
+/**
+ * "restore" icon for windows (frames and dialogs).
+ *
+ * @author Karl Tauber
+ */
+public class FlatWindowRestoreIcon
+ extends FlatWindowAbstractIcon
+{
+ public FlatWindowRestoreIcon() {
+ }
+
+ @Override
+ protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
+ int iwh = (int) (10 * scaleFactor);
+ int ix = x + ((width - iwh) / 2);
+ int iy = y + ((height - iwh) / 2);
+ int thickness = (int) scaleFactor;
+
+ int rwh = (int) (8 * scaleFactor);
+ int ro2 = iwh - rwh;
+
+ Path2D r1 = FlatUIUtils.createRectangle( ix + ro2, iy, rwh, rwh, thickness );
+ Path2D r2 = FlatUIUtils.createRectangle( ix, iy + ro2, rwh, rwh, thickness );
+
+ Area area = new Area( r1 );
+ area.subtract( new Area( new Rectangle2D.Float( ix, iy + ro2, rwh, rwh ) ) );
+ g.fill( area );
+
+ g.fill( r2 );
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemBorder.java
index 9d51a5af..aa8079a6 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemBorder.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemBorder.java
@@ -40,7 +40,7 @@ public class FlatMenuItemBorder
if( c.getParent() instanceof JMenuBar ) {
insets.top = scale( menuBarItemMargins.top );
insets.left = scale( menuBarItemMargins.left );
- insets.bottom = scale( menuBarItemMargins.bottom + 1 );
+ insets.bottom = scale( menuBarItemMargins.bottom );
insets.right = scale( menuBarItemMargins.right );
return insets;
} else
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 f09b560e..06891bce 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
@@ -17,30 +17,84 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
+import java.awt.Component;
import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.awt.LayoutManager2;
+import java.awt.Toolkit;
+import java.beans.PropertyChangeEvent;
+import java.util.function.Function;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
+import javax.swing.JLayeredPane;
+import javax.swing.JMenuBar;
import javax.swing.JRootPane;
+import javax.swing.LookAndFeel;
import javax.swing.UIManager;
+import javax.swing.plaf.BorderUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicRootPaneUI;
+import com.formdev.flatlaf.FlatClientProperties;
+import com.formdev.flatlaf.FlatLaf;
+import com.formdev.flatlaf.util.HiDPIUtils;
+import com.formdev.flatlaf.util.SystemInfo;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JRootPane}.
*
+ *
+ *
+ * @uiDefault RootPane.border Border
+ *
+ *
+ *
+ * @uiDefault RootPane.borderDragThickness int
+ * @uiDefault RootPane.cornerDragWidth int
+ * @uiDefault RootPane.honorMinimumSizeOnResize boolean
+ *
* @author Karl Tauber
*/
public class FlatRootPaneUI
extends BasicRootPaneUI
{
- private static ComponentUI instance;
+ // check this field before using class JBRCustomDecorations to avoid unnecessary loading of that class
+ static final boolean canUseJBRCustomDecorations
+ = SystemInfo.IS_JETBRAINS_JVM_11_OR_LATER && SystemInfo.IS_WINDOWS_10_OR_LATER;
+
+ private JRootPane rootPane;
+ private FlatTitlePane titlePane;
+ private LayoutManager oldLayout;
+ private FlatWindowResizer windowResizer;
public static ComponentUI createUI( JComponent c ) {
- if( instance == null )
- instance = new FlatRootPaneUI();
- return instance;
+ return new FlatRootPaneUI();
+ }
+
+ @Override
+ public void installUI( JComponent c ) {
+ super.installUI( c );
+
+ rootPane = (JRootPane) c;
+
+ if( rootPane.getWindowDecorationStyle() != JRootPane.NONE )
+ installClientDecorations();
+
+ if( canUseJBRCustomDecorations )
+ JBRCustomDecorations.install( rootPane );
+ }
+
+ @Override
+ public void uninstallUI( JComponent c ) {
+ super.uninstallUI( c );
+
+ uninstallClientDecorations();
+ rootPane = null;
}
@Override
@@ -58,5 +112,235 @@ public class FlatRootPaneUI
if( background == null || background instanceof UIResource )
parent.setBackground( UIManager.getColor( "control" ) );
}
+
+ // enable dark window appearance on macOS when running in JetBrains Runtime
+ if( SystemInfo.IS_JETBRAINS_JVM && SystemInfo.IS_MAC_OS_10_14_MOJAVE ) {
+ LookAndFeel laf = UIManager.getLookAndFeel();
+ boolean isDark = laf instanceof FlatLaf && ((FlatLaf)laf).isDark();
+ c.putClientProperty( "jetbrains.awt.windowDarkAppearance", isDark );
+ }
+ }
+
+ protected void installClientDecorations() {
+ boolean isJBRSupported = canUseJBRCustomDecorations && JBRCustomDecorations.isSupported();
+
+ // install border
+ if( rootPane.getWindowDecorationStyle() != JRootPane.NONE && !isJBRSupported )
+ LookAndFeel.installBorder( rootPane, "RootPane.border" );
+ else
+ LookAndFeel.uninstallBorder( rootPane );
+
+ // install title pane
+ setTitlePane( createTitlePane() );
+
+ // install layout
+ oldLayout = rootPane.getLayout();
+ rootPane.setLayout( createRootLayout() );
+
+ // install window resizer
+ if( !isJBRSupported )
+ windowResizer = createWindowResizer();
+ }
+
+ protected void uninstallClientDecorations() {
+ LookAndFeel.uninstallBorder( rootPane );
+ setTitlePane( null );
+
+ if( windowResizer != null ) {
+ windowResizer.uninstall();
+ windowResizer = null;
+ }
+
+ if( oldLayout != null ) {
+ rootPane.setLayout( oldLayout );
+ oldLayout = null;
+ }
+
+ if( rootPane.getWindowDecorationStyle() == JRootPane.NONE ) {
+ rootPane.revalidate();
+ rootPane.repaint();
+ }
+ }
+
+ protected FlatRootLayout createRootLayout() {
+ return new FlatRootLayout();
+ }
+
+ protected FlatWindowResizer createWindowResizer() {
+ return new FlatWindowResizer( rootPane );
+ }
+
+ protected FlatTitlePane createTitlePane() {
+ return new FlatTitlePane( rootPane );
+ }
+
+ // layer title pane under frame content layer to allow placing menu bar over title pane
+ protected final static Integer TITLE_PANE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 1;
+
+ protected void setTitlePane( FlatTitlePane newTitlePane ) {
+ JLayeredPane layeredPane = rootPane.getLayeredPane();
+
+ if( titlePane != null )
+ layeredPane.remove( titlePane );
+
+ if( newTitlePane != null )
+ layeredPane.add( newTitlePane, TITLE_PANE_LAYER );
+
+ titlePane = newTitlePane;
+ }
+
+ @Override
+ public void propertyChange( PropertyChangeEvent e ) {
+ super.propertyChange( e );
+
+ switch( e.getPropertyName() ) {
+ case "windowDecorationStyle":
+ uninstallClientDecorations();
+ if( rootPane.getWindowDecorationStyle() != JRootPane.NONE )
+ installClientDecorations();
+ break;
+
+ case FlatClientProperties.MENU_BAR_EMBEDDED:
+ if( titlePane != null ) {
+ titlePane.menuBarChanged();
+ rootPane.revalidate();
+ rootPane.repaint();
+ }
+ break;
+ }
+ }
+
+ //---- class FlatRootLayout -----------------------------------------------
+
+ protected class FlatRootLayout
+ implements LayoutManager2
+ {
+ @Override public void addLayoutComponent( String name, Component comp ) {}
+ @Override public void addLayoutComponent( Component comp, Object constraints ) {}
+ @Override public void removeLayoutComponent( Component comp ) {}
+
+ @Override
+ public Dimension preferredLayoutSize( Container parent ) {
+ return computeLayoutSize( parent, c -> c.getPreferredSize() );
+ }
+
+ @Override
+ public Dimension minimumLayoutSize( Container parent ) {
+ return computeLayoutSize( parent, c -> c.getMinimumSize() );
+ }
+
+ @Override
+ public Dimension maximumLayoutSize( Container parent ) {
+ return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
+ }
+
+ private Dimension computeLayoutSize( Container parent, Function getSizeFunc ) {
+ JRootPane rootPane = (JRootPane) parent;
+
+ Dimension titlePaneSize = (titlePane != null)
+ ? getSizeFunc.apply( titlePane )
+ : new Dimension();
+ Dimension contentSize = (rootPane.getContentPane() != null)
+ ? getSizeFunc.apply( rootPane.getContentPane() )
+ : rootPane.getSize();
+
+ int width = Math.max( titlePaneSize.width, contentSize.width );
+ int height = titlePaneSize.height + contentSize.height;
+ if( titlePane == null || !titlePane.isMenuBarEmbedded() ) {
+ Dimension menuBarSize = (rootPane.getJMenuBar() != null)
+ ? getSizeFunc.apply( rootPane.getJMenuBar() )
+ : new Dimension();
+
+ width = Math.max( width, menuBarSize.width );
+ height += menuBarSize.height;
+ }
+
+ Insets insets = rootPane.getInsets();
+
+ return new Dimension(
+ width + insets.left + insets.right,
+ height + insets.top + insets.bottom );
+ }
+
+ @Override
+ public void layoutContainer( Container parent ) {
+ JRootPane rootPane = (JRootPane) parent;
+
+ Insets insets = rootPane.getInsets();
+ int x = insets.left;
+ int y = insets.top;
+ int width = rootPane.getWidth() - insets.left - insets.right;
+ int height = rootPane.getHeight() - insets.top - insets.bottom;
+
+ if( rootPane.getLayeredPane() != null )
+ rootPane.getLayeredPane().setBounds( x, y, width, height );
+ if( rootPane.getGlassPane() != null )
+ rootPane.getGlassPane().setBounds( x, y, width, height );
+
+ int nextY = 0;
+ if( titlePane != null ) {
+ Dimension prefSize = titlePane.getPreferredSize();
+ titlePane.setBounds( 0, 0, width, prefSize.height );
+ nextY += prefSize.height;
+ }
+
+ JMenuBar menuBar = rootPane.getJMenuBar();
+ if( menuBar != null ) {
+ if( titlePane != null && titlePane.isMenuBarEmbedded() ) {
+ titlePane.validate();
+ menuBar.setBounds( titlePane.getMenuBarBounds() );
+ } else {
+ Dimension prefSize = menuBar.getPreferredSize();
+ menuBar.setBounds( 0, nextY, width, prefSize.height );
+ nextY += prefSize.height;
+ }
+ }
+
+ Container contentPane = rootPane.getContentPane();
+ if( contentPane != null )
+ contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) );
+ }
+
+ @Override
+ public void invalidateLayout( Container parent ) {
+ if( titlePane != null )
+ titlePane.menuBarChanged();
+ }
+
+ @Override
+ public float getLayoutAlignmentX( Container target ) {
+ return 0;
+ }
+
+ @Override
+ public float getLayoutAlignmentY( Container target ) {
+ return 0;
+ }
+ }
+
+ //---- class FlatWindowBorder ---------------------------------------------
+
+ public static class FlatWindowBorder
+ extends BorderUIResource.EmptyBorderUIResource
+ {
+ public FlatWindowBorder() {
+ super( 1, 1, 1, 1 );
+ }
+
+ @Override
+ public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
+ Object borderColorObj = Toolkit.getDefaultToolkit().getDesktopProperty(
+ "win.frame.activeBorderColor" );
+ Color borderColor = (borderColorObj instanceof Color)
+ ? (Color) borderColorObj
+ : UIManager.getColor( "windowBorder" );
+
+ g.setColor( borderColor );
+ HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
+ }
+
+ private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
+ g.drawRect( x, y, width - 1, height - 1 );
+ }
}
}
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
new file mode 100644
index 00000000..a2a76824
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java
@@ -0,0 +1,727 @@
+/*
+ * Copyright 2020 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.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+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;
+import java.awt.event.WindowAdapter;
+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 java.util.Objects;
+import javax.accessibility.AccessibleContext;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JRootPane;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.border.AbstractBorder;
+import javax.swing.border.Border;
+import com.formdev.flatlaf.FlatClientProperties;
+import com.formdev.flatlaf.FlatSystemProperties;
+import com.formdev.flatlaf.ui.JBRCustomDecorations.JBRWindowTopBorder;
+import com.formdev.flatlaf.util.ScaledImageIcon;
+import com.formdev.flatlaf.util.SystemInfo;
+import com.formdev.flatlaf.util.UIScale;
+
+/**
+ * Provides the Flat LaF title bar.
+ *
+ * @uiDefault TitlePane.background Color
+ * @uiDefault TitlePane.inactiveBackground Color
+ * @uiDefault TitlePane.foreground Color
+ * @uiDefault TitlePane.inactiveForeground Color
+ * @uiDefault TitlePane.embeddedForeground Color
+ * @uiDefault TitlePane.iconSize Dimension
+ * @uiDefault TitlePane.iconMargins Insets
+ * @uiDefault TitlePane.titleMargins Insets
+ * @uiDefault TitlePane.menuBarMargins Insets
+ * @uiDefault TitlePane.menuBarEmbedded boolean
+ * @uiDefault TitlePane.buttonMaximizedHeight int
+ * @uiDefault TitlePane.closeIcon Icon
+ * @uiDefault TitlePane.iconifyIcon Icon
+ * @uiDefault TitlePane.maximizeIcon Icon
+ * @uiDefault TitlePane.restoreIcon Icon
+ *
+ * @author Karl Tauber
+ */
+public class FlatTitlePane
+ extends JComponent
+{
+ private final Color activeBackground = UIManager.getColor( "TitlePane.background" );
+ private final Color inactiveBackground = UIManager.getColor( "TitlePane.inactiveBackground" );
+ private final Color activeForeground = UIManager.getColor( "TitlePane.foreground" );
+ private final Color inactiveForeground = UIManager.getColor( "TitlePane.inactiveForeground" );
+ private final Color embeddedForeground = UIManager.getColor( "TitlePane.embeddedForeground" );
+
+ private final Insets menuBarMargins = UIManager.getInsets( "TitlePane.menuBarMargins" );
+ private final Dimension iconSize = UIManager.getDimension( "TitlePane.iconSize" );
+ private final int buttonMaximizedHeight = UIManager.getInt( "TitlePane.buttonMaximizedHeight" );
+
+ private final JRootPane rootPane;
+
+ private JPanel leftPanel;
+ private JLabel iconLabel;
+ private JComponent menuBarPlaceholder;
+ private JLabel titleLabel;
+ private JPanel buttonPanel;
+ private JButton iconifyButton;
+ private JButton maximizeButton;
+ private JButton restoreButton;
+ private JButton closeButton;
+
+ private final Handler handler;
+ private Window window;
+
+ public FlatTitlePane( JRootPane rootPane ) {
+ this.rootPane = rootPane;
+
+ handler = createHandler();
+ setBorder( createTitlePaneBorder() );
+
+ addSubComponents();
+ activeChanged( true );
+
+ addMouseListener( handler );
+ addMouseMotionListener( handler );
+
+ // necessary for closing window with double-click on icon
+ iconLabel.addMouseListener( handler );
+ }
+
+ protected FlatTitlePaneBorder createTitlePaneBorder() {
+ return new FlatTitlePaneBorder();
+ }
+
+ protected Handler createHandler() {
+ return new Handler();
+ }
+
+ protected void addSubComponents() {
+ leftPanel = new JPanel();
+ iconLabel = new JLabel();
+ titleLabel = new JLabel();
+ iconLabel.setBorder( new FlatEmptyBorder( UIManager.getInsets( "TitlePane.iconMargins" ) ) );
+ titleLabel.setBorder( new FlatEmptyBorder( UIManager.getInsets( "TitlePane.titleMargins" ) ) );
+
+ leftPanel.setLayout( new BoxLayout( leftPanel, BoxLayout.LINE_AXIS ) );
+ leftPanel.setOpaque( false );
+ leftPanel.add( iconLabel );
+
+ menuBarPlaceholder = new JComponent() {
+ @Override
+ public Dimension getPreferredSize() {
+ JMenuBar menuBar = rootPane.getJMenuBar();
+ return (menuBar != null && isMenuBarEmbedded())
+ ? FlatUIUtils.addInsets( menuBar.getPreferredSize(), UIScale.scale( menuBarMargins ) )
+ : new Dimension();
+ }
+ };
+ leftPanel.add( menuBarPlaceholder );
+
+ createButtons();
+
+ setLayout( new BorderLayout() );
+ add( leftPanel, BorderLayout.LINE_START );
+ add( titleLabel, BorderLayout.CENTER );
+ add( buttonPanel, BorderLayout.LINE_END );
+ }
+
+ protected void createButtons() {
+ iconifyButton = createButton( "TitlePane.iconifyIcon", "Iconify", e -> iconify() );
+ maximizeButton = createButton( "TitlePane.maximizeIcon", "Maximize", e -> maximize() );
+ restoreButton = createButton( "TitlePane.restoreIcon", "Restore", e -> restore() );
+ closeButton = createButton( "TitlePane.closeIcon", "Close", e -> close() );
+
+ buttonPanel = new JPanel() {
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ if( buttonMaximizedHeight > 0 &&
+ window instanceof Frame &&
+ (((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0 )
+ {
+ // make title pane height smaller when frame is maximized
+ size = new Dimension( size.width, Math.min( size.height, UIScale.scale( buttonMaximizedHeight ) ) );
+ }
+ return size;
+ }
+ };
+ buttonPanel.setOpaque( false );
+ buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.LINE_AXIS ) );
+ if( rootPane.getWindowDecorationStyle() == JRootPane.FRAME ) {
+ // JRootPane.FRAME works only for frames (and not for dialogs)
+ // but at this time the owner window type is unknown (not yet added)
+ // so we add the iconify/maximize/restore buttons and they are hidden
+ // later in frameStateChanged(), which is invoked from addNotify()
+
+ restoreButton.setVisible( false );
+
+ buttonPanel.add( iconifyButton );
+ buttonPanel.add( maximizeButton );
+ buttonPanel.add( restoreButton );
+ }
+ buttonPanel.add( closeButton );
+ }
+
+ protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) {
+ JButton button = new JButton( UIManager.getIcon( iconKey ) );
+ button.setFocusable( false );
+ button.setContentAreaFilled( false );
+ button.setBorder( BorderFactory.createEmptyBorder() );
+ button.putClientProperty( AccessibleContext.ACCESSIBLE_NAME_PROPERTY, accessibleName );
+ button.addActionListener( action );
+ return button;
+ }
+
+ protected void activeChanged( boolean active ) {
+ Color background = FlatUIUtils.nonUIResource( active ? activeBackground : inactiveBackground );
+ Color foreground = FlatUIUtils.nonUIResource( active
+ ? (rootPane.getJMenuBar() != null && isMenuBarEmbedded() ? embeddedForeground : activeForeground)
+ : inactiveForeground );
+
+ setBackground( background );
+ titleLabel.setForeground( foreground );
+
+ // this is necessary because hover/pressed colors are derived from background color
+ iconifyButton.setBackground( background );
+ maximizeButton.setBackground( background );
+ restoreButton.setBackground( background );
+ closeButton.setBackground( background );
+ }
+
+ protected void frameStateChanged() {
+ if( window == null || rootPane.getWindowDecorationStyle() != JRootPane.FRAME )
+ return;
+
+ if( window instanceof Frame ) {
+ Frame frame = (Frame) window;
+ boolean resizable = frame.isResizable();
+ boolean maximized = ((frame.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0);
+
+ iconifyButton.setVisible( true );
+ maximizeButton.setVisible( resizable && !maximized );
+ restoreButton.setVisible( resizable && maximized );
+ } else {
+ // hide buttons because they are only supported in frames
+ iconifyButton.setVisible( false );
+ maximizeButton.setVisible( false );
+ restoreButton.setVisible( false );
+
+ revalidate();
+ repaint();
+ }
+ }
+
+ protected void updateIcon() {
+ // get window images
+ List images = window.getIconImages();
+ if( images.isEmpty() ) {
+ // search in owners
+ for( Window owner = window.getOwner(); owner != null; owner = owner.getOwner() ) {
+ images = owner.getIconImages();
+ if( !images.isEmpty() )
+ break;
+ }
+ }
+
+ boolean hasIcon = true;
+
+ // set icon
+ if( !images.isEmpty() )
+ iconLabel.setIcon( FlatTitlePaneIcon.create( images, iconSize ) );
+ else {
+ // no icon set on window --> use default icon
+ Icon defaultIcon = UIManager.getIcon( "InternalFrame.icon" );
+ if( defaultIcon != null ) {
+ if( defaultIcon instanceof ImageIcon )
+ defaultIcon = new ScaledImageIcon( (ImageIcon) defaultIcon, iconSize.width, iconSize.height );
+ iconLabel.setIcon( defaultIcon );
+ } else
+ hasIcon = false;
+ }
+
+ // show/hide icon
+ iconLabel.setVisible( hasIcon );
+
+ updateJBRHitTestSpotsAndTitleBarHeightLater();
+ }
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+
+ uninstallWindowListeners();
+
+ window = SwingUtilities.getWindowAncestor( this );
+ if( window != null ) {
+ frameStateChanged();
+ activeChanged( window.isActive() );
+ updateIcon();
+ titleLabel.setText( getWindowTitle() );
+ installWindowListeners();
+ }
+
+ updateJBRHitTestSpotsAndTitleBarHeightLater();
+ }
+
+ @Override
+ public void removeNotify() {
+ super.removeNotify();
+
+ uninstallWindowListeners();
+ window = null;
+ }
+
+ protected String getWindowTitle() {
+ if( window instanceof Frame )
+ return ((Frame)window).getTitle();
+ if( window instanceof Dialog )
+ return ((Dialog)window).getTitle();
+ return null;
+ }
+
+ protected void installWindowListeners() {
+ if( window == null )
+ return;
+
+ window.addPropertyChangeListener( handler );
+ window.addWindowListener( handler );
+ window.addWindowStateListener( handler );
+ window.addComponentListener( handler );
+ }
+
+ protected void uninstallWindowListeners() {
+ if( window == null )
+ return;
+
+ window.removePropertyChangeListener( handler );
+ window.removeWindowListener( handler );
+ window.removeWindowStateListener( handler );
+ window.removeComponentListener( handler );
+ }
+
+ protected boolean isMenuBarEmbedded() {
+ // not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime
+ return UIManager.getBoolean( "TitlePane.menuBarEmbedded" ) &&
+ FlatClientProperties.clientPropertyBoolean( rootPane, FlatClientProperties.MENU_BAR_EMBEDDED, true ) &&
+ FlatSystemProperties.getBoolean( FlatSystemProperties.MENUBAR_EMBEDDED, true );
+ }
+
+ protected Rectangle getMenuBarBounds() {
+ Insets insets = rootPane.getInsets();
+ Rectangle bounds = new Rectangle(
+ SwingUtilities.convertPoint( menuBarPlaceholder, -insets.left, -insets.top, rootPane ),
+ menuBarPlaceholder.getSize() );
+
+ // add menu bar bottom border insets to bounds so that menu bar overlaps
+ // title pane border (menu bar border is painted over title pane border)
+ Insets borderInsets = getBorder().getBorderInsets( this );
+ bounds.height += borderInsets.bottom;
+
+ return FlatUIUtils.subtractInsets( bounds, UIScale.scale( getMenuBarMargins() ) );
+ }
+
+ protected void menuBarChanged() {
+ menuBarPlaceholder.invalidate();
+
+ // update title foreground color
+ EventQueue.invokeLater( () -> {
+ activeChanged( window == null || window.isActive() );
+ } );
+ }
+
+ protected Insets getMenuBarMargins() {
+ return getComponentOrientation().isLeftToRight()
+ ? menuBarMargins
+ : new Insets( menuBarMargins.top, menuBarMargins.right, menuBarMargins.bottom, menuBarMargins.left );
+ }
+
+ @Override
+ protected void paintComponent( Graphics g ) {
+ g.setColor( getBackground() );
+ g.fillRect( 0, 0, getWidth(), getHeight() );
+ }
+
+ /**
+ * Iconifies the window.
+ */
+ protected void iconify() {
+ if( window instanceof Frame ) {
+ Frame frame = (Frame) window;
+ frame.setExtendedState( frame.getExtendedState() | Frame.ICONIFIED );
+ }
+ }
+
+ /**
+ * Maximizes the window.
+ */
+ protected void maximize() {
+ if( !(window instanceof Frame) )
+ return;
+
+ Frame frame = (Frame) window;
+
+ // set maximized bounds to avoid that maximized window overlaps Windows task bar
+ // (if not running in JBR and if not modified from the application)
+ if( !hasJBRCustomDecoration() &&
+ (frame.getMaximizedBounds() == null ||
+ Objects.equals( frame.getMaximizedBounds(), rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) )
+ {
+ GraphicsConfiguration gc = window.getGraphicsConfiguration();
+
+ // Screen bounds, which may be smaller than physical size on Java 9+.
+ // E.g. if running a 3840x2160 screen at 200%, screenBounds.size is 1920x1080.
+ // In Java 9+, each screen can have its own scale factor.
+ //
+ // On Java 8, which does not scale, screenBounds.size of the primary screen
+ // is identical to its physical size. But when the primary screen is scaled,
+ // then screenBounds.size of secondary screens is scaled with the scale factor
+ // of the primary screen.
+ // E.g. primary 3840x2160 screen at 150%, secondary 1920x1080 screen at 100%,
+ // then screenBounds.size is 3840x2160 on primary and 2880x1560 on secondary.
+ Rectangle screenBounds = gc.getBounds();
+
+ int maximizedX = screenBounds.x;
+ int maximizedY = screenBounds.y;
+ int maximizedWidth = screenBounds.width;
+ int maximizedHeight = screenBounds.height;
+
+ if( !SystemInfo.IS_JAVA_15_OR_LATER ) {
+ // on Java 8 to 14, maximized x,y are 0,0 based on all screens in a multi-screen environment
+ maximizedX = 0;
+ maximizedY = 0;
+
+ // scale maximized screen size to get physical screen size for Java 9 to 14
+ AffineTransform defaultTransform = gc.getDefaultTransform();
+ maximizedWidth = (int) (maximizedWidth * defaultTransform.getScaleX());
+ maximizedHeight = (int) (maximizedHeight * defaultTransform.getScaleY());
+ }
+
+ // screen insets are in physical size, except for Java 15+
+ // (see https://bugs.openjdk.java.net/browse/JDK-8243925)
+ // and except for Java 8 on secondary screens where primary screen is scaled
+ Insets screenInsets = window.getToolkit().getScreenInsets( gc );
+
+ // maximized bounds are required in physical size, except for Java 15+
+ // (see https://bugs.openjdk.java.net/browse/JDK-8231564 and
+ // https://bugs.openjdk.java.net/browse/JDK-8176359)
+ // and except for Java 8 on secondary screens where primary screen is scaled
+ Rectangle maximizedBounds = new Rectangle(
+ maximizedX + screenInsets.left,
+ maximizedY + screenInsets.top,
+ maximizedWidth - screenInsets.left - screenInsets.right,
+ maximizedHeight - screenInsets.top - screenInsets.bottom );
+
+ // change maximized bounds
+ frame.setMaximizedBounds( maximizedBounds );
+
+ // remember maximized bounds in client property to be able to detect
+ // whether maximized bounds are modified from the application
+ rootPane.putClientProperty( "_flatlaf.maximizedBounds", maximizedBounds );
+ }
+
+ // maximize window
+ frame.setExtendedState( frame.getExtendedState() | Frame.MAXIMIZED_BOTH );
+ }
+
+ /**
+ * Restores the window size.
+ */
+ protected void restore() {
+ if( window instanceof Frame ) {
+ Frame frame = (Frame) window;
+ int state = frame.getExtendedState();
+ frame.setExtendedState( ((state & Frame.ICONIFIED) != 0)
+ ? (state & ~Frame.ICONIFIED)
+ : (state & ~Frame.MAXIMIZED_BOTH) );
+ }
+ }
+
+ /**
+ * Closes the window.
+ */
+ protected void close() {
+ if( window != null )
+ window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
+ }
+
+ protected boolean hasJBRCustomDecoration() {
+ return FlatRootPaneUI.canUseJBRCustomDecorations &&
+ window != null &&
+ JBRCustomDecorations.hasCustomDecoration( window );
+ }
+
+ protected void updateJBRHitTestSpotsAndTitleBarHeightLater() {
+ EventQueue.invokeLater( () -> {
+ updateJBRHitTestSpotsAndTitleBarHeight();
+ } );
+ }
+
+ protected void updateJBRHitTestSpotsAndTitleBarHeight() {
+ if( !isDisplayable() )
+ return;
+
+ if( !hasJBRCustomDecoration() )
+ return;
+
+ List hitTestSpots = new ArrayList<>();
+ if( iconLabel.isVisible() )
+ addJBRHitTestSpot( iconLabel, false, hitTestSpots );
+ addJBRHitTestSpot( buttonPanel, false, hitTestSpots );
+ addJBRHitTestSpot( menuBarPlaceholder, true, hitTestSpots );
+
+ int titleBarHeight = getHeight();
+ // slightly reduce height so that component receives mouseExit events
+ if( titleBarHeight > 0 )
+ titleBarHeight--;
+
+ JBRCustomDecorations.setHitTestSpotsAndTitleBarHeight( window, hitTestSpots, titleBarHeight );
+ }
+
+ protected void addJBRHitTestSpot( JComponent c, boolean subtractMenuBarMargins, List hitTestSpots ) {
+ Dimension size = c.getSize();
+ if( size.width <= 0 || size.height <= 0 )
+ return;
+
+ Point location = SwingUtilities.convertPoint( c, 0, 0, window );
+ Rectangle r = new Rectangle( location, size );
+ if( subtractMenuBarMargins )
+ r = FlatUIUtils.subtractInsets( r, UIScale.scale( getMenuBarMargins() ) );
+ // slightly increase rectangle so that component receives mouseExit events
+ r.grow( 2, 2 );
+ hitTestSpots.add( r );
+ }
+
+ //---- class TitlePaneBorder ----------------------------------------------
+
+ protected class FlatTitlePaneBorder
+ extends AbstractBorder
+ {
+ @Override
+ public Insets getBorderInsets( Component c, Insets insets ) {
+ super.getBorderInsets( c, insets );
+
+ // if menu bar is embedded, add bottom insets of menu bar border
+ Border menuBarBorder = getMenuBarBorder();
+ if( menuBarBorder != null ) {
+ Insets menuBarInsets = menuBarBorder.getBorderInsets( c );
+ insets.bottom += menuBarInsets.bottom;
+ }
+
+ if( hasJBRCustomDecoration() )
+ insets = FlatUIUtils.addInsets( insets, JBRWindowTopBorder.getInstance().getBorderInsets() );
+
+ return insets;
+ }
+
+ @Override
+ public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
+ // if menu bar is embedded, paint menu bar border
+ Border menuBarBorder = getMenuBarBorder();
+ if( menuBarBorder != null )
+ menuBarBorder.paintBorder( c, g, x, y, width, height );
+
+ if( hasJBRCustomDecoration() )
+ JBRWindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height );
+ }
+
+ protected Border getMenuBarBorder() {
+ JMenuBar menuBar = rootPane.getJMenuBar();
+ return (menuBar != null && isMenuBarEmbedded()) ? menuBar.getBorder() : null;
+ }
+ }
+
+ //---- class Handler ------------------------------------------------------
+
+ protected class Handler
+ extends WindowAdapter
+ implements PropertyChangeListener, MouseListener, MouseMotionListener, ComponentListener
+ {
+ //---- interface PropertyChangeListener ----
+
+ @Override
+ public void propertyChange( PropertyChangeEvent e ) {
+ switch( e.getPropertyName() ) {
+ case "title":
+ titleLabel.setText( getWindowTitle() );
+ break;
+
+ case "resizable":
+ if( window instanceof Frame )
+ frameStateChanged();
+ break;
+
+ case "iconImage":
+ updateIcon();
+ break;
+
+ case "componentOrientation":
+ updateJBRHitTestSpotsAndTitleBarHeightLater();
+ break;
+ }
+ }
+
+ //---- interface WindowListener ----
+
+ @Override
+ public void windowActivated( WindowEvent e ) {
+ activeChanged( true );
+ updateJBRHitTestSpotsAndTitleBarHeight();
+
+ if( hasJBRCustomDecoration() )
+ JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
+ }
+
+ @Override
+ public void windowDeactivated( WindowEvent e ) {
+ activeChanged( false );
+ updateJBRHitTestSpotsAndTitleBarHeight();
+
+ if( hasJBRCustomDecoration() )
+ JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
+ }
+
+ @Override
+ public void windowStateChanged( WindowEvent e ) {
+ frameStateChanged();
+ updateJBRHitTestSpotsAndTitleBarHeight();
+ }
+
+ //---- interface MouseListener ----
+
+ private int lastXOnScreen;
+ private int lastYOnScreen;
+
+ @Override
+ public void mouseClicked( MouseEvent e ) {
+ if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) {
+ if( e.getSource() == iconLabel ) {
+ // double-click on icon closes window
+ close();
+ } else if( !hasJBRCustomDecoration() &&
+ window instanceof Frame &&
+ ((Frame)window).isResizable() )
+ {
+ // maximize/restore on double-click
+ Frame frame = (Frame) window;
+ if( (frame.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0 )
+ restore();
+ else
+ maximize();
+ }
+ }
+ }
+
+ @Override
+ public void mousePressed( MouseEvent e ) {
+ lastXOnScreen = e.getXOnScreen();
+ lastYOnScreen = e.getYOnScreen();
+ }
+
+ @Override public void mouseReleased( MouseEvent e ) {}
+ @Override public void mouseEntered( MouseEvent e ) {}
+ @Override public void mouseExited( MouseEvent e ) {}
+
+ //---- interface MouseMotionListener ----
+
+ @Override
+ public void mouseDragged( MouseEvent e ) {
+ if( hasJBRCustomDecoration() )
+ return; // do nothing if running in JBR
+
+ int xOnScreen = e.getXOnScreen();
+ int yOnScreen = e.getYOnScreen();
+ if( lastXOnScreen == xOnScreen && lastYOnScreen == yOnScreen )
+ return;
+
+ // restore window if it is maximized
+ if( window instanceof Frame ) {
+ Frame frame = (Frame) window;
+ int state = frame.getExtendedState();
+ if( (state & Frame.MAXIMIZED_BOTH) != 0 ) {
+ int maximizedX = window.getX();
+ int maximizedY = window.getY();
+
+ // restore window size, which also moves window to pre-maximized location
+ frame.setExtendedState( state & ~Frame.MAXIMIZED_BOTH );
+
+ int restoredWidth = window.getWidth();
+ int newX = maximizedX;
+ JComponent rightComp = getComponentOrientation().isLeftToRight() ? buttonPanel : leftPanel;
+ if( xOnScreen >= maximizedX + restoredWidth - rightComp.getWidth() - 10 )
+ newX = xOnScreen + rightComp.getWidth() + 10 - restoredWidth;
+
+ // move window near mouse
+ window.setLocation( newX, maximizedY );
+ return;
+ }
+ }
+
+ // compute new window location
+ int newX = window.getX() + (xOnScreen - lastXOnScreen);
+ int newY = window.getY() + (yOnScreen - lastYOnScreen);
+
+ // move window
+ window.setLocation( newX, newY );
+
+ lastXOnScreen = xOnScreen;
+ lastYOnScreen = yOnScreen;
+ }
+
+ @Override public void mouseMoved( MouseEvent e ) {}
+
+ //---- interface ComponentListener ----
+
+ @Override
+ public void componentResized( ComponentEvent e ) {
+ updateJBRHitTestSpotsAndTitleBarHeightLater();
+ }
+
+ @Override public void componentMoved( ComponentEvent e ) {}
+ @Override public void componentShown( ComponentEvent e ) {}
+ @Override public void componentHidden( ComponentEvent e ) {}
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePaneIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePaneIcon.java
new file mode 100644
index 00000000..fbe166a0
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePaneIcon.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2020 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.Dimension;
+import java.awt.Image;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import com.formdev.flatlaf.util.MultiResolutionImageSupport;
+import com.formdev.flatlaf.util.ScaledImageIcon;
+
+/**
+ * @author Karl Tauber
+ */
+public class FlatTitlePaneIcon
+ extends ScaledImageIcon
+{
+ public static Icon create( List images, Dimension size ) {
+ // collect all images including multi-resolution variants
+ List allImages = new ArrayList<>();
+ for( Image image : images ) {
+ if( MultiResolutionImageSupport.isMultiResolutionImage( image ) )
+ allImages.addAll( MultiResolutionImageSupport.getResolutionVariants( image ) );
+ else
+ allImages.add( image );
+ }
+
+ // sort images by size
+ allImages.sort( (image1, image2) -> {
+ return image1.getWidth( null ) - image2.getWidth( null );
+ } );
+
+ // create icon
+ return new FlatTitlePaneIcon( allImages, size );
+ }
+
+ private final List images;
+
+ private FlatTitlePaneIcon( List images, Dimension size ) {
+ super( new ImageIcon( images.get( 0 ) ), size.width, size.height );
+ this.images = images;
+ }
+
+ @Override
+ protected Image getResolutionVariant( int destImageWidth, int destImageHeight ) {
+ for( Image image : images ) {
+ if( destImageWidth <= image.getWidth( null ) &&
+ destImageHeight <= image.getHeight( null ) )
+ return image;
+ }
+
+ return images.get( images.size() - 1 );
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowResizer.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowResizer.java
new file mode 100644
index 00000000..c8f0365b
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowResizer.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2020 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 static java.awt.Cursor.*;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.Window;
+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;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowStateListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import javax.swing.JComponent;
+import javax.swing.JLayeredPane;
+import javax.swing.JRootPane;
+import javax.swing.UIManager;
+import com.formdev.flatlaf.util.UIScale;
+
+/**
+ * Resizes frames and dialogs.
+ *
+ * @author Karl Tauber
+ */
+public class FlatWindowResizer
+ extends JComponent
+ implements PropertyChangeListener, WindowStateListener, ComponentListener
+{
+ private final static Integer WINDOW_RESIZER_LAYER = JLayeredPane.DRAG_LAYER + 1;
+
+ private final JRootPane rootPane;
+
+ private final int borderDragThickness = FlatUIUtils.getUIInt( "RootPane.borderDragThickness", 5 );
+ private final int cornerDragWidth = FlatUIUtils.getUIInt( "RootPane.cornerDragWidth", 16 );
+ private final boolean honorMinimumSizeOnResize = UIManager.getBoolean( "RootPane.honorMinimumSizeOnResize" );
+
+ private Window window;
+
+ public FlatWindowResizer( JRootPane rootPane ) {
+ this.rootPane = rootPane;
+
+ setLayout( new BorderLayout() );
+ add( createDragBorderComponent( NW_RESIZE_CURSOR, N_RESIZE_CURSOR, NE_RESIZE_CURSOR ), BorderLayout.NORTH );
+ add( createDragBorderComponent( SW_RESIZE_CURSOR, S_RESIZE_CURSOR, SE_RESIZE_CURSOR ), BorderLayout.SOUTH );
+ add( createDragBorderComponent( NW_RESIZE_CURSOR, W_RESIZE_CURSOR, SW_RESIZE_CURSOR ), BorderLayout.WEST );
+ add( createDragBorderComponent( NE_RESIZE_CURSOR, E_RESIZE_CURSOR, SE_RESIZE_CURSOR ), BorderLayout.EAST );
+
+ rootPane.addComponentListener( this );
+ rootPane.getLayeredPane().add( this, WINDOW_RESIZER_LAYER );
+
+ if( rootPane.isDisplayable() )
+ setBounds( 0, 0, rootPane.getWidth(), rootPane.getHeight() );
+ }
+
+ protected DragBorderComponent createDragBorderComponent( int leadingResizeDir, int centerResizeDir, int trailingResizeDir ) {
+ return new DragBorderComponent( leadingResizeDir, centerResizeDir, trailingResizeDir );
+ }
+
+ public void uninstall() {
+ rootPane.removeComponentListener( this );
+ rootPane.getLayeredPane().remove( this );
+ }
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+
+ Container parent = rootPane.getParent();
+ window = (parent instanceof Window) ? (Window) parent : null;
+ if( window instanceof Frame ) {
+ window.addPropertyChangeListener( "resizable", this );
+ window.addWindowStateListener( this );
+ }
+
+ updateVisibility();
+ }
+
+ @Override
+ public void removeNotify() {
+ super.removeNotify();
+
+ if( window instanceof Frame ) {
+ window.removePropertyChangeListener( "resizable", this );
+ window.removeWindowStateListener( this );
+ }
+ window = null;
+
+ updateVisibility();
+ }
+
+ @Override
+ protected void paintChildren( Graphics g ) {
+ super.paintChildren( g );
+
+ // this is necessary because Dialog.setResizable() does not fire events
+ if( window instanceof Dialog )
+ updateVisibility();
+ }
+
+ private void updateVisibility() {
+ boolean visible = isWindowResizable();
+ if( visible == getComponent( 0 ).isVisible() )
+ return;
+
+ for( Component c : getComponents() )
+ c.setVisible( visible );
+ }
+
+ private boolean isWindowResizable() {
+ if( window instanceof Frame )
+ return ((Frame)window).isResizable() && (((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) == 0;
+ if( window instanceof Dialog )
+ return ((Dialog)window).isResizable();
+ return false;
+ }
+
+ @Override
+ public void propertyChange( PropertyChangeEvent e ) {
+ updateVisibility();
+ }
+
+ @Override
+ public void windowStateChanged( WindowEvent e ) {
+ updateVisibility();
+ }
+
+ @Override
+ public void componentResized( ComponentEvent e ) {
+ setBounds( 0, 0, rootPane.getWidth(), rootPane.getHeight() );
+ validate();
+ }
+
+ @Override public void componentMoved( ComponentEvent e ) {}
+ @Override public void componentShown( ComponentEvent e ) {}
+ @Override public void componentHidden( ComponentEvent e ) {}
+
+ //---- class DragBorderComponent ------------------------------------------
+
+ protected class DragBorderComponent
+ extends JComponent
+ implements MouseListener, MouseMotionListener
+ {
+ private final int leadingResizeDir;
+ private final int centerResizeDir;
+ private final int trailingResizeDir;
+
+ private int resizeDir = -1;
+ private int dragStartMouseX;
+ private int dragStartMouseY;
+ private Rectangle dragStartWindowBounds;
+
+ protected DragBorderComponent( int leadingResizeDir, int centerResizeDir, int trailingResizeDir ) {
+ this.leadingResizeDir = leadingResizeDir;
+ this.centerResizeDir = centerResizeDir;
+ this.trailingResizeDir = trailingResizeDir;
+
+ setResizeDir( centerResizeDir );
+ setVisible( false );
+
+ addMouseListener( this );
+ addMouseMotionListener( this );
+ }
+
+ protected void setResizeDir( int resizeDir ) {
+ if( this.resizeDir == resizeDir )
+ return;
+ this.resizeDir = resizeDir;
+
+ setCursor( getPredefinedCursor( resizeDir ) );
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ int thickness = UIScale.scale( borderDragThickness );
+ return new Dimension( thickness, thickness );
+ }
+
+/*debug
+ @Override
+ protected void paintComponent( Graphics g ) {
+ g.setColor( java.awt.Color.red );
+ g.drawRect( 0, 0, getWidth() - 1, getHeight() - 1 );
+ }
+debug*/
+
+ @Override
+ public void mouseClicked( MouseEvent e ) {
+ }
+
+ @Override
+ public void mousePressed( MouseEvent e ) {
+ if( window == null )
+ return;
+
+ dragStartMouseX = e.getXOnScreen();
+ dragStartMouseY = e.getYOnScreen();
+ dragStartWindowBounds = window.getBounds();
+ }
+
+ @Override
+ public void mouseReleased( MouseEvent e ) {
+ dragStartWindowBounds = null;
+ }
+
+ @Override
+ public void mouseEntered( MouseEvent e ) {
+ }
+
+ @Override
+ public void mouseExited( MouseEvent e ) {
+ }
+
+ @Override
+ public void mouseMoved( MouseEvent e ) {
+ boolean topBottom = (centerResizeDir == N_RESIZE_CURSOR || centerResizeDir == S_RESIZE_CURSOR);
+ int xy = topBottom ? e.getX() : e.getY();
+ int wh = topBottom ? getWidth() : getHeight();
+ int cornerWH = UIScale.scale( cornerDragWidth - (topBottom ? 0 : borderDragThickness) );
+
+ setResizeDir( xy <= cornerWH
+ ? leadingResizeDir
+ : (xy >= wh - cornerWH
+ ? trailingResizeDir
+ : centerResizeDir) );
+ }
+
+ @Override
+ public void mouseDragged( MouseEvent e ) {
+ if( dragStartWindowBounds == null )
+ return;
+
+ if( !isWindowResizable() )
+ return;
+
+ int mouseDeltaX = e.getXOnScreen() - dragStartMouseX;
+ int mouseDeltaY = e.getYOnScreen() - dragStartMouseY;
+
+ int deltaX = 0;
+ int deltaY = 0;
+ int deltaWidth = 0;
+ int deltaHeight = 0;
+
+ // north
+ if( resizeDir == N_RESIZE_CURSOR || resizeDir == NW_RESIZE_CURSOR || resizeDir == NE_RESIZE_CURSOR ) {
+ deltaY = mouseDeltaY;
+ deltaHeight = -mouseDeltaY;
+ }
+
+ // south
+ if( resizeDir == S_RESIZE_CURSOR || resizeDir == SW_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR )
+ deltaHeight = mouseDeltaY;
+
+ // west
+ if( resizeDir == W_RESIZE_CURSOR || resizeDir == NW_RESIZE_CURSOR || resizeDir == SW_RESIZE_CURSOR ) {
+ deltaX = mouseDeltaX;
+ deltaWidth = -mouseDeltaX;
+ }
+
+ // east
+ if( resizeDir == E_RESIZE_CURSOR || resizeDir == NE_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR )
+ deltaWidth = mouseDeltaX;
+
+ // compute new window bounds
+ Rectangle newBounds = new Rectangle( dragStartWindowBounds );
+ newBounds.x += deltaX;
+ newBounds.y += deltaY;
+ newBounds.width += deltaWidth;
+ newBounds.height += deltaHeight;
+
+ // apply minimum window size
+ Dimension minimumSize = honorMinimumSizeOnResize ? window.getMinimumSize() : null;
+ if( minimumSize == null )
+ minimumSize = UIScale.scale( new Dimension( 150, 50 ) );
+ if( newBounds.width < minimumSize.width ) {
+ if( deltaX != 0 )
+ newBounds.x -= (minimumSize.width - newBounds.width);
+ newBounds.width = minimumSize.width;
+ }
+ if( newBounds.height < minimumSize.height ) {
+ if( deltaY != 0 )
+ newBounds.y -= (minimumSize.height - newBounds.height);
+ newBounds.height = minimumSize.height;
+ }
+
+ // set window bounds
+ if( !newBounds.equals( dragStartWindowBounds ) ) {
+ window.setBounds( newBounds );
+
+ // immediately layout drag border components
+ FlatWindowResizer.this.setBounds( 0, 0, newBounds.width, newBounds.height );
+ FlatWindowResizer.this.validate();
+
+ if( Toolkit.getDefaultToolkit().isDynamicLayoutActive() ) {
+ window.validate();
+ rootPane.repaint();
+ }
+ }
+ }
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java
new file mode 100644
index 00000000..9054c3f4
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2020 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.Container;
+import java.awt.EventQueue;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.beans.PropertyChangeListener;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JRootPane;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.plaf.BorderUIResource;
+import com.formdev.flatlaf.FlatLaf;
+import com.formdev.flatlaf.FlatSystemProperties;
+import com.formdev.flatlaf.util.HiDPIUtils;
+import com.formdev.flatlaf.util.SystemInfo;
+
+/**
+ * Support for custom window decorations provided by JetBrains Runtime (based on OpenJDK).
+ * Requires that the application runs on Windows 10 in a JetBrains Runtime 11 or later.
+ *
+ *
+ * @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 ) {
+ if( !isSupported() )
+ return;
+
+ // check whether root pane already has a parent, which is the case when switching LaF
+ if( rootPane.getParent() != null )
+ return;
+
+ // Use hierarchy listener to wait until the root pane is added to a window.
+ // Enabling JBR decorations must be done very early, probably before
+ // window becomes displayable (window.isDisplayable()). Tried also using
+ // "ancestor" property change event on root pane, but this is invoked too late.
+ HierarchyListener addListener = new HierarchyListener() {
+ @Override
+ public void hierarchyChanged( HierarchyEvent e ) {
+ if( e.getChanged() != rootPane || (e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) == 0 )
+ return;
+
+ Container parent = e.getChangedParent();
+ if( parent instanceof Window )
+ install( (Window) parent );
+
+ // 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 void install( Window window ) {
+ if( !isSupported() )
+ return;
+
+ // do not enable JBR decorations if LaF provides decorations
+ if( UIManager.getLookAndFeel().getSupportsWindowDecorations() )
+ return;
+
+ if( window instanceof JFrame ) {
+ JFrame frame = (JFrame) window;
+
+ // do not enable JBR decorations if JFrame should use system window decorations
+ // and if not forced to use JBR decorations
+ if( !JFrame.isDefaultLookAndFeelDecorated() &&
+ !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false ))
+ 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
+ frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME );
+
+ } else if( window instanceof JDialog ) {
+ JDialog dialog = (JDialog) window;
+
+ // do not enable JBR decorations if JDialog should use system window decorations
+ // and if not forced to use JBR decorations
+ if( !JDialog.isDefaultLookAndFeelDecorated() &&
+ !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false ))
+ 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
+ dialog.getRootPane().setWindowDecorationStyle( JRootPane.PLAIN_DIALOG );
+ }
+ }
+
+ 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 hitTestSpots, int titleBarHeight ) {
+ if( !isSupported() )
+ return;
+
+ try {
+ Object compAccessor = AWTAccessor_getComponentAccessor.invoke( null );
+ Object peer = AWTAccessor_ComponentAccessor_getPeer.invoke( compAccessor, window );
+ WWindowPeer_setCustomDecorationHitTestSpots.invoke( peer, hitTestSpots );
+ WWindowPeer_setCustomDecorationTitleBarHeight.invoke( peer, titleBarHeight );
+ } catch( Exception ex ) {
+ Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
+ }
+ }
+
+ private static void initialize() {
+ if( initialized )
+ return;
+ initialized = true;
+
+ // requires JetBrains Runtime 11 and Windows 10
+ if( !SystemInfo.IS_JETBRAINS_JVM_11_OR_LATER || !SystemInfo.IS_WINDOWS_10_OR_LATER )
+ return;
+
+ if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, true ) )
+ return;
+
+ try {
+ Class> awtAcessorClass = Class.forName( "sun.awt.AWTAccessor" );
+ Class> compAccessorClass = Class.forName( "sun.awt.AWTAccessor$ComponentAccessor" );
+ AWTAccessor_getComponentAccessor = awtAcessorClass.getDeclaredMethod( "getComponentAccessor" );
+ AWTAccessor_ComponentAccessor_getPeer = compAccessorClass.getDeclaredMethod( "getPeer", Component.class );
+
+ Class> peerClass = Class.forName( "sun.awt.windows.WWindowPeer" );
+ WWindowPeer_setCustomDecorationHitTestSpots = peerClass.getDeclaredMethod( "setCustomDecorationHitTestSpots", List.class );
+ WWindowPeer_setCustomDecorationTitleBarHeight = peerClass.getDeclaredMethod( "setCustomDecorationTitleBarHeight", int.class );
+ WWindowPeer_setCustomDecorationHitTestSpots.setAccessible( true );
+ WWindowPeer_setCustomDecorationTitleBarHeight.setAccessible( true );
+
+ Window_hasCustomDecoration = Window.class.getDeclaredMethod( "hasCustomDecoration" );
+ Window_setHasCustomDecoration = Window.class.getDeclaredMethod( "setHasCustomDecoration" );
+ Window_hasCustomDecoration.setAccessible( true );
+ Window_setHasCustomDecoration.setAccessible( true );
+ } catch( Exception ex ) {
+ // ignore
+ }
+ }
+
+ //---- class JBRWindowTopBorder -------------------------------------------
+
+ static class JBRWindowTopBorder
+ extends BorderUIResource.EmptyBorderUIResource
+ {
+ private static JBRWindowTopBorder instance;
+
+ private final Color defaultActiveBorder = new Color( 0x707070 );
+ private final Color inactiveLightColor = new Color( 0xaaaaaa );
+ private final Color inactiveDarkColor = new Color( 0x3f3f3f );
+
+ private boolean colorizationAffectsBorders;
+ private Color activeColor = defaultActiveBorder;
+
+ static JBRWindowTopBorder getInstance() {
+ if( instance == null )
+ instance = new JBRWindowTopBorder();
+ return instance;
+ }
+
+ private JBRWindowTopBorder() {
+ super( 1, 0, 0, 0 );
+
+ colorizationAffectsBorders = calculateAffectsBorders();
+ activeColor = calculateActiveBorderColor();
+
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ toolkit.addPropertyChangeListener( "win.dwm.colorizationColor.affects.borders", e -> {
+ colorizationAffectsBorders = calculateAffectsBorders();
+ activeColor = calculateActiveBorderColor();
+ } );
+
+ PropertyChangeListener l = e -> {
+ activeColor = calculateActiveBorderColor();
+ };
+ toolkit.addPropertyChangeListener( "win.dwm.colorizationColor", l );
+ toolkit.addPropertyChangeListener( "win.dwm.colorizationColorBalance", l );
+ toolkit.addPropertyChangeListener( "win.frame.activeBorderColor", l );
+ }
+
+ private boolean calculateAffectsBorders() {
+ Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor.affects.borders" );
+ return (value instanceof Boolean) ? (Boolean) value : true;
+ }
+
+ private Color calculateActiveBorderColor() {
+ if( !colorizationAffectsBorders )
+ return defaultActiveBorder;
+
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ Color colorizationColor = (Color) toolkit.getDesktopProperty( "win.dwm.colorizationColor" );
+ if( colorizationColor != null ) {
+ Object colorizationColorBalanceObj = toolkit.getDesktopProperty( "win.dwm.colorizationColorBalance" );
+ if( colorizationColorBalanceObj instanceof Integer ) {
+ int colorizationColorBalance = (Integer) colorizationColorBalanceObj;
+ if( colorizationColorBalance < 0 )
+ colorizationColorBalance = 100;
+
+ if( colorizationColorBalance == 0 )
+ return new Color( 0xD9D9D9 );
+ if( colorizationColorBalance == 100 )
+ return colorizationColor;
+
+ float alpha = colorizationColorBalance / 100.0f;
+ float remainder = 1 - alpha;
+ int r = Math.round( (colorizationColor.getRed() * alpha + 0xD9 * remainder) );
+ int g = Math.round( (colorizationColor.getGreen() * alpha + 0xD9 * remainder) );
+ int b = Math.round( (colorizationColor.getBlue() * alpha + 0xD9 * remainder) );
+ return new Color( r, g, b );
+ }
+ return colorizationColor;
+ }
+
+ Color activeBorderColor = (Color) toolkit.getDesktopProperty( "win.frame.activeBorderColor" );
+ return (activeBorderColor != null) ? activeBorderColor : UIManager.getColor( "MenuBar.borderColor" );
+ }
+
+ @Override
+ public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
+ Window window = SwingUtilities.windowForComponent( c );
+ boolean active = (window != null) ? window.isActive() : false;
+
+ g.setColor( active ? activeColor : (FlatLaf.isLafDark() ? inactiveDarkColor : inactiveLightColor) );
+ HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
+ }
+
+ private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
+ g.drawRect( x, y, width - 1, 0 );
+ }
+
+ void repaintBorder( Component c ) {
+ c.repaint( 0, 0, c.getWidth(), 1 );
+ }
+ }
+}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java
index 93b417a6..12b2f441 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java
@@ -23,6 +23,7 @@ import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator;
import javax.swing.JComponent;
+import com.formdev.flatlaf.FlatSystemProperties;
/**
* @author Karl Tauber
@@ -104,7 +105,7 @@ public class HiDPIUtils
private static boolean useTextYCorrection() {
if( useTextYCorrection == null )
- useTextYCorrection = Boolean.valueOf( System.getProperty( "flatlaf.useTextYCorrection", "true" ) );
+ useTextYCorrection = FlatSystemProperties.getBoolean( FlatSystemProperties.USE_TEXT_Y_CORRECTION, true );
return useTextYCorrection;
}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java
index 91c5549f..cb503c31 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java
@@ -37,30 +37,38 @@ public class ScaledImageIcon
implements Icon
{
private final ImageIcon imageIcon;
+ private final int iconWidth;
+ private final int iconHeight;
private double lastSystemScaleFactor;
private float lastUserScaleFactor;
private Image lastImage;
public ScaledImageIcon( ImageIcon imageIcon ) {
+ this( imageIcon, imageIcon.getIconWidth(), imageIcon.getIconHeight() );
+ }
+
+ public ScaledImageIcon( ImageIcon imageIcon, int iconWidth, int iconHeight ) {
this.imageIcon = imageIcon;
+ this.iconWidth = iconWidth;
+ this.iconHeight = iconHeight;
}
@Override
public int getIconWidth() {
- return UIScale.scale( imageIcon.getIconWidth() );
+ return UIScale.scale( iconWidth );
}
@Override
public int getIconHeight() {
- return UIScale.scale( imageIcon.getIconHeight() );
+ return UIScale.scale( iconHeight );
}
@Override
public void paintIcon( Component c, Graphics g, int x, int y ) {
/*debug
g.setColor( Color.red );
- g.drawRect( x, y, getIconWidth(), getIconHeight() );
+ g.drawRect( x, y, getIconWidth() - 1, getIconHeight() - 1 );
debug*/
// scale factor
@@ -69,7 +77,7 @@ debug*/
double scaleFactor = systemScaleFactor * userScaleFactor;
// paint input image icon if not necessary to scale
- if( scaleFactor == 1 ) {
+ if( scaleFactor == 1 && iconWidth == imageIcon.getIconWidth() && iconHeight == imageIcon.getIconHeight() ) {
imageIcon.paintIcon( c, g, x, y );
return;
}
@@ -84,12 +92,11 @@ debug*/
}
// destination image size
- int destImageWidth = (int) Math.round( imageIcon.getIconWidth() * scaleFactor );
- int destImageHeight = (int) Math.round( imageIcon.getIconHeight() * scaleFactor );
+ int destImageWidth = (int) Math.round( iconWidth * scaleFactor );
+ int destImageHeight = (int) Math.round( iconHeight * scaleFactor );
// get resolution variant of image if it is a multi-resolution image
- Image image = MultiResolutionImageSupport.getResolutionVariant(
- imageIcon.getImage(), destImageWidth, destImageHeight );
+ Image image = getResolutionVariant( destImageWidth, destImageHeight );
// size of image
int imageWidth = image.getWidth( null );
@@ -124,6 +131,11 @@ debug*/
paintLastImage( g, x, y );
}
+ protected Image getResolutionVariant( int destImageWidth, int destImageHeight ) {
+ return MultiResolutionImageSupport.getResolutionVariant(
+ imageIcon.getImage(), destImageWidth, destImageHeight );
+ }
+
private void paintLastImage( Graphics g, int x, int y ) {
if( lastSystemScaleFactor > 1 ) {
HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, 100, 100, // width and height are not used
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemInfo.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemInfo.java
index c9459817..19f50696 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemInfo.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemInfo.java
@@ -32,14 +32,19 @@ public class SystemInfo
public static final boolean IS_LINUX;
// OS versions
+ public static final boolean IS_WINDOWS_10_OR_LATER;
public static final boolean IS_MAC_OS_10_11_EL_CAPITAN_OR_LATER;
+ public static final boolean IS_MAC_OS_10_14_MOJAVE;
public static final boolean IS_MAC_OS_10_15_CATALINA_OR_LATER;
// Java versions
public static final boolean IS_JAVA_9_OR_LATER;
+ public static final boolean IS_JAVA_11_OR_LATER;
+ public static final boolean IS_JAVA_15_OR_LATER;
// Java VMs
public static final boolean IS_JETBRAINS_JVM;
+ public static final boolean IS_JETBRAINS_JVM_11_OR_LATER;
// UI toolkits
public static final boolean IS_KDE;
@@ -53,16 +58,21 @@ public class SystemInfo
// OS versions
long osVersion = scanVersion( System.getProperty( "os.version" ) );
+ IS_WINDOWS_10_OR_LATER = (IS_WINDOWS && osVersion >= toVersion( 10, 0, 0, 0 ));
IS_MAC_OS_10_11_EL_CAPITAN_OR_LATER = (IS_MAC && osVersion >= toVersion( 10, 11, 0, 0 ));
+ IS_MAC_OS_10_14_MOJAVE = (IS_MAC && osVersion >= toVersion( 10, 14, 0, 0 ));
IS_MAC_OS_10_15_CATALINA_OR_LATER = (IS_MAC && osVersion >= toVersion( 10, 15, 0, 0 ));
// Java versions
long javaVersion = scanVersion( System.getProperty( "java.version" ) );
IS_JAVA_9_OR_LATER = (javaVersion >= toVersion( 9, 0, 0, 0 ));
+ IS_JAVA_11_OR_LATER = (javaVersion >= toVersion( 11, 0, 0, 0 ));
+ IS_JAVA_15_OR_LATER = (javaVersion >= toVersion( 15, 0, 0, 0 ));
// Java VMs
IS_JETBRAINS_JVM = System.getProperty( "java.vm.vendor", "Unknown" )
.toLowerCase( Locale.ENGLISH ).contains( "jetbrains" );
+ IS_JETBRAINS_JVM_11_OR_LATER = IS_JETBRAINS_JVM && IS_JAVA_11_OR_LATER;
// UI toolkits
IS_KDE = (IS_LINUX && System.getenv( "KDE_FULL_SESSION" ) != null);
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/UIScale.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/UIScale.java
index 2338f25e..b1ded6bd 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/UIScale.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/UIScale.java
@@ -32,6 +32,7 @@ import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.InsetsUIResource;
import javax.swing.plaf.UIResource;
+import com.formdev.flatlaf.FlatSystemProperties;
/**
* Two scaling modes are supported for HiDPI displays:
@@ -195,8 +196,7 @@ public class UIScale
private static boolean isUserScalingEnabled() {
// same as in IntelliJ IDEA
- String hidpi = System.getProperty( "hidpi" );
- return (hidpi != null) ? Boolean.parseBoolean( hidpi ) : true;
+ return FlatSystemProperties.getBoolean( "hidpi", true );
}
/**
@@ -204,7 +204,7 @@ public class UIScale
* to the given font.
*/
public static FontUIResource applyCustomScaleFactor( FontUIResource font ) {
- String uiScale = System.getProperty( "flatlaf.uiScale" );
+ String uiScale = System.getProperty( FlatSystemProperties.UI_SCALE );
float scaleFactor = parseScaleFactor( uiScale );
if( scaleFactor <= 0 )
return font;
diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties
index ce773fdb..47b91971 100644
--- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties
+++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties
@@ -293,6 +293,13 @@ TableHeader.separatorColor=lighten($TableHeader.background,10%)
TableHeader.bottomSeparatorColor=$TableHeader.separatorColor
+#---- TitlePane ----
+
+TitlePane.embeddedForeground=darken($TitlePane.foreground,15%)
+TitlePane.buttonHoverBackground=lighten($TitlePane.background,10%,derived)
+TitlePane.buttonPressedBackground=lighten($TitlePane.background,20%,derived)
+
+
#---- ToggleButton ----
ToggleButton.selectedBackground=lighten($ToggleButton.background,10%,derived)
diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
index 27efa701..6f0e7139 100644
--- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
+++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
@@ -425,6 +425,14 @@ RadioButtonMenuItem.borderPainted=true
RadioButtonMenuItem.background=@menuBackground
+#---- RootPane ----
+
+RootPane.border=com.formdev.flatlaf.ui.FlatRootPaneUI$FlatWindowBorder
+RootPane.borderDragThickness=5
+RootPane.cornerDragWidth=16
+RootPane.honorMinimumSizeOnResize=true
+
+
#---- ScrollBar ----
ScrollBar.width=10
@@ -578,6 +586,31 @@ TitledBorder.titleColor=@foreground
TitledBorder.border=1,1,1,1,$Separator.foreground
+#---- TitlePane ----
+
+TitlePane.menuBarEmbedded=true
+TitlePane.iconSize=16,16
+TitlePane.iconMargins=3,8,3,0
+TitlePane.menuBarMargins=0,8,0,22
+TitlePane.titleMargins=3,8,3,8
+TitlePane.buttonSize=44,30
+TitlePane.buttonMaximizedHeight=22
+TitlePane.closeIcon=com.formdev.flatlaf.icons.FlatWindowCloseIcon
+TitlePane.iconifyIcon=com.formdev.flatlaf.icons.FlatWindowIconifyIcon
+TitlePane.maximizeIcon=com.formdev.flatlaf.icons.FlatWindowMaximizeIcon
+TitlePane.restoreIcon=com.formdev.flatlaf.icons.FlatWindowRestoreIcon
+
+TitlePane.background=$MenuBar.background
+TitlePane.inactiveBackground=$TitlePane.background
+TitlePane.foreground=@foreground
+TitlePane.inactiveForeground=@disabledText
+
+TitlePane.closeHoverBackground=#e81123
+TitlePane.closePressedBackground=rgba($TitlePane.closeHoverBackground,60%)
+TitlePane.closeHoverForeground=#fff
+TitlePane.closePressedForeground=#fff
+
+
#---- ToggleButton ----
ToggleButton.border=com.formdev.flatlaf.ui.FlatButtonBorder
diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties
index 0febccb4..e455f328 100644
--- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties
+++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties
@@ -305,6 +305,13 @@ TableHeader.separatorColor=darken($TableHeader.background,10%)
TableHeader.bottomSeparatorColor=$TableHeader.separatorColor
+#---- TitlePane ----
+
+TitlePane.embeddedForeground=lighten($TitlePane.foreground,35%)
+TitlePane.buttonHoverBackground=darken($TitlePane.background,10%,derived)
+TitlePane.buttonPressedBackground=darken($TitlePane.background,20%,derived)
+
+
#---- ToggleButton ----
ToggleButton.selectedBackground=darken($ToggleButton.background,20%,derived)
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 71f85fb0..c7357383 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
@@ -23,11 +23,13 @@ import java.util.Arrays;
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.extras.*;
import com.formdev.flatlaf.demo.intellijthemes.*;
import com.formdev.flatlaf.extras.FlatSVGIcon;
import com.formdev.flatlaf.extras.SVGUtils;
+import com.formdev.flatlaf.ui.JBRCustomDecorations;
import net.miginfocom.swing.*;
/**
@@ -74,6 +76,26 @@ class DemoFrame
} );
}
+ private void windowDecorationsChanged() {
+ boolean windowDecorations = windowDecorationsCheckBoxMenuItem.isSelected();
+
+ dispose();
+ setUndecorated( windowDecorations );
+ getRootPane().setWindowDecorationStyle( windowDecorations ? JRootPane.FRAME : JRootPane.NONE );
+ menuBarEmbeddedCheckBoxMenuItem.setEnabled( windowDecorations );
+ setVisible( true );
+ }
+
+ 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();
+ }
+
private void underlineMenuSelection() {
UIManager.put( "MenuItem.selectionType", underlineMenuSelectionMenuItem.isSelected() ? "underline" : null );
}
@@ -222,6 +244,8 @@ class DemoFrame
JMenuItem incrFontMenuItem = new JMenuItem();
JMenuItem decrFontMenuItem = new JMenuItem();
JMenu optionsMenu = new JMenu();
+ windowDecorationsCheckBoxMenuItem = new JCheckBoxMenuItem();
+ menuBarEmbeddedCheckBoxMenuItem = new JCheckBoxMenuItem();
underlineMenuSelectionMenuItem = new JCheckBoxMenuItem();
alwaysShowMnemonicsMenuItem = new JCheckBoxMenuItem();
JMenu helpMenu = new JMenu();
@@ -456,6 +480,18 @@ class DemoFrame
{
optionsMenu.setText("Options");
+ //---- windowDecorationsCheckBoxMenuItem ----
+ windowDecorationsCheckBoxMenuItem.setText("Window decorations");
+ windowDecorationsCheckBoxMenuItem.setSelected(true);
+ windowDecorationsCheckBoxMenuItem.addActionListener(e -> windowDecorationsChanged());
+ optionsMenu.add(windowDecorationsCheckBoxMenuItem);
+
+ //---- menuBarEmbeddedCheckBoxMenuItem ----
+ menuBarEmbeddedCheckBoxMenuItem.setText("Embedded menu bar");
+ menuBarEmbeddedCheckBoxMenuItem.setSelected(true);
+ menuBarEmbeddedCheckBoxMenuItem.addActionListener(e -> menuBarEmbeddedChanged());
+ optionsMenu.add(menuBarEmbeddedCheckBoxMenuItem);
+
//---- underlineMenuSelectionMenuItem ----
underlineMenuSelectionMenuItem.setText("Use underline menu selection");
underlineMenuSelectionMenuItem.addActionListener(e -> underlineMenuSelection());
@@ -571,10 +607,17 @@ class DemoFrame
cutMenuItem.addActionListener( new DefaultEditorKit.CutAction() );
copyMenuItem.addActionListener( new DefaultEditorKit.CopyAction() );
pasteMenuItem.addActionListener( new DefaultEditorKit.PasteAction() );
+
+ boolean supportsWindowDecorations = UIManager.getLookAndFeel()
+ .getSupportsWindowDecorations() || JBRCustomDecorations.isSupported();
+ windowDecorationsCheckBoxMenuItem.setEnabled( supportsWindowDecorations && !JBRCustomDecorations.isSupported() );
+ menuBarEmbeddedCheckBoxMenuItem.setEnabled( supportsWindowDecorations );
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JMenu fontMenu;
+ private JCheckBoxMenuItem windowDecorationsCheckBoxMenuItem;
+ private JCheckBoxMenuItem menuBarEmbeddedCheckBoxMenuItem;
private JCheckBoxMenuItem underlineMenuSelectionMenuItem;
private JCheckBoxMenuItem alwaysShowMnemonicsMenuItem;
private JTabbedPane tabbedPane;
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
index c2aec3f5..0146ff6b 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
@@ -322,6 +322,24 @@ new FormModel {
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
name: "optionsMenu"
"text": "Options"
+ add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) {
+ name: "windowDecorationsCheckBoxMenuItem"
+ "text": "Window decorations"
+ "selected": true
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "windowDecorationsChanged", false ) )
+ } )
+ add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) {
+ name: "menuBarEmbeddedCheckBoxMenuItem"
+ "text": "Embedded menu bar"
+ "selected": true
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuBarEmbeddedChanged", false ) )
+ } )
add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) {
name: "underlineMenuSelectionMenuItem"
"text": "Use underline menu selection"
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java
index 5e5bb17e..94e927d6 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java
@@ -16,6 +16,8 @@
package com.formdev.flatlaf.demo;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import com.formdev.flatlaf.extras.FlatInspector;
import com.formdev.flatlaf.util.SystemInfo;
@@ -36,6 +38,10 @@ public class FlatLafDemo
SwingUtilities.invokeLater( () -> {
DemoPrefs.init( PREFS_ROOT_PATH );
+ // enable window decorations
+ JFrame.setDefaultLookAndFeelDecorated( true );
+ JDialog.setDefaultLookAndFeelDecorated( true );
+
// set look and feel
DemoPrefs.initLaf( args );
diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatInspector.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatInspector.java
index 33366b38..334b5ece 100644
--- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatInspector.java
+++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatInspector.java
@@ -293,6 +293,9 @@ public class FlatInspector
if( c.getParent() instanceof JRootPane && c == ((JRootPane)c.getParent()).getGlassPane() )
continue;
+ if( "com.formdev.flatlaf.ui.FlatWindowResizer".equals( c.getClass().getName() ) )
+ continue;
+
return c;
}
}
@@ -309,8 +312,9 @@ public class FlatInspector
highlightFigure.setVisible( c != null );
if( c != null ) {
+ Insets insets = rootPane.getInsets();
highlightFigure.setBounds( new Rectangle(
- SwingUtilities.convertPoint( c, 0, 0, rootPane ),
+ SwingUtilities.convertPoint( c, -insets.left, -insets.top, rootPane ),
c.getSize() ) );
}
}
diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java
index d9c6ae81..93945555 100644
--- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java
+++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGIcon.java
@@ -29,7 +29,6 @@ import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Icon;
-import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import com.formdev.flatlaf.FlatIconColors;
import com.formdev.flatlaf.FlatLaf;
@@ -164,8 +163,7 @@ public class FlatSVGIcon
}
private static void lafChanged() {
- LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
- darkLaf = (lookAndFeel instanceof FlatLaf && ((FlatLaf)lookAndFeel).isDark());
+ darkLaf = FlatLaf.isLafDark();
}
//---- class ColorFilter --------------------------------------------------
diff --git a/flatlaf-testing/Window Icon Test Images.sketch b/flatlaf-testing/Window Icon Test Images.sketch
new file mode 100644
index 00000000..3fa6d1df
Binary files /dev/null and b/flatlaf-testing/Window Icon Test Images.sketch differ
diff --git a/flatlaf-testing/Windows 10 decorations.png b/flatlaf-testing/Windows 10 decorations.png
new file mode 100644
index 00000000..04af6c4c
Binary files /dev/null and b/flatlaf-testing/Windows 10 decorations.png differ
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatDisabledIconsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatDisabledIconsTest.java
index e375530d..95e960b2 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatDisabledIconsTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatDisabledIconsTest.java
@@ -598,12 +598,7 @@ public class FlatDisabledIconsTest
}
private ImageIcon getCurrentIcon() {
- return isDark() ? darkIcon : lightIcon;
- }
-
- private boolean isDark() {
- LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
- return lookAndFeel instanceof FlatLaf && ((FlatLaf)lookAndFeel).isDark();
+ return FlatLaf.isLafDark() ? darkIcon : lightIcon;
}
@Override
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java
index 60c6246b..3a9ab3cd 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java
@@ -26,6 +26,7 @@ import java.awt.Insets;
import java.awt.geom.AffineTransform;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
+import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
@@ -40,7 +41,7 @@ public class FlatPaintingStringTest
extends JPanel
{
public static void main( String[] args ) {
- System.setProperty( "flatlaf.uiScale", "1x" );
+ System.setProperty( FlatSystemProperties.UI_SCALE, "1x" );
System.setProperty( "sun.java2d.uiScale", "1x" );
SwingUtilities.invokeLater( () -> {
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java
index 15088619..eae07aea 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java
@@ -35,6 +35,7 @@ import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
+import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.IntelliJTheme;
import com.formdev.flatlaf.demo.LookAndFeelsComboBox;
import com.formdev.flatlaf.demo.DemoPrefs;
@@ -66,10 +67,10 @@ public class FlatTestFrame
DemoPrefs.init( PREFS_ROOT_PATH );
// set scale factor
- if( System.getProperty( "flatlaf.uiScale", System.getProperty( "sun.java2d.uiScale" ) ) == null ) {
+ if( System.getProperty( FlatSystemProperties.UI_SCALE, System.getProperty( "sun.java2d.uiScale" ) ) == null ) {
String scaleFactor = DemoPrefs.getState().get( KEY_SCALE_FACTOR, null );
if( scaleFactor != null )
- System.setProperty( "flatlaf.uiScale", scaleFactor );
+ System.setProperty( FlatSystemProperties.UI_SCALE, scaleFactor );
}
// set look and feel
@@ -145,7 +146,7 @@ public class FlatTestFrame
lookAndFeelComboBox.setModel( lafModel );
updateScaleFactorComboBox();
- String scaleFactor = System.getProperty( "flatlaf.uiScale", System.getProperty( "sun.java2d.uiScale" ) );
+ String scaleFactor = System.getProperty( FlatSystemProperties.UI_SCALE, System.getProperty( "sun.java2d.uiScale" ) );
if( scaleFactor != null )
scaleFactorComboBox.setSelectedItem( scaleFactor );
@@ -399,8 +400,7 @@ public class FlatTestFrame
boolean explicit = explicitColorsCheckBox.isSelected();
ColorUIResource restoreColor = new ColorUIResource( Color.white );
- LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
- boolean dark = (lookAndFeel instanceof FlatLaf && ((FlatLaf)lookAndFeel).isDark());
+ boolean dark = FlatLaf.isLafDark();
Color magenta = dark ? Color.magenta.darker() : Color.magenta;
Color orange = dark ? Color.orange.darker() : Color.orange;
Color blue = dark ? Color.blue.darker() : Color.blue;
@@ -473,10 +473,10 @@ public class FlatTestFrame
scaleFactorComboBox.setPopupVisible( false );
if( scaleFactor != null ) {
- System.setProperty( "flatlaf.uiScale", scaleFactor );
+ System.setProperty( FlatSystemProperties.UI_SCALE, scaleFactor );
DemoPrefs.getState().put( KEY_SCALE_FACTOR, scaleFactor );
} else {
- System.clearProperty( "flatlaf.uiScale" );
+ System.clearProperty( FlatSystemProperties.UI_SCALE );
DemoPrefs.getState().remove( KEY_SCALE_FACTOR );
}
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
new file mode 100644
index 00000000..13b77661
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java
@@ -0,0 +1,627 @@
+/*
+ * Copyright 2020 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 java.awt.Dialog.ModalityType;
+import java.awt.event.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.swing.*;
+import com.formdev.flatlaf.FlatClientProperties;
+import net.miginfocom.swing.*;
+
+/**
+ * @author Karl Tauber
+ */
+public class FlatWindowDecorationsTest
+ extends FlatTestPanel
+{
+ 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" );
+
+ // 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(),
+ new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test24.png" ) ).getImage(),
+ new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test32.png" ) ).getImage(),
+ new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test48.png" ) ).getImage(),
+ new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test64.png" ) ).getImage(),
+ new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test128.png" ) ).getImage()
+ );
+ // shuffle to test whether FlatLaf chooses the right size
+ Collections.shuffle( images );
+ frame.setIconImages( images );
+
+ frame.showFrame( FlatWindowDecorationsTest::new, panel -> ((FlatWindowDecorationsTest)panel).menuBar );
+ } );
+ }
+
+ private List images;
+
+ FlatWindowDecorationsTest() {
+ initComponents();
+ }
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+
+ Window window = SwingUtilities.windowForComponent( this );
+ menuBarCheckBox.setEnabled( window instanceof JFrame );
+ menuBarEmbeddedCheckBox.setEnabled( window instanceof JFrame );
+ maximizedBoundsCheckBox.setEnabled( window instanceof Frame );
+
+ boolean windowHasIcons = (window != null && !window.getIconImages().isEmpty());
+ iconNoneRadioButton.setEnabled( windowHasIcons );
+ iconTestAllRadioButton.setEnabled( windowHasIcons );
+ iconTestRandomRadioButton.setEnabled( windowHasIcons );
+
+ if( window instanceof Frame )
+ undecoratedCheckBox.setSelected( ((Frame)window).isUndecorated() );
+ else if( window instanceof Dialog )
+ undecoratedCheckBox.setSelected( ((Dialog)window).isUndecorated() );
+
+ JRootPane rootPane = getWindowRootPane();
+ if( rootPane != null ) {
+ int style = rootPane.getWindowDecorationStyle();
+ if( style == JRootPane.NONE )
+ styleNoneRadioButton.setSelected( true );
+ else if( style == JRootPane.FRAME )
+ styleFrameRadioButton.setSelected( true );
+ else if( style == JRootPane.PLAIN_DIALOG )
+ stylePlainRadioButton.setSelected( true );
+ else if( style == JRootPane.INFORMATION_DIALOG )
+ styleInfoRadioButton.setSelected( true );
+ else
+ throw new RuntimeException(); // not used
+ }
+ }
+
+ private void menuBarChanged() {
+ Window window = SwingUtilities.windowForComponent( this );
+ if( window instanceof JFrame ) {
+ ((JFrame)window).setJMenuBar( menuBarCheckBox.isSelected() ? menuBar : null );
+ window.revalidate();
+ window.repaint();
+ }
+ }
+
+ private void menuBarEmbeddedChanged() {
+ JRootPane rootPane = getWindowRootPane();
+ if( rootPane != null )
+ rootPane.putClientProperty( FlatClientProperties.MENU_BAR_EMBEDDED, menuBarEmbeddedCheckBox.isSelected() );
+ }
+
+ private void resizableChanged() {
+ Window window = SwingUtilities.windowForComponent( this );
+ if( window instanceof Frame )
+ ((Frame)window).setResizable( resizableCheckBox.isSelected() );
+ else if( window instanceof Dialog )
+ ((Dialog)window).setResizable( resizableCheckBox.isSelected() );
+ }
+
+ private void undecoratedChanged() {
+ Window window = SwingUtilities.windowForComponent( this );
+ if( window == null )
+ return;
+
+ window.dispose();
+
+ if( window instanceof Frame )
+ ((Frame)window).setUndecorated( undecoratedCheckBox.isSelected() );
+ else if( window instanceof Dialog )
+ ((Dialog)window).setUndecorated( undecoratedCheckBox.isSelected() );
+
+ window.setVisible( true );
+ }
+
+ private void maximizedBoundsChanged() {
+ Window window = SwingUtilities.windowForComponent( this );
+ if( window instanceof Frame ) {
+ ((Frame)window).setMaximizedBounds( maximizedBoundsCheckBox.isSelected()
+ ? new Rectangle( 50, 100, 1000, 700 )
+ : null );
+ }
+ }
+
+ private void menuItemActionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater( () -> {
+ JOptionPane.showMessageDialog( this, e.getActionCommand(), "Menu Item", JOptionPane.PLAIN_MESSAGE );
+ } );
+ }
+
+ private void openDialog() {
+ Window owner = SwingUtilities.windowForComponent( this );
+ JDialog dialog = new JDialog( owner, "Dialog", ModalityType.APPLICATION_MODAL );
+ dialog.add( new FlatWindowDecorationsTest() );
+ dialog.pack();
+ dialog.setLocationRelativeTo( this );
+ dialog.setVisible( true );
+ }
+
+ private void decorationStyleChanged() {
+ int style;
+ if( styleFrameRadioButton.isSelected() )
+ style = JRootPane.FRAME;
+ else if( stylePlainRadioButton.isSelected() )
+ style = JRootPane.PLAIN_DIALOG;
+ else if( styleInfoRadioButton.isSelected() )
+ style = JRootPane.INFORMATION_DIALOG;
+ else if( styleErrorRadioButton.isSelected() )
+ style = JRootPane.ERROR_DIALOG;
+ else if( styleQuestionRadioButton.isSelected() )
+ style = JRootPane.QUESTION_DIALOG;
+ else if( styleWarningRadioButton.isSelected() )
+ style = JRootPane.WARNING_DIALOG;
+ else if( styleColorChooserRadioButton.isSelected() )
+ style = JRootPane.COLOR_CHOOSER_DIALOG;
+ else if( styleFileChooserRadioButton.isSelected() )
+ style = JRootPane.FILE_CHOOSER_DIALOG;
+ else
+ style = JRootPane.NONE;
+
+ JRootPane rootPane = getWindowRootPane();
+ if( rootPane != null )
+ rootPane.setWindowDecorationStyle( style );
+ }
+
+ private void iconChanged() {
+ Window window = SwingUtilities.windowForComponent( this );
+ if( window == null )
+ return;
+
+ if( images == null )
+ images = window.getIconImages();
+
+ if( iconNoneRadioButton.isSelected() )
+ window.setIconImage( null );
+ else if( iconTestAllRadioButton.isSelected() )
+ window.setIconImages( images );
+ else if( iconTestRandomRadioButton.isSelected() )
+ window.setIconImage( images.get( (int) (Math.random() * images.size()) ) );
+ }
+
+ private JRootPane getWindowRootPane() {
+ Window window = SwingUtilities.windowForComponent( this );
+ if( window instanceof JFrame )
+ return ((JFrame)window).getRootPane();
+ else if( window instanceof JDialog )
+ return ((JDialog)window).getRootPane();
+ return null;
+ }
+
+ private void initComponents() {
+ // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
+ menuBarCheckBox = new JCheckBox();
+ menuBarEmbeddedCheckBox = new JCheckBox();
+ resizableCheckBox = new JCheckBox();
+ maximizedBoundsCheckBox = new JCheckBox();
+ undecoratedCheckBox = new JCheckBox();
+ JLabel label1 = new JLabel();
+ JLabel label2 = new JLabel();
+ JPanel panel1 = new JPanel();
+ styleNoneRadioButton = new JRadioButton();
+ styleFrameRadioButton = new JRadioButton();
+ stylePlainRadioButton = new JRadioButton();
+ styleInfoRadioButton = new JRadioButton();
+ styleErrorRadioButton = new JRadioButton();
+ styleQuestionRadioButton = new JRadioButton();
+ styleWarningRadioButton = new JRadioButton();
+ styleColorChooserRadioButton = new JRadioButton();
+ styleFileChooserRadioButton = new JRadioButton();
+ JPanel panel2 = new JPanel();
+ iconNoneRadioButton = new JRadioButton();
+ iconTestAllRadioButton = new JRadioButton();
+ iconTestRandomRadioButton = new JRadioButton();
+ JButton openDialogButton = new JButton();
+ menuBar = new JMenuBar();
+ JMenu fileMenu = new JMenu();
+ JMenuItem newMenuItem = new JMenuItem();
+ JMenuItem openMenuItem = new JMenuItem();
+ JMenuItem closeMenuItem = new JMenuItem();
+ JMenuItem closeMenuItem2 = new JMenuItem();
+ JMenuItem exitMenuItem = new JMenuItem();
+ JMenu editMenu = new JMenu();
+ JMenuItem undoMenuItem = new JMenuItem();
+ JMenuItem redoMenuItem = new JMenuItem();
+ JMenuItem cutMenuItem = new JMenuItem();
+ JMenuItem copyMenuItem = new JMenuItem();
+ JMenuItem pasteMenuItem = new JMenuItem();
+ JMenuItem deleteMenuItem = new JMenuItem();
+ JMenu viewMenu = new JMenu();
+ JCheckBoxMenuItem checkBoxMenuItem1 = new JCheckBoxMenuItem();
+ JMenu menu1 = new JMenu();
+ JMenu subViewsMenu = new JMenu();
+ JMenu subSubViewsMenu = new JMenu();
+ JMenuItem errorLogViewMenuItem = new JMenuItem();
+ JMenuItem searchViewMenuItem = new JMenuItem();
+ JMenuItem projectViewMenuItem = new JMenuItem();
+ JMenuItem structureViewMenuItem = new JMenuItem();
+ JMenuItem propertiesViewMenuItem = new JMenuItem();
+ JMenu helpMenu = new JMenu();
+ JMenuItem aboutMenuItem = new JMenuItem();
+
+ //======== this ========
+ setLayout(new MigLayout(
+ "ltr,insets dialog,hidemode 3",
+ // columns
+ "[left]para" +
+ "[fill]",
+ // rows
+ "para[]0" +
+ "[]0" +
+ "[]0" +
+ "[]" +
+ "[]" +
+ "[top]" +
+ "[]"));
+
+ //---- menuBarCheckBox ----
+ menuBarCheckBox.setText("menu bar");
+ menuBarCheckBox.setSelected(true);
+ menuBarCheckBox.addActionListener(e -> menuBarChanged());
+ add(menuBarCheckBox, "cell 0 0");
+
+ //---- menuBarEmbeddedCheckBox ----
+ menuBarEmbeddedCheckBox.setText("embedded menu bar");
+ menuBarEmbeddedCheckBox.setSelected(true);
+ menuBarEmbeddedCheckBox.addActionListener(e -> menuBarEmbeddedChanged());
+ add(menuBarEmbeddedCheckBox, "cell 0 1");
+
+ //---- resizableCheckBox ----
+ resizableCheckBox.setText("resizable");
+ resizableCheckBox.setSelected(true);
+ resizableCheckBox.addActionListener(e -> resizableChanged());
+ add(resizableCheckBox, "cell 0 2");
+
+ //---- maximizedBoundsCheckBox ----
+ maximizedBoundsCheckBox.setText("maximized bounds (50,100, 1000,700)");
+ maximizedBoundsCheckBox.addActionListener(e -> maximizedBoundsChanged());
+ add(maximizedBoundsCheckBox, "cell 1 2");
+
+ //---- undecoratedCheckBox ----
+ undecoratedCheckBox.setText("undecorated");
+ undecoratedCheckBox.addActionListener(e -> undecoratedChanged());
+ add(undecoratedCheckBox, "cell 0 3");
+
+ //---- label1 ----
+ label1.setText("Style:");
+ add(label1, "cell 0 4");
+
+ //---- label2 ----
+ label2.setText("Icon:");
+ add(label2, "cell 1 4");
+
+ //======== panel1 ========
+ {
+ panel1.setLayout(new MigLayout(
+ "ltr,insets 0,hidemode 3,gap 0 0",
+ // columns
+ "[fill]",
+ // rows
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]"));
+
+ //---- styleNoneRadioButton ----
+ styleNoneRadioButton.setText("none");
+ styleNoneRadioButton.addActionListener(e -> decorationStyleChanged());
+ panel1.add(styleNoneRadioButton, "cell 0 0");
+
+ //---- styleFrameRadioButton ----
+ styleFrameRadioButton.setText("frame");
+ styleFrameRadioButton.setSelected(true);
+ styleFrameRadioButton.addActionListener(e -> decorationStyleChanged());
+ panel1.add(styleFrameRadioButton, "cell 0 1");
+
+ //---- stylePlainRadioButton ----
+ stylePlainRadioButton.setText("plain dialog");
+ stylePlainRadioButton.addActionListener(e -> decorationStyleChanged());
+ panel1.add(stylePlainRadioButton, "cell 0 2");
+
+ //---- styleInfoRadioButton ----
+ styleInfoRadioButton.setText("info dialog");
+ styleInfoRadioButton.addActionListener(e -> decorationStyleChanged());
+ panel1.add(styleInfoRadioButton, "cell 0 3");
+
+ //---- styleErrorRadioButton ----
+ styleErrorRadioButton.setText("error dialog");
+ styleErrorRadioButton.addActionListener(e -> decorationStyleChanged());
+ panel1.add(styleErrorRadioButton, "cell 0 4");
+
+ //---- styleQuestionRadioButton ----
+ styleQuestionRadioButton.setText("question dialog");
+ styleQuestionRadioButton.addActionListener(e -> decorationStyleChanged());
+ panel1.add(styleQuestionRadioButton, "cell 0 5");
+
+ //---- styleWarningRadioButton ----
+ styleWarningRadioButton.setText("warning dialog");
+ styleWarningRadioButton.addActionListener(e -> decorationStyleChanged());
+ panel1.add(styleWarningRadioButton, "cell 0 6");
+
+ //---- styleColorChooserRadioButton ----
+ styleColorChooserRadioButton.setText("color chooser dialog");
+ styleColorChooserRadioButton.addActionListener(e -> decorationStyleChanged());
+ panel1.add(styleColorChooserRadioButton, "cell 0 7");
+
+ //---- styleFileChooserRadioButton ----
+ styleFileChooserRadioButton.setText("file chooser dialog");
+ styleFileChooserRadioButton.addActionListener(e -> decorationStyleChanged());
+ panel1.add(styleFileChooserRadioButton, "cell 0 8");
+ }
+ add(panel1, "cell 0 5");
+
+ //======== panel2 ========
+ {
+ panel2.setLayout(new MigLayout(
+ "ltr,insets 0,hidemode 3,gap 0 0",
+ // columns
+ "[fill]",
+ // rows
+ "[]" +
+ "[]" +
+ "[]"));
+
+ //---- iconNoneRadioButton ----
+ iconNoneRadioButton.setText("none");
+ iconNoneRadioButton.addActionListener(e -> iconChanged());
+ panel2.add(iconNoneRadioButton, "cell 0 0");
+
+ //---- iconTestAllRadioButton ----
+ iconTestAllRadioButton.setText("test all");
+ iconTestAllRadioButton.setSelected(true);
+ iconTestAllRadioButton.addActionListener(e -> iconChanged());
+ panel2.add(iconTestAllRadioButton, "cell 0 1");
+
+ //---- iconTestRandomRadioButton ----
+ iconTestRandomRadioButton.setText("test random");
+ iconTestRandomRadioButton.addActionListener(e -> iconChanged());
+ panel2.add(iconTestRandomRadioButton, "cell 0 2");
+ }
+ add(panel2, "cell 1 5");
+
+ //---- openDialogButton ----
+ openDialogButton.setText("Open Dialog");
+ openDialogButton.addActionListener(e -> openDialog());
+ add(openDialogButton, "cell 0 6");
+
+ //======== menuBar ========
+ {
+
+ //======== fileMenu ========
+ {
+ fileMenu.setText("File");
+ fileMenu.setMnemonic('F');
+
+ //---- newMenuItem ----
+ newMenuItem.setText("New");
+ newMenuItem.setMnemonic('N');
+ newMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ fileMenu.add(newMenuItem);
+
+ //---- openMenuItem ----
+ openMenuItem.setText("Open");
+ openMenuItem.setMnemonic('O');
+ openMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ fileMenu.add(openMenuItem);
+ fileMenu.addSeparator();
+
+ //---- closeMenuItem ----
+ closeMenuItem.setText("Close");
+ closeMenuItem.setMnemonic('C');
+ closeMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ fileMenu.add(closeMenuItem);
+
+ //---- closeMenuItem2 ----
+ closeMenuItem2.setText("Close All");
+ closeMenuItem2.addActionListener(e -> menuItemActionPerformed(e));
+ fileMenu.add(closeMenuItem2);
+ fileMenu.addSeparator();
+
+ //---- exitMenuItem ----
+ exitMenuItem.setText("Exit");
+ exitMenuItem.setMnemonic('X');
+ exitMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ fileMenu.add(exitMenuItem);
+ }
+ menuBar.add(fileMenu);
+
+ //======== editMenu ========
+ {
+ editMenu.setText("Edit");
+ editMenu.setMnemonic('E');
+
+ //---- undoMenuItem ----
+ undoMenuItem.setText("Undo");
+ undoMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
+ undoMenuItem.setMnemonic('U');
+ undoMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ editMenu.add(undoMenuItem);
+
+ //---- redoMenuItem ----
+ redoMenuItem.setText("Redo");
+ redoMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
+ redoMenuItem.setMnemonic('R');
+ redoMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ editMenu.add(redoMenuItem);
+ editMenu.addSeparator();
+
+ //---- cutMenuItem ----
+ cutMenuItem.setText("Cut");
+ cutMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
+ cutMenuItem.setMnemonic('C');
+ editMenu.add(cutMenuItem);
+
+ //---- copyMenuItem ----
+ copyMenuItem.setText("Copy");
+ copyMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
+ copyMenuItem.setMnemonic('O');
+ editMenu.add(copyMenuItem);
+
+ //---- pasteMenuItem ----
+ pasteMenuItem.setText("Paste");
+ pasteMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
+ pasteMenuItem.setMnemonic('P');
+ editMenu.add(pasteMenuItem);
+ editMenu.addSeparator();
+
+ //---- deleteMenuItem ----
+ deleteMenuItem.setText("Delete");
+ deleteMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
+ deleteMenuItem.setMnemonic('D');
+ deleteMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ editMenu.add(deleteMenuItem);
+ }
+ menuBar.add(editMenu);
+
+ //======== viewMenu ========
+ {
+ viewMenu.setText("View");
+ viewMenu.setMnemonic('V');
+
+ //---- checkBoxMenuItem1 ----
+ checkBoxMenuItem1.setText("Show Toolbar");
+ checkBoxMenuItem1.setSelected(true);
+ checkBoxMenuItem1.setMnemonic('T');
+ checkBoxMenuItem1.addActionListener(e -> menuItemActionPerformed(e));
+ viewMenu.add(checkBoxMenuItem1);
+
+ //======== menu1 ========
+ {
+ menu1.setText("Show View");
+ menu1.setMnemonic('V');
+
+ //======== subViewsMenu ========
+ {
+ subViewsMenu.setText("Sub Views");
+ subViewsMenu.setMnemonic('S');
+
+ //======== subSubViewsMenu ========
+ {
+ subSubViewsMenu.setText("Sub sub Views");
+ subSubViewsMenu.setMnemonic('U');
+
+ //---- errorLogViewMenuItem ----
+ errorLogViewMenuItem.setText("Error Log");
+ errorLogViewMenuItem.setMnemonic('E');
+ errorLogViewMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ subSubViewsMenu.add(errorLogViewMenuItem);
+ }
+ subViewsMenu.add(subSubViewsMenu);
+
+ //---- searchViewMenuItem ----
+ searchViewMenuItem.setText("Search");
+ searchViewMenuItem.setMnemonic('S');
+ searchViewMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ subViewsMenu.add(searchViewMenuItem);
+ }
+ menu1.add(subViewsMenu);
+
+ //---- projectViewMenuItem ----
+ projectViewMenuItem.setText("Project");
+ projectViewMenuItem.setMnemonic('P');
+ projectViewMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ menu1.add(projectViewMenuItem);
+
+ //---- structureViewMenuItem ----
+ structureViewMenuItem.setText("Structure");
+ structureViewMenuItem.setMnemonic('T');
+ structureViewMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ menu1.add(structureViewMenuItem);
+
+ //---- propertiesViewMenuItem ----
+ propertiesViewMenuItem.setText("Properties");
+ propertiesViewMenuItem.setMnemonic('O');
+ propertiesViewMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ menu1.add(propertiesViewMenuItem);
+ }
+ viewMenu.add(menu1);
+ }
+ menuBar.add(viewMenu);
+
+ //======== helpMenu ========
+ {
+ helpMenu.setText("Help");
+ helpMenu.setMnemonic('H');
+
+ //---- aboutMenuItem ----
+ aboutMenuItem.setText("About");
+ aboutMenuItem.setMnemonic('A');
+ aboutMenuItem.addActionListener(e -> menuItemActionPerformed(e));
+ helpMenu.add(aboutMenuItem);
+ }
+ menuBar.add(helpMenu);
+ }
+
+ //---- styleButtonGroup ----
+ ButtonGroup styleButtonGroup = new ButtonGroup();
+ styleButtonGroup.add(styleNoneRadioButton);
+ styleButtonGroup.add(styleFrameRadioButton);
+ styleButtonGroup.add(stylePlainRadioButton);
+ styleButtonGroup.add(styleInfoRadioButton);
+ styleButtonGroup.add(styleErrorRadioButton);
+ styleButtonGroup.add(styleQuestionRadioButton);
+ styleButtonGroup.add(styleWarningRadioButton);
+ styleButtonGroup.add(styleColorChooserRadioButton);
+ styleButtonGroup.add(styleFileChooserRadioButton);
+
+ //---- iconButtonGroup ----
+ ButtonGroup iconButtonGroup = new ButtonGroup();
+ iconButtonGroup.add(iconNoneRadioButton);
+ iconButtonGroup.add(iconTestAllRadioButton);
+ iconButtonGroup.add(iconTestRandomRadioButton);
+ // JFormDesigner - End of component initialization //GEN-END:initComponents
+ }
+
+ // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
+ private JCheckBox menuBarCheckBox;
+ private JCheckBox menuBarEmbeddedCheckBox;
+ private JCheckBox resizableCheckBox;
+ private JCheckBox maximizedBoundsCheckBox;
+ private JCheckBox undecoratedCheckBox;
+ private JRadioButton styleNoneRadioButton;
+ private JRadioButton styleFrameRadioButton;
+ private JRadioButton stylePlainRadioButton;
+ private JRadioButton styleInfoRadioButton;
+ private JRadioButton styleErrorRadioButton;
+ private JRadioButton styleQuestionRadioButton;
+ private JRadioButton styleWarningRadioButton;
+ private JRadioButton styleColorChooserRadioButton;
+ private JRadioButton styleFileChooserRadioButton;
+ private JRadioButton iconNoneRadioButton;
+ private JRadioButton iconTestAllRadioButton;
+ private JRadioButton iconTestRandomRadioButton;
+ private JMenuBar menuBar;
+ // JFormDesigner - End of variables declaration //GEN-END:variables
+}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd
new file mode 100644
index 00000000..e5021fd8
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd
@@ -0,0 +1,421 @@
+JFDML JFormDesigner: "7.0.2.0.298" Java: "13.0.2" 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 net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "ltr,insets dialog,hidemode 3"
+ "$columnConstraints": "[left]para[fill]"
+ "$rowConstraints": "para[]0[]0[]0[][][top][]"
+ } ) {
+ name: "this"
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "menuBarCheckBox"
+ "text": "menu bar"
+ "selected": true
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuBarChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "menuBarEmbeddedCheckBox"
+ "text": "embedded menu bar"
+ "selected": true
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuBarEmbeddedChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "resizableCheckBox"
+ "text": "resizable"
+ "selected": true
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "resizableChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "maximizedBoundsCheckBox"
+ "text": "maximized bounds (50,100, 1000,700)"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "maximizedBoundsChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "undecoratedCheckBox"
+ "text": "undecorated"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "undecoratedChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "label1"
+ "text": "Style:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 4"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "label2"
+ "text": "Icon:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 4"
+ } )
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$columnConstraints": "[fill]"
+ "$rowConstraints": "[][][][][][][][][]"
+ "$layoutConstraints": "ltr,insets 0,hidemode 3,gap 0 0"
+ } ) {
+ name: "panel1"
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "styleNoneRadioButton"
+ "text": "none"
+ "$buttonGroup": new FormReference( "styleButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decorationStyleChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "styleFrameRadioButton"
+ "text": "frame"
+ "$buttonGroup": new FormReference( "styleButtonGroup" )
+ "selected": true
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decorationStyleChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "stylePlainRadioButton"
+ "text": "plain dialog"
+ "$buttonGroup": new FormReference( "styleButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decorationStyleChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "styleInfoRadioButton"
+ "text": "info dialog"
+ "$buttonGroup": new FormReference( "styleButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decorationStyleChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "styleErrorRadioButton"
+ "text": "error dialog"
+ "$buttonGroup": new FormReference( "styleButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decorationStyleChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 4"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "styleQuestionRadioButton"
+ "text": "question dialog"
+ "$buttonGroup": new FormReference( "styleButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decorationStyleChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 5"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "styleWarningRadioButton"
+ "text": "warning dialog"
+ "$buttonGroup": new FormReference( "styleButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decorationStyleChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "styleColorChooserRadioButton"
+ "text": "color chooser dialog"
+ "$buttonGroup": new FormReference( "styleButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decorationStyleChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 7"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "styleFileChooserRadioButton"
+ "text": "file chooser dialog"
+ "$buttonGroup": new FormReference( "styleButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decorationStyleChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 8"
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 5"
+ } )
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$columnConstraints": "[fill]"
+ "$rowConstraints": "[][][]"
+ "$layoutConstraints": "ltr,insets 0,hidemode 3,gap 0 0"
+ } ) {
+ name: "panel2"
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "iconNoneRadioButton"
+ "text": "none"
+ "$buttonGroup": new FormReference( "iconButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "iconChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "iconTestAllRadioButton"
+ "text": "test all"
+ "selected": true
+ "$buttonGroup": new FormReference( "iconButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "iconChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JRadioButton" ) {
+ name: "iconTestRandomRadioButton"
+ "text": "test random"
+ "$buttonGroup": new FormReference( "iconButtonGroup" )
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "iconChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 5"
+ } )
+ add( new FormComponent( "javax.swing.JButton" ) {
+ name: "openDialogButton"
+ "text": "Open Dialog"
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openDialog", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 6"
+ } )
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 0 )
+ "size": new java.awt.Dimension( 450, 380 )
+ } )
+ add( new FormContainer( "javax.swing.JMenuBar", new FormLayoutManager( class javax.swing.JMenuBar ) ) {
+ name: "menuBar"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
+ name: "fileMenu"
+ "text": "File"
+ "mnemonic": 70
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "newMenuItem"
+ "text": "New"
+ "mnemonic": 78
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "openMenuItem"
+ "text": "Open"
+ "mnemonic": 79
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
+ name: "separator2"
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "closeMenuItem"
+ "text": "Close"
+ "mnemonic": 67
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "closeMenuItem2"
+ "text": "Close All"
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
+ name: "separator1"
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "exitMenuItem"
+ "text": "Exit"
+ "mnemonic": 88
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ } )
+ add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
+ name: "editMenu"
+ "text": "Edit"
+ "mnemonic": 69
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "undoMenuItem"
+ "text": "Undo"
+ "accelerator": static javax.swing.KeyStroke getKeyStroke( 90, 4226, false )
+ "mnemonic": 85
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "redoMenuItem"
+ "text": "Redo"
+ "accelerator": static javax.swing.KeyStroke getKeyStroke( 89, 4226, false )
+ "mnemonic": 82
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
+ name: "separator4"
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "cutMenuItem"
+ "text": "Cut"
+ "accelerator": static javax.swing.KeyStroke getKeyStroke( 88, 4226, false )
+ "mnemonic": 67
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "copyMenuItem"
+ "text": "Copy"
+ "accelerator": static javax.swing.KeyStroke getKeyStroke( 67, 4226, false )
+ "mnemonic": 79
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "pasteMenuItem"
+ "text": "Paste"
+ "accelerator": static javax.swing.KeyStroke getKeyStroke( 86, 4226, false )
+ "mnemonic": 80
+ } )
+ add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
+ name: "separator3"
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "deleteMenuItem"
+ "text": "Delete"
+ "accelerator": static javax.swing.KeyStroke getKeyStroke( 127, 0, false )
+ "mnemonic": 68
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ } )
+ add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
+ name: "viewMenu"
+ "text": "View"
+ "mnemonic": 86
+ add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) {
+ name: "checkBoxMenuItem1"
+ "text": "Show Toolbar"
+ "selected": true
+ "mnemonic": 84
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
+ name: "menu1"
+ "text": "Show View"
+ "mnemonic": 86
+ add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
+ name: "subViewsMenu"
+ "text": "Sub Views"
+ "mnemonic": 83
+ add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
+ name: "subSubViewsMenu"
+ "text": "Sub sub Views"
+ "mnemonic": 85
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "errorLogViewMenuItem"
+ "text": "Error Log"
+ "mnemonic": 69
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "searchViewMenuItem"
+ "text": "Search"
+ "mnemonic": 83
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "projectViewMenuItem"
+ "text": "Project"
+ "mnemonic": 80
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "structureViewMenuItem"
+ "text": "Structure"
+ "mnemonic": 84
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "propertiesViewMenuItem"
+ "text": "Properties"
+ "mnemonic": 79
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ } )
+ } )
+ add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
+ name: "helpMenu"
+ "text": "Help"
+ "mnemonic": 72
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "aboutMenuItem"
+ "text": "About"
+ "mnemonic": 65
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
+ } )
+ } )
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 410 )
+ "size": new java.awt.Dimension( 255, 30 )
+ } )
+ add( new FormNonVisual( "javax.swing.ButtonGroup" ) {
+ name: "styleButtonGroup"
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 450 )
+ } )
+ add( new FormNonVisual( "javax.swing.ButtonGroup" ) {
+ name: "iconButtonGroup"
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 502 )
+ } )
+ }
+}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/uidefaults/UIDefaultsDump.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/uidefaults/UIDefaultsDump.java
index 53a01ecf..b552165f 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/uidefaults/UIDefaultsDump.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/uidefaults/UIDefaultsDump.java
@@ -84,7 +84,7 @@ public class UIDefaultsDump
public static void main( String[] args ) {
Locale.setDefault( Locale.ENGLISH );
System.setProperty( "sun.java2d.uiScale", "1x" );
- System.setProperty( "flatlaf.uiScale", "1x" );
+ System.setProperty( FlatSystemProperties.UI_SCALE, "1x" );
File dir = new File( "src/main/resources/com/formdev/flatlaf/testing/uidefaults" );
diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties
index 8d7612fb..70a76ca6 100644
--- a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties
+++ b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties
@@ -316,6 +316,14 @@ TitledBorder.titleColor=#ff00ff
TitledBorder.border=1,1,1,1,#ff00ff
+#---- TitlePane ----
+
+TitlePane.background=#0f0
+TitlePane.inactiveBackground=#080
+TitlePane.foreground=#00f
+TitlePane.inactiveForeground=#fff
+
+
#---- ToggleButton ----
ToggleButton.background=#ddddff
diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test128.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test128.png
new file mode 100644
index 00000000..b26ffcea
Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test128.png differ
diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test16.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test16.png
new file mode 100644
index 00000000..2e0e2727
Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test16.png differ
diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test24.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test24.png
new file mode 100644
index 00000000..bb6da8bf
Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test24.png differ
diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test32.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test32.png
new file mode 100644
index 00000000..1cf56f82
Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test32.png differ
diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test48.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test48.png
new file mode 100644
index 00000000..9641e283
Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test48.png differ
diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test64.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test64.png
new file mode 100644
index 00000000..ad67439b
Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test64.png differ
diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatDarkLaf_1.8.0_202.txt b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatDarkLaf_1.8.0_202.txt
index d06c4682..e34c4911 100644
--- a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatDarkLaf_1.8.0_202.txt
+++ b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatDarkLaf_1.8.0_202.txt
@@ -752,6 +752,9 @@ Resizable.resizeBorder [lazy] 4,4,4,4 false com.formdev.flatlaf.ui.F
#---- RootPane ----
+RootPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatRootPaneUI$FlatWindowBorder [UI]
+RootPane.borderDragThickness 5
+RootPane.cornerDragWidth 16
RootPane.defaultButtonWindowKeyBindings length=8 [Ljava.lang.Object;
[0] ENTER
[1] press
@@ -761,6 +764,7 @@ RootPane.defaultButtonWindowKeyBindings length=8 [Ljava.lang.Object;
[5] press
[6] ctrl released ENTER
[7] release
+RootPane.honorMinimumSizeOnResize true
RootPaneUI com.formdev.flatlaf.ui.FlatRootPaneUI
@@ -1052,6 +1056,32 @@ TextPane.selectionForeground #bbbbbb javax.swing.plaf.ColorUIResource [UI]
TextPaneUI com.formdev.flatlaf.ui.FlatTextPaneUI
+#---- TitlePane ----
+
+TitlePane.background #303234 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.buttonHoverBackground #484c4f com.formdev.flatlaf.util.DerivedColor [UI] lighten(10% autoInverse)
+TitlePane.buttonMaximizedHeight 22
+TitlePane.buttonPressedBackground #616569 com.formdev.flatlaf.util.DerivedColor [UI] lighten(20% autoInverse)
+TitlePane.buttonSize 44,30 javax.swing.plaf.DimensionUIResource [UI]
+TitlePane.closeHoverBackground #e81123 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.closeHoverForeground #ffffff javax.swing.plaf.ColorUIResource [UI]
+TitlePane.closeIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowCloseIcon [UI]
+TitlePane.closePressedBackground #99e81123 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.closePressedForeground #ffffff javax.swing.plaf.ColorUIResource [UI]
+TitlePane.embeddedForeground #959595 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.foreground #bbbbbb javax.swing.plaf.ColorUIResource [UI]
+TitlePane.iconMargins 3,8,3,0 javax.swing.plaf.InsetsUIResource [UI]
+TitlePane.iconSize 16,16 javax.swing.plaf.DimensionUIResource [UI]
+TitlePane.iconifyIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowIconifyIcon [UI]
+TitlePane.inactiveBackground #303234 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.inactiveForeground #777777 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.maximizeIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowMaximizeIcon [UI]
+TitlePane.menuBarEmbedded true
+TitlePane.menuBarMargins 0,8,0,22 javax.swing.plaf.InsetsUIResource [UI]
+TitlePane.restoreIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowRestoreIcon [UI]
+TitlePane.titleMargins 3,8,3,8 javax.swing.plaf.InsetsUIResource [UI]
+
+
#---- TitledBorder ----
TitledBorder.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatLineBorder [UI] lineColor=#515151 javax.swing.plaf.ColorUIResource [UI] lineThickness=1.000000
diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatLightLaf_1.8.0_202.txt b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatLightLaf_1.8.0_202.txt
index 31b022ee..341e7d0a 100644
--- a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatLightLaf_1.8.0_202.txt
+++ b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatLightLaf_1.8.0_202.txt
@@ -757,6 +757,9 @@ Resizable.resizeBorder [lazy] 4,4,4,4 false com.formdev.flatlaf.ui.F
#---- RootPane ----
+RootPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatRootPaneUI$FlatWindowBorder [UI]
+RootPane.borderDragThickness 5
+RootPane.cornerDragWidth 16
RootPane.defaultButtonWindowKeyBindings length=8 [Ljava.lang.Object;
[0] ENTER
[1] press
@@ -766,6 +769,7 @@ RootPane.defaultButtonWindowKeyBindings length=8 [Ljava.lang.Object;
[5] press
[6] ctrl released ENTER
[7] release
+RootPane.honorMinimumSizeOnResize true
RootPaneUI com.formdev.flatlaf.ui.FlatRootPaneUI
@@ -1057,6 +1061,32 @@ TextPane.selectionForeground #ffffff javax.swing.plaf.ColorUIResource [UI]
TextPaneUI com.formdev.flatlaf.ui.FlatTextPaneUI
+#---- TitlePane ----
+
+TitlePane.background #ffffff javax.swing.plaf.ColorUIResource [UI]
+TitlePane.buttonHoverBackground #e6e6e6 com.formdev.flatlaf.util.DerivedColor [UI] darken(10% autoInverse)
+TitlePane.buttonMaximizedHeight 22
+TitlePane.buttonPressedBackground #cccccc com.formdev.flatlaf.util.DerivedColor [UI] darken(20% autoInverse)
+TitlePane.buttonSize 44,30 javax.swing.plaf.DimensionUIResource [UI]
+TitlePane.closeHoverBackground #e81123 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.closeHoverForeground #ffffff javax.swing.plaf.ColorUIResource [UI]
+TitlePane.closeIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowCloseIcon [UI]
+TitlePane.closePressedBackground #99e81123 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.closePressedForeground #ffffff javax.swing.plaf.ColorUIResource [UI]
+TitlePane.embeddedForeground #595959 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.foreground #000000 javax.swing.plaf.ColorUIResource [UI]
+TitlePane.iconMargins 3,8,3,0 javax.swing.plaf.InsetsUIResource [UI]
+TitlePane.iconSize 16,16 javax.swing.plaf.DimensionUIResource [UI]
+TitlePane.iconifyIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowIconifyIcon [UI]
+TitlePane.inactiveBackground #ffffff javax.swing.plaf.ColorUIResource [UI]
+TitlePane.inactiveForeground #8c8c8c javax.swing.plaf.ColorUIResource [UI]
+TitlePane.maximizeIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowMaximizeIcon [UI]
+TitlePane.menuBarEmbedded true
+TitlePane.menuBarMargins 0,8,0,22 javax.swing.plaf.InsetsUIResource [UI]
+TitlePane.restoreIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowRestoreIcon [UI]
+TitlePane.titleMargins 3,8,3,8 javax.swing.plaf.InsetsUIResource [UI]
+
+
#---- TitledBorder ----
TitledBorder.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatLineBorder [UI] lineColor=#d1d1d1 javax.swing.plaf.ColorUIResource [UI] lineThickness=1.000000