diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java index e02abedc..20ee7ab5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -183,6 +183,15 @@ public interface FlatClientProperties */ String PROGRESS_BAR_SQUARE = "JProgressBar.square"; + /** + * Specifies whether the menu bar is embedded into the title pane if custom + * window decorations are enabled. Default is {@code true}. + *

+ * Component {@link javax.swing.JRootPane}
+ * Value type {@link java.lang.Boolean} + */ + String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"; + /** * Specifies whether the decrease/increase arrow buttons of a scrollbar are shown. *

diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index bbff8cf0..ff319ea2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -43,6 +43,8 @@ import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; import javax.swing.LookAndFeel; import javax.swing.PopupFactory; import javax.swing.SwingUtilities; @@ -57,6 +59,7 @@ import javax.swing.plaf.basic.BasicLookAndFeel; import javax.swing.text.StyleContext; import javax.swing.text.html.HTMLEditorKit; import com.formdev.flatlaf.ui.FlatPopupFactory; +import com.formdev.flatlaf.ui.JBRCustomDecorations; import com.formdev.flatlaf.util.GrayFilter; import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.SystemInfo; @@ -85,6 +88,9 @@ public abstract class FlatLaf private Consumer 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