From 0b127caa83ed4eb8e2c8cfc47ee34cb20482b0d7 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 5 Jul 2021 11:07:32 +0200 Subject: [PATCH] ComboBox: fixed minimum width if focusWidth > 0 (to be equal with button minimum width) added some unit tests to compare component sizes --- flatlaf-core/build.gradle.kts | 11 + .../com/formdev/flatlaf/LinuxFontPolicy.java | 3 + .../com/formdev/flatlaf/ui/FlatButtonUI.java | 6 +- .../formdev/flatlaf/ui/FlatComboBoxUI.java | 3 +- .../flatlaf/ui/TestFlatComponentSizes.java | 209 ++++++++++++++++++ .../ui/TestFlatComponentSizesWithFocus.java | 31 +++ .../com/formdev/flatlaf/ui/TestUtils.java | 51 +++++ 7 files changed, 310 insertions(+), 4 deletions(-) create mode 100644 flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatComponentSizes.java create mode 100644 flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatComponentSizesWithFocus.java create mode 100644 flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java diff --git a/flatlaf-core/build.gradle.kts b/flatlaf-core/build.gradle.kts index 01f7576c..32e61414 100644 --- a/flatlaf-core/build.gradle.kts +++ b/flatlaf-core/build.gradle.kts @@ -21,6 +21,12 @@ plugins { `flatlaf-publish` } +dependencies { + testImplementation( "org.junit.jupiter:junit-jupiter-api:5.7.2" ) + testRuntimeOnly( "org.junit.jupiter:junit-jupiter-engine" ) + testRuntimeOnly( "org.junit.jupiter:junit-jupiter-params" ) +} + java { withSourcesJar() withJavadocJar() @@ -52,6 +58,11 @@ tasks { named( "javadocJar" ) { archiveBaseName.set( "flatlaf" ) } + + test { + useJUnitPlatform() + testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } } flatlafPublish { 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 e5d91ac8..b20fba54 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/LinuxFontPolicy.java @@ -309,6 +309,9 @@ class LinuxFontPolicy * - running on JetBrains Runtime 11 or later and scaling is enabled in system Settings */ private static boolean isSystemScaling() { + if( GraphicsEnvironment.isHeadless() ) + return true; + GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment() .getDefaultScreenDevice().getDefaultConfiguration(); return UIScale.getSystemScaleFactor( gc ) > 1; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java index 45b6f402..92d3f013 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java @@ -501,9 +501,9 @@ public class FlatButtonUI prefSize.width = Math.max( prefSize.width, prefSize.height ); } else if( !isIconOnlyOrSingleCharacter && !isToolBarButton( c ) && c.getBorder() instanceof FlatButtonBorder ) { // apply minimum width/height - float focusWidth = FlatUIUtils.getBorderFocusWidth( c ); - prefSize.width = Math.max( prefSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) + Math.round( focusWidth * 2 ) ); - prefSize.height = Math.max( prefSize.height, scale( FlatUIUtils.minimumHeight( c, 0 ) ) + Math.round( focusWidth * 2 ) ); + int fw = Math.round( FlatUIUtils.getBorderFocusWidth( c ) * 2 ); + prefSize.width = Math.max( prefSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) + fw ); + prefSize.height = Math.max( prefSize.height, scale( FlatUIUtils.minimumHeight( c, 0 ) ) + fw ); } return prefSize; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java index 884c5137..76bb0369 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java @@ -534,7 +534,8 @@ public class FlatComboBoxUI @Override public Dimension getMinimumSize( JComponent c ) { Dimension minimumSize = super.getMinimumSize( c ); - minimumSize.width = Math.max( minimumSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) ); + int fw = Math.round( FlatUIUtils.getBorderFocusWidth( c ) * 2 ); + minimumSize.width = Math.max( minimumSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) + fw ); return minimumSize; } diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatComponentSizes.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatComponentSizes.java new file mode 100644 index 00000000..044b98e5 --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatComponentSizes.java @@ -0,0 +1,209 @@ +/* + * Copyright 2021 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 org.junit.jupiter.api.Assertions.assertEquals; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.LineBorder; +import javax.swing.plaf.basic.BasicComboBoxEditor; +import javax.swing.plaf.basic.BasicComboBoxRenderer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import com.formdev.flatlaf.util.UIScale; + +/** + * @author Karl Tauber + */ +public class TestFlatComponentSizes +{ + @BeforeAll + static void setup() { + TestUtils.setup( false ); + } + + @AfterAll + static void cleanup() { + TestUtils.cleanup(); + } + + static float[] factors() { + return new float[] { 1f, 1.25f, 1.5f, 1.75f, 2f, 2.25f, 2.5f, 3f, 4f }; + } + + @ParameterizedTest + @MethodSource( "factors" ) + void sizes( float factor ) { + TestUtils.scaleFont( factor ); + + // TODO on some scale factors the combobox/spinner sizes are slightly different + // because different size calculation methods and rounding + boolean testComboBoxAndSpinner = (UIManager.getInt( "Component.focusWidth" ) > 0) + ? (factor != 1.25 && factor != 2.25) + : (factor != 1.75); + + + // should have same default size (minimumWidth is 64) + JTextField textField = new JTextField(); + JFormattedTextField formattedTextField = new JFormattedTextField(); + JPasswordField passwordField = new JPasswordField(); + JSpinner spinner = new JSpinner(); + + Dimension textFieldSize = textField.getPreferredSize(); + assertEquals( textFieldSize, formattedTextField.getPreferredSize() ); + assertEquals( textFieldSize, passwordField.getPreferredSize() ); + if( testComboBoxAndSpinner ) + assertEquals( textFieldSize, spinner.getPreferredSize() ); + + + // should have same default size (minimumWidth is 72) + JButton button = new JButton( "text" ); + JComboBox comboBox = new JComboBox<>(); + JComboBox comboBoxEditable = new JComboBox<>(); + comboBoxEditable.setEditable( true ); + + Dimension buttonSize = button.getPreferredSize(); + if( testComboBoxAndSpinner ) { + assertEquals( buttonSize, comboBox.getPreferredSize() ); + assertEquals( buttonSize, comboBoxEditable.getPreferredSize() ); + } + + + // should have same height + JToggleButton toggleButton = new JToggleButton( "text" ); + + assertEquals( textFieldSize.height, button.getPreferredSize().height ); + assertEquals( textFieldSize.height, toggleButton.getPreferredSize().height ); + + + // should have same size + JCheckBox checkBox = new JCheckBox( "text" ); + JRadioButton radioButton = new JRadioButton( "text" ); + assertEquals( checkBox.getPreferredSize(), radioButton.getPreferredSize() ); + + + // should have same size + JMenu menu = new JMenu( "text" ); + JMenuItem menuItem = new JMenuItem( "text" ); + JCheckBoxMenuItem checkBoxMenuItem = new JCheckBoxMenuItem( "text" ); + JRadioButtonMenuItem radioButtonMenuItem = new JRadioButtonMenuItem( "text" ); + + Dimension menuSize = menu.getPreferredSize(); + assertEquals( menuSize, menuItem.getPreferredSize() ); + assertEquals( menuSize, checkBoxMenuItem.getPreferredSize() ); + assertEquals( menuSize, radioButtonMenuItem.getPreferredSize() ); + + + TestUtils.resetFont(); + } + + @ParameterizedTest + @MethodSource( "factors" ) + void comboBox( float factor ) { + TestUtils.scaleFont( factor ); + + JComboBox comboBox = new JComboBox<>(); + JComboBox comboBox2 = new JComboBox<>(); + JComboBox comboBox3 = new JComboBox<>(); + JComboBox comboBox4 = new JComboBox<>(); + + applyCustomComboBoxRendererBorder( comboBox2, new LineBorder( Color.orange, UIScale.scale( 3 ) ) ); + applyCustomComboBoxRendererBorder( comboBox3, new BorderWithIcon() ); + applyCustomComboBoxRendererBorder( comboBox4, null ); + + Dimension size = comboBox.getPreferredSize(); + assertEquals( size, comboBox2.getPreferredSize() ); + assertEquals( size, comboBox3.getPreferredSize() ); + assertEquals( size, comboBox4.getPreferredSize() ); + + TestUtils.resetFont(); + } + + @SuppressWarnings( "unchecked" ) + private void applyCustomComboBoxRendererBorder( JComboBox comboBox, Border border ) { + BasicComboBoxRenderer customRenderer = new BasicComboBoxRenderer(); + customRenderer.setBorder( border ); + comboBox.setRenderer( customRenderer ); + } + + @ParameterizedTest + @MethodSource( "factors" ) + void comboBoxEditable( float factor ) { + TestUtils.scaleFont( factor ); + + JComboBox comboBox = new JComboBox<>(); + JComboBox comboBox2 = new JComboBox<>(); + JComboBox comboBox3 = new JComboBox<>(); + JComboBox comboBox4 = new JComboBox<>(); + + comboBox.setEditable( true ); + comboBox2.setEditable( true ); + comboBox3.setEditable( true ); + comboBox4.setEditable( true ); + + applyCustomComboBoxEditorBorder( comboBox2, new LineBorder( Color.orange, UIScale.scale( 3 ) ) ); + applyCustomComboBoxEditorBorder( comboBox3, new BorderWithIcon() ); + applyCustomComboBoxEditorBorder( comboBox4, null ); + + Dimension size = comboBox.getPreferredSize(); + assertEquals( size.width, comboBox2.getPreferredSize().width ); + assertEquals( size.height + (2 * UIScale.scale( 3 )), comboBox2.getPreferredSize().height ); + assertEquals( size, comboBox3.getPreferredSize() ); + assertEquals( size, comboBox4.getPreferredSize() ); + + TestUtils.resetFont(); + } + + private void applyCustomComboBoxEditorBorder( JComboBox comboBox, Border border ) { + JTextField customTextField = new JTextField(); + if( border != null ) + customTextField.setBorder( border ); + comboBox.setEditor( new BasicComboBoxEditor() { + @Override + protected JTextField createEditorComponent() { + return customTextField; + } + } ); + } + + //---- class BorderWithIcon ----------------------------------------------- + + private static class BorderWithIcon + implements Border + { + @Override + public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { + } + + @Override + public boolean isBorderOpaque() { + return false; + } + + @Override + public Insets getBorderInsets( Component c ) { + return new Insets( 0, 0, 0, UIScale.scale( 16 ) + 4 ); + } + } +} diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatComponentSizesWithFocus.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatComponentSizesWithFocus.java new file mode 100644 index 00000000..074cacd7 --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatComponentSizesWithFocus.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021 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 org.junit.jupiter.api.BeforeAll; + +/** + * @author Karl Tauber + */ +public class TestFlatComponentSizesWithFocus + extends TestFlatComponentSizes +{ + @BeforeAll + static void setup() { + TestUtils.setup( true ); + } +} diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java new file mode 100644 index 00000000..d4e31a76 --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021 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.Font; +import javax.swing.UIManager; +import com.formdev.flatlaf.FlatIntelliJLaf; +import com.formdev.flatlaf.FlatLightLaf; +import com.formdev.flatlaf.FlatSystemProperties; + +/** + * @author Karl Tauber + */ +public class TestUtils +{ + public static void setup( boolean withFocus ) { + System.setProperty( FlatSystemProperties.UI_SCALE, "1x" ); + if( withFocus ) + FlatIntelliJLaf.setup(); + else + FlatLightLaf.setup(); + System.clearProperty( FlatSystemProperties.UI_SCALE ); + } + + public static void cleanup() { + UIManager.put( "defaultFont", null ); + } + + public static void scaleFont( float factor ) { + Font defaultFont = UIManager.getLookAndFeelDefaults().getFont( "defaultFont" ); + UIManager.put( "defaultFont", defaultFont.deriveFont( (float) Math.round( defaultFont.getSize() * factor ) ) ); + } + + public static void resetFont() { + UIManager.put( "defaultFont", null ); + } +}