From 28fb2e2a0845c288c8dc89c042afd7d39933bd03 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 21 Jun 2021 17:24:45 +0200 Subject: [PATCH] Styling: support Menu, MenuItem, CheckBoxMenuItem and RadioButtonMenuItem --- .../icons/FlatCheckBoxMenuItemIcon.java | 15 +- .../flatlaf/icons/FlatMenuArrowIcon.java | 19 ++- .../flatlaf/ui/FlatCheckBoxMenuItemUI.java | 36 +++++ .../flatlaf/ui/FlatMenuItemRenderer.java | 85 ++++++++-- .../formdev/flatlaf/ui/FlatMenuItemUI.java | 49 ++++++ .../com/formdev/flatlaf/ui/FlatMenuUI.java | 36 +++++ .../flatlaf/ui/FlatRadioButtonMenuItemUI.java | 36 +++++ .../formdev/flatlaf/ui/FlatStyleSupport.java | 55 ++++++- .../formdev/flatlaf/ui/FlatStylingTests.java | 150 ++++++++++++++++-- 9 files changed, 435 insertions(+), 46 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxMenuItemIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxMenuItemIcon.java index 501602b6..97c76300 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxMenuItemIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxMenuItemIcon.java @@ -24,6 +24,8 @@ import java.awt.geom.Path2D; import javax.swing.AbstractButton; import javax.swing.JMenuItem; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStyleSupport; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; /** * Icon for {@link javax.swing.JCheckBoxMenuItem}. @@ -38,14 +40,21 @@ import javax.swing.UIManager; public class FlatCheckBoxMenuItemIcon extends FlatAbstractIcon { - protected final Color checkmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.checkmarkColor" ); - protected final Color disabledCheckmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.disabledCheckmarkColor" ); - protected final Color selectionForeground = UIManager.getColor( "MenuItem.selectionForeground" ); + @Styleable protected Color checkmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.checkmarkColor" ); + @Styleable protected Color disabledCheckmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.disabledCheckmarkColor" ); + @Styleable protected Color selectionForeground = UIManager.getColor( "MenuItem.selectionForeground" ); public FlatCheckBoxMenuItemIcon() { super( 15, 15, null ); } + /** + * @since TODO + */ + public Object applyStyleProperty( String key, Object value ) { + return FlatStyleSupport.applyToAnnotatedObject( this, key, value ); + } + @Override protected void paintIcon( Component c, Graphics2D g2 ) { boolean selected = (c instanceof AbstractButton) && ((AbstractButton)c).isSelected(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatMenuArrowIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatMenuArrowIcon.java index 2afb8faf..b71e5c53 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatMenuArrowIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatMenuArrowIcon.java @@ -23,7 +23,9 @@ import java.awt.Graphics2D; import java.awt.geom.Path2D; import javax.swing.JMenu; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStyleSupport; import com.formdev.flatlaf.ui.FlatUIUtils; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; /** * "arrow" icon for {@link javax.swing.JMenu}. @@ -39,22 +41,29 @@ import com.formdev.flatlaf.ui.FlatUIUtils; public class FlatMenuArrowIcon extends FlatAbstractIcon { - protected final boolean chevron = FlatUIUtils.isChevron( UIManager.getString( "Component.arrowType" ) ); - protected final Color arrowColor = UIManager.getColor( "Menu.icon.arrowColor" ); - protected final Color disabledArrowColor = UIManager.getColor( "Menu.icon.disabledArrowColor" ); - protected final Color selectionForeground = UIManager.getColor( "Menu.selectionForeground" ); + @Styleable protected String arrowType = UIManager.getString( "Component.arrowType" ); + @Styleable protected Color arrowColor = UIManager.getColor( "Menu.icon.arrowColor" ); + @Styleable protected Color disabledArrowColor = UIManager.getColor( "Menu.icon.disabledArrowColor" ); + @Styleable protected Color selectionForeground = UIManager.getColor( "Menu.selectionForeground" ); public FlatMenuArrowIcon() { super( 6, 10, null ); } + /** + * @since TODO + */ + public Object applyStyleProperty( String key, Object value ) { + return FlatStyleSupport.applyToAnnotatedObject( this, key, value ); + } + @Override protected void paintIcon( Component c, Graphics2D g ) { if( !c.getComponentOrientation().isLeftToRight() ) g.rotate( Math.toRadians( 180 ), width / 2., height / 2. ); g.setColor( getArrowColor( c ) ); - if( chevron ) { + if( FlatUIUtils.isChevron( arrowType ) ) { // chevron arrow Path2D path = FlatUIUtils.createPath( false, 1,1, 5,5, 1,9 ); g.setStroke( new BasicStroke( 1f ) ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java index 5bcb2f88..08bbd167 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java @@ -18,11 +18,14 @@ package com.formdev.flatlaf.ui; import java.awt.Dimension; import java.awt.Graphics; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.LookAndFeel; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI; +import com.formdev.flatlaf.ui.FlatStyleSupport.UnknownStyleException; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JCheckBoxMenuItem}. @@ -56,11 +59,19 @@ public class FlatCheckBoxMenuItemUI extends BasicCheckBoxMenuItemUI { private FlatMenuItemRenderer renderer; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatCheckBoxMenuItemUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStyleSupport.getStyle( menuItem ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -81,6 +92,31 @@ public class FlatCheckBoxMenuItemUI return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); } + @Override + protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { + return FlatStyleSupport.createPropertyChangeListener( c, this::applyStyle, super.createPropertyChangeListener( c ) ); + } + + /** + * @since TODO + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStyleSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + return renderer.applyStyleProperty( key, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + return FlatMenuItemUI.applyStyleProperty( this, key, value ); + } + @Override protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { return renderer.getPreferredMenuItemSize(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java index 3d4deb21..79580de1 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java @@ -39,6 +39,10 @@ import javax.swing.UIManager; import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.View; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.icons.FlatCheckBoxMenuItemIcon; +import com.formdev.flatlaf.icons.FlatMenuArrowIcon; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStyleSupport.UnknownStyleException; import com.formdev.flatlaf.util.DerivedColor; import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.HiDPIUtils; @@ -57,33 +61,32 @@ import com.formdev.flatlaf.util.SystemInfo; * @uiDefault MenuItem.underlineSelectionCheckBackground Color * @uiDefault MenuItem.underlineSelectionColor Color * @uiDefault MenuItem.underlineSelectionHeight int - * @uiDefault MenuItem.selectionBackground Color * * @author Karl Tauber */ public class FlatMenuItemRenderer { protected final JMenuItem menuItem; - protected final Icon checkIcon; - protected final Icon arrowIcon; + protected Icon checkIcon; + protected Icon arrowIcon; protected final Font acceleratorFont; protected final String acceleratorDelimiter; - protected final int minimumWidth = UIManager.getInt( "MenuItem.minimumWidth" ); - protected final Dimension minimumIconSize; - protected final int textAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textAcceleratorGap", 28 ); - protected final int textNoAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textNoAcceleratorGap", 6 ); - protected final int acceleratorArrowGap = FlatUIUtils.getUIInt( "MenuItem.acceleratorArrowGap", 2 ); + @Styleable protected int minimumWidth = UIManager.getInt( "MenuItem.minimumWidth" ); + @Styleable protected Dimension minimumIconSize; + @Styleable protected int textAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textAcceleratorGap", 28 ); + @Styleable protected int textNoAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textNoAcceleratorGap", 6 ); + @Styleable protected int acceleratorArrowGap = FlatUIUtils.getUIInt( "MenuItem.acceleratorArrowGap", 2 ); - protected final Color checkBackground = UIManager.getColor( "MenuItem.checkBackground" ); - protected final Insets checkMargins = UIManager.getInsets( "MenuItem.checkMargins" ); + @Styleable protected Color checkBackground = UIManager.getColor( "MenuItem.checkBackground" ); + @Styleable protected Insets checkMargins = UIManager.getInsets( "MenuItem.checkMargins" ); - protected final Color underlineSelectionBackground = UIManager.getColor( "MenuItem.underlineSelectionBackground" ); - protected final Color underlineSelectionCheckBackground = UIManager.getColor( "MenuItem.underlineSelectionCheckBackground" ); - protected final Color underlineSelectionColor = UIManager.getColor( "MenuItem.underlineSelectionColor" ); - protected final int underlineSelectionHeight = UIManager.getInt( "MenuItem.underlineSelectionHeight" ); + @Styleable protected Color underlineSelectionBackground = UIManager.getColor( "MenuItem.underlineSelectionBackground" ); + @Styleable protected Color underlineSelectionCheckBackground = UIManager.getColor( "MenuItem.underlineSelectionCheckBackground" ); + @Styleable protected Color underlineSelectionColor = UIManager.getColor( "MenuItem.underlineSelectionColor" ); + @Styleable protected int underlineSelectionHeight = UIManager.getInt( "MenuItem.underlineSelectionHeight" ); - protected final Color selectionBackground = UIManager.getColor( "MenuItem.selectionBackground" ); + private boolean iconsShared = true; protected FlatMenuItemRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon, Font acceleratorFont, String acceleratorDelimiter ) @@ -98,6 +101,54 @@ public class FlatMenuItemRenderer this.minimumIconSize = (minimumIconSize != null) ? minimumIconSize : new Dimension( 16, 16 ); } + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + // style icon + if( key.startsWith( "icon." ) || key.equals( "selectionForeground" ) ) { + if( iconsShared ) { + if( checkIcon instanceof FlatCheckBoxMenuItemIcon ) + checkIcon = FlatStyleSupport.cloneIcon( checkIcon ); + if( arrowIcon instanceof FlatMenuArrowIcon ) + arrowIcon = FlatStyleSupport.cloneIcon( arrowIcon ); + iconsShared = false; + } + + if( key.startsWith( "icon." ) ) { + String key2 = key.substring( "icon.".length() ); + + try { + if( checkIcon instanceof FlatCheckBoxMenuItemIcon ) + return ((FlatCheckBoxMenuItemIcon)checkIcon).applyStyleProperty( key2, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + try { + if( arrowIcon instanceof FlatMenuArrowIcon ) + return ((FlatMenuArrowIcon)arrowIcon).applyStyleProperty( key2, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + // keys with prefix "icon." are only for icons + throw new UnknownStyleException( key ); + } else if( key.equals( "selectionForeground" ) ) { + // special case: same key is used in icons and in menuitem + if( checkIcon instanceof FlatCheckBoxMenuItemIcon ) + ((FlatCheckBoxMenuItemIcon)checkIcon).applyStyleProperty( key, value ); + if( arrowIcon instanceof FlatMenuArrowIcon ) + ((FlatMenuArrowIcon)arrowIcon).applyStyleProperty( key, value ); + + // throw exception because the caller should also apply this key + throw new UnknownStyleException( key ); + } + } + + return FlatStyleSupport.applyToAnnotatedObject( this, key, value ); + } + protected Dimension getPreferredMenuItemSize() { int width = 0; int height = 0; @@ -254,7 +305,7 @@ debug*/ paintBackground( g, underlineSelection ? underlineSelectionBackground : selectionBackground ); if( underlineSelection && isArmedOrSelected( menuItem ) ) paintUnderlineSelection( g, underlineSelectionColor, underlineSelectionHeight ); - paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground ); + paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground ); paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground ); paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground ); if( !isTopLevelMenu( menuItem ) ) @@ -301,7 +352,7 @@ debug*/ return FlatUIUtils.deriveColor( background, baseColor ); } - protected void paintIcon( Graphics g, Rectangle iconRect, Icon icon, Color checkBackground ) { + protected void paintIcon( Graphics g, Rectangle iconRect, Icon icon, Color checkBackground, Color selectionBackground ) { // if checkbox/radiobutton menu item is selected and also has a custom icon, // then use filled icon background to indicate selection (instead of using checkIcon) if( menuItem.isSelected() && checkIcon != null && icon != checkIcon ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java index 78ae0d9e..db500433 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java @@ -18,11 +18,14 @@ package com.formdev.flatlaf.ui; import java.awt.Dimension; import java.awt.Graphics; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.LookAndFeel; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicMenuItemUI; +import com.formdev.flatlaf.ui.FlatStyleSupport.UnknownStyleException; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JMenuItem}. @@ -56,11 +59,19 @@ public class FlatMenuItemUI extends BasicMenuItemUI { private FlatMenuItemRenderer renderer; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatMenuItemUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStyleSupport.getStyle( menuItem ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -81,6 +92,44 @@ public class FlatMenuItemUI return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); } + @Override + protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { + return FlatStyleSupport.createPropertyChangeListener( c, this::applyStyle, super.createPropertyChangeListener( c ) ); + } + + /** + * @since TODO + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStyleSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + return renderer.applyStyleProperty( key, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + return applyStyleProperty( this, key, value ); + } + + static Object applyStyleProperty( BasicMenuItemUI ui, String key, Object value ) { + switch( key ) { + case "selectionBackground": + case "selectionForeground": + case "disabledForeground": + case "acceleratorForeground": + case "acceleratorSelectionForeground": + return FlatStyleSupport.applyToField( ui, key, key, value ); + + default: throw new UnknownStyleException( key ); + } + } + @Override protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { return renderer.getPreferredMenuItemSize(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java index 8f80c6c2..cc5366b4 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java @@ -21,6 +21,8 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.event.MouseEvent; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.ButtonModel; import javax.swing.Icon; import javax.swing.JComponent; @@ -31,6 +33,7 @@ import javax.swing.UIManager; import javax.swing.event.MouseInputListener; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicMenuUI; +import com.formdev.flatlaf.ui.FlatStyleSupport.UnknownStyleException; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JMenu}. @@ -75,11 +78,19 @@ public class FlatMenuUI { private Color hoverBackground; private FlatMenuItemRenderer renderer; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatMenuUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStyleSupport.getStyle( menuItem ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -129,6 +140,31 @@ public class FlatMenuUI }; } + @Override + protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { + return FlatStyleSupport.createPropertyChangeListener( c, this::applyStyle, super.createPropertyChangeListener( c ) ); + } + + /** + * @since TODO + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStyleSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + return renderer.applyStyleProperty( key, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + return FlatMenuItemUI.applyStyleProperty( this, key, value ); + } + @Override public Dimension getMinimumSize( JComponent c ) { // avoid that top-level menus (in menu bar) are made smaller if horizontal space is rare diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java index 5f65012c..1908f4ec 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java @@ -18,11 +18,14 @@ package com.formdev.flatlaf.ui; import java.awt.Dimension; import java.awt.Graphics; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.LookAndFeel; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI; +import com.formdev.flatlaf.ui.FlatStyleSupport.UnknownStyleException; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JRadioButtonMenuItem}. @@ -56,11 +59,19 @@ public class FlatRadioButtonMenuItemUI extends BasicRadioButtonMenuItemUI { private FlatMenuItemRenderer renderer; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatRadioButtonMenuItemUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStyleSupport.getStyle( menuItem ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -81,6 +92,31 @@ public class FlatRadioButtonMenuItemUI return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); } + @Override + protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { + return FlatStyleSupport.createPropertyChangeListener( c, this::applyStyle, super.createPropertyChangeListener( c ) ); + } + + /** + * @since TODO + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStyleSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + return renderer.applyStyleProperty( key, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + return FlatMenuItemUI.applyStyleProperty( this, key, value ); + } + @Override protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { return renderer.getPreferredMenuItemSize(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java index 3fb6b7de..23ad2c3f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.ui; +import java.beans.PropertyChangeListener; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,6 +27,8 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Predicate; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.UIManager; @@ -204,13 +207,38 @@ public class FlatStyleSupport + key.substring( dotIndex + 2 ); } + return applyToField( obj, fieldName, key, value, field -> { + Styleable styleable = field.getAnnotation( Styleable.class ); + return styleable != null && styleable.dot() == (dotIndex >= 0); + } ); + } + + /** + * Applies the given value to a field of the given object. + * + * @param obj the object + * @param fieldName the name of the field + * @param key the key (only used for error reporting) + * @param value the new value + * @return the old value of the field + * @throws UnknownStyleException if object does not have a field with given name + * @throws IllegalArgumentException if value type does not fit to expected type  + */ + static Object applyToField( Object obj, String fieldName, String key, Object value ) + throws UnknownStyleException, IllegalArgumentException + { + return applyToField( obj, fieldName, key, value, null ); + } + + private static Object applyToField( Object obj, String fieldName, String key, Object value, Predicate predicate ) + throws UnknownStyleException, IllegalArgumentException + { Class cls = obj.getClass(); for(;;) { try { Field f = cls.getDeclaredField( fieldName ); - Styleable styleable = f.getAnnotation( Styleable.class ); - if( styleable != null && styleable.dot() == (dotIndex >= 0) ) { + if( predicate == null || predicate.test( f ) ) { if( Modifier.isFinal( f.getModifiers() ) ) throw new IllegalArgumentException( "field '" + cls.getName() + "." + fieldName + "' is final" ); @@ -234,9 +262,11 @@ public class FlatStyleSupport if( cls == null ) throw new UnknownStyleException( key ); - String superclassName = cls.getName(); - if( superclassName.startsWith( "java." ) || superclassName.startsWith( "javax." ) ) - throw new UnknownStyleException( key ); + if( predicate != null ) { + String superclassName = cls.getName(); + if( superclassName.startsWith( "java." ) || superclassName.startsWith( "javax." ) ) + throw new UnknownStyleException( key ); + } } } @@ -244,6 +274,21 @@ public class FlatStyleSupport return c.getClientProperty( FlatClientProperties.STYLE ); } + static PropertyChangeListener createPropertyChangeListener( JComponent c, + Consumer applyStyle, PropertyChangeListener superListener ) + { + return e -> { + if( superListener != null ) + superListener.propertyChange( e ); + + if( FlatClientProperties.STYLE.equals( e.getPropertyName() ) ) { + applyStyle.accept( e.getNewValue() ); + c.revalidate(); + c.repaint(); + } + }; + } + static Border cloneBorder( Border border ) { Class borderClass = border.getClass(); try { diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java index 2a1a4170..96086f48 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java @@ -23,23 +23,9 @@ import java.awt.Insets; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; -import javax.swing.AbstractButton; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JEditorPane; -import javax.swing.JFormattedTextField; -import javax.swing.JPasswordField; -import javax.swing.JRadioButton; -import javax.swing.JSplitPane; -import javax.swing.JTextArea; -import javax.swing.JTextField; -import javax.swing.JTextPane; -import javax.swing.JToggleButton; -import javax.swing.UIManager; +import javax.swing.*; import org.junit.jupiter.api.Test; -import com.formdev.flatlaf.icons.FlatCapsLockIcon; -import com.formdev.flatlaf.icons.FlatCheckBoxIcon; -import com.formdev.flatlaf.icons.FlatRadioButtonIcon; +import com.formdev.flatlaf.icons.*; /** * @author Karl Tauber @@ -157,6 +143,95 @@ public class FlatStylingTests textField( ui ); } + @Test + void menu() { + UIManager.put( "Menu.arrowIcon", new FlatMenuArrowIcon() ); + UIManager.put( "Menu.checkIcon", null ); + FlatMenuUI ui = new FlatMenuUI(); + ui.installUI( new JMenu() ); + + Consumer applyStyle = style -> ui.applyStyle( style ); + menuItem( applyStyle ); + menuItem_arrowIcon( applyStyle ); + } + + @Test + void menuItem() { + UIManager.put( "MenuItem.arrowIcon", new FlatMenuItemArrowIcon() ); + UIManager.put( "MenuItem.checkIcon", null ); + FlatMenuItemUI ui = new FlatMenuItemUI(); + ui.installUI( new JMenuItem() ); + + Consumer applyStyle = style -> ui.applyStyle( style ); + menuItem( applyStyle ); + menuItem_arrowIcon( applyStyle ); + } + + @Test + void checkBoxMenuItem() { + UIManager.put( "CheckBoxMenuItem.arrowIcon", new FlatMenuItemArrowIcon() ); + UIManager.put( "CheckBoxMenuItem.checkIcon", new FlatCheckBoxMenuItemIcon() ); + FlatCheckBoxMenuItemUI ui = new FlatCheckBoxMenuItemUI(); + ui.installUI( new JCheckBoxMenuItem() ); + + Consumer applyStyle = style -> ui.applyStyle( style ); + menuItem( applyStyle ); + menuItem_arrowIcon( applyStyle ); + menuItem_checkIcon( applyStyle ); + } + + @Test + void radioButtonMenuItem() { + UIManager.put( "RadioButtonMenuItem.arrowIcon", new FlatMenuItemArrowIcon() ); + UIManager.put( "RadioButtonMenuItem.checkIcon", new FlatRadioButtonMenuItemIcon() ); + FlatRadioButtonMenuItemUI ui = new FlatRadioButtonMenuItemUI(); + ui.installUI( new JRadioButtonMenuItem() ); + + Consumer applyStyle = style -> ui.applyStyle( style ); + menuItem( applyStyle ); + menuItem_arrowIcon( applyStyle ); + menuItem_checkIcon( applyStyle ); + } + + private void menuItem( Consumer applyStyle ) { + applyStyle.accept( "selectionBackground: #fff" ); + applyStyle.accept( "selectionForeground: #fff" ); + applyStyle.accept( "disabledForeground: #fff" ); + applyStyle.accept( "acceleratorForeground: #fff" ); + applyStyle.accept( "acceleratorSelectionForeground: #fff" ); + + menuItemRenderer( applyStyle ); + } + + private void menuItemRenderer( Consumer applyStyle ) { + applyStyle.accept( "minimumWidth: 10" ); + applyStyle.accept( "minimumIconSize: 16,16" ); + applyStyle.accept( "textAcceleratorGap: 28" ); + applyStyle.accept( "textNoAcceleratorGap: 6" ); + applyStyle.accept( "acceleratorArrowGap: 2" ); + + applyStyle.accept( "checkBackground: #fff" ); + applyStyle.accept( "checkMargins: 1,2,3,4" ); + + applyStyle.accept( "underlineSelectionBackground: #fff" ); + applyStyle.accept( "underlineSelectionCheckBackground: #fff" ); + applyStyle.accept( "underlineSelectionColor: #fff" ); + applyStyle.accept( "underlineSelectionHeight: 3" ); + } + + private void menuItem_checkIcon( Consumer applyStyle ) { + applyStyle.accept( "icon.checkmarkColor: #fff" ); + applyStyle.accept( "icon.disabledCheckmarkColor: #fff" ); + applyStyle.accept( "icon.selectionForeground: #fff" ); + } + + private void menuItem_arrowIcon( Consumer applyStyle ) { + applyStyle.accept( "icon.arrowType: chevron" ); + applyStyle.accept( "icon.arrowColor: #fff" ); + applyStyle.accept( "icon.disabledArrowColor: #fff" ); + applyStyle.accept( "selectionForeground: #fff" ); + } + @Test void passwordField() { FlatPasswordFieldUI ui = new FlatPasswordFieldUI(); @@ -556,4 +631,47 @@ public class FlatStylingTests icon.applyStyleProperty( "pressedBackground", Color.WHITE ); icon.applyStyleProperty( "selectedPressedBackground", Color.WHITE ); } + + @Test + void flatCheckBoxMenuItemIcon() { + FlatCheckBoxMenuItemIcon icon = new FlatCheckBoxMenuItemIcon(); + + flatCheckBoxMenuItemIcon( icon ); + } + + @Test + void flatRadioButtonMenuItemIcon() { + FlatRadioButtonMenuItemIcon icon = new FlatRadioButtonMenuItemIcon(); + + // FlatRadioButtonMenuItemIcon extends FlatCheckBoxMenuItemIcon + flatCheckBoxMenuItemIcon( icon ); + } + + private void flatCheckBoxMenuItemIcon( FlatCheckBoxMenuItemIcon icon ) { + icon.applyStyleProperty( "checkmarkColor", Color.WHITE ); + icon.applyStyleProperty( "disabledCheckmarkColor", Color.WHITE ); + icon.applyStyleProperty( "selectionForeground", Color.WHITE ); + } + + @Test + void flatMenuArrowIcon() { + FlatMenuArrowIcon icon = new FlatMenuArrowIcon(); + + flatMenuArrowIcon( icon ); + } + + @Test + void flatMenuItemArrowIcon() { + FlatMenuItemArrowIcon icon = new FlatMenuItemArrowIcon(); + + // FlatMenuItemArrowIcon extends FlatMenuArrowIcon + flatMenuArrowIcon( icon ); + } + + private void flatMenuArrowIcon( FlatMenuArrowIcon icon ) { + icon.applyStyleProperty( "arrowType", "chevron" ); + icon.applyStyleProperty( "arrowColor", Color.WHITE ); + icon.applyStyleProperty( "disabledArrowColor", Color.WHITE ); + icon.applyStyleProperty( "selectionForeground", Color.WHITE ); + } }