From 7ebc1b27c17aa3ad68c8660cf08736aac4bb6216 Mon Sep 17 00:00:00 2001 From: daWoife Date: Sat, 15 Nov 2025 11:37:18 +0100 Subject: [PATCH 1/2] Update FlatRadioButtonUI.java The method applyStyleProperty of class FlatRadioButtonUI currently styles the variable icon, a field of the parent class BasicRadioButtonUI and the default icon which is set with UIManager.getIcon in method installDefaults. So the wrong icon is styled if someone subclasses FlatRadioBoxIcon or FlatCheckBoxIcon and sets this as the new icon for a JRadioButton or JCheckBox instance. (An example why someone would do so is shown in issue #413). With this change styling takes account of the current icon of a JRadioButton or JCheckBox. --- .../java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java index 73706178..5c9bbf88 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java @@ -203,10 +203,15 @@ public class FlatRadioButtonUI protected Object applyStyleProperty( AbstractButton b, String key, Object value ) { // style icon if( key.startsWith( "icon." ) ) { - if( !(icon instanceof FlatCheckBoxIcon) ) + Icon styleIcon = b.getIcon(); + + if (styleIcon == null) + styleIcon = icon; + + if( !(styleIcon instanceof FlatCheckBoxIcon) ) return new UnknownStyleException( key ); - if( iconShared ) { + if( styleIcon == icon && iconShared ) { icon = FlatStylingSupport.cloneIcon( icon ); iconShared = false; } From 04602ac22742381043eb0f3a89eb77224332cc5b Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 25 Nov 2025 18:18:22 +0100 Subject: [PATCH 2/2] CheckBox and RadioButton: - fixed styling of custom icon - fixed focus width (and preferred size) if using custom icon - added unit tests --- .../formdev/flatlaf/ui/FlatRadioButtonUI.java | 26 ++++---- .../flatlaf/ui/TestFlatStyleableInfo.java | 27 ++++++++- .../flatlaf/ui/TestFlatStyleableValue.java | 33 +++++++++- .../formdev/flatlaf/ui/TestFlatStyling.java | 60 ++++++++++++++++++- 4 files changed, 125 insertions(+), 21 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java index 5c9bbf88..7c36df31 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java @@ -203,16 +203,12 @@ public class FlatRadioButtonUI protected Object applyStyleProperty( AbstractButton b, String key, Object value ) { // style icon if( key.startsWith( "icon." ) ) { - Icon styleIcon = b.getIcon(); - - if (styleIcon == null) - styleIcon = icon; - - if( !(styleIcon instanceof FlatCheckBoxIcon) ) + Icon icon = getRealIcon( b ); + if( !(icon instanceof FlatCheckBoxIcon) ) return new UnknownStyleException( key ); - if( styleIcon == icon && iconShared ) { - icon = FlatStylingSupport.cloneIcon( icon ); + if( icon == this.icon && iconShared ) { + this.icon = icon = FlatStylingSupport.cloneIcon( icon ); iconShared = false; } @@ -230,6 +226,7 @@ public class FlatRadioButtonUI @Override public Map> getStyleableInfos( JComponent c ) { Map> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this ); + Icon icon = getRealIcon( c ); if( icon instanceof FlatCheckBoxIcon ) { for( Map.Entry> e : ((FlatCheckBoxIcon)icon).getStyleableInfos().entrySet() ) infos.put( "icon.".concat( e.getKey() ), e.getValue() ); @@ -242,6 +239,7 @@ public class FlatRadioButtonUI public Object getStyleableValue( JComponent c, String key ) { // style icon if( key.startsWith( "icon." ) ) { + Icon icon = getRealIcon( c ); return (icon instanceof FlatCheckBoxIcon) ? ((FlatCheckBoxIcon)icon).getStyleableValue( key.substring( "icon.".length() ) ) : null; @@ -337,16 +335,18 @@ public class FlatRadioButtonUI } private int getIconFocusWidth( JComponent c ) { - AbstractButton b = (AbstractButton) c; - Icon icon = b.getIcon(); - if( icon == null ) - icon = getDefaultIcon(); - + Icon icon = getRealIcon( c ); return (icon instanceof FlatCheckBoxIcon) ? Math.round( UIScale.scale( ((FlatCheckBoxIcon)icon).getFocusWidth() ) ) : 0; } + private Icon getRealIcon( JComponent c ) { + AbstractButton b = (AbstractButton) c; + Icon icon = b.getIcon(); + return (icon != null) ? icon : getDefaultIcon(); + } + @Override public int getBaseline( JComponent c, int width, int height ) { return FlatButtonUI.getBaselineImpl( c, width, height ); diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java index 4366f280..3f5ce955 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java @@ -30,6 +30,9 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import com.formdev.flatlaf.icons.*; +import com.formdev.flatlaf.ui.TestFlatStyling.CustomCheckBoxIcon; +import com.formdev.flatlaf.ui.TestFlatStyling.CustomIcon; +import com.formdev.flatlaf.ui.TestFlatStyling.CustomRadioButtonIcon; /** * @author Karl Tauber @@ -144,7 +147,12 @@ public class TestFlatStyleableInfo @Test void checkBox() { - JCheckBox c = new JCheckBox(); + checkBox( new JCheckBox() ); + checkBox( new JCheckBox( new CustomIcon() ) ); + checkBox( new JCheckBox( new CustomCheckBoxIcon() ) ); + } + + private void checkBox( JCheckBox c ) { FlatCheckBoxUI ui = (FlatCheckBoxUI) c.getUI(); assertTrue( ui.getDefaultIcon() instanceof FlatCheckBoxIcon ); @@ -153,6 +161,11 @@ public class TestFlatStyleableInfo Map> expected = new LinkedHashMap<>(); radioButton( expected ); + // remove "icon." keys if check box has custom icon + Icon icon = c.getIcon(); + if( icon != null && !(icon instanceof FlatCheckBoxIcon) ) + expected.keySet().removeIf( key -> key.startsWith( "icon." ) ); + assertMapEquals( expected, ui.getStyleableInfos( c ) ); } @@ -492,7 +505,12 @@ public class TestFlatStyleableInfo @Test void radioButton() { - JRadioButton c = new JRadioButton(); + radioButton( new JRadioButton() ); + radioButton( new JRadioButton( new CustomIcon() ) ); + radioButton( new JRadioButton( new CustomRadioButtonIcon() ) ); + } + + private void radioButton( JRadioButton c ) { FlatRadioButtonUI ui = (FlatRadioButtonUI) c.getUI(); assertTrue( ui.getDefaultIcon() instanceof FlatRadioButtonIcon ); @@ -504,6 +522,11 @@ public class TestFlatStyleableInfo "icon.centerDiameter", float.class ); + // remove "icon." keys if radio button has custom icon + Icon icon = c.getIcon(); + if( icon != null && !(icon instanceof FlatRadioButtonIcon) ) + expected.keySet().removeIf( key -> key.startsWith( "icon." ) ); + assertMapEquals( expected, ui.getStyleableInfos( c ) ); } diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java index d4276768..4f0acac9 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java @@ -77,6 +77,9 @@ import com.formdev.flatlaf.icons.FlatRadioButtonMenuItemIcon; import com.formdev.flatlaf.icons.FlatSearchIcon; import com.formdev.flatlaf.icons.FlatSearchWithHistoryIcon; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.ui.TestFlatStyling.CustomCheckBoxIcon; +import com.formdev.flatlaf.ui.TestFlatStyling.CustomIcon; +import com.formdev.flatlaf.ui.TestFlatStyling.CustomRadioButtonIcon; /** * @author Karl Tauber @@ -269,11 +272,20 @@ public class TestFlatStyleableValue @Test void checkBox() { - JCheckBox c = new JCheckBox(); + checkBox( new JCheckBox() ); + checkBox( new JCheckBox( new CustomCheckBoxIcon() ) ); + checkBox( new JCheckBox( new CustomIcon() ) ); + } + + private void checkBox( JCheckBox c ) { FlatCheckBoxUI ui = (FlatCheckBoxUI) c.getUI(); // FlatCheckBoxUI extends FlatRadioButtonUI radioButton( ui, c ); + + // necessary to clear FlatRadioButtonUI.oldStyleValues because + // ui.applyStyle(...) operates on shared instance + ui.uninstallUI( c ); } @Test @@ -536,14 +548,24 @@ public class TestFlatStyleableValue @Test void radioButton() { - JRadioButton c = new JRadioButton(); + radioButton( new JRadioButton() ); + radioButton( new JRadioButton( new CustomRadioButtonIcon() ) ); + radioButton( new JRadioButton( new CustomIcon() ) ); + } + + private void radioButton( JRadioButton c ) { FlatRadioButtonUI ui = (FlatRadioButtonUI) c.getUI(); assertTrue( ui.getDefaultIcon() instanceof FlatRadioButtonIcon ); radioButton( ui, c ); - testFloat( c, ui, "icon.centerDiameter", 1.23f ); + if( !(c.getIcon() instanceof CustomIcon) ) + testFloat( c, ui, "icon.centerDiameter", 1.23f ); + + // necessary to clear FlatRadioButtonUI.oldStyleValues because + // ui.applyStyle(...) operates on shared instance + ui.uninstallUI( c ); } private void radioButton( FlatRadioButtonUI ui, AbstractButton b ) { @@ -551,6 +573,11 @@ public class TestFlatStyleableValue //---- icon ---- + if( b.getIcon() instanceof CustomIcon ) { + assertEquals( null, ui.getStyleableValue( b, "icon.focusWidth" ) ); + return; + } + testFloat( b, ui, "icon.focusWidth", 1.23f ); testColor( b, ui, "icon.focusColor", 0x123456 ); testFloat( b, ui, "icon.borderWidth", 1.23f ); diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java index a2057080..4d56f0f1 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java @@ -19,7 +19,9 @@ package com.formdev.flatlaf.ui; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.awt.Color; +import java.awt.Component; import java.awt.Dimension; +import java.awt.Graphics; import java.awt.Insets; import java.util.HashMap; import java.util.Map; @@ -294,13 +296,22 @@ public class TestFlatStyling @Test void checkBox() { - JCheckBox c = new JCheckBox(); + checkBox( new JCheckBox() ); + checkBox( new JCheckBox( new CustomIcon() ) ); + checkBox( new JCheckBox( new CustomCheckBoxIcon() ) ); + } + + private void checkBox( JCheckBox c ) { FlatCheckBoxUI ui = (FlatCheckBoxUI) c.getUI(); assertTrue( ui.getDefaultIcon() instanceof FlatCheckBoxIcon ); // FlatCheckBoxUI extends FlatRadioButtonUI radioButton( ui, c ); + + // necessary to clear FlatRadioButtonUI.oldStyleValues because + // ui.applyStyle(...) operates on shared instance + ui.uninstallUI( c ); } @Test @@ -651,14 +662,24 @@ public class TestFlatStyling @Test void radioButton() { - JRadioButton c = new JRadioButton(); + radioButton( new JRadioButton() ); + radioButton( new JRadioButton( new CustomIcon() ) ); + radioButton( new JRadioButton( new CustomRadioButtonIcon() ) ); + } + + private void radioButton( JRadioButton c ) { FlatRadioButtonUI ui = (FlatRadioButtonUI) c.getUI(); assertTrue( ui.getDefaultIcon() instanceof FlatRadioButtonIcon ); radioButton( ui, c ); - ui.applyStyle( c, "icon.centerDiameter: 8" ); + if( !(c.getIcon() instanceof CustomIcon) ) + ui.applyStyle( c, "icon.centerDiameter: 8" ); + + // necessary to clear FlatRadioButtonUI.oldStyleValues because + // ui.applyStyle(...) operates on shared instance + ui.uninstallUI( c ); } private void radioButton( FlatRadioButtonUI ui, AbstractButton b ) { @@ -676,6 +697,9 @@ public class TestFlatStyling //---- icon ---- + if( b.getIcon() instanceof CustomIcon ) + return; + ui.applyStyle( b, "icon.focusWidth: 1.5" ); ui.applyStyle( b, "icon.focusColor: #fff" ); ui.applyStyle( b, "icon.borderWidth: 1.5" ); @@ -1565,4 +1589,34 @@ public class TestFlatStyling UIManager.put( "test.enum", null ); assertEquals( SomeEnum.enumValue1, FlatUIUtils.getUIEnum( "test.enum", SomeEnum.class, SomeEnum.enumValue1 ) ); } + + //---- class CustomIcon --------------------------------------------------- + + static class CustomIcon + implements Icon + { + @Override public void paintIcon( Component c, Graphics g, int x, int y ) {} + @Override public int getIconWidth() { return 1; } + @Override public int getIconHeight() { return 1; } + } + + //---- class CustomCheckBoxIcon ---------------------------------------- + + static class CustomCheckBoxIcon + extends FlatCheckBoxIcon + { + CustomCheckBoxIcon() { + background = Color.green; + } + } + + //---- class CustomRadioButtonIcon ---------------------------------------- + + static class CustomRadioButtonIcon + extends FlatRadioButtonIcon + { + CustomRadioButtonIcon() { + background = Color.green; + } + } }