diff --git a/CHANGELOG.md b/CHANGELOG.md index e65348fc..5bbfbcdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ FlatLaf Change Log ## 2.0-SNAPSHOT +#### New features and improvements + +- Styling components using string in CSS syntax or `java.util.Map`. (PR #341)\ + For example: `mySlider.putClientProperty( "FlatLaf.style", "trackWidth: 2" );` + #### Fixed bugs - Tree: Fixed editing cell issue with custom cell renderer and cell editor that 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 dace152f..0d83eb42 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -126,6 +126,25 @@ public interface FlatClientProperties //---- JComponent --------------------------------------------------------- + /** + * Specifies the style of a component as String in CSS syntax ("key1: value1; key2: value2; ...") + * or as {@link java.util.Map}<String, Object> with binary values. + *

+ * The keys are the same as used in UI defaults, but without component type prefix. + * E.g. for UI default {@code Slider.thumbSize} use key {@code thumbSize}. + *

+ * The syntax of the CSS values is the same as used in FlatLaf properties files + * (https://www.formdev.com/flatlaf/properties-files/), + * but some features are not supported (e.g. variables). + * When using a map, the values are not parsed from a string. They must be binary. + *

+ * Components {@link javax.swing.JComponent}
+ * Value type {@link java.lang.String} or {@link java.util.Map}<String, Object>
+ * + * @since 2 + */ + String STYLE = "FlatLaf.style"; + /** * Specifies minimum width of a component. *

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 9ff1ff0a..d9589d19 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -32,6 +32,7 @@ import java.beans.PropertyChangeListener; import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; @@ -55,6 +56,7 @@ import javax.swing.RootPaneContainer; import javax.swing.SwingUtilities; import javax.swing.UIDefaults; import javax.swing.UIDefaults.ActiveValue; +import javax.swing.UIDefaults.LazyValue; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.plaf.ColorUIResource; @@ -779,6 +781,34 @@ public abstract class FlatLaf customDefaultsSources.remove( folder ); } + /** + * Parses a UI defaults value string and converts it into a binary object. + *

+ * See: https://www.formdev.com/flatlaf/properties-files/ + * + * @param key the key, which is used to determine the value type if parameter {@code valueType} is {@code null} + * @param value the value string + * @param valueType the expected value type, or {@code null} + * @return the binary value + * @throws IllegalArgumentException on syntax errors + * @since 2 + */ + public static Object parseDefaultsValue( String key, String value, Class valueType ) + throws IllegalArgumentException + { + // parse value + Object val = UIDefaultsLoader.parseValue( key, value, valueType, null, + v -> UIDefaultsLoader.resolveValueFromUIManager( v ), Collections.emptyList() ); + + // create actual value if lazy or active + if( val instanceof LazyValue ) + val = ((LazyValue)val).createValue( null ); + else if( val instanceof ActiveValue ) + val = ((ActiveValue)val).createValue( null ); + + return val; + } + private static void reSetLookAndFeel() { EventQueue.invokeLater( () -> { LookAndFeel lookAndFeel = UIManager.getLookAndFeel(); 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 5a2fa716..554f7d2b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java @@ -338,7 +338,7 @@ public class IntelliJTheme // parse value try { - uiValue = UIDefaultsLoader.parseValue( key, valueStr ); + uiValue = UIDefaultsLoader.parseValue( key, valueStr, null ); } catch( RuntimeException ex ) { UIDefaultsLoader.logParseError( key, valueStr, ex, false ); return; // ignore invalid value diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java index 1b6d1ab3..0edf48e4 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -33,8 +33,10 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.function.Function; +import javax.swing.Icon; import javax.swing.UIDefaults; import javax.swing.UIManager; +import javax.swing.border.Border; import javax.swing.UIDefaults.ActiveValue; import javax.swing.UIDefaults.LazyValue; import javax.swing.plaf.ColorUIResource; @@ -241,7 +243,7 @@ class UIDefaultsLoader String value = resolveValue( (String) e.getValue(), propertiesGetter ); try { - defaults.put( key, parseValue( key, value, null, resolver, addonClassLoaders ) ); + defaults.put( key, parseValue( key, value, null, null, resolver, addonClassLoaders ) ); } catch( RuntimeException ex ) { logParseError( key, value, ex, true ); } @@ -288,16 +290,36 @@ class UIDefaultsLoader return resolveValue( newValue, propertiesGetter ); } + static String resolveValueFromUIManager( String value ) { + if( !value.startsWith( PROPERTY_PREFIX ) ) + return value; + + String key = value.substring( PROPERTY_PREFIX.length() ); + Object newValue = UIManager.get( key ); + if( newValue == null ) + throw new IllegalArgumentException( "property '" + key + "' not found" ); + + // convert binary color to string + if( newValue instanceof Color ) { + Color color = (Color) newValue; + int alpha = color.getAlpha(); + return String.format( (alpha != 255) ? "#%06x%02x" : "#%06x", color.getRGB() & 0xffffff, alpha ); + } + + throw new IllegalArgumentException( "property value type '" + newValue.getClass().getName() + "' not supported in references" ); + } + enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY } private static ValueType[] tempResultValueType = new ValueType[1]; + private static Map, ValueType> javaValueTypes; - static Object parseValue( String key, String value ) { - return parseValue( key, value, null, v -> v, Collections.emptyList() ); + static Object parseValue( String key, String value, Class valueType ) { + return parseValue( key, value, valueType, null, v -> v, Collections.emptyList() ); } - static Object parseValue( String key, String value, ValueType[] resultValueType, + static Object parseValue( String key, String value, Class javaValueType, ValueType[] resultValueType, Function resolver, List addonClassLoaders ) { if( resultValueType == null ) @@ -305,70 +327,106 @@ class UIDefaultsLoader value = value.trim(); - // null, false, true - switch( value ) { - case "null": resultValueType[0] = ValueType.NULL; return null; - case "false": resultValueType[0] = ValueType.BOOLEAN; return false; - case "true": resultValueType[0] = ValueType.BOOLEAN; return true; - } - - // check for function "lazy" - // Syntax: lazy(uiKey) - if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) { - resultValueType[0] = ValueType.LAZY; - String uiKey = value.substring( 5, value.length() - 1 ).trim(); - return (LazyValue) t -> { - return lazyUIManagerGet( uiKey ); - }; + // null + if( value.equals( "null" ) ) { + resultValueType[0] = ValueType.NULL; + return null; } ValueType valueType = ValueType.UNKNOWN; - // check whether value type is specified in the value - if( value.startsWith( "#" ) ) - valueType = ValueType.COLOR; - else if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) { - valueType = ValueType.STRING; - value = value.substring( 1, value.length() - 1 ); - } else if( value.startsWith( TYPE_PREFIX ) ) { - int end = value.indexOf( TYPE_PREFIX_END ); - if( end != -1 ) { - try { - String typeStr = value.substring( TYPE_PREFIX.length(), end ); - valueType = ValueType.valueOf( typeStr.toUpperCase( Locale.ENGLISH ) ); + if( javaValueType != null ) { + if( javaValueTypes == null ) { + // create lazy + javaValueTypes = new HashMap<>(); + javaValueTypes.put( String.class, ValueType.STRING ); + javaValueTypes.put( boolean.class, ValueType.BOOLEAN ); + javaValueTypes.put( Boolean.class, ValueType.BOOLEAN ); + javaValueTypes.put( char.class, ValueType.CHARACTER ); + javaValueTypes.put( Character.class, ValueType.CHARACTER ); + javaValueTypes.put( int.class, ValueType.INTEGER ); + javaValueTypes.put( Integer.class, ValueType.INTEGER ); + javaValueTypes.put( float.class, ValueType.FLOAT ); + javaValueTypes.put( Float.class, ValueType.FLOAT ); + javaValueTypes.put( Border.class, ValueType.BORDER ); + javaValueTypes.put( Icon.class, ValueType.ICON ); + javaValueTypes.put( Insets.class, ValueType.INSETS ); + javaValueTypes.put( Dimension.class, ValueType.DIMENSION ); + javaValueTypes.put( Color.class, ValueType.COLOR ); + } - // remove type from value - value = value.substring( end + TYPE_PREFIX_END.length() ); - } catch( IllegalArgumentException ex ) { - // ignore + // map java value type to parser value type + valueType = javaValueTypes.get( javaValueType ); + if( valueType == null ) + throw new IllegalArgumentException( "unsupported value type '" + javaValueType.getName() + "'" ); + + // remove '"' from strings + if( valueType == ValueType.STRING && value.startsWith( "\"" ) && value.endsWith( "\"" ) ) + value = value.substring( 1, value.length() - 1 ); + } else { + // false, true + switch( value ) { + case "false": resultValueType[0] = ValueType.BOOLEAN; return false; + case "true": resultValueType[0] = ValueType.BOOLEAN; return true; + } + + // check for function "lazy" + // Syntax: lazy(uiKey) + if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) { + resultValueType[0] = ValueType.LAZY; + String uiKey = value.substring( 5, value.length() - 1 ).trim(); + return (LazyValue) t -> { + return lazyUIManagerGet( uiKey ); + }; + } + + // check whether value type is specified in the value + if( value.startsWith( "#" ) ) + valueType = ValueType.COLOR; + else if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) { + valueType = ValueType.STRING; + value = value.substring( 1, value.length() - 1 ); + } else if( value.startsWith( TYPE_PREFIX ) ) { + int end = value.indexOf( TYPE_PREFIX_END ); + if( end != -1 ) { + try { + String typeStr = value.substring( TYPE_PREFIX.length(), end ); + valueType = ValueType.valueOf( typeStr.toUpperCase( Locale.ENGLISH ) ); + + // remove type from value + value = value.substring( end + TYPE_PREFIX_END.length() ); + } catch( IllegalArgumentException ex ) { + // ignore + } } } - } - // determine value type from key - if( valueType == ValueType.UNKNOWN ) { - if( key.endsWith( "UI" ) ) - valueType = ValueType.STRING; - else if( key.endsWith( "Color" ) || - (key.endsWith( "ground" ) && - (key.endsWith( ".background" ) || key.endsWith( "Background" ) || - key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ))) ) - valueType = ValueType.COLOR; - else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) ) - valueType = ValueType.BORDER; - else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) ) - valueType = ValueType.ICON; - else if( key.endsWith( ".margin" ) || key.endsWith( ".padding" ) || - key.endsWith( "Margins" ) || key.endsWith( "Insets" ) ) - valueType = ValueType.INSETS; - else if( key.endsWith( "Size" ) ) - valueType = ValueType.DIMENSION; - else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) ) - valueType = ValueType.INTEGER; - else if( key.endsWith( "Char" ) ) - valueType = ValueType.CHARACTER; - else if( key.endsWith( "grayFilter" ) ) - valueType = ValueType.GRAYFILTER; + // determine value type from key + if( valueType == ValueType.UNKNOWN ) { + if( key.endsWith( "UI" ) ) + valueType = ValueType.STRING; + else if( key.endsWith( "Color" ) || + (key.endsWith( "ground" ) && + (key.endsWith( ".background" ) || key.endsWith( "Background" ) || key.equals( "background" ) || + key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ) || key.equals( "foreground" ))) ) + valueType = ValueType.COLOR; + else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) || key.equals( "border" ) ) + valueType = ValueType.BORDER; + else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) || key.equals( "icon" ) ) + valueType = ValueType.ICON; + else if( key.endsWith( ".margin" ) || key.equals( "margin" ) || + key.endsWith( ".padding" ) || key.equals( "padding" ) || + key.endsWith( "Margins" ) || key.endsWith( "Insets" ) ) + valueType = ValueType.INSETS; + else if( key.endsWith( "Size" ) ) + valueType = ValueType.DIMENSION; + else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) ) + valueType = ValueType.INTEGER; + else if( key.endsWith( "Char" ) ) + valueType = ValueType.CHARACTER; + else if( key.endsWith( "grayFilter" ) ) + valueType = ValueType.GRAYFILTER; + } } resultValueType[0] = valueType; @@ -376,6 +434,7 @@ class UIDefaultsLoader // parse value switch( valueType ) { case STRING: return value; + case BOOLEAN: return parseBoolean( value ); case CHARACTER: return parseCharacter( value ); case INTEGER: return parseInteger( value, true ); case FLOAT: return parseFloat( value, true ); @@ -868,6 +927,14 @@ class UIDefaultsLoader return val; } + private static Boolean parseBoolean( String value ) { + switch( value ) { + case "false": return false; + case "true": return true; + } + throw new IllegalArgumentException( "invalid boolean '" + value + "'" ); + } + private static Character parseCharacter( String value ) { if( value.length() != 1 ) throw new IllegalArgumentException( "invalid character '" + value + "'" ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java index 9675479c..8448f128 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java @@ -39,7 +39,7 @@ public abstract class FlatAbstractIcon { protected final int width; protected final int height; - protected final Color color; + protected Color color; public FlatAbstractIcon( int width, int height, Color color ) { this.width = width; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAscendingSortIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAscendingSortIcon.java index 77ee805a..8456dc0c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAscendingSortIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAscendingSortIcon.java @@ -21,7 +21,11 @@ import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Path2D; +import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.plaf.TableHeaderUI; +import javax.swing.table.JTableHeader; +import com.formdev.flatlaf.ui.FlatTableHeaderUI; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -35,8 +39,8 @@ import com.formdev.flatlaf.ui.FlatUIUtils; public class FlatAscendingSortIcon extends FlatAbstractIcon { - protected final boolean chevron = FlatUIUtils.isChevron( UIManager.getString( "Component.arrowType" ) ); - protected final Color sortIconColor = UIManager.getColor( "Table.sortIconColor" ); + protected boolean chevron = FlatUIUtils.isChevron( UIManager.getString( "Component.arrowType" ) ); + protected Color sortIconColor = UIManager.getColor( "Table.sortIconColor" ); public FlatAscendingSortIcon() { super( 10, 5, null ); @@ -44,7 +48,28 @@ public class FlatAscendingSortIcon @Override protected void paintIcon( Component c, Graphics2D g ) { + boolean chevron = this.chevron; + Color sortIconColor = this.sortIconColor; + + // Because this icons are always shared for all table headers, + // get icon specific style from FlatTableHeaderUI. + JTableHeader tableHeader = (JTableHeader) SwingUtilities.getAncestorOfClass( JTableHeader.class, c ); + if( tableHeader != null ) { + TableHeaderUI ui = tableHeader.getUI(); + if( ui instanceof FlatTableHeaderUI ) { + FlatTableHeaderUI fui = (FlatTableHeaderUI) ui; + if( fui.arrowType != null ) + chevron = FlatUIUtils.isChevron( fui.arrowType ); + if( fui.sortIconColor != null ) + sortIconColor = fui.sortIconColor; + } + } + g.setColor( sortIconColor ); + paintArrow( c, g, chevron ); + } + + protected void paintArrow( Component c, Graphics2D g, boolean chevron ) { if( chevron ) { // chevron arrow Path2D path = FlatUIUtils.createPath( false, 1,4, 5,0, 9,4 ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCapsLockIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCapsLockIcon.java index 64fedf47..5db34209 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCapsLockIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCapsLockIcon.java @@ -16,12 +16,14 @@ package com.formdev.flatlaf.icons; +import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -38,6 +40,17 @@ public class FlatCapsLockIcon super( 16, 16, UIManager.getColor( "PasswordField.capsLockIconColor" ) ); } + /** + * @since 2 + */ + public Object applyStyleProperty( String key, Object value ) { + Object oldValue; + switch( key ) { + case "capsLockIconColor": oldValue = color; color = (Color) value; return oldValue; + default: throw new UnknownStyleException( key ); + } + } + @Override protected void paintIcon( Component c, Graphics2D g ) { /* diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxIcon.java index c74c6976..7249c884 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxIcon.java @@ -23,10 +23,13 @@ import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Path2D; import java.awt.geom.RoundRectangle2D; +import java.util.Map; import javax.swing.AbstractButton; import javax.swing.JComponent; import javax.swing.UIManager; import com.formdev.flatlaf.ui.FlatButtonUI; +import com.formdev.flatlaf.ui.FlatStylingSupport; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -67,39 +70,39 @@ public class FlatCheckBoxIcon extends FlatAbstractIcon { protected final String style = UIManager.getString( "CheckBox.icon.style" ); - public final int focusWidth = getUIInt( "CheckBox.icon.focusWidth", + @Styleable public int focusWidth = getUIInt( "CheckBox.icon.focusWidth", UIManager.getInt( "Component.focusWidth" ), style ); - protected final Color focusColor = FlatUIUtils.getUIColor( "CheckBox.icon.focusColor", + @Styleable protected Color focusColor = FlatUIUtils.getUIColor( "CheckBox.icon.focusColor", UIManager.getColor( "Component.focusColor" ) ); - protected final int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 ); + @Styleable protected int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 ); // enabled - protected final Color borderColor = getUIColor( "CheckBox.icon.borderColor", style ); - protected final Color background = getUIColor( "CheckBox.icon.background", style ); - protected final Color selectedBorderColor = getUIColor( "CheckBox.icon.selectedBorderColor", style ); - protected final Color selectedBackground = getUIColor( "CheckBox.icon.selectedBackground", style ); - protected final Color checkmarkColor = getUIColor( "CheckBox.icon.checkmarkColor", style ); + @Styleable protected Color borderColor = getUIColor( "CheckBox.icon.borderColor", style ); + @Styleable protected Color background = getUIColor( "CheckBox.icon.background", style ); + @Styleable protected Color selectedBorderColor = getUIColor( "CheckBox.icon.selectedBorderColor", style ); + @Styleable protected Color selectedBackground = getUIColor( "CheckBox.icon.selectedBackground", style ); + @Styleable protected Color checkmarkColor = getUIColor( "CheckBox.icon.checkmarkColor", style ); // disabled - protected final Color disabledBorderColor = getUIColor( "CheckBox.icon.disabledBorderColor", style ); - protected final Color disabledBackground = getUIColor( "CheckBox.icon.disabledBackground", style ); - protected final Color disabledCheckmarkColor = getUIColor( "CheckBox.icon.disabledCheckmarkColor", style ); + @Styleable protected Color disabledBorderColor = getUIColor( "CheckBox.icon.disabledBorderColor", style ); + @Styleable protected Color disabledBackground = getUIColor( "CheckBox.icon.disabledBackground", style ); + @Styleable protected Color disabledCheckmarkColor = getUIColor( "CheckBox.icon.disabledCheckmarkColor", style ); // focused - protected final Color focusedBorderColor = getUIColor( "CheckBox.icon.focusedBorderColor", style ); - protected final Color focusedBackground = getUIColor( "CheckBox.icon.focusedBackground", style ); - protected final Color selectedFocusedBorderColor = getUIColor( "CheckBox.icon.selectedFocusedBorderColor", style ); - protected final Color selectedFocusedBackground = getUIColor( "CheckBox.icon.selectedFocusedBackground", style ); - protected final Color selectedFocusedCheckmarkColor = getUIColor( "CheckBox.icon.selectedFocusedCheckmarkColor", style ); + @Styleable protected Color focusedBorderColor = getUIColor( "CheckBox.icon.focusedBorderColor", style ); + @Styleable protected Color focusedBackground = getUIColor( "CheckBox.icon.focusedBackground", style ); + @Styleable protected Color selectedFocusedBorderColor = getUIColor( "CheckBox.icon.selectedFocusedBorderColor", style ); + @Styleable protected Color selectedFocusedBackground = getUIColor( "CheckBox.icon.selectedFocusedBackground", style ); + @Styleable protected Color selectedFocusedCheckmarkColor = getUIColor( "CheckBox.icon.selectedFocusedCheckmarkColor", style ); // hover - protected final Color hoverBorderColor = getUIColor( "CheckBox.icon.hoverBorderColor", style ); - protected final Color hoverBackground = getUIColor( "CheckBox.icon.hoverBackground", style ); - protected final Color selectedHoverBackground = getUIColor( "CheckBox.icon.selectedHoverBackground", style ); + @Styleable protected Color hoverBorderColor = getUIColor( "CheckBox.icon.hoverBorderColor", style ); + @Styleable protected Color hoverBackground = getUIColor( "CheckBox.icon.hoverBackground", style ); + @Styleable protected Color selectedHoverBackground = getUIColor( "CheckBox.icon.selectedHoverBackground", style ); // pressed - protected final Color pressedBackground = getUIColor( "CheckBox.icon.pressedBackground", style ); - protected final Color selectedPressedBackground = getUIColor( "CheckBox.icon.selectedPressedBackground", style ); + @Styleable protected Color pressedBackground = getUIColor( "CheckBox.icon.pressedBackground", style ); + @Styleable protected Color selectedPressedBackground = getUIColor( "CheckBox.icon.selectedPressedBackground", style ); protected static Color getUIColor( String key, String style ) { if( style != null ) { @@ -129,6 +132,20 @@ public class FlatCheckBoxIcon super( ICON_SIZE, ICON_SIZE, null ); } + /** + * @since 2 + */ + public Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @Override protected void paintIcon( Component c, Graphics2D g ) { boolean indeterminate = isIndeterminate( c ); 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..7e6e0279 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 @@ -21,9 +21,12 @@ import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Path2D; +import java.util.Map; import javax.swing.AbstractButton; import javax.swing.JMenuItem; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStylingSupport; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; /** * Icon for {@link javax.swing.JCheckBoxMenuItem}. @@ -38,14 +41,28 @@ 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 2 + */ + public Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @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/FlatClearIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatClearIcon.java index b756b509..f0e7590c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatClearIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatClearIcon.java @@ -22,9 +22,12 @@ import java.awt.Graphics2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Path2D; +import java.util.Map; import javax.swing.AbstractButton; import javax.swing.ButtonModel; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStylingSupport; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -40,14 +43,28 @@ import com.formdev.flatlaf.ui.FlatUIUtils; public class FlatClearIcon extends FlatAbstractIcon { - protected Color clearIconColor = UIManager.getColor( "SearchField.clearIconColor" ); - protected Color clearIconHoverColor = UIManager.getColor( "SearchField.clearIconHoverColor" ); - protected Color clearIconPressedColor = UIManager.getColor( "SearchField.clearIconPressedColor" ); + @Styleable protected Color clearIconColor = UIManager.getColor( "SearchField.clearIconColor" ); + @Styleable protected Color clearIconHoverColor = UIManager.getColor( "SearchField.clearIconHoverColor" ); + @Styleable protected Color clearIconPressedColor = UIManager.getColor( "SearchField.clearIconPressedColor" ); public FlatClearIcon() { super( 16, 16, null ); } + /** + * @since 2 + */ + public Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @Override protected void paintIcon( Component c, Graphics2D g ) { if( c instanceof AbstractButton ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatDescendingSortIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatDescendingSortIcon.java index c43f751d..0ae9ac4c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatDescendingSortIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatDescendingSortIcon.java @@ -17,11 +17,9 @@ 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.Path2D; -import javax.swing.UIManager; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -33,18 +31,14 @@ import com.formdev.flatlaf.ui.FlatUIUtils; * @author Karl Tauber */ public class FlatDescendingSortIcon - extends FlatAbstractIcon + extends FlatAscendingSortIcon { - protected final boolean chevron = FlatUIUtils.isChevron( UIManager.getString( "Component.arrowType" ) ); - protected final Color sortIconColor = UIManager.getColor( "Table.sortIconColor" ); - public FlatDescendingSortIcon() { - super( 10, 5, null ); + super(); } @Override - protected void paintIcon( Component c, Graphics2D g ) { - g.setColor( sortIconColor ); + protected void paintArrow( Component c, Graphics2D g, boolean chevron ) { if( chevron ) { // chevron arrow Path2D path = FlatUIUtils.createPath( false, 1,0, 5,4, 9,0 ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatHelpButtonIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatHelpButtonIcon.java index 9283b49d..f4f62d1c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatHelpButtonIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatHelpButtonIcon.java @@ -22,8 +22,11 @@ import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; +import java.util.Map; import javax.swing.UIManager; import com.formdev.flatlaf.ui.FlatButtonUI; +import com.formdev.flatlaf.ui.FlatStylingSupport; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -50,29 +53,41 @@ import com.formdev.flatlaf.ui.FlatUIUtils; public class FlatHelpButtonIcon extends FlatAbstractIcon { - protected final int focusWidth = UIManager.getInt( "Component.focusWidth" ); - protected final Color focusColor = UIManager.getColor( "Component.focusColor" ); - protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "HelpButton.innerFocusWidth", FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ) ); - protected final int borderWidth = FlatUIUtils.getUIInt( "HelpButton.borderWidth", 1 ); + @Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" ); + @Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" ); + @Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "HelpButton.innerFocusWidth", FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ) ); + @Styleable protected int borderWidth = FlatUIUtils.getUIInt( "HelpButton.borderWidth", 1 ); - protected final Color borderColor = UIManager.getColor( "HelpButton.borderColor" ); - protected final Color disabledBorderColor = UIManager.getColor( "HelpButton.disabledBorderColor" ); - protected final Color focusedBorderColor = UIManager.getColor( "HelpButton.focusedBorderColor" ); - protected final Color hoverBorderColor = UIManager.getColor( "HelpButton.hoverBorderColor" ); - protected final Color background = UIManager.getColor( "HelpButton.background" ); - protected final Color disabledBackground = UIManager.getColor( "HelpButton.disabledBackground" ); - protected final Color focusedBackground = UIManager.getColor( "HelpButton.focusedBackground" ); - protected final Color hoverBackground = UIManager.getColor( "HelpButton.hoverBackground" ); - protected final Color pressedBackground = UIManager.getColor( "HelpButton.pressedBackground" ); - protected final Color questionMarkColor = UIManager.getColor( "HelpButton.questionMarkColor" ); - protected final Color disabledQuestionMarkColor = UIManager.getColor( "HelpButton.disabledQuestionMarkColor" ); - - protected final int iconSize = 22 + (focusWidth * 2); + @Styleable protected Color borderColor = UIManager.getColor( "HelpButton.borderColor" ); + @Styleable protected Color disabledBorderColor = UIManager.getColor( "HelpButton.disabledBorderColor" ); + @Styleable protected Color focusedBorderColor = UIManager.getColor( "HelpButton.focusedBorderColor" ); + @Styleable protected Color hoverBorderColor = UIManager.getColor( "HelpButton.hoverBorderColor" ); + @Styleable protected Color background = UIManager.getColor( "HelpButton.background" ); + @Styleable protected Color disabledBackground = UIManager.getColor( "HelpButton.disabledBackground" ); + @Styleable protected Color focusedBackground = UIManager.getColor( "HelpButton.focusedBackground" ); + @Styleable protected Color hoverBackground = UIManager.getColor( "HelpButton.hoverBackground" ); + @Styleable protected Color pressedBackground = UIManager.getColor( "HelpButton.pressedBackground" ); + @Styleable protected Color questionMarkColor = UIManager.getColor( "HelpButton.questionMarkColor" ); + @Styleable protected Color disabledQuestionMarkColor = UIManager.getColor( "HelpButton.disabledQuestionMarkColor" ); public FlatHelpButtonIcon() { super( 0, 0, null ); } + /** + * @since 2 + */ + public Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @Override protected void paintIcon( Component c, Graphics2D g2 ) { /* @@ -89,7 +104,7 @@ public class FlatHelpButtonIcon boolean focused = FlatUIUtils.isPermanentFocusOwner( c ); float xy = 0.5f; - float wh = iconSize - 1; + float wh = iconSize() - 1; // paint outer focus border if( focused && FlatButtonUI.isFocusPainted( c ) ) { @@ -151,11 +166,15 @@ public class FlatHelpButtonIcon @Override public int getIconWidth() { - return scale( iconSize ); + return scale( iconSize() ); } @Override public int getIconHeight() { - return scale( iconSize ); + return scale( iconSize() ); + } + + private int iconSize() { + return 22 + (focusWidth * 2); } } 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..b346820e 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 @@ -21,9 +21,12 @@ import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Path2D; +import java.util.Map; import javax.swing.JMenu; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStylingSupport; import com.formdev.flatlaf.ui.FlatUIUtils; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; /** * "arrow" icon for {@link javax.swing.JMenu}. @@ -39,22 +42,36 @@ 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 2 + */ + public Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @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/icons/FlatRadioButtonIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatRadioButtonIcon.java index 06095b0f..381d2910 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatRadioButtonIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatRadioButtonIcon.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.icons; import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Ellipse2D; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; /** * Icon for {@link javax.swing.JRadioButton}. @@ -34,7 +35,7 @@ import java.awt.geom.Ellipse2D; public class FlatRadioButtonIcon extends FlatCheckBoxIcon { - protected final int centerDiameter = getUIInt( "RadioButton.icon.centerDiameter", 8, style ); + @Styleable protected int centerDiameter = getUIInt( "RadioButton.icon.centerDiameter", 8, style ); @Override protected void paintFocusBorder( Component c, Graphics2D g ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatSearchIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatSearchIcon.java index 1bc726a6..0549b813 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatSearchIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatSearchIcon.java @@ -21,8 +21,11 @@ import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; +import java.util.Map; import javax.swing.UIManager; import com.formdev.flatlaf.ui.FlatButtonUI; +import com.formdev.flatlaf.ui.FlatStylingSupport; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -38,14 +41,28 @@ import com.formdev.flatlaf.ui.FlatUIUtils; public class FlatSearchIcon extends FlatAbstractIcon { - protected Color searchIconColor = UIManager.getColor( "SearchField.searchIconColor" ); - protected Color searchIconHoverColor = UIManager.getColor( "SearchField.searchIconHoverColor" ); - protected Color searchIconPressedColor = UIManager.getColor( "SearchField.searchIconPressedColor" ); + @Styleable protected Color searchIconColor = UIManager.getColor( "SearchField.searchIconColor" ); + @Styleable protected Color searchIconHoverColor = UIManager.getColor( "SearchField.searchIconHoverColor" ); + @Styleable protected Color searchIconPressedColor = UIManager.getColor( "SearchField.searchIconPressedColor" ); public FlatSearchIcon() { super( 16, 16, null ); } + /** + * @since 2 + */ + public Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @Override protected void paintIcon( Component c, Graphics2D g ) { /* diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTabbedPaneCloseIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTabbedPaneCloseIcon.java index 39e90587..f93d71d3 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTabbedPaneCloseIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTabbedPaneCloseIcon.java @@ -23,8 +23,11 @@ import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.geom.Line2D; import java.awt.geom.Path2D; +import java.util.Map; import javax.swing.UIManager; import com.formdev.flatlaf.ui.FlatButtonUI; +import com.formdev.flatlaf.ui.FlatStylingSupport; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -47,39 +50,53 @@ import com.formdev.flatlaf.ui.FlatUIUtils; public class FlatTabbedPaneCloseIcon extends FlatAbstractIcon { - protected final Dimension size = UIManager.getDimension( "TabbedPane.closeSize" ); - protected final int arc = UIManager.getInt( "TabbedPane.closeArc" ); - protected final float crossPlainSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossPlainSize", 7.5f ); - protected final float crossFilledSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossFilledSize", crossPlainSize ); - protected final float closeCrossLineWidth = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossLineWidth", 1f ); - protected final Color background = UIManager.getColor( "TabbedPane.closeBackground" ); - protected final Color foreground = UIManager.getColor( "TabbedPane.closeForeground" ); - protected final Color hoverBackground = UIManager.getColor( "TabbedPane.closeHoverBackground" ); - protected final Color hoverForeground = UIManager.getColor( "TabbedPane.closeHoverForeground" ); - protected final Color pressedBackground = UIManager.getColor( "TabbedPane.closePressedBackground" ); - protected final Color pressedForeground = UIManager.getColor( "TabbedPane.closePressedForeground" ); + @Styleable protected Dimension closeSize = UIManager.getDimension( "TabbedPane.closeSize" ); + @Styleable protected int closeArc = UIManager.getInt( "TabbedPane.closeArc" ); + @Styleable protected float closeCrossPlainSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossPlainSize", 7.5f ); + @Styleable protected float closeCrossFilledSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossFilledSize", closeCrossPlainSize ); + @Styleable protected float closeCrossLineWidth = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossLineWidth", 1f ); + @Styleable protected Color closeBackground = UIManager.getColor( "TabbedPane.closeBackground" ); + @Styleable protected Color closeForeground = UIManager.getColor( "TabbedPane.closeForeground" ); + @Styleable protected Color closeHoverBackground = UIManager.getColor( "TabbedPane.closeHoverBackground" ); + @Styleable protected Color closeHoverForeground = UIManager.getColor( "TabbedPane.closeHoverForeground" ); + @Styleable protected Color closePressedBackground = UIManager.getColor( "TabbedPane.closePressedBackground" ); + @Styleable protected Color closePressedForeground = UIManager.getColor( "TabbedPane.closePressedForeground" ); public FlatTabbedPaneCloseIcon() { super( 16, 16, null ); } + /** + * @since 2 + */ + public Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @Override protected void paintIcon( Component c, Graphics2D g ) { // paint background - Color bg = FlatButtonUI.buttonStateColor( c, background, null, null, hoverBackground, pressedBackground ); + Color bg = FlatButtonUI.buttonStateColor( c, closeBackground, null, null, closeHoverBackground, closePressedBackground ); if( bg != null ) { g.setColor( FlatUIUtils.deriveColor( bg, c.getBackground() ) ); - g.fillRoundRect( (width - size.width) / 2, (height - size.height) / 2, - size.width, size.height, arc, arc ); + g.fillRoundRect( (width - closeSize.width) / 2, (height - closeSize.height) / 2, + closeSize.width, closeSize.height, closeArc, closeArc ); } // set cross color - Color fg = FlatButtonUI.buttonStateColor( c, foreground, null, null, hoverForeground, pressedForeground ); + Color fg = FlatButtonUI.buttonStateColor( c, closeForeground, null, null, closeHoverForeground, closePressedForeground ); g.setColor( FlatUIUtils.deriveColor( fg, c.getForeground() ) ); float mx = width / 2; float my = height / 2; - float r = ((bg != null) ? crossFilledSize : crossPlainSize) / 2; + float r = ((bg != null) ? closeCrossFilledSize : closeCrossPlainSize) / 2; // paint cross Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeClosedIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeClosedIcon.java index df160011..c149bb9c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeClosedIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeClosedIcon.java @@ -37,6 +37,8 @@ public class FlatTreeClosedIcon @Override protected void paintIcon( Component c, Graphics2D g ) { + FlatTreeCollapsedIcon.setStyleColorFromTreeUI( c, g, ui -> ui.iconClosedColor ); + /* diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeCollapsedIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeCollapsedIcon.java index 481b2e42..d245f79c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeCollapsedIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeCollapsedIcon.java @@ -19,7 +19,12 @@ package com.formdev.flatlaf.icons; import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; +import java.util.function.Function; +import javax.swing.JTree; +import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.plaf.TreeUI; +import com.formdev.flatlaf.ui.FlatTreeUI; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -46,8 +51,12 @@ public class FlatTreeCollapsedIcon @Override protected void paintIcon( Component c, Graphics2D g ) { + setStyleColorFromTreeUI( c, g ); rotate( c, g ); + String arrowType = getStyleFromTreeUI( c, ui -> ui.iconArrowType ); + boolean chevron = (arrowType != null) ? FlatUIUtils.isChevron( arrowType ) : this.chevron; + if( chevron ) { // chevron arrow g.fill( FlatUIUtils.createPath( 3,1, 3,2.5, 6,5.5, 3,8.5, 3,10, 4.5,10, 9,5.5, 4.5,1 ) ); @@ -57,8 +66,34 @@ public class FlatTreeCollapsedIcon } } + void setStyleColorFromTreeUI( Component c, Graphics2D g ) { + setStyleColorFromTreeUI( c, g, ui -> ui.iconCollapsedColor ); + } + void rotate( Component c, Graphics2D g ) { if( !c.getComponentOrientation().isLeftToRight() ) g.rotate( Math.toRadians( 180 ), width / 2., height / 2. ); } + + /** + * Because this icons are always shared for all trees, + * get icon specific style from FlatTreeUI. + */ + static T getStyleFromTreeUI( Component c, Function f ) { + JTree tree = (c instanceof JTree) + ? (JTree) c + : (JTree) SwingUtilities.getAncestorOfClass( JTree.class, c ); + if( tree != null ) { + TreeUI ui = tree.getUI(); + if( ui instanceof FlatTreeUI ) + return f.apply( (FlatTreeUI) ui ); + } + return null; + } + + static void setStyleColorFromTreeUI( Component c, Graphics2D g, Function f ) { + Color color = getStyleFromTreeUI( c, f ); + if( color != null ) + g.setColor( color ); + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeExpandedIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeExpandedIcon.java index 113139e9..3e99f3ef 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeExpandedIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeExpandedIcon.java @@ -34,6 +34,11 @@ public class FlatTreeExpandedIcon super( UIManager.getColor( "Tree.icon.expandedColor" ) ); } + @Override + void setStyleColorFromTreeUI( Component c, Graphics2D g ) { + setStyleColorFromTreeUI( c, g, ui -> ui.iconExpandedColor ); + } + @Override void rotate( Component c, Graphics2D g ) { g.rotate( Math.toRadians( 90 ), width / 2., height / 2. ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeLeafIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeLeafIcon.java index 6ec8900d..b85ccbb1 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeLeafIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeLeafIcon.java @@ -37,6 +37,8 @@ public class FlatTreeLeafIcon @Override protected void paintIcon( Component c, Graphics2D g ) { + FlatTreeCollapsedIcon.setStyleColorFromTreeUI( c, g, ui -> ui.iconLeafColor ); + /* diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeOpenIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeOpenIcon.java index f8754d9e..e2ca5213 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeOpenIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTreeOpenIcon.java @@ -37,6 +37,8 @@ public class FlatTreeOpenIcon @Override protected void paintIcon( Component c, Graphics2D g ) { + FlatTreeCollapsedIcon.setStyleColorFromTreeUI( c, g, ui -> ui.iconOpenColor ); + /* diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatArrowButton.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatArrowButton.java index 53088a60..6fc774fd 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatArrowButton.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatArrowButton.java @@ -39,13 +39,13 @@ public class FlatArrowButton { public static final int DEFAULT_ARROW_WIDTH = 8; - protected final boolean chevron; - protected final Color foreground; - protected final Color disabledForeground; - protected final Color hoverForeground; - protected final Color hoverBackground; - protected final Color pressedForeground; - protected final Color pressedBackground; + protected boolean chevron; + protected Color foreground; + protected Color disabledForeground; + protected Color hoverForeground; + protected Color hoverBackground; + protected Color pressedForeground; + protected Color pressedBackground; private int arrowWidth = DEFAULT_ARROW_WIDTH; private float xOffset = 0; @@ -58,14 +58,8 @@ public class FlatArrowButton Color hoverForeground, Color hoverBackground, Color pressedForeground, Color pressedBackground ) { super( direction, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE ); - - this.chevron = FlatUIUtils.isChevron( type ); - this.foreground = foreground; - this.disabledForeground = disabledForeground; - this.hoverForeground = hoverForeground; - this.hoverBackground = hoverBackground; - this.pressedForeground = pressedForeground; - this.pressedBackground = pressedBackground; + updateStyle( type, foreground, disabledForeground, hoverForeground, hoverBackground, + pressedForeground, pressedBackground ); setOpaque( false ); setBorder( null ); @@ -101,6 +95,21 @@ public class FlatArrowButton } } + /** + * @since 2 + */ + public void updateStyle( String type, Color foreground, Color disabledForeground, + Color hoverForeground, Color hoverBackground, Color pressedForeground, Color pressedBackground ) + { + this.chevron = FlatUIUtils.isChevron( type ); + this.foreground = foreground; + this.disabledForeground = disabledForeground; + this.hoverForeground = hoverForeground; + this.hoverBackground = hoverBackground; + this.pressedForeground = pressedForeground; + this.pressedBackground = pressedBackground; + } + public int getArrowWidth() { return arrowWidth; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java index 276a591a..dccdffb5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java @@ -23,6 +23,7 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Paint; +import java.util.Map; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JScrollPane; @@ -31,6 +32,8 @@ import javax.swing.JViewport; import javax.swing.UIManager; import javax.swing.plaf.basic.BasicBorders; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder; import com.formdev.flatlaf.util.DerivedColor; /** @@ -61,20 +64,37 @@ import com.formdev.flatlaf.util.DerivedColor; */ public class FlatBorder extends BasicBorders.MarginBorder + implements StyleableBorder { - protected final int focusWidth = UIManager.getInt( "Component.focusWidth" ); - protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ); - protected final float innerOutlineWidth = FlatUIUtils.getUIFloat( "Component.innerOutlineWidth", 0 ); - protected final Color focusColor = UIManager.getColor( "Component.focusColor" ); - protected final Color borderColor = UIManager.getColor( "Component.borderColor" ); - protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" ); - protected final Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" ); + @Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" ); + @Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ); + @Styleable protected float innerOutlineWidth = FlatUIUtils.getUIFloat( "Component.innerOutlineWidth", 0 ); + @Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" ); + @Styleable protected Color borderColor = UIManager.getColor( "Component.borderColor" ); + @Styleable protected Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" ); + @Styleable protected Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" ); - protected final Color errorBorderColor = UIManager.getColor( "Component.error.borderColor" ); - protected final Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" ); - protected final Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" ); - protected final Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" ); - protected final Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" ); + @Styleable(dot=true) protected Color errorBorderColor = UIManager.getColor( "Component.error.borderColor" ); + @Styleable(dot=true) protected Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" ); + @Styleable(dot=true) protected Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" ); + @Styleable(dot=true) protected Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" ); + @Styleable(dot=true) protected Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" ); + + /** + * @since 2 + */ + @Override + public Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java index d813c4b6..d9ac552a 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java @@ -26,6 +26,7 @@ import java.awt.Paint; import javax.swing.AbstractButton; import javax.swing.UIManager; import javax.swing.plaf.UIResource; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.util.UIScale; /** @@ -40,16 +41,16 @@ import com.formdev.flatlaf.util.UIScale; * @uiDefault Button.default.borderColor Color * @uiDefault Button.default.startBorderColor Color optional; if set, a gradient paint is used and Button.default.borderColor is ignored * @uiDefault Button.default.endBorderColor Color optional; if set, a gradient paint is used - * @uiDefault Button.default.hoverBorderColor Color optional * @uiDefault Button.default.focusedBorderColor Color * @uiDefault Button.default.focusColor Color + * @uiDefault Button.default.hoverBorderColor Color optional * @uiDefault Button.toolbar.focusColor Color optional; defaults to Component.focusColor * @uiDefault Button.borderWidth int * @uiDefault Button.default.borderWidth int * @uiDefault Button.innerFocusWidth int or float optional; defaults to Component.innerFocusWidth * @uiDefault Button.toolbar.margin Insets * @uiDefault Button.toolbar.spacingInsets Insets - * @uiDefault Button.toolbar.focusWidth int or float optional; default is 1 + * @uiDefault Button.toolbar.focusWidth int or float optional; default is 1.5 * @uiDefault Button.arc int * * @author Karl Tauber @@ -57,26 +58,31 @@ import com.formdev.flatlaf.util.UIScale; public class FlatButtonBorder extends FlatBorder { - protected final Color borderColor = FlatUIUtils.getUIColor( "Button.startBorderColor", "Button.borderColor" ); - protected final Color endBorderColor = UIManager.getColor( "Button.endBorderColor" ); - protected final Color disabledBorderColor = UIManager.getColor( "Button.disabledBorderColor" ); - protected final Color focusedBorderColor = UIManager.getColor( "Button.focusedBorderColor" ); - protected final Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" ); - protected final Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" ); - protected final Color defaultEndBorderColor = UIManager.getColor( "Button.default.endBorderColor" ); - protected final Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" ); - protected final Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" ); - protected final Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" ); + @Styleable protected Color borderColor = FlatUIUtils.getUIColor( "Button.startBorderColor", "Button.borderColor" ); + protected Color endBorderColor = UIManager.getColor( "Button.endBorderColor" ); + @Styleable protected Color disabledBorderColor = UIManager.getColor( "Button.disabledBorderColor" ); + @Styleable protected Color focusedBorderColor = UIManager.getColor( "Button.focusedBorderColor" ); + @Styleable protected Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" ); + + @Styleable(dot=true) protected Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" ); + protected Color defaultEndBorderColor = UIManager.getColor( "Button.default.endBorderColor" ); + @Styleable(dot=true) protected Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" ); + @Styleable(dot=true) protected Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" ); + @Styleable(dot=true) protected Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" ); /** @since 1.4 */ - protected final Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" ); - protected final int borderWidth = UIManager.getInt( "Button.borderWidth" ); - protected final int defaultBorderWidth = UIManager.getInt( "Button.default.borderWidth" ); - protected final float buttonInnerFocusWidth = FlatUIUtils.getUIFloat( "Button.innerFocusWidth", innerFocusWidth ); - protected final Insets toolbarMargin = UIManager.getInsets( "Button.toolbar.margin" ); - protected final Insets toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" ); + @Styleable(dot=true) protected Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" ); + + @Styleable protected int borderWidth = UIManager.getInt( "Button.borderWidth" ); + @Styleable(dot=true) protected int defaultBorderWidth = UIManager.getInt( "Button.default.borderWidth" ); + @Styleable(dot=true) protected Insets toolbarMargin = UIManager.getInsets( "Button.toolbar.margin" ); + @Styleable(dot=true) protected Insets toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" ); /** @since 1.4 */ - protected final float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f ); - protected final int arc = UIManager.getInt( "Button.arc" ); + @Styleable(dot=true) protected float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f ); + @Styleable protected int arc = UIManager.getInt( "Button.arc" ); + + public FlatButtonBorder() { + innerFocusWidth = FlatUIUtils.getUIFloat( "Button.innerFocusWidth", innerFocusWidth ); + } @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { @@ -174,11 +180,6 @@ public class FlatButtonBorder return FlatToggleButtonUI.isTabButton( c ) ? 0 : super.getFocusWidth( c ); } - @Override - protected float getInnerFocusWidth( Component c ) { - return buttonInnerFocusWidth; - } - @Override protected int getBorderWidth( Component c ) { return FlatButtonUI.isDefaultButton( c ) ? defaultBorderWidth : borderWidth; 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 0afd6eb1..a171cfe2 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 @@ -30,7 +30,9 @@ import java.awt.Insets; import java.awt.Rectangle; import java.awt.geom.RoundRectangle2D; import java.beans.PropertyChangeEvent; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.AbstractButton; import javax.swing.ButtonModel; import javax.swing.Icon; @@ -45,6 +47,10 @@ import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicButtonListener; import javax.swing.plaf.basic.BasicButtonUI; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.icons.FlatHelpButtonIcon; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.util.UIScale; /** @@ -94,8 +100,9 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatButtonUI extends BasicButtonUI + implements StyleableUI { - protected int minimumWidth; + @Styleable protected int minimumWidth; protected int iconTextGap; protected Color background; @@ -103,39 +110,57 @@ public class FlatButtonUI protected Color startBackground; protected Color endBackground; - protected Color focusedBackground; - protected Color hoverBackground; - protected Color pressedBackground; - protected Color selectedBackground; - protected Color selectedForeground; - protected Color disabledBackground; - protected Color disabledText; - protected Color disabledSelectedBackground; + @Styleable protected Color focusedBackground; + @Styleable protected Color hoverBackground; + @Styleable protected Color pressedBackground; + @Styleable protected Color selectedBackground; + @Styleable protected Color selectedForeground; + @Styleable protected Color disabledBackground; + @Styleable protected Color disabledText; + @Styleable protected Color disabledSelectedBackground; - protected Color defaultBackground; + @Styleable(dot=true) protected Color defaultBackground; protected Color defaultEndBackground; - protected Color defaultForeground; - protected Color defaultFocusedBackground; - protected Color defaultHoverBackground; - protected Color defaultPressedBackground; - protected boolean defaultBoldText; + @Styleable(dot=true) protected Color defaultForeground; + @Styleable(dot=true) protected Color defaultFocusedBackground; + @Styleable(dot=true) protected Color defaultHoverBackground; + @Styleable(dot=true) protected Color defaultPressedBackground; + @Styleable(dot=true) protected boolean defaultBoldText; - protected int shadowWidth; - protected Color shadowColor; - protected Color defaultShadowColor; + @Styleable protected boolean paintShadow; + @Styleable protected int shadowWidth; + @Styleable protected Color shadowColor; + @Styleable(dot=true) protected Color defaultShadowColor; - protected Insets toolbarSpacingInsets; - protected Color toolbarHoverBackground; - protected Color toolbarPressedBackground; - protected Color toolbarSelectedBackground; + @Styleable(dot=true) protected Insets toolbarSpacingInsets; + @Styleable(dot=true) protected Color toolbarHoverBackground; + @Styleable(dot=true) protected Color toolbarPressedBackground; + @Styleable(dot=true) protected Color toolbarSelectedBackground; private Icon helpButtonIcon; private Insets defaultMargin; + private final boolean shared; + private boolean helpButtonIconShared = true; private boolean defaults_initialized = false; + private Map oldStyleValues; + private AtomicBoolean borderShared; public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.createSharedUI( FlatButtonUI.class, FlatButtonUI::new ); + return FlatUIUtils.canUseSharedUI( c ) + ? FlatUIUtils.createSharedUI( FlatButtonUI.class, () -> new FlatButtonUI( true ) ) + : new FlatButtonUI( false ); + } + + protected FlatButtonUI( boolean shared ) { + this.shared = shared; + } + + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( (AbstractButton) c, FlatStylingSupport.getStyle( c ) ); } @Override @@ -162,16 +187,6 @@ public class FlatButtonUI disabledText = UIManager.getColor( prefix + "disabledText" ); disabledSelectedBackground = UIManager.getColor( prefix + "disabledSelectedBackground" ); - if( UIManager.getBoolean( "Button.paintShadow" ) ) { - shadowWidth = FlatUIUtils.getUIInt( "Button.shadowWidth", 2 ); - shadowColor = UIManager.getColor( "Button.shadowColor" ); - defaultShadowColor = UIManager.getColor( "Button.default.shadowColor" ); - } else { - shadowWidth = 0; - shadowColor = null; - defaultShadowColor = null; - } - defaultBackground = FlatUIUtils.getUIColor( "Button.default.startBackground", "Button.default.background" ); defaultEndBackground = UIManager.getColor( "Button.default.endBackground" ); defaultForeground = UIManager.getColor( "Button.default.foreground" ); @@ -180,6 +195,11 @@ public class FlatButtonUI defaultPressedBackground = UIManager.getColor( "Button.default.pressedBackground" ); defaultBoldText = UIManager.getBoolean( "Button.default.boldText" ); + paintShadow = UIManager.getBoolean( "Button.paintShadow" ); + shadowWidth = FlatUIUtils.getUIInt( "Button.shadowWidth", 2 ); + shadowColor = UIManager.getColor( "Button.shadowColor" ); + defaultShadowColor = UIManager.getColor( "Button.default.shadowColor" ); + toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" ); toolbarHoverBackground = UIManager.getColor( prefix + "toolbar.hoverBackground" ); toolbarPressedBackground = UIManager.getColor( prefix + "toolbar.pressedBackground" ); @@ -188,6 +208,7 @@ public class FlatButtonUI helpButtonIcon = UIManager.getIcon( "HelpButton.icon" ); defaultMargin = UIManager.getInsets( prefix + "margin" ); + helpButtonIconShared = true; defaults_initialized = true; } @@ -207,6 +228,9 @@ public class FlatButtonUI protected void uninstallDefaults( AbstractButton b ) { super.uninstallDefaults( b ); + oldStyleValues = null; + borderShared = null; + MigLayoutVisualPadding.uninstall( b ); defaults_initialized = false; } @@ -228,9 +252,62 @@ public class FlatButtonUI b.revalidate(); b.repaint(); break; + + case STYLE: + Object style = e.getNewValue(); + if( style != null && shared ) { + // unshare component UI if necessary + // updateUI() invokes applyStyle() from installUI() + b.updateUI(); + } else + applyStyle( b, style ); + b.revalidate(); + b.repaint(); + break; } } + /** + * @since 2 + */ + protected void applyStyle( AbstractButton b, Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, + (key, value) -> applyStyleProperty( b, key, value ) ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( AbstractButton b, String key, Object value ) { + if( key.startsWith( "help." ) ) { + if( !(helpButtonIcon instanceof FlatHelpButtonIcon) ) + return new UnknownStyleException( key ); + + if( helpButtonIconShared ) { + helpButtonIcon = FlatStylingSupport.cloneIcon( helpButtonIcon ); + helpButtonIconShared = false; + } + + key = key.substring( "help.".length() ); + return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value ); + } + + if( borderShared == null ) + borderShared = new AtomicBoolean( true ); + return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, b, borderShared ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + Map> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this, c.getBorder() ); + if( helpButtonIcon instanceof FlatHelpButtonIcon ) + FlatStylingSupport.putAllPrefixKey( infos, "help.", ((FlatHelpButtonIcon)helpButtonIcon).getStyleableInfos() ); + return infos; + } + static boolean isContentAreaFilled( Component c ) { return !(c instanceof AbstractButton) || ((AbstractButton)c).isContentAreaFilled(); } @@ -339,7 +416,8 @@ public class FlatButtonUI // paint shadow Color shadowColor = def ? defaultShadowColor : this.shadowColor; - if( shadowColor != null && shadowWidth > 0 && focusWidth > 0 && c.isEnabled() && + if( paintShadow && + shadowColor != null && shadowWidth > 0 && focusWidth > 0 && c.isEnabled() && !isToolBarButton && !isBorderlessButton( c ) && !(isFocusPainted( c ) && FlatUIUtils.isPermanentFocusOwner( c )) ) { 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..b899d7b3 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,15 @@ 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.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JCheckBoxMenuItem}. @@ -54,13 +58,22 @@ import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI; */ public class FlatCheckBoxMenuItemUI extends BasicCheckBoxMenuItemUI + implements StyleableUI { 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( FlatStylingSupport.getStyle( menuItem ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -75,12 +88,46 @@ public class FlatCheckBoxMenuItemUI super.uninstallDefaults(); renderer = null; + oldStyleValues = null; } protected FlatMenuItemRenderer createRenderer() { return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); } + @Override + protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { + return FlatStylingSupport.createPropertyChangeListener( c, this::applyStyle, super.createPropertyChangeListener( c ) ); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + return renderer.applyStyleProperty( key, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + return FlatMenuItemUI.applyStyleProperty( this, menuItem, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatMenuItemUI.getStyleableInfos( renderer ); + } + @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/FlatCheckBoxUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxUI.java index e545fa66..5d6f5526 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxUI.java @@ -43,7 +43,16 @@ public class FlatCheckBoxUI extends FlatRadioButtonUI { public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.createSharedUI( FlatCheckBoxUI.class, FlatCheckBoxUI::new ); + return FlatUIUtils.canUseSharedUI( c ) + ? FlatUIUtils.createSharedUI( FlatCheckBoxUI.class, () -> new FlatCheckBoxUI( true ) ) + : new FlatCheckBoxUI( false ); + } + + /** + * @since 2 + */ + protected FlatCheckBoxUI( boolean shared ) { + super( shared ); } @Override 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 392efd52..6982a601 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 @@ -41,6 +41,9 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeListener; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.CellRendererPane; @@ -67,6 +70,8 @@ import javax.swing.plaf.basic.BasicComboPopup; import javax.swing.plaf.basic.ComboPopup; import javax.swing.text.JTextComponent; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.SystemInfo; /** @@ -108,29 +113,30 @@ import com.formdev.flatlaf.util.SystemInfo; */ public class FlatComboBoxUI extends BasicComboBoxUI + implements StyleableUI { - protected int minimumWidth; - protected int editorColumns; - protected String buttonStyle; - protected String arrowType; + @Styleable protected int minimumWidth; + @Styleable protected int editorColumns; + @Styleable protected String buttonStyle; + @Styleable protected String arrowType; protected boolean isIntelliJTheme; - protected Color borderColor; - protected Color disabledBorderColor; + @Styleable protected Color borderColor; + @Styleable protected Color disabledBorderColor; - protected Color editableBackground; - protected Color focusedBackground; - protected Color disabledBackground; - protected Color disabledForeground; + @Styleable protected Color editableBackground; + @Styleable protected Color focusedBackground; + @Styleable protected Color disabledBackground; + @Styleable protected Color disabledForeground; - protected Color buttonBackground; - protected Color buttonEditableBackground; - protected Color buttonFocusedBackground; - protected Color buttonArrowColor; - protected Color buttonDisabledArrowColor; - protected Color buttonHoverArrowColor; - protected Color buttonPressedArrowColor; + @Styleable protected Color buttonBackground; + @Styleable protected Color buttonEditableBackground; + @Styleable protected Color buttonFocusedBackground; + @Styleable protected Color buttonArrowColor; + @Styleable protected Color buttonDisabledArrowColor; + @Styleable protected Color buttonHoverArrowColor; + @Styleable protected Color buttonPressedArrowColor; - protected Color popupBackground; + @Styleable protected Color popupBackground; private MouseListener hoverListener; protected boolean hover; @@ -138,10 +144,20 @@ public class FlatComboBoxUI private CellPaddingBorder paddingBorder; + private Map oldStyleValues; + private AtomicBoolean borderShared; + public static ComponentUI createUI( JComponent c ) { return new FlatComboBoxUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( comboBox ) ); + } + @Override protected void installListeners() { super.installListeners(); @@ -250,6 +266,9 @@ public class FlatComboBoxUI paddingBorder.uninstall(); + oldStyleValues = null; + borderShared = null; + MigLayoutVisualPadding.uninstall( comboBox ); } @@ -328,6 +347,11 @@ public class FlatComboBoxUI comboBox.repaint(); else if( FlatClientProperties.MINIMUM_WIDTH.equals( propertyName ) ) comboBox.revalidate(); + else if( FlatClientProperties.STYLE.equals( propertyName ) ) { + applyStyle( e.getNewValue() ); + comboBox.revalidate(); + comboBox.repaint(); + } }; } @@ -414,6 +438,55 @@ public class FlatComboBoxUI return new FlatComboBoxButton(); } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + Insets oldPadding = padding; + int oldEditorColumns = editorColumns; + + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + if( !padding.equals( oldPadding ) ) { + paddingBorder.padding = padding; + updateEditorPadding(); + } + if( arrowButton instanceof FlatComboBoxButton ) + ((FlatComboBoxButton)arrowButton).updateStyle(); + if( popup instanceof FlatComboPopup ) + ((FlatComboPopup)popup).updateStyle(); + if( editorColumns != oldEditorColumns && editor instanceof JTextField ) + ((JTextField)editor).setColumns( editorColumns ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + // BasicComboBoxUI + if( key.equals( "padding" ) ) { + Object oldValue = padding; + padding = (Insets) value; + return oldValue; + } + + if( borderShared == null ) + borderShared = new AtomicBoolean( true ); + return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, comboBox, borderShared ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + Map> infos = new LinkedHashMap<>(); + infos.put( "padding", Insets.class ); + FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos ); + FlatStylingSupport.collectStyleableInfos( comboBox.getBorder(), infos ); + return infos; + } + @Override public void update( Graphics g, JComponent c ) { float focusWidth = FlatUIUtils.getBorderFocusWidth( c ); @@ -612,6 +685,11 @@ public class FlatComboBoxUI hoverForeground, hoverBackground, pressedForeground, pressedBackground ); } + protected void updateStyle() { + updateStyle( arrowType, buttonArrowColor, buttonDisabledArrowColor, + buttonHoverArrowColor, null, buttonPressedArrowColor, null ); + } + @Override protected boolean isHover() { return super.isHover() || (!comboBox.isEditable() ? hover : false); @@ -708,6 +786,10 @@ public class FlatComboBoxUI super.configureList(); list.setCellRenderer( new PopupListCellRenderer() ); + updateStyle(); + } + + void updateStyle() { if( popupBackground != null ) list.setBackground( popupBackground ); } @@ -788,7 +870,7 @@ public class FlatComboBoxUI private static class CellPaddingBorder extends AbstractBorder { - private final Insets padding; + private Insets padding; private JComponent rendererComponent; private Border rendererBorder; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDropShadowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDropShadowBorder.java index 505b9276..2f8df4dc 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDropShadowBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDropShadowBorder.java @@ -24,6 +24,9 @@ import java.awt.Image; import java.awt.Insets; import java.awt.RadialGradientPaint; import java.awt.image.BufferedImage; +import java.util.Map; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder; import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.UIScale; @@ -40,14 +43,17 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatDropShadowBorder extends FlatEmptyBorder + implements StyleableBorder { - private final Color shadowColor; - private final Insets shadowInsets; - private final float shadowOpacity; + @Styleable protected Color shadowColor; + @Styleable protected Insets shadowInsets; + @Styleable protected float shadowOpacity; - private final int shadowSize; + private int shadowSize; private Image shadowImage; private Color lastShadowColor; + private float lastShadowOpacity; + private int lastShadowSize; private double lastSystemScaleFactor; private float lastUserScaleFactor; @@ -64,17 +70,47 @@ public class FlatDropShadowBorder } public FlatDropShadowBorder( Color shadowColor, Insets shadowInsets, float shadowOpacity ) { - super( Math.max( shadowInsets.top, 0 ), Math.max( shadowInsets.left, 0 ), - Math.max( shadowInsets.bottom, 0 ), Math.max( shadowInsets.right, 0 ) ); + super( nonNegativeInsets( shadowInsets ) ); + this.shadowColor = shadowColor; this.shadowInsets = shadowInsets; this.shadowOpacity = shadowOpacity; - shadowSize = Math.max( + shadowSize = maxInset( shadowInsets ); + } + + private static Insets nonNegativeInsets( Insets shadowInsets ) { + return new Insets( Math.max( shadowInsets.top, 0 ), Math.max( shadowInsets.left, 0 ), + Math.max( shadowInsets.bottom, 0 ), Math.max( shadowInsets.right, 0 ) ); + } + + private int maxInset( Insets shadowInsets ) { + return Math.max( Math.max( shadowInsets.left, shadowInsets.right ), Math.max( shadowInsets.top, shadowInsets.bottom ) ); } + /** + * @since 2 + */ + @Override + public Object applyStyleProperty( String key, Object value ) { + Object oldValue = FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + if( key.equals( "shadowInsets" ) ) { + applyStyleProperty( nonNegativeInsets( shadowInsets ) ); + shadowSize = maxInset( shadowInsets ); + } + return oldValue; + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { if( shadowSize <= 0 ) @@ -91,12 +127,16 @@ public class FlatDropShadowBorder float userScaleFactor = UIScale.getUserScaleFactor(); if( shadowImage == null || !shadowColor.equals( lastShadowColor ) || + lastShadowOpacity != shadowOpacity || + lastShadowSize != shadowSize || lastSystemScaleFactor != scaleFactor || lastUserScaleFactor != userScaleFactor ) { shadowImage = createShadowImage( shadowColor, shadowSize, shadowOpacity, (float) (scaleFactor * userScaleFactor) ); lastShadowColor = shadowColor; + lastShadowOpacity = shadowOpacity; + lastShadowSize = shadowSize; lastSystemScaleFactor = scaleFactor; lastUserScaleFactor = userScaleFactor; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java index 61bbcaf2..33a1a381 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java @@ -24,6 +24,8 @@ import java.awt.Graphics2D; import java.awt.Insets; import java.awt.event.FocusListener; import java.beans.PropertyChangeEvent; +import java.util.Map; +import java.util.function.Consumer; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.UIManager; @@ -31,6 +33,8 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicEditorPaneUI; import javax.swing.text.JTextComponent; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.HiDPIUtils; /** @@ -61,20 +65,35 @@ import com.formdev.flatlaf.util.HiDPIUtils; */ public class FlatEditorPaneUI extends BasicEditorPaneUI + implements StyleableUI { - protected int minimumWidth; + @Styleable protected int minimumWidth; protected boolean isIntelliJTheme; - protected Color focusedBackground; + private Color background; + @Styleable protected Color disabledBackground; + @Styleable protected Color inactiveBackground; + @Styleable protected Color focusedBackground; + + private Color oldDisabledBackground; + private Color oldInactiveBackground; private Insets defaultMargin; private Object oldHonorDisplayProperties; private FocusListener focusListener; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatEditorPaneUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -82,6 +101,9 @@ public class FlatEditorPaneUI String prefix = getPropertyPrefix(); minimumWidth = UIManager.getInt( "Component.minimumWidth" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); + background = UIManager.getColor( prefix + ".background" ); + disabledBackground = UIManager.getColor( prefix + ".disabledBackground" ); + inactiveBackground = UIManager.getColor( prefix + ".inactiveBackground" ); focusedBackground = UIManager.getColor( prefix + ".focusedBackground" ); defaultMargin = UIManager.getInsets( prefix + ".margin" ); @@ -95,8 +117,16 @@ public class FlatEditorPaneUI protected void uninstallDefaults() { super.uninstallDefaults(); + background = null; + disabledBackground = null; + inactiveBackground = null; focusedBackground = null; + oldDisabledBackground = null; + oldInactiveBackground = null; + + oldStyleValues = null; + getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties ); } @@ -119,18 +149,62 @@ public class FlatEditorPaneUI @Override protected void propertyChange( PropertyChangeEvent e ) { + // invoke updateBackground() before super.propertyChange() + String propertyName = e.getPropertyName(); + if( "editable".equals( propertyName ) || "enabled".equals( propertyName ) ) + updateBackground(); + super.propertyChange( e ); - propertyChange( getComponent(), e ); + propertyChange( getComponent(), e, this::applyStyle ); } - static void propertyChange( JTextComponent c, PropertyChangeEvent e ) { + static void propertyChange( JTextComponent c, PropertyChangeEvent e, Consumer applyStyle ) { switch( e.getPropertyName() ) { case FlatClientProperties.MINIMUM_WIDTH: c.revalidate(); break; + + case FlatClientProperties.STYLE: + applyStyle.accept( e.getNewValue() ); + c.revalidate(); + c.repaint(); + break; } } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldDisabledBackground = disabledBackground; + oldInactiveBackground = inactiveBackground; + + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + updateBackground(); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + + private void updateBackground() { + FlatTextFieldUI.updateBackground( getComponent(), background, + disabledBackground, inactiveBackground, + oldDisabledBackground, oldInactiveBackground ); + } + @Override public Dimension getPreferredSize( JComponent c ) { return applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth, defaultMargin ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEmptyBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEmptyBorder.java index 55d08c72..34e5fb04 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEmptyBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEmptyBorder.java @@ -50,6 +50,12 @@ public class FlatEmptyBorder @Override public Insets getBorderInsets( Component c, Insets insets ) { + return scaleInsets( c, insets, top, left, bottom, right ); + } + + protected static Insets scaleInsets( Component c, Insets insets, + int top, int left, int bottom, int right ) + { boolean leftToRight = left == right || c.getComponentOrientation().isLeftToRight(); insets.left = scale( leftToRight ? left : right ); insets.top = scale( top ); @@ -61,4 +67,13 @@ public class FlatEmptyBorder public Insets getUnscaledBorderInsets() { return super.getBorderInsets(); } + + public Object applyStyleProperty( Insets insets ) { + Insets oldInsets = getUnscaledBorderInsets(); + top = insets.top; + left = insets.left; + bottom = insets.bottom; + right = insets.right; + return oldInsets; + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java index f53b3d87..834ebfc5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java @@ -24,6 +24,10 @@ import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.MouseEvent; +import java.beans.PropertyChangeListener; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.JComponent; import javax.swing.JInternalFrame; import javax.swing.LookAndFeel; @@ -31,6 +35,9 @@ import javax.swing.UIManager; import javax.swing.event.MouseInputAdapter; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicInternalFrameUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JInternalFrame}. @@ -86,9 +93,13 @@ import javax.swing.plaf.basic.BasicInternalFrameUI; */ public class FlatInternalFrameUI extends BasicInternalFrameUI + implements StyleableUI { protected FlatWindowResizer windowResizer; + private Map oldStyleValues; + private AtomicBoolean borderShared; + public static ComponentUI createUI( JComponent c ) { return new FlatInternalFrameUI( (JInternalFrame) c ); } @@ -104,6 +115,8 @@ public class FlatInternalFrameUI LookAndFeel.installProperty( frame, "opaque", false ); windowResizer = createWindowResizer(); + + applyStyle( FlatStylingSupport.getStyle( c ) ); } @Override @@ -114,6 +127,9 @@ public class FlatInternalFrameUI windowResizer.uninstall(); windowResizer = null; } + + oldStyleValues = null; + borderShared = null; } @Override @@ -130,15 +146,46 @@ public class FlatInternalFrameUI return new FlatBorderListener(); } + @Override + protected PropertyChangeListener createPropertyChangeListener() { + return FlatStylingSupport.createPropertyChangeListener( frame, this::applyStyle, + super.createPropertyChangeListener() ); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + if( borderShared == null ) + borderShared = new AtomicBoolean( true ); + return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, frame, borderShared ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this, frame.getBorder() ); + } + //---- class FlatInternalFrameBorder -------------------------------------- public static class FlatInternalFrameBorder extends FlatEmptyBorder + implements StyleableBorder { - private final Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" ); - private final Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" ); - private final int borderLineWidth = FlatUIUtils.getUIInt( "InternalFrame.borderLineWidth", 1 ); - private final boolean dropShadowPainted = UIManager.getBoolean( "InternalFrame.dropShadowPainted" ); + @Styleable protected Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" ); + @Styleable protected Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" ); + @Styleable protected int borderLineWidth = FlatUIUtils.getUIInt( "InternalFrame.borderLineWidth", 1 ); + @Styleable protected boolean dropShadowPainted = UIManager.getBoolean( "InternalFrame.dropShadowPainted" ); private final FlatDropShadowBorder activeDropShadowBorder = new FlatDropShadowBorder( UIManager.getColor( "InternalFrame.activeDropShadowColor" ), @@ -153,6 +200,36 @@ public class FlatInternalFrameUI super( UIManager.getInsets( "InternalFrame.borderMargins" ) ); } + @Override + public Object applyStyleProperty( String key, Object value ) { + switch( key ) { + case "borderMargins": return applyStyleProperty( (Insets) value ); + + case "activeDropShadowColor": return activeDropShadowBorder.applyStyleProperty( "shadowColor", value ); + case "activeDropShadowInsets": return activeDropShadowBorder.applyStyleProperty( "shadowInsets", value ); + case "activeDropShadowOpacity": return activeDropShadowBorder.applyStyleProperty( "shadowOpacity", value ); + case "inactiveDropShadowColor": return inactiveDropShadowBorder.applyStyleProperty( "shadowColor", value ); + case "inactiveDropShadowInsets": return inactiveDropShadowBorder.applyStyleProperty( "shadowInsets", value ); + case "inactiveDropShadowOpacity": return inactiveDropShadowBorder.applyStyleProperty( "shadowOpacity", value ); + } + + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + @Override + public Map> getStyleableInfos() { + Map> infos = new LinkedHashMap<>(); + FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos ); + infos.put( "borderMargins", Insets.class ); + infos.put( "activeDropShadowColor", Color.class ); + infos.put( "activeDropShadowInsets", Insets.class ); + infos.put( "activeDropShadowOpacity", float.class ); + infos.put( "inactiveDropShadowColor", Color.class ); + infos.put( "inactiveDropShadowInsets", Insets.class ); + infos.put( "inactiveDropShadowOpacity", float.class ); + return infos; + } + @Override public Insets getBorderInsets( Component c, Insets insets ) { if( c instanceof JInternalFrame && ((JInternalFrame)c).isMaximum() ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java index ae629b55..6334cd6e 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java @@ -24,6 +24,7 @@ import java.awt.Rectangle; import java.beans.PropertyChangeEvent; import java.util.Arrays; import java.util.HashSet; +import java.util.Map; import java.util.Set; import javax.swing.Icon; import javax.swing.JComponent; @@ -33,7 +34,10 @@ import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicHTML; import javax.swing.plaf.basic.BasicLabelUI; +import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.UIScale; @@ -54,13 +58,32 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatLabelUI extends BasicLabelUI + implements StyleableUI { - private Color disabledForeground; + @Styleable protected Color disabledForeground; + private final boolean shared; private boolean defaults_initialized = false; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.createSharedUI( FlatLabelUI.class, FlatLabelUI::new ); + return FlatUIUtils.canUseSharedUI( c ) + ? FlatUIUtils.createSharedUI( FlatLabelUI.class, () -> new FlatLabelUI( true ) ) + : new FlatLabelUI( false ); + } + + /** + * @since 2 + */ + protected FlatLabelUI( boolean shared ) { + this.shared = shared; + } + + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( (JLabel) c, FlatStylingSupport.getStyle( c ) ); } @Override @@ -77,7 +100,9 @@ public class FlatLabelUI @Override protected void uninstallDefaults( JLabel c ) { super.uninstallDefaults( c ); + defaults_initialized = false; + oldStyleValues = null; } @Override @@ -94,10 +119,44 @@ public class FlatLabelUI if( name == "text" || name == "font" || name == "foreground" ) { JLabel label = (JLabel) e.getSource(); updateHTMLRenderer( label, label.getText(), true ); + } else if( name.equals( FlatClientProperties.STYLE ) ) { + JLabel label = (JLabel) e.getSource(); + Object style = e.getNewValue(); + if( style != null && shared ) { + // unshare component UI if necessary + // updateUI() invokes applyStyle() from installUI() + label.updateUI(); + } else + applyStyle( label, style ); + label.revalidate(); + label.repaint(); } else super.propertyChange( e ); } + /** + * @since 2 + */ + protected void applyStyle( JLabel c, Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, + (key, value) -> applyStyleProperty( c, key, value ) ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( JLabel c, String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, c, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + /** * Checks whether text contains HTML tags that use "absolute-size" keywords * (e.g. "x-large") for font-size in default style sheet diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLineBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLineBorder.java index a529f096..d8e3ea69 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLineBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLineBorder.java @@ -61,8 +61,8 @@ public class FlatLineBorder Graphics2D g2 = (Graphics2D) g.create(); try { FlatUIUtils.setRenderingHints( g2 ); - g2.setColor( lineColor ); - FlatUIUtils.paintComponentBorder( g2, x, y, width, height, 0f, scale( lineThickness ), 0f ); + g2.setColor( getLineColor() ); + FlatUIUtils.paintComponentBorder( g2, x, y, width, height, 0f, scale( getLineThickness() ), 0f ); } finally { g2.dispose(); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListCellBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListCellBorder.java index 16d3a23d..4e87570e 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListCellBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListCellBorder.java @@ -16,11 +16,15 @@ package com.formdev.flatlaf.ui; +import java.awt.Color; import java.awt.Component; import java.awt.Graphics; +import java.awt.Insets; +import java.util.function.Function; import javax.swing.JList; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.plaf.ListUI; /** * Cell border for {@link javax.swing.DefaultListCellRenderer} @@ -33,12 +37,54 @@ import javax.swing.UIManager; public class FlatListCellBorder extends FlatLineBorder { - final boolean showCellFocusIndicator = UIManager.getBoolean( "List.showCellFocusIndicator" ); + protected boolean showCellFocusIndicator = UIManager.getBoolean( "List.showCellFocusIndicator" ); + + private Component c; protected FlatListCellBorder() { super( UIManager.getInsets( "List.cellMargins" ), UIManager.getColor( "List.cellFocusColor" ) ); } + @Override + public Insets getBorderInsets( Component c, Insets insets ) { + Insets m = getStyleFromListUI( c, ui -> ui.cellMargins ); + if( m != null ) + return scaleInsets( c, insets, m.top, m.left, m.bottom, m.right ); + + return super.getBorderInsets( c, insets ); + } + + @Override + public Color getLineColor() { + if( c != null ) { + Color color = getStyleFromListUI( c, ui -> ui.cellFocusColor ); + if( color != null ) + return color; + } + return super.getLineColor(); + } + + @Override + public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { + this.c = c; + super.paintBorder( c, g, x, y, width, height ); + this.c = null; + } + + /** + * Because this borders are always shared for all lists, + * get border specific style from FlatListUI. + */ + static T getStyleFromListUI( Component c, Function f ) { + JList list = (JList) SwingUtilities.getAncestorOfClass( JList.class, c ); + if( list != null ) { + ListUI ui = list.getUI(); + if( ui instanceof FlatListUI ) + return f.apply( (FlatListUI) ui ); + } + return null; + } + //---- class Default ------------------------------------------------------ /** @@ -74,6 +120,8 @@ public class FlatListCellBorder { @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { + Boolean b = getStyleFromListUI( c, ui -> ui.showCellFocusIndicator ); + boolean showCellFocusIndicator = (b != null) ? b : this.showCellFocusIndicator; if( !showCellFocusIndicator ) return; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java index 5c1fe585..1d9821b8 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java @@ -18,14 +18,18 @@ package com.formdev.flatlaf.ui; import java.awt.Color; import java.awt.EventQueue; +import java.awt.Insets; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.JComponent; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicListUI; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JList}. @@ -65,16 +69,31 @@ import com.formdev.flatlaf.FlatClientProperties; */ public class FlatListUI extends BasicListUI + implements StyleableUI { - protected Color selectionBackground; - protected Color selectionForeground; - protected Color selectionInactiveBackground; - protected Color selectionInactiveForeground; + @Styleable protected Color selectionBackground; + @Styleable protected Color selectionForeground; + @Styleable protected Color selectionInactiveBackground; + @Styleable protected Color selectionInactiveForeground; + + // for FlatListCellBorder + @Styleable protected Insets cellMargins; + @Styleable protected Color cellFocusColor; + @Styleable protected boolean showCellFocusIndicator; + + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatListUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -95,17 +114,8 @@ public class FlatListUI selectionForeground = null; selectionInactiveBackground = null; selectionInactiveForeground = null; - } - @Override - protected PropertyChangeListener createPropertyChangeListener() { - PropertyChangeListener superListener = super.createPropertyChangeListener(); - return e -> { - superListener.propertyChange( e ); - - if( FlatClientProperties.COMPONENT_FOCUS_OWNER.equals( e.getPropertyName() ) ) - toggleSelectionColors(); - }; + oldStyleValues = null; } @Override @@ -129,6 +139,71 @@ public class FlatListUI }; } + @Override + protected PropertyChangeListener createPropertyChangeListener() { + PropertyChangeListener superListener = super.createPropertyChangeListener(); + return e -> { + superListener.propertyChange( e ); + + switch( e.getPropertyName() ) { + case FlatClientProperties.COMPONENT_FOCUS_OWNER: + toggleSelectionColors(); + break; + + case FlatClientProperties.STYLE: + applyStyle( e.getNewValue() ); + list.revalidate(); + list.repaint(); + break; + } + }; + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + Color oldSelectionBackground = selectionBackground; + Color oldSelectionForeground = selectionForeground; + Color oldSelectionInactiveBackground = selectionInactiveBackground; + Color oldSelectionInactiveForeground = selectionInactiveForeground; + + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + // update selection background + if( selectionBackground != oldSelectionBackground ) { + Color selBg = list.getSelectionBackground(); + if( selBg == oldSelectionBackground ) + list.setSelectionBackground( selectionBackground ); + else if( selBg == oldSelectionInactiveBackground ) + list.setSelectionBackground( selectionInactiveBackground ); + } + + // update selection foreground + if( selectionForeground != oldSelectionForeground ) { + Color selFg = list.getSelectionForeground(); + if( selFg == oldSelectionForeground ) + list.setSelectionForeground( selectionForeground ); + else if( selFg == oldSelectionInactiveForeground ) + list.setSelectionForeground( selectionInactiveForeground ); + } + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, list, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + /** * Toggle selection colors from focused to inactive and vice versa. * diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMarginBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMarginBorder.java index 27240fef..36b58f00 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMarginBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMarginBorder.java @@ -29,7 +29,7 @@ import javax.swing.plaf.basic.BasicBorders; public class FlatMarginBorder extends BasicBorders.MarginBorder { - private final int left, right, top, bottom; + protected int left, right, top, bottom; public FlatMarginBorder() { left = right = top = bottom = 0; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuBarBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuBarBorder.java index 5701f935..6331e035 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuBarBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuBarBorder.java @@ -21,8 +21,11 @@ import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Insets; +import java.util.Map; import javax.swing.JMenuBar; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder; /** * Border for {@link javax.swing.JMenuBar}. @@ -33,8 +36,22 @@ import javax.swing.UIManager; */ public class FlatMenuBarBorder extends FlatMarginBorder + implements StyleableBorder { - private final Color borderColor = UIManager.getColor( "MenuBar.borderColor" ); + @Styleable protected Color borderColor = UIManager.getColor( "MenuBar.borderColor" ); + + /** + * @since 2 + */ + @Override + public Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + @Override + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuBarUI.java index d5e34fe7..39fb218b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuBarUI.java @@ -18,8 +18,12 @@ package com.formdev.flatlaf.ui; import java.awt.Color; import java.awt.Graphics; +import java.awt.Insets; import java.awt.Window; import java.awt.event.ActionEvent; +import java.beans.PropertyChangeListener; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.JComponent; @@ -35,7 +39,10 @@ import javax.swing.plaf.ActionMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicMenuBarUI; +import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.SystemInfo; /** @@ -47,13 +54,30 @@ import com.formdev.flatlaf.util.SystemInfo; * @uiDefault MenuBar.background Color * @uiDefault MenuBar.foreground Color * @uiDefault MenuBar.border Border + * + * + * * @uiDefault TitlePane.unifiedBackground boolean * * @author Karl Tauber */ public class FlatMenuBarUI extends BasicMenuBarUI + implements StyleableUI { + // used in FlatMenuItemBorder + /** @since 2 */ @Styleable protected Insets itemMargins; + + // used in FlatMenuUI + /** @since 2 */ @Styleable protected Color hoverBackground; + /** @since 2 */ @Styleable protected Color underlineSelectionBackground; + /** @since 2 */ @Styleable protected Color underlineSelectionColor; + /** @since 2 */ @Styleable protected int underlineSelectionHeight = -1; + + private PropertyChangeListener propertyChangeListener; + private Map oldStyleValues; + private AtomicBoolean borderShared; + public static ComponentUI createUI( JComponent c ) { return new FlatMenuBarUI(); } @@ -63,6 +87,13 @@ public class FlatMenuBarUI * Do not add any functionality here. */ + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -70,6 +101,31 @@ public class FlatMenuBarUI LookAndFeel.installProperty( menuBar, "opaque", false ); } + @Override + protected void uninstallDefaults() { + super.uninstallDefaults(); + + oldStyleValues = null; + borderShared = null; + } + + @Override + protected void installListeners() { + super.installListeners(); + + propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( + menuBar, this::applyStyle, null ); + menuBar.addPropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + } + + @Override + protected void uninstallListeners() { + super.uninstallListeners(); + + menuBar.removePropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + propertyChangeListener = null; + } + @Override protected void installKeyboardActions() { super.installKeyboardActions(); @@ -82,6 +138,30 @@ public class FlatMenuBarUI map.put( "takeFocus", new TakeFocus() ); } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + if( borderShared == null ) + borderShared = new AtomicBoolean( true ); + return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, menuBar, borderShared ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this, menuBar.getBorder() ); + } + @Override public void update( Graphics g, JComponent c ) { // paint background 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 aa8079a6..108371ec 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 @@ -18,9 +18,11 @@ package com.formdev.flatlaf.ui; import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Component; +import java.awt.Container; import java.awt.Insets; import javax.swing.JMenuBar; import javax.swing.UIManager; +import javax.swing.plaf.MenuBarUI; /** * Border for {@link javax.swing.JMenu}, {@link javax.swing.JMenuItem}, @@ -33,15 +35,22 @@ import javax.swing.UIManager; public class FlatMenuItemBorder extends FlatMarginBorder { + // only used if parent menubar is not a instance of FlatMenuBarUI private final Insets menuBarItemMargins = UIManager.getInsets( "MenuBar.itemMargins" ); @Override public Insets getBorderInsets( Component c, Insets insets ) { - if( c.getParent() instanceof JMenuBar ) { - insets.top = scale( menuBarItemMargins.top ); - insets.left = scale( menuBarItemMargins.left ); - insets.bottom = scale( menuBarItemMargins.bottom ); - insets.right = scale( menuBarItemMargins.right ); + Container parent = c.getParent(); + if( parent instanceof JMenuBar ) { + // get margins from FlatMenuBarUI to allow styling + MenuBarUI ui = ((JMenuBar)parent).getUI(); + Insets margins = (ui instanceof FlatMenuBarUI && ((FlatMenuBarUI)ui).itemMargins != null) + ? ((FlatMenuBarUI)ui).itemMargins + : this.menuBarItemMargins; + insets.top = scale( margins.top ); + insets.left = scale( margins.left ); + insets.bottom = scale( margins.bottom ); + insets.right = scale( margins.right ); return insets; } else return super.getBorderInsets( c, insets ); 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..1886d08d 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 @@ -30,6 +30,7 @@ import java.awt.Rectangle; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.text.AttributedCharacterIterator; +import java.util.Map; import javax.swing.Icon; import javax.swing.JMenu; import javax.swing.JMenuItem; @@ -39,6 +40,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.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.util.DerivedColor; import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.HiDPIUtils; @@ -57,33 +62,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 +102,67 @@ public class FlatMenuItemRenderer this.minimumIconSize = (minimumIconSize != null) ? minimumIconSize : new Dimension( 16, 16 ); } + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + // style icon + if( key.startsWith( "icon." ) || key.equals( "selectionForeground" ) ) { + if( iconsShared ) { + if( checkIcon instanceof FlatCheckBoxMenuItemIcon ) + checkIcon = FlatStylingSupport.cloneIcon( checkIcon ); + if( arrowIcon instanceof FlatMenuArrowIcon ) + arrowIcon = FlatStylingSupport.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 FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + public Map> getStyleableInfos() { + Map> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this ); + if( checkIcon instanceof FlatCheckBoxMenuItemIcon ) + FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((FlatCheckBoxMenuItemIcon)checkIcon).getStyleableInfos() ); + if( arrowIcon instanceof FlatMenuArrowIcon ) + FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((FlatMenuArrowIcon)arrowIcon).getStyleableInfos() ); + infos.remove( "icon.selectionForeground" ); + return infos; + } + protected Dimension getPreferredMenuItemSize() { int width = 0; int height = 0; @@ -254,7 +319,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 +366,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..4d85b14b 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 @@ -16,13 +16,20 @@ package com.formdev.flatlaf.ui; +import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; +import java.beans.PropertyChangeListener; +import java.util.LinkedHashMap; +import java.util.Map; import javax.swing.Icon; import javax.swing.JComponent; +import javax.swing.JMenuItem; import javax.swing.LookAndFeel; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicMenuItemUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JMenuItem}. @@ -54,13 +61,22 @@ import javax.swing.plaf.basic.BasicMenuItemUI; */ public class FlatMenuItemUI extends BasicMenuItemUI + implements StyleableUI { 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( FlatStylingSupport.getStyle( menuItem ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -75,12 +91,72 @@ public class FlatMenuItemUI super.uninstallDefaults(); renderer = null; + oldStyleValues = null; } protected FlatMenuItemRenderer createRenderer() { return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); } + @Override + protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { + return FlatStylingSupport.createPropertyChangeListener( c, this::applyStyle, super.createPropertyChangeListener( c ) ); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + return renderer.applyStyleProperty( key, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + return applyStyleProperty( this, menuItem, key, value ); + } + + static Object applyStyleProperty( BasicMenuItemUI ui, JMenuItem menuItem, String key, Object value ) { + switch( key ) { + // BasicMenuItemUI + case "selectionBackground": + case "selectionForeground": + case "disabledForeground": + case "acceleratorForeground": + case "acceleratorSelectionForeground": + return FlatStylingSupport.applyToField( ui, key, key, value ); + + default: + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( ui, menuItem, key, value ); + } + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return getStyleableInfos( renderer ); + } + + static Map> getStyleableInfos( FlatMenuItemRenderer renderer ) { + Map> infos = new LinkedHashMap<>(); + infos.put( "selectionBackground", Color.class ); + infos.put( "selectionForeground", Color.class ); + infos.put( "disabledForeground", Color.class ); + infos.put( "acceleratorForeground", Color.class ); + infos.put( "acceleratorSelectionForeground", Color.class ); + infos.putAll( renderer.getStyleableInfos() ); + return infos; + } + @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..edf94112 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,16 +21,23 @@ 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 java.util.function.Function; import javax.swing.ButtonModel; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JMenu; +import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.LookAndFeel; import javax.swing.UIManager; import javax.swing.event.MouseInputListener; import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.MenuBarUI; import javax.swing.plaf.basic.BasicMenuUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JMenu}. @@ -60,10 +67,10 @@ import javax.swing.plaf.basic.BasicMenuUI; * * * @uiDefault MenuItem.iconTextGap int - * @uiDefault MenuBar.hoverBackground Color * * * + * @uiDefault MenuBar.hoverBackground Color * @uiDefault MenuBar.underlineSelectionBackground Color * @uiDefault MenuBar.underlineSelectionColor Color * @uiDefault MenuBar.underlineSelectionHeight int @@ -72,14 +79,22 @@ import javax.swing.plaf.basic.BasicMenuUI; */ public class FlatMenuUI extends BasicMenuUI + implements StyleableUI { - 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( FlatStylingSupport.getStyle( menuItem ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -88,7 +103,6 @@ public class FlatMenuUI menuItem.setRolloverEnabled( true ); - hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" ); renderer = createRenderer(); } @@ -96,8 +110,8 @@ public class FlatMenuUI protected void uninstallDefaults() { super.uninstallDefaults(); - hoverBackground = null; renderer = null; + oldStyleValues = null; } protected FlatMenuItemRenderer createRenderer() { @@ -129,6 +143,39 @@ public class FlatMenuUI }; } + @Override + protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { + return FlatStylingSupport.createPropertyChangeListener( c, this::applyStyle, super.createPropertyChangeListener( c ) ); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + return renderer.applyStyleProperty( key, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + return FlatMenuItemUI.applyStyleProperty( this, menuItem, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatMenuItemUI.getStyleableInfos( renderer ); + } + @Override public Dimension getMinimumSize( JComponent c ) { // avoid that top-level menus (in menu bar) are made smaller if horizontal space is rare @@ -153,9 +200,10 @@ public class FlatMenuUI protected class FlatMenuRenderer extends FlatMenuItemRenderer { - protected final Color menuBarUnderlineSelectionBackground = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionBackground", underlineSelectionBackground ); - protected final Color menuBarUnderlineSelectionColor = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionColor", underlineSelectionColor ); - protected final int menuBarUnderlineSelectionHeight = FlatUIUtils.getUIInt( "MenuBar.underlineSelectionHeight", underlineSelectionHeight ); + protected Color hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" ); + protected Color menuBarUnderlineSelectionBackground = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionBackground", underlineSelectionBackground ); + protected Color menuBarUnderlineSelectionColor = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionColor", underlineSelectionColor ); + protected int menuBarUnderlineSelectionHeight = FlatUIUtils.getUIInt( "MenuBar.underlineSelectionHeight", underlineSelectionHeight ); protected FlatMenuRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon, Font acceleratorFont, String acceleratorDelimiter ) @@ -165,27 +213,39 @@ public class FlatMenuUI @Override protected void paintBackground( Graphics g, Color selectionBackground ) { - if( isUnderlineSelection() && ((JMenu)menuItem).isTopLevelMenu() ) - selectionBackground = menuBarUnderlineSelectionBackground; + if( ((JMenu)menuItem).isTopLevelMenu() ) { + if( isUnderlineSelection() ) + selectionBackground = getStyleFromMenuBarUI( ui -> ui.underlineSelectionBackground, menuBarUnderlineSelectionBackground ); - ButtonModel model = menuItem.getModel(); - if( model.isRollover() && !model.isArmed() && !model.isSelected() && - model.isEnabled() && ((JMenu)menuItem).isTopLevelMenu() ) - { - g.setColor( deriveBackground( hoverBackground ) ); - g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() ); - } else - super.paintBackground( g, selectionBackground ); + ButtonModel model = menuItem.getModel(); + if( model.isRollover() && !model.isArmed() && !model.isSelected() && model.isEnabled() ) { + g.setColor( deriveBackground( getStyleFromMenuBarUI( ui -> ui.hoverBackground, hoverBackground ) ) ); + g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() ); + return; + } + } + + super.paintBackground( g, selectionBackground ); } @Override protected void paintUnderlineSelection( Graphics g, Color underlineSelectionColor, int underlineSelectionHeight ) { if( ((JMenu)menuItem).isTopLevelMenu() ) { - underlineSelectionColor = menuBarUnderlineSelectionColor; - underlineSelectionHeight = menuBarUnderlineSelectionHeight; + underlineSelectionColor = getStyleFromMenuBarUI( ui -> ui.underlineSelectionColor, menuBarUnderlineSelectionColor ); + underlineSelectionHeight = getStyleFromMenuBarUI( ui -> (ui.underlineSelectionHeight != -1) + ? ui.underlineSelectionHeight : null, menuBarUnderlineSelectionHeight ); } super.paintUnderlineSelection( g, underlineSelectionColor, underlineSelectionHeight ); } + + private T getStyleFromMenuBarUI( Function f, T defaultValue ) { + MenuBarUI ui = ((JMenuBar)menuItem.getParent()).getUI(); + if( !(ui instanceof FlatMenuBarUI) ) + return defaultValue; + + T value = f.apply( (FlatMenuBarUI) ui ); + return (value != null) ? value : defaultValue; + } } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java index e1f74421..9e90ecde 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.ui; +import java.awt.Color; import java.awt.Graphics; import java.awt.Insets; import java.awt.Shape; @@ -23,6 +24,7 @@ import java.awt.Toolkit; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; +import java.util.Map; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.Icon; @@ -36,6 +38,8 @@ import javax.swing.text.Element; import javax.swing.text.JTextComponent; import javax.swing.text.PasswordView; import javax.swing.text.View; +import com.formdev.flatlaf.icons.FlatCapsLockIcon; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JPasswordField}. @@ -75,10 +79,11 @@ import javax.swing.text.View; public class FlatPasswordFieldUI extends FlatTextFieldUI { - protected boolean showCapsLock; + @Styleable protected boolean showCapsLock; protected Icon capsLockIcon; private KeyListener capsLockListener; + private boolean capsLockIconShared = true; public static ComponentUI createUI( JComponent c ) { return new FlatPasswordFieldUI(); @@ -100,6 +105,7 @@ public class FlatPasswordFieldUI showCapsLock = UIManager.getBoolean( "PasswordField.showCapsLock" ); capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" ); + capsLockIconShared = true; } @Override @@ -155,6 +161,32 @@ public class FlatPasswordFieldUI } } + /** + * @since 2 + */ + @Override + protected Object applyStyleProperty( String key, Object value ) { + if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon ) { + if( capsLockIconShared ) { + capsLockIcon = FlatStylingSupport.cloneIcon( capsLockIcon ); + capsLockIconShared = false; + } + return ((FlatCapsLockIcon)capsLockIcon).applyStyleProperty( key, value ); + } + + return super.applyStyleProperty( key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + Map> infos = super.getStyleableInfos( c ); + infos.put( "capsLockIconColor", Color.class ); + return infos; + } + @Override public View create( Element elem ) { return new PasswordView( elem ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuBorder.java index ce1b314e..8367a97f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuBorder.java @@ -16,11 +16,16 @@ package com.formdev.flatlaf.ui; +import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Insets; +import java.util.LinkedHashMap; +import java.util.Map; import javax.swing.JScrollPane; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.util.UIScale; /** @@ -33,12 +38,44 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatPopupMenuBorder extends FlatLineBorder + implements StyleableBorder { + private Color borderColor; + public FlatPopupMenuBorder() { super( UIManager.getInsets( "PopupMenu.borderInsets" ), UIManager.getColor( "PopupMenu.borderColor" ) ); } + /** + * @since 2 + */ + @Override + public Object applyStyleProperty( String key, Object value ) { + Object oldValue; + switch( key ) { + case "borderInsets": return applyStyleProperty( (Insets) value ); + case "borderColor": oldValue = getLineColor(); borderColor = (Color) value; return oldValue; + } + throw new UnknownStyleException( key ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos() { + Map> infos = new LinkedHashMap<>(); + infos.put( "borderInsets", Insets.class ); + infos.put( "borderColor", Color.class ); + return infos; + } + + @Override + public Color getLineColor() { + return (borderColor != null) ? borderColor : super.getLineColor(); + } + @Override public Insets getBorderInsets( Component c, Insets insets ) { if( c instanceof Container && diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuSeparatorUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuSeparatorUI.java index f6181fe4..87bf88ef 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuSeparatorUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuSeparatorUI.java @@ -39,7 +39,16 @@ public class FlatPopupMenuSeparatorUI extends FlatSeparatorUI { public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.createSharedUI( FlatPopupMenuSeparatorUI.class, FlatPopupMenuSeparatorUI::new ); + return FlatUIUtils.canUseSharedUI( c ) + ? FlatUIUtils.createSharedUI( FlatPopupMenuSeparatorUI.class, () -> new FlatPopupMenuSeparatorUI( true ) ) + : new FlatPopupMenuSeparatorUI( false ); + } + + /** + * @since 2 + */ + protected FlatPopupMenuSeparatorUI( boolean shared ) { + super( shared ); } @Override diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java index 480b0222..7bcea2fa 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java @@ -16,9 +16,14 @@ package com.formdev.flatlaf.ui; +import java.beans.PropertyChangeListener; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.JComponent; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicPopupMenuUI; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JPopupMenu}. @@ -34,8 +39,68 @@ import javax.swing.plaf.basic.BasicPopupMenuUI; */ public class FlatPopupMenuUI extends BasicPopupMenuUI + implements StyleableUI { + private PropertyChangeListener propertyChangeListener; + private Map oldStyleValues; + private AtomicBoolean borderShared; + public static ComponentUI createUI( JComponent c ) { return new FlatPopupMenuUI(); } + + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + + @Override + public void uninstallUI( JComponent c ) { + super.uninstallUI( c ); + + oldStyleValues = null; + borderShared = null; + } + + @Override + protected void installListeners() { + super.installListeners(); + + propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( popupMenu, this::applyStyle, null ); + popupMenu.addPropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + } + + @Override + protected void uninstallListeners() { + super.uninstallListeners(); + + popupMenu.removePropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + propertyChangeListener = null; + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + if( borderShared == null ) + borderShared = new AtomicBoolean( true ); + return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, popupMenu, borderShared ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this, popupMenu.getBorder() ); + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatProgressBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatProgressBarUI.java index dddc87b7..04178b39 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatProgressBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatProgressBarUI.java @@ -25,12 +25,15 @@ import java.awt.Insets; import java.awt.geom.Area; import java.awt.geom.RoundRectangle2D; import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.JComponent; import javax.swing.JProgressBar; import javax.swing.LookAndFeel; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicProgressBarUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.UIScale; @@ -58,17 +61,26 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatProgressBarUI extends BasicProgressBarUI + implements StyleableUI { - protected int arc; - protected Dimension horizontalSize; - protected Dimension verticalSize; + @Styleable protected int arc; + @Styleable protected Dimension horizontalSize; + @Styleable protected Dimension verticalSize; private PropertyChangeListener propertyChangeListener; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatProgressBarUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( progressBar ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -80,6 +92,13 @@ public class FlatProgressBarUI verticalSize = UIManager.getDimension( "ProgressBar.verticalSize" ); } + @Override + protected void uninstallDefaults() { + super.uninstallDefaults(); + + oldStyleValues = null; + } + @Override protected void installListeners() { super.installListeners(); @@ -91,6 +110,12 @@ public class FlatProgressBarUI progressBar.revalidate(); progressBar.repaint(); break; + + case STYLE: + applyStyle( e.getNewValue() ); + progressBar.revalidate(); + progressBar.repaint(); + break; } }; progressBar.addPropertyChangeListener( propertyChangeListener ); @@ -104,6 +129,28 @@ public class FlatProgressBarUI propertyChangeListener = null; } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, progressBar, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @Override public Dimension getPreferredSize( JComponent c ) { Dimension size = super.getPreferredSize( c ); 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..0510ba3b 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,15 @@ 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.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JRadioButtonMenuItem}. @@ -54,13 +58,22 @@ import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI; */ public class FlatRadioButtonMenuItemUI extends BasicRadioButtonMenuItemUI + implements StyleableUI { 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( FlatStylingSupport.getStyle( menuItem ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -75,12 +88,46 @@ public class FlatRadioButtonMenuItemUI super.uninstallDefaults(); renderer = null; + oldStyleValues = null; } protected FlatMenuItemRenderer createRenderer() { return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); } + @Override + protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { + return FlatStylingSupport.createPropertyChangeListener( c, this::applyStyle, super.createPropertyChangeListener( c ) ); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + return renderer.applyStyleProperty( key, value ); + } catch ( UnknownStyleException ex ) { + // ignore + } + + return FlatMenuItemUI.applyStyleProperty( this, menuItem, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatMenuItemUI.getStyleableInfos( renderer ); + } + @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/FlatRadioButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java index 56450014..642e09a0 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 @@ -23,6 +23,8 @@ import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.util.Map; import java.util.Objects; import javax.swing.AbstractButton; import javax.swing.CellRendererPane; @@ -30,8 +32,13 @@ import javax.swing.JComponent; import javax.swing.LookAndFeel; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.basic.BasicButtonListener; import javax.swing.plaf.basic.BasicRadioButtonUI; +import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.icons.FlatCheckBoxIcon; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.util.UIScale; /** @@ -56,16 +63,36 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatRadioButtonUI extends BasicRadioButtonUI + implements StyleableUI { protected int iconTextGap; - protected Color disabledText; + @Styleable protected Color disabledText; private Color defaultBackground; + private final boolean shared; + private boolean iconShared = true; private boolean defaults_initialized = false; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.createSharedUI( FlatRadioButtonUI.class, FlatRadioButtonUI::new ); + return FlatUIUtils.canUseSharedUI( c ) + ? FlatUIUtils.createSharedUI( FlatRadioButtonUI.class, () -> new FlatRadioButtonUI( true ) ) + : new FlatRadioButtonUI( false ); + } + + /** + * @since 2 + */ + protected FlatRadioButtonUI( boolean shared ) { + this.shared = shared; + } + + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( (AbstractButton) c, FlatStylingSupport.getStyle( c ) ); } @Override @@ -80,6 +107,7 @@ public class FlatRadioButtonUI defaultBackground = UIManager.getColor( prefix + "background" ); + iconShared = true; defaults_initialized = true; } @@ -93,10 +121,78 @@ public class FlatRadioButtonUI protected void uninstallDefaults( AbstractButton b ) { super.uninstallDefaults( b ); + oldStyleValues = null; + MigLayoutVisualPadding.uninstall( b ); defaults_initialized = false; } + @Override + protected BasicButtonListener createButtonListener( AbstractButton b ) { + return new FlatRadioButtonListener( b ); + } + + /** + * @since 2 + */ + protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) { + switch( e.getPropertyName() ) { + case FlatClientProperties.STYLE: + Object style = e.getNewValue(); + if( style != null && shared ) { + // unshare component UI if necessary + // updateUI() invokes applyStyle() from installUI() + b.updateUI(); + } else + applyStyle( b, style ); + b.revalidate(); + b.repaint(); + break; + } + } + + /** + * @since 2 + */ + protected void applyStyle( AbstractButton b, Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, + (key, value) -> applyStyleProperty( b, key, value ) ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( AbstractButton b, String key, Object value ) { + // style icon + if( key.startsWith( "icon." ) ) { + if( !(icon instanceof FlatCheckBoxIcon) ) + return new UnknownStyleException( key ); + + if( iconShared ) { + icon = FlatStylingSupport.cloneIcon( icon ); + iconShared = false; + } + + key = key.substring( "icon.".length() ); + return ((FlatCheckBoxIcon)icon).applyStyleProperty( key, value ); + } + + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, b, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + Map> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this ); + if( icon instanceof FlatCheckBoxIcon ) { + for( Map.Entry> e : ((FlatCheckBoxIcon)icon).getStyleableInfos().entrySet() ) + infos.put( "icon.".concat( e.getKey() ), e.getValue() ); + } + return infos; + } + private static Insets tempInsets = new Insets( 0, 0, 0, 0 ); @Override @@ -182,4 +278,26 @@ public class FlatRadioButtonUI ? UIScale.scale( ((FlatCheckBoxIcon)getDefaultIcon()).focusWidth ) : 0; } + + //---- class FlatRadioButtonListener -------------------------------------- + + /** + * @since 2 + */ + protected class FlatRadioButtonListener + extends BasicButtonListener + { + private final AbstractButton b; + + protected FlatRadioButtonListener( AbstractButton b ) { + super( b ); + this.b = b; + } + + @Override + public void propertyChange( PropertyChangeEvent e ) { + super.propertyChange( e ); + FlatRadioButtonUI.this.propertyChange( b, e ); + } + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRoundBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRoundBorder.java index 3703be9c..f9cce14d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRoundBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRoundBorder.java @@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui; import java.awt.Component; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; /** * Border for various components (e.g. {@link javax.swing.JComboBox}). @@ -29,7 +30,7 @@ import javax.swing.UIManager; public class FlatRoundBorder extends FlatBorder { - protected final int arc = UIManager.getInt( "Component.arc" ); + @Styleable protected int arc = UIManager.getInt( "Component.arc" ); @Override protected int getArc( Component c ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java index bdc1d925..3c83968c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java @@ -24,6 +24,8 @@ import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeListener; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Objects; import javax.swing.InputMap; import javax.swing.JButton; @@ -35,6 +37,8 @@ import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicScrollBarUI; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.UIScale; /** @@ -74,33 +78,46 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatScrollBarUI extends BasicScrollBarUI + implements StyleableUI { - protected Insets trackInsets; - protected Insets thumbInsets; - protected int trackArc; - protected int thumbArc; - protected Color hoverTrackColor; - protected Color hoverThumbColor; - protected boolean hoverThumbWithTrack; - protected Color pressedTrackColor; - protected Color pressedThumbColor; - protected boolean pressedThumbWithTrack; + // overrides BasicScrollBarUI.supportsAbsolutePositioning (which is private) + @Styleable protected boolean allowsAbsolutePositioning; - protected boolean showButtons; - protected String arrowType; - protected Color buttonArrowColor; - protected Color buttonDisabledArrowColor; - protected Color hoverButtonBackground; - protected Color pressedButtonBackground; + @Styleable protected Insets trackInsets; + @Styleable protected Insets thumbInsets; + @Styleable protected int trackArc; + @Styleable protected int thumbArc; + @Styleable protected Color hoverTrackColor; + @Styleable protected Color hoverThumbColor; + @Styleable protected boolean hoverThumbWithTrack; + @Styleable protected Color pressedTrackColor; + @Styleable protected Color pressedThumbColor; + @Styleable protected boolean pressedThumbWithTrack; + + @Styleable protected boolean showButtons; + @Styleable protected String arrowType; + @Styleable protected Color buttonArrowColor; + @Styleable protected Color buttonDisabledArrowColor; + @Styleable protected Color hoverButtonBackground; + @Styleable protected Color pressedButtonBackground; private MouseAdapter hoverListener; protected boolean hoverTrack; protected boolean hoverThumb; + private Map oldStyleValues; + public static ComponentUI createUI( JComponent c ) { return new FlatScrollBarUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + @Override protected void installListeners() { super.installListeners(); @@ -123,6 +140,8 @@ public class FlatScrollBarUI protected void installDefaults() { super.installDefaults(); + allowsAbsolutePositioning = super.getSupportsAbsolutePositioning(); + trackInsets = UIManager.getInsets( "ScrollBar.trackInsets" ); thumbInsets = UIManager.getInsets( "ScrollBar.thumbInsets" ); trackArc = UIManager.getInt( "ScrollBar.trackArc" ); @@ -163,6 +182,8 @@ public class FlatScrollBarUI buttonDisabledArrowColor = null; hoverButtonBackground = null; pressedButtonBackground = null; + + oldStyleValues = null; } @Override @@ -177,6 +198,12 @@ public class FlatScrollBarUI scrollbar.repaint(); break; + case FlatClientProperties.STYLE: + applyStyle( e.getNewValue() ); + scrollbar.revalidate(); + scrollbar.repaint(); + break; + case "componentOrientation": // this is missing in BasicScrollBarUI.Handler.propertyChange() InputMap inputMap = (InputMap) UIManager.get( "ScrollBar.ancestorInputMap" ); @@ -193,6 +220,50 @@ public class FlatScrollBarUI }; } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + if( incrButton instanceof FlatScrollBarButton ) + ((FlatScrollBarButton)incrButton).updateStyle(); + if( decrButton instanceof FlatScrollBarButton ) + ((FlatScrollBarButton)decrButton).updateStyle(); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + Object oldValue; + switch( key ) { + // BasicScrollBarUI + case "track": oldValue = trackColor; trackColor = (Color) value; return oldValue; + case "thumb": oldValue = thumbColor; thumbColor = (Color) value; return oldValue; + case "width": oldValue = scrollBarWidth; scrollBarWidth = (int) value; return oldValue; + case "minimumThumbSize": oldValue = minimumThumbSize; minimumThumbSize = (Dimension) value; return oldValue; + case "maximumThumbSize": oldValue = maximumThumbSize; maximumThumbSize = (Dimension) value; return oldValue; + } + + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, scrollbar, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + Map> infos = new LinkedHashMap<>(); + infos.put( "track", Color.class ); + infos.put( "thumb", Color.class ); + infos.put( "width", int.class ); + infos.put( "minimumThumbSize", Dimension.class ); + infos.put( "maximumThumbSize", Dimension.class ); + FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos ); + return infos; + } + @Override public Dimension getPreferredSize( JComponent c ) { return UIScale.scale( super.getPreferredSize( c ) ); @@ -295,6 +366,11 @@ public class FlatScrollBarUI return UIScale.scale( FlatUIUtils.addInsets( super.getMaximumThumbSize(), thumbInsets ) ); } + @Override + public boolean getSupportsAbsolutePositioning() { + return allowsAbsolutePositioning; + } + //---- class ScrollBarHoverListener --------------------------------------- // using static field to disabling hover for other scroll bars @@ -368,6 +444,11 @@ public class FlatScrollBarUI setRequestFocusEnabled( false ); } + protected void updateStyle() { + updateStyle( arrowType, buttonArrowColor, buttonDisabledArrowColor, + null, hoverButtonBackground, null, pressedButtonBackground ); + } + @Override protected Color deriveBackground( Color background ) { return FlatUIUtils.deriveColor( background, scrollbar.getBackground() ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java index 0cc8a740..1b46ef38 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java @@ -29,6 +29,8 @@ import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComponent; @@ -46,6 +48,7 @@ import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicScrollPaneUI; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}. @@ -66,9 +69,13 @@ import com.formdev.flatlaf.FlatClientProperties; */ public class FlatScrollPaneUI extends BasicScrollPaneUI + implements StyleableUI { private Handler handler; + private Map oldStyleValues; + private AtomicBoolean borderShared; + public static ComponentUI createUI( JComponent c ) { return new FlatScrollPaneUI(); } @@ -80,6 +87,8 @@ public class FlatScrollPaneUI int focusWidth = UIManager.getInt( "Component.focusWidth" ); LookAndFeel.installProperty( c, "opaque", focusWidth == 0 ); + applyStyle( FlatStylingSupport.getStyle( c ) ); + MigLayoutVisualPadding.install( scrollpane ); } @@ -88,6 +97,9 @@ public class FlatScrollPaneUI MigLayoutVisualPadding.uninstall( scrollpane ); super.uninstallUI( c ); + + oldStyleValues = null; + borderShared = null; } @Override @@ -272,7 +284,13 @@ public class FlatScrollPaneUI ((JButton)corner).setBorder( BorderFactory.createEmptyBorder() ); ((JButton)corner).setFocusable( false ); } - break; + break; + + case FlatClientProperties.STYLE: + applyStyle( e.getNewValue() ); + scrollpane.revalidate(); + scrollpane.repaint(); + break; } }; } @@ -283,6 +301,35 @@ public class FlatScrollPaneUI return handler; } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + if( key.equals( "focusWidth" ) ) { + int focusWidth = (value instanceof Integer) ? (int) value : UIManager.getInt( "Component.focusWidth" ); + LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 ); + } + + if( borderShared == null ) + borderShared = new AtomicBoolean( true ); + return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, scrollpane, borderShared ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this, scrollpane.getBorder() ); + } + @Override protected void updateViewport( PropertyChangeEvent e ) { super.updateViewport( e ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSeparatorUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSeparatorUI.java index 7ace822b..8310e59e 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSeparatorUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSeparatorUI.java @@ -21,11 +21,16 @@ import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.JComponent; import javax.swing.JSeparator; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicSeparatorUI; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JSeparator}. @@ -45,15 +50,39 @@ import javax.swing.plaf.basic.BasicSeparatorUI; */ public class FlatSeparatorUI extends BasicSeparatorUI + implements StyleableUI { - protected int height; - protected int stripeWidth; - protected int stripeIndent; + @Styleable protected int height; + @Styleable protected int stripeWidth; + @Styleable protected int stripeIndent; + private final boolean shared; private boolean defaults_initialized = false; + private PropertyChangeListener propertyChangeListener; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.createSharedUI( FlatSeparatorUI.class, FlatSeparatorUI::new ); + return FlatUIUtils.canUseSharedUI( c ) + ? FlatUIUtils.createSharedUI( FlatSeparatorUI.class, () -> new FlatSeparatorUI( true ) ) + : new FlatSeparatorUI( false ); + } + + /** + * @since 2 + */ + protected FlatSeparatorUI( boolean shared ) { + this.shared = shared; + } + + protected String getPropertyPrefix() { + return "Separator"; + } + + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( (JSeparator) c, FlatStylingSupport.getStyle( c ) ); } @Override @@ -73,11 +102,60 @@ public class FlatSeparatorUI @Override protected void uninstallDefaults( JSeparator s ) { super.uninstallDefaults( s ); + defaults_initialized = false; + oldStyleValues = null; } - protected String getPropertyPrefix() { - return "Separator"; + @Override + protected void installListeners( JSeparator s ) { + super.installListeners( s ); + + propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( + s, style -> applyStyle( s, this, style ), null ); + s.addPropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + } + + @Override + protected void uninstallListeners( JSeparator s ) { + super.uninstallListeners( s ); + + s.removePropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + propertyChangeListener = null; + } + + private static void applyStyle( JSeparator s, FlatSeparatorUI ui, Object style ) { + if( style != null && ui.shared ) { + // unshare component UI if necessary + // updateUI() invokes applyStyle() from installUI() + s.updateUI(); + } else + ui.applyStyle( s, style ); + s.revalidate(); + s.repaint(); + } + + /** + * @since 2 + */ + protected void applyStyle( JSeparator s, Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, + (key, value) -> applyStyleProperty( s, key, value ) ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( JSeparator s, String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, s, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); } @Override diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java index 5a1b7352..d72fa7b1 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java @@ -29,6 +29,8 @@ import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; import java.awt.geom.RoundRectangle2D; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.JComponent; import javax.swing.JSlider; import javax.swing.LookAndFeel; @@ -36,6 +38,9 @@ import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicSliderUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.UIScale; @@ -75,23 +80,25 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatSliderUI extends BasicSliderUI + implements StyleableUI { - protected int trackWidth; - protected Dimension thumbSize; - protected int focusWidth; + @Styleable protected int trackWidth; + @Styleable protected Dimension thumbSize; + @Styleable protected int focusWidth; - protected Color trackValueColor; - protected Color trackColor; - protected Color thumbColor; - protected Color thumbBorderColor; + @Styleable protected Color trackValueColor; + @Styleable protected Color trackColor; + @Styleable protected Color thumbColor; + @Styleable protected Color thumbBorderColor; protected Color focusBaseColor; - protected Color focusedColor; - protected Color focusedThumbBorderColor; - protected Color hoverThumbColor; - protected Color pressedThumbColor; - protected Color disabledTrackColor; - protected Color disabledThumbColor; - protected Color disabledThumbBorderColor; + @Styleable protected Color focusedColor; + @Styleable protected Color focusedThumbBorderColor; + @Styleable protected Color hoverThumbColor; + @Styleable protected Color pressedThumbColor; + @Styleable protected Color disabledTrackColor; + @Styleable protected Color disabledThumbColor; + @Styleable protected Color disabledThumbBorderColor; + @Styleable protected Color tickColor; private Color defaultBackground; private Color defaultForeground; @@ -100,6 +107,7 @@ public class FlatSliderUI protected boolean thumbPressed; private Object[] oldRenderingHints; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatSliderUI(); @@ -109,6 +117,13 @@ public class FlatSliderUI super( null ); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( slider ) ); + } + @Override protected void installDefaults( JSlider slider ) { super.installDefaults( slider ); @@ -136,6 +151,7 @@ public class FlatSliderUI disabledTrackColor = UIManager.getColor( "Slider.disabledTrackColor" ); disabledThumbColor = UIManager.getColor( "Slider.disabledThumbColor" ); disabledThumbBorderColor = FlatUIUtils.getUIColor( "Slider.disabledThumbBorderColor", "Component.disabledBorderColor" ); + tickColor = FlatUIUtils.getUIColor( "Slider.tickColor", Color.BLACK ); // see BasicSliderUI.paintTicks() defaultBackground = UIManager.getColor( "Slider.background" ); defaultForeground = UIManager.getColor( "Slider.foreground" ); @@ -157,9 +173,12 @@ public class FlatSliderUI disabledTrackColor = null; disabledThumbColor = null; disabledThumbBorderColor = null; + tickColor = null; defaultBackground = null; defaultForeground = null; + + oldStyleValues = null; } @Override @@ -167,6 +186,34 @@ public class FlatSliderUI return new FlatTrackListener(); } + @Override + protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) { + return FlatStylingSupport.createPropertyChangeListener( slider, this::applyStyle, + super.createPropertyChangeListener( slider ) ); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, slider, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + @Override public int getBaseline( JComponent c, int width, int height ) { if( c == null ) @@ -326,6 +373,19 @@ debug*/ ((Graphics2D)g).fill( track ); } + @Override + public void paintTicks( Graphics g ) { + // because BasicSliderUI.paintTicks() always uses + // g.setColor( UIManager.getColor("Slider.tickColor") ) + // we override this method and use our tickColor field to allow styling + super.paintTicks( new Graphics2DProxy( (Graphics2D) g ) { + @Override + public void setColor( Color c ) { + super.setColor( tickColor ); + } + } ); + } + @Override public void paintThumb( Graphics g ) { Color thumbColor = getThumbColor(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java index fdfdf657..1092434f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java @@ -33,6 +33,8 @@ import java.awt.event.FocusListener; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.JComponent; import javax.swing.JSpinner; import javax.swing.JTextField; @@ -43,6 +45,8 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicSpinnerUI; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JSpinner}. @@ -79,29 +83,40 @@ import com.formdev.flatlaf.FlatClientProperties; */ public class FlatSpinnerUI extends BasicSpinnerUI + implements StyleableUI { private Handler handler; - protected int minimumWidth; - protected String buttonStyle; - protected String arrowType; + @Styleable protected int minimumWidth; + @Styleable protected String buttonStyle; + @Styleable protected String arrowType; protected boolean isIntelliJTheme; - protected Color borderColor; - protected Color disabledBorderColor; - protected Color disabledBackground; - protected Color disabledForeground; - protected Color focusedBackground; - protected Color buttonBackground; - protected Color buttonArrowColor; - protected Color buttonDisabledArrowColor; - protected Color buttonHoverArrowColor; - protected Color buttonPressedArrowColor; - protected Insets padding; + @Styleable protected Color borderColor; + @Styleable protected Color disabledBorderColor; + @Styleable protected Color disabledBackground; + @Styleable protected Color disabledForeground; + @Styleable protected Color focusedBackground; + @Styleable protected Color buttonBackground; + @Styleable protected Color buttonArrowColor; + @Styleable protected Color buttonDisabledArrowColor; + @Styleable protected Color buttonHoverArrowColor; + @Styleable protected Color buttonPressedArrowColor; + @Styleable protected Insets padding; + + private Map oldStyleValues; + private AtomicBoolean borderShared; public static ComponentUI createUI( JComponent c ) { return new FlatSpinnerUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( spinner ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -143,6 +158,9 @@ public class FlatSpinnerUI buttonPressedArrowColor = null; padding = null; + oldStyleValues = null; + borderShared = null; + MigLayoutVisualPadding.uninstall( spinner ); } @@ -172,6 +190,32 @@ public class FlatSpinnerUI return handler; } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + updateEditorPadding(); + updateArrowButtonsStyle(); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + if( borderShared == null ) + borderShared = new AtomicBoolean( true ); + return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, spinner, borderShared ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this, spinner.getBorder() ); + } + @Override protected JComponent createEditor() { JComponent editor = super.createEditor(); @@ -297,6 +341,15 @@ public class FlatSpinnerUI return button; } + private void updateArrowButtonsStyle() { + for( Component c : spinner.getComponents() ) { + if( c instanceof FlatArrowButton ) { + ((FlatArrowButton)c).updateStyle( arrowType, buttonArrowColor, + buttonDisabledArrowColor, buttonHoverArrowColor, null, buttonPressedArrowColor, null ); + } + } + } + @Override public void update( Graphics g, JComponent c ) { float focusWidth = FlatUIUtils.getBorderFocusWidth( c ); @@ -483,6 +536,12 @@ public class FlatSpinnerUI case FlatClientProperties.MINIMUM_WIDTH: spinner.revalidate(); break; + + case FlatClientProperties.STYLE: + applyStyle( e.getNewValue() ); + spinner.revalidate(); + spinner.repaint(); + break; } } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSplitPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSplitPaneUI.java index 16ec4eba..1eb62027 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSplitPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSplitPaneUI.java @@ -23,6 +23,8 @@ import java.awt.Graphics; import java.awt.Insets; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JSplitPane; @@ -32,6 +34,10 @@ import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicSplitPaneDivider; import javax.swing.plaf.basic.BasicSplitPaneUI; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.util.UIScale; /** @@ -66,16 +72,27 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatSplitPaneUI extends BasicSplitPaneUI + implements StyleableUI { - protected String arrowType; - protected Color oneTouchArrowColor; - protected Color oneTouchHoverArrowColor; - protected Color oneTouchPressedArrowColor; + @Styleable protected String arrowType; + @Styleable protected Color oneTouchArrowColor; + @Styleable protected Color oneTouchHoverArrowColor; + @Styleable protected Color oneTouchPressedArrowColor; + + private PropertyChangeListener propertyChangeListener; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatSplitPaneUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( splitPane ) ); + } + @Override protected void installDefaults() { arrowType = UIManager.getString( "Component.arrowType" ); @@ -96,6 +113,24 @@ public class FlatSplitPaneUI oneTouchArrowColor = null; oneTouchHoverArrowColor = null; oneTouchPressedArrowColor = null; + + oldStyleValues = null; + } + + @Override + protected void installListeners() { + super.installListeners(); + + propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( splitPane, this::applyStyle, null ); + splitPane.addPropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + } + + @Override + protected void uninstallListeners() { + super.uninstallListeners(); + + splitPane.removePropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + propertyChangeListener = null; } @Override @@ -103,16 +138,50 @@ public class FlatSplitPaneUI return new FlatSplitPaneDivider( this ); } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + if( divider instanceof FlatSplitPaneDivider ) + ((FlatSplitPaneDivider)divider).updateStyle(); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + if( divider instanceof FlatSplitPaneDivider ) + return ((FlatSplitPaneDivider)divider).applyStyleProperty( key, value ); + } catch( UnknownStyleException ex ) { + // ignore + } + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, splitPane, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + Map> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this ); + if( divider instanceof FlatSplitPaneDivider ) + infos.putAll( ((FlatSplitPaneDivider)divider).getStyleableInfos() ); + return infos; + } + //---- class FlatSplitPaneDivider ----------------------------------------- protected class FlatSplitPaneDivider extends BasicSplitPaneDivider { - protected final String style = UIManager.getString( "SplitPaneDivider.style" ); - protected final Color gripColor = UIManager.getColor( "SplitPaneDivider.gripColor" ); - protected final int gripDotCount = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotCount", 3 ); - protected final int gripDotSize = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotSize", 3 ); - protected final int gripGap = FlatUIUtils.getUIInt( "SplitPaneDivider.gripGap", 2 ); + @Styleable protected String style = UIManager.getString( "SplitPaneDivider.style" ); + @Styleable protected Color gripColor = UIManager.getColor( "SplitPaneDivider.gripColor" ); + @Styleable protected int gripDotCount = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotCount", 3 ); + @Styleable protected int gripDotSize = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotSize", 3 ); + @Styleable protected int gripGap = FlatUIUtils.getUIInt( "SplitPaneDivider.gripGap", 2 ); protected FlatSplitPaneDivider( BasicSplitPaneUI ui ) { super( ui ); @@ -120,6 +189,27 @@ public class FlatSplitPaneUI setLayout( new FlatDividerLayout() ); } + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + public Map> getStyleableInfos() { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + + void updateStyle() { + if( leftButton instanceof FlatOneTouchButton ) + ((FlatOneTouchButton)leftButton).updateStyle(); + if( rightButton instanceof FlatOneTouchButton ) + ((FlatOneTouchButton)rightButton).updateStyle(); + } + @Override public void setDividerSize( int newSize ) { super.setDividerSize( UIScale.scale( newSize ) ); @@ -200,6 +290,11 @@ public class FlatSplitPaneUI this.left = left; } + protected void updateStyle() { + updateStyle( arrowType, oneTouchArrowColor, null, + oneTouchHoverArrowColor, null, oneTouchPressedArrowColor, null ); + } + @Override public int getDirection() { return (orientation == JSplitPane.VERTICAL_SPLIT) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java new file mode 100644 index 00000000..d66657e3 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java @@ -0,0 +1,532 @@ +/* + * 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.beans.PropertyChangeListener; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +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; +import javax.swing.border.Border; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.util.StringUtils; +import com.formdev.flatlaf.util.SystemInfo; + +/** + * Support for styling components in CSS syntax. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatStylingSupport +{ + /** + * Indicates that a field is intended to be used by FlatLaf styling support. + *

+ * Do not rename fields annotated with this annotation. + */ + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Styleable { + boolean dot() default false; + Class type() default Void.class; + } + + public interface StyleableUI { + Map> getStyleableInfos( JComponent c ); + } + + public interface StyleableBorder { + Object applyStyleProperty( String key, Object value ); + Map> getStyleableInfos(); + } + + /** + * Parses styles in CSS syntax ("key1: value1; key2: value2; ..."), + * converts the value strings into binary and invokes the given function + * to apply the properties. + * + * @param oldStyleValues map of old values modified by the previous invocation, or {@code null} + * @param style the style in CSS syntax as string, or a Map, or {@code null} + * @param applyProperty function that is invoked to apply the properties; + * first parameter is the key, second the binary value; + * the function must return the old value + * @return map of old values modified by the given style, or {@code null} + * @throws UnknownStyleException on unknown style keys + * @throws IllegalArgumentException on syntax errors + * @throws ClassCastException if value type does not fit to expected type + */ + public static Map parseAndApply( Map oldStyleValues, + Object style, BiFunction applyProperty ) + throws UnknownStyleException, IllegalArgumentException + { + // restore previous values + if( oldStyleValues != null ) { + for( Map.Entry e : oldStyleValues.entrySet() ) + applyProperty.apply( e.getKey(), e.getValue() ); + } + + // ignore empty style + if( style == null ) + return null; + + if( style instanceof String ) { + // handle style in CSS syntax + String str = (String) style; + if( str.trim().isEmpty() ) + return null; + + return applyStyle( parse( str ), applyProperty ); + } else if( style instanceof Map ) { + // handle style of type Map + @SuppressWarnings( "unchecked" ) + Map map = (Map) style; + return applyStyle( map, applyProperty ); + } else + return null; + } + + private static Map applyStyle( Map style, + BiFunction applyProperty ) + { + if( style.isEmpty() ) + return null; + + Map oldValues = new HashMap<>(); + for( Map.Entry e : style.entrySet() ) { + String key = e.getKey(); + Object newValue = e.getValue(); + + // handle key prefix + if( key.startsWith( "[" ) ) { + if( (SystemInfo.isWindows && key.startsWith( "[win]" )) || + (SystemInfo.isMacOS && key.startsWith( "[mac]" )) || + (SystemInfo.isLinux && key.startsWith( "[linux]" )) || + (key.startsWith( "[light]" ) && !FlatLaf.isLafDark()) || + (key.startsWith( "[dark]" ) && FlatLaf.isLafDark()) ) + { + // prefix is known and enabled --> remove prefix + key = key.substring( key.indexOf( ']' ) + 1 ); + } else + continue; + } + + Object oldValue = applyProperty.apply( key, newValue ); + oldValues.put( key, oldValue ); + } + return oldValues; + } + + /** + * Parses styles in CSS syntax ("key1: value1; key2: value2; ..."), + * converts the value strings into binary and returns all key/value pairs as map. + * + * @param style the style in CSS syntax, or {@code null} + * @return map of parsed styles, or {@code null} + * @throws IllegalArgumentException on syntax errors + */ + public static Map parse( String style ) + throws IllegalArgumentException + { + if( style == null || style.trim().isEmpty() ) + return null; + + Map map = null; + + // split style into parts and process them + for( String part : StringUtils.split( style, ';' ) ) { + // ignore empty parts + part = part.trim(); + if( part.isEmpty() ) + continue; + + // find separator colon + int sepIndex = part.indexOf( ':' ); + if( sepIndex < 0 ) + throw new IllegalArgumentException( "missing colon in '" + part + "'" ); + + // split into key and value + String key = part.substring( 0, sepIndex ).trim(); + String value = part.substring( sepIndex + 1 ).trim(); + if( key.isEmpty() ) + throw new IllegalArgumentException( "missing key in '" + part + "'" ); + if( value.isEmpty() ) + throw new IllegalArgumentException( "missing value in '" + part + "'" ); + + // parse value string and convert it into binary value + if( map == null ) + map = new LinkedHashMap<>(); + map.put( key, parseValue( key, value ) ); + } + + return map; + } + + private static Object parseValue( String key, String value ) { + // simple reference + if( value.startsWith( "$" ) ) + return UIManager.get( value.substring( 1 ) ); + + // remove key prefix for correct value type detection + // (e.g. "[light]padding" would not parse to Insets) + if( key.startsWith( "[" ) ) + key = key.substring( key.indexOf( ']' ) + 1 ); + + // parse string + return FlatLaf.parseDefaultsValue( key, value, null ); + } + + /** + * Applies the given value to an annotated field of the given object. + * The field must be annotated with {@link Styleable}. + * + * @param obj the object + * @param key the name of the field + * @param value the new value + * @return the old value of the field + * @throws UnknownStyleException if object does not have a annotated field with given name + * @throws IllegalArgumentException if value type does not fit to expected type + */ + public static Object applyToAnnotatedObject( Object obj, String key, Object value ) + throws UnknownStyleException, IllegalArgumentException + { + String fieldName = key; + int dotIndex = key.indexOf( '.' ); + if( dotIndex >= 0 ) { + // remove first dot in key and change subsequent character to uppercase + fieldName = key.substring( 0, dotIndex ) + + Character.toUpperCase( key.charAt( dotIndex + 1 ) ) + + 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 ); + if( predicate == null || predicate.test( f ) ) { + if( !isValidField( f ) ) + throw new IllegalArgumentException( "field '" + cls.getName() + "." + fieldName + "' is final or static" ); + + try { + // necessary to access protected fields in other packages + f.setAccessible( true ); + + // get old value and set new value + Object oldValue = f.get( obj ); + f.set( obj, value ); + return oldValue; + } catch( IllegalAccessException ex ) { + throw new IllegalArgumentException( "failed to access field '" + cls.getName() + "." + fieldName + "'" ); + } + } + } catch( NoSuchFieldException ex ) { + // field not found in class --> try superclass + } + + cls = cls.getSuperclass(); + if( cls == null ) + throw new UnknownStyleException( key ); + + if( predicate != null ) { + String superclassName = cls.getName(); + if( superclassName.startsWith( "java." ) || superclassName.startsWith( "javax." ) ) + throw new UnknownStyleException( key ); + } + } + } + + private static boolean isValidField( Field f ) { + int modifiers = f.getModifiers(); + return (modifiers & (Modifier.FINAL|Modifier.STATIC)) == 0 && !f.isSynthetic(); + } + + /** + * Applies the given value to a property of the given object. + * Works only for properties that have public getter and setter methods. + * First the property getter is invoked to get the old value, + * then the property setter is invoked to set the new value. + * + * @param obj the object + * @param name the name of the property + * @param value the new value + * @return the old value of the property + * @throws UnknownStyleException if object does not have a property with given name + * @throws IllegalArgumentException if value type does not fit to expected type + */ + private static Object applyToProperty( Object obj, String name, Object value ) + throws UnknownStyleException, IllegalArgumentException + { + Class cls = obj.getClass(); + String getterName = buildMethodName( "get", name ); + String setterName = buildMethodName( "set", name ); + + try { + Method getter; + try { + getter = cls.getMethod( getterName ); + } catch( NoSuchMethodException ex ) { + getter = cls.getMethod( buildMethodName( "is", name ) ); + } + Method setter = cls.getMethod( setterName, getter.getReturnType() ); + Object oldValue = getter.invoke( obj ); + setter.invoke( obj, value ); + return oldValue; + } catch( NoSuchMethodException ex ) { + throw new UnknownStyleException( name ); + } catch( Exception ex ) { + throw new IllegalArgumentException( "failed to invoke property methods '" + cls.getName() + "." + + getterName + "()' or '" + setterName + "(...)'", ex ); + } + } + + private static String buildMethodName( String prefix, String name ) { + int prefixLength = prefix.length(); + int nameLength = name.length(); + char[] chars = new char[prefixLength + nameLength]; + prefix.getChars( 0, prefixLength, chars, 0 ); + name.getChars( 0, nameLength, chars, prefixLength ); + chars[prefixLength] = Character.toUpperCase( chars[prefixLength] ); + return new String( chars ); + } + + /** + * Applies the given value to an annotated field of the given object + * or to a property of the given component. + * The field must be annotated with {@link Styleable}. + * The component property must have public getter and setter methods. + * + * @param obj the object + * @param comp the component, or {@code null} + * @param key the name of the field + * @param value the new value + * @return the old value of the field + * @throws UnknownStyleException if object does not have a annotated field with given name + * @throws IllegalArgumentException if value type does not fit to expected type + */ + public static Object applyToAnnotatedObjectOrComponent( Object obj, Object comp, String key, Object value ) { + try { + return applyToAnnotatedObject( obj, key, value ); + } catch( UnknownStyleException ex ) { + try { + if( comp != null ) + return applyToProperty( comp, key, value ); + } catch( UnknownStyleException ex2 ) { + // ignore + } + throw ex; + } + } + + static Object applyToAnnotatedObjectOrBorder( Object obj, String key, Object value, + JComponent c, AtomicBoolean borderShared ) + { + try { + return applyToAnnotatedObject( obj, key, value ); + } catch( UnknownStyleException ex ) { + // apply to border + Border border = c.getBorder(); + if( border instanceof StyleableBorder ) { + if( borderShared.get() ) { + border = cloneBorder( border ); + c.setBorder( border ); + borderShared.set( false ); + } + + try { + return ((StyleableBorder)border).applyStyleProperty( key, value ); + } catch( UnknownStyleException ex2 ) { + // ignore + } + } + + // apply to component property + try { + return applyToProperty( c, key, value ); + } catch( UnknownStyleException ex2 ) { + // ignore + } + throw ex; + } + } + + public static Object getStyle( JComponent c ) { + 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 { + return borderClass.getDeclaredConstructor().newInstance(); + } catch( Exception ex ) { + throw new IllegalArgumentException( "failed to clone border '" + borderClass.getName() + "'" ); + } + } + + static Icon cloneIcon( Icon icon ) { + Class iconClass = icon.getClass(); + try { + return iconClass.getDeclaredConstructor().newInstance(); + } catch( Exception ex ) { + throw new IllegalArgumentException( "failed to clone icon '" + iconClass.getName() + "'" ); + } + } + + /** + * Returns a map of all fields annotated with {@link Styleable}. + * The key is the name of the field and the value the type of the field. + */ + public static Map> getAnnotatedStyleableInfos( Object obj ) { + return getAnnotatedStyleableInfos( obj, null ); + } + + public static Map> getAnnotatedStyleableInfos( Object obj, Border border ) { + Map> infos = new LinkedHashMap<>(); + collectAnnotatedStyleableInfos( obj, infos ); + collectStyleableInfos( border, infos ); + return infos; + } + + /** + * Search for all fields annotated with {@link Styleable} and add them to the given map. + * The key is the name of the field and the value the type of the field. + */ + public static void collectAnnotatedStyleableInfos( Object obj, Map> infos ) { + Class cls = obj.getClass(); + + for(;;) { + for( Field f : cls.getDeclaredFields() ) { + if( !isValidField( f ) ) + continue; + + Styleable styleable = f.getAnnotation( Styleable.class ); + if( styleable == null ) + continue; + + String name = f.getName(); + Class type = f.getType(); + + // handle "dot" keys (e.g. change field name "iconArrowType" to style key "icon.arrowType") + if( styleable.dot() ) { + int len = name.length(); + for( int i = 0; i < len; i++ ) { + if( Character.isUpperCase( name.charAt( i ) ) ) { + name = name.substring( 0, i ) + '.' + + Character.toLowerCase( name.charAt( i ) ) + + name.substring( i + 1 ); + break; + } + } + } + + // field has a different type + if( styleable.type() != Void.class ) + type = styleable.type(); + + infos.put( name, type ); + } + + cls = cls.getSuperclass(); + if( cls == null ) + return; + + String superclassName = cls.getName(); + if( superclassName.startsWith( "java." ) || superclassName.startsWith( "javax." ) ) + return; + } + } + + public static void collectStyleableInfos( Border border, Map> infos ) { + if( border instanceof StyleableBorder ) + infos.putAll( ((StyleableBorder)border).getStyleableInfos() ); + } + + public static void putAllPrefixKey( Map> infos, String keyPrefix, Map> infos2 ) { + for( Map.Entry> e : infos2.entrySet() ) + infos.put( keyPrefix.concat( e.getKey() ), e.getValue() ); + } + + //---- class UnknownStyleException ---------------------------------------- + + public static class UnknownStyleException + extends IllegalArgumentException + { + public UnknownStyleException( String key ) { + super( key ); + } + + @Override + public String getMessage() { + return "unknown style '" + super.getMessage() + "'"; + } + } +} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java index 906eb9e5..a63bd555 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java @@ -52,7 +52,9 @@ import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.IntConsumer; @@ -84,6 +86,10 @@ import javax.swing.plaf.basic.BasicTabbedPaneUI; import javax.swing.text.JTextComponent; import javax.swing.text.View; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.CubicBezierEasing; import com.formdev.flatlaf.util.JavaCompatibility; @@ -101,7 +107,7 @@ import com.formdev.flatlaf.util.UIScale; * @uiDefault TabbedPane.font Font * @uiDefault TabbedPane.background Color * @uiDefault TabbedPane.foreground Color - * @uiDefault TabbedPane.shadow Color used for scroll arrows and cropped line + * @uiDefault TabbedPane.shadow Color used for cropped line * @uiDefault TabbedPane.textIconGap int * @uiDefault TabbedPane.tabInsets Insets * @uiDefault TabbedPane.selectedTabPadInsets Insets unused @@ -156,6 +162,7 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatTabbedPaneUI extends BasicTabbedPaneUI + implements StyleableUI { // tabs popup policy / scroll arrows policy protected static final int NEVER = 0; @@ -177,43 +184,43 @@ public class FlatTabbedPaneUI private static Set focusBackwardTraversalKeys; protected Color foreground; - protected Color disabledForeground; - protected Color selectedBackground; - protected Color selectedForeground; - protected Color underlineColor; - protected Color disabledUnderlineColor; - protected Color hoverColor; - protected Color focusColor; - protected Color tabSeparatorColor; - protected Color contentAreaColor; + @Styleable protected Color disabledForeground; + @Styleable protected Color selectedBackground; + @Styleable protected Color selectedForeground; + @Styleable protected Color underlineColor; + @Styleable protected Color disabledUnderlineColor; + @Styleable protected Color hoverColor; + @Styleable protected Color focusColor; + @Styleable protected Color tabSeparatorColor; + @Styleable protected Color contentAreaColor; private int textIconGapUnscaled; - protected int minimumTabWidth; - protected int maximumTabWidth; - protected int tabHeight; - protected int tabSelectionHeight; - protected int contentSeparatorHeight; - protected boolean showTabSeparators; - protected boolean tabSeparatorsFullHeight; - protected boolean hasFullBorder; - protected boolean tabsOpaque = true; + @Styleable protected int minimumTabWidth; + @Styleable protected int maximumTabWidth; + @Styleable protected int tabHeight; + @Styleable protected int tabSelectionHeight; + @Styleable protected int contentSeparatorHeight; + @Styleable protected boolean showTabSeparators; + @Styleable protected boolean tabSeparatorsFullHeight; + @Styleable protected boolean hasFullBorder; + @Styleable protected boolean tabsOpaque = true; - private int tabsPopupPolicy; - private int scrollButtonsPolicy; - private int scrollButtonsPlacement; + @Styleable(type=String.class) private int tabsPopupPolicy; + @Styleable(type=String.class) private int scrollButtonsPolicy; + @Styleable(type=String.class) private int scrollButtonsPlacement; - private int tabAreaAlignment; - private int tabAlignment; - private int tabWidthMode; + @Styleable(type=String.class) private int tabAreaAlignment; + @Styleable(type=String.class) private int tabAlignment; + @Styleable(type=String.class) private int tabWidthMode; protected Icon closeIcon; - protected String arrowType; - protected Insets buttonInsets; - protected int buttonArc; - protected Color buttonHoverBackground; - protected Color buttonPressedBackground; + @Styleable protected String arrowType; + @Styleable protected Insets buttonInsets; + @Styleable protected int buttonArc; + @Styleable protected Color buttonHoverBackground; + @Styleable protected Color buttonPressedBackground; - protected String moreTabsButtonToolTipText; + @Styleable protected String moreTabsButtonToolTipText; protected JViewport tabViewport; protected FlatWheelTabScroller wheelTabScroller; @@ -231,6 +238,8 @@ public class FlatTabbedPaneUI private boolean pressedTabClose; private Object[] oldRenderingHints; + private Map oldStyleValues; + private boolean closeIconShared = true; public static ComponentUI createUI( JComponent c ) { return new FlatTabbedPaneUI(); @@ -259,6 +268,8 @@ public class FlatTabbedPaneUI buttonPressedBackground = UIManager.getColor( "TabbedPane.buttonPressedBackground" ); super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); } @Override @@ -313,6 +324,7 @@ public class FlatTabbedPaneUI tabAlignment = parseAlignment( UIManager.getString( "TabbedPane.tabAlignment" ), CENTER ); tabWidthMode = parseTabWidthMode( UIManager.getString( "TabbedPane.tabWidthMode" ) ); closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" ); + closeIconShared = true; buttonInsets = UIManager.getInsets( "TabbedPane.buttonInsets" ); buttonArc = UIManager.getInt( "TabbedPane.buttonArc" ); @@ -361,6 +373,8 @@ public class FlatTabbedPaneUI buttonHoverBackground = null; buttonPressedBackground = null; + oldStyleValues = null; + MigLayoutVisualPadding.uninstall( tabPane ); } @@ -558,6 +572,78 @@ public class FlatTabbedPaneUI return new FlatScrollableTabButton( direction ); } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + // update buttons + for( Component c : tabPane.getComponents() ) { + if( c instanceof FlatTabAreaButton ) + ((FlatTabAreaButton)c).updateStyle(); + } + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + // close icon + if( key.startsWith( "close" ) ) { + if( !(closeIcon instanceof FlatTabbedPaneCloseIcon) ) + return new UnknownStyleException( key ); + + if( closeIconShared ) { + closeIcon = FlatStylingSupport.cloneIcon( closeIcon ); + closeIconShared = false; + } + + return ((FlatTabbedPaneCloseIcon)closeIcon).applyStyleProperty( key, value ); + } + + if( value instanceof String ) { + switch( key ) { + case "tabsPopupPolicy": value = parseTabsPopupPolicy( (String) value ); break; + case "scrollButtonsPolicy": value = parseScrollButtonsPolicy( (String) value ); break; + case "scrollButtonsPlacement": value = parseScrollButtonsPlacement( (String) value ); break; + + case "tabAreaAlignment": value = parseAlignment( (String) value, LEADING ); break; + case "tabAlignment": value = parseAlignment( (String) value, CENTER ); break; + case "tabWidthMode": value = parseTabWidthMode( (String) value ); break; + } + } else { + Object oldValue = null; + switch( key ) { + // BasicTabbedPaneUI + case "tabInsets": oldValue = tabInsets; tabInsets = (Insets) value; return oldValue; + case "tabAreaInsets": oldValue = tabAreaInsets; tabAreaInsets = (Insets) value; return oldValue; + case "textIconGap": + oldValue = textIconGapUnscaled; + textIconGapUnscaled = (int) value; + textIconGap = scale( textIconGapUnscaled ); + return oldValue; + } + } + + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, tabPane, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + Map> infos = new LinkedHashMap<>(); + infos.put( "tabInsets", Insets.class ); + infos.put( "tabAreaInsets", Insets.class ); + infos.put( "textIconGap", int.class ); + FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos ); + if( closeIcon instanceof FlatTabbedPaneCloseIcon ) + infos.putAll( ((FlatTabbedPaneCloseIcon)closeIcon).getStyleableInfos() ); + return infos; + } + protected void setRolloverTab( int x, int y ) { setRolloverTab( tabForCoordinate( tabPane, x, y ) ); } @@ -1567,6 +1653,12 @@ public class FlatTabbedPaneUI setArrowWidth( 10 ); } + protected void updateStyle() { + updateStyle( arrowType, + FlatTabbedPaneUI.this.foreground, FlatTabbedPaneUI.this.disabledForeground, + null, buttonHoverBackground, null, buttonPressedBackground ); + } + @Override protected Color deriveBackground( Color background ) { return FlatUIUtils.deriveColor( background, tabPane.getBackground() ); @@ -2351,6 +2443,12 @@ public class FlatTabbedPaneUI tabPane.repaint(); ensureSelectedTabIsVisibleLater(); break; + + case STYLE: + applyStyle( e.getNewValue() ); + tabPane.revalidate(); + tabPane.repaint(); + break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableCellBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableCellBorder.java index cd25abae..76263b12 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableCellBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableCellBorder.java @@ -16,11 +16,15 @@ package com.formdev.flatlaf.ui; +import java.awt.Color; import java.awt.Component; import java.awt.Graphics; +import java.awt.Insets; +import java.util.function.Function; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.plaf.TableUI; /** * Cell border for {@link javax.swing.table.DefaultTableCellRenderer} @@ -33,12 +37,54 @@ import javax.swing.UIManager; public class FlatTableCellBorder extends FlatLineBorder { - final boolean showCellFocusIndicator = UIManager.getBoolean( "Table.showCellFocusIndicator" ); + protected boolean showCellFocusIndicator = UIManager.getBoolean( "Table.showCellFocusIndicator" ); + + private Component c; protected FlatTableCellBorder() { super( UIManager.getInsets( "Table.cellMargins" ), UIManager.getColor( "Table.cellFocusColor" ) ); } + @Override + public Insets getBorderInsets( Component c, Insets insets ) { + Insets m = getStyleFromTableUI( c, ui -> ui.cellMargins ); + if( m != null ) + return scaleInsets( c, insets, m.top, m.left, m.bottom, m.right ); + + return super.getBorderInsets( c, insets ); + } + + @Override + public Color getLineColor() { + if( c != null ) { + Color color = getStyleFromTableUI( c, ui -> ui.cellFocusColor ); + if( color != null ) + return color; + } + return super.getLineColor(); + } + + @Override + public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { + this.c = c; + super.paintBorder( c, g, x, y, width, height ); + this.c = null; + } + + /** + * Because this borders are always shared for all tables, + * get border specific style from FlatTableUI. + */ + static T getStyleFromTableUI( Component c, Function f ) { + JTable table = (JTable) SwingUtilities.getAncestorOfClass( JTable.class, c ); + if( table != null ) { + TableUI ui = table.getUI(); + if( ui instanceof FlatTableUI ) + return f.apply( (FlatTableUI) ui ); + } + return null; + } + //---- class Default ------------------------------------------------------ /** @@ -74,6 +120,9 @@ public class FlatTableCellBorder { @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { + Boolean b = getStyleFromTableUI( c, ui -> ui.showCellFocusIndicator ); + boolean showCellFocusIndicator = (b != null) ? b : this.showCellFocusIndicator; + if( !showCellFocusIndicator ) { JTable table = (JTable) SwingUtilities.getAncestorOfClass( JTable.class, c ); if( table != null && !isSelectionEditable( table ) ) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderBorder.java index 3c992560..30f7a06b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderBorder.java @@ -21,6 +21,7 @@ import java.awt.Component; import java.awt.Container; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Insets; import java.awt.geom.Rectangle2D; import javax.swing.JScrollBar; import javax.swing.JScrollPane; @@ -51,12 +52,30 @@ public class FlatTableHeaderBorder super( UIManager.getInsets( "TableHeader.cellMargins" ) ); } + @Override + public Insets getBorderInsets( Component c, Insets insets ) { + JTableHeader header = (JTableHeader) SwingUtilities.getAncestorOfClass( JTableHeader.class, c ); + if( header != null ) { + if( header.getUI() instanceof FlatTableHeaderUI ) { + FlatTableHeaderUI ui = (FlatTableHeaderUI) header.getUI(); + if( ui.cellMargins != null ) { + Insets m = ui.cellMargins; + return scaleInsets( c, insets, m.top, m.left, m.bottom, m.right ); + } + } + } + + return super.getBorderInsets( c, insets ); + } + @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { JTableHeader header = (JTableHeader) SwingUtilities.getAncestorOfClass( JTableHeader.class, c ); boolean leftToRight = (header != null ? header : c).getComponentOrientation().isLeftToRight(); boolean paintLeft = !leftToRight; boolean paintRight = leftToRight; + Color separatorColor = this.separatorColor; + Color bottomSeparatorColor = this.bottomSeparatorColor; if( header != null ) { int hx = SwingUtilities.convertPoint( c, x, y, header ).x; @@ -68,6 +87,16 @@ public class FlatTableHeaderBorder if( hx + width >= header.getWidth() && leftToRight && hideTrailingVerticalLine( header ) ) paintRight = false; } + + // Because this border is always shared for all table headers, + // get border specific style from FlatTableHeaderUI. + if( header.getUI() instanceof FlatTableHeaderUI ) { + FlatTableHeaderUI ui = (FlatTableHeaderUI) header.getUI(); + if( ui.separatorColor != null ) + separatorColor = ui.separatorColor; + if( ui.bottomSeparatorColor != null ) + bottomSeparatorColor = ui.bottomSeparatorColor; + } } float lineWidth = UIScale.scale( 1f ); @@ -110,6 +139,12 @@ public class FlatTableHeaderBorder } protected boolean hideTrailingVerticalLine( JTableHeader header ) { + if( header.getUI() instanceof FlatTableHeaderUI ) { + FlatTableHeaderUI ui = (FlatTableHeaderUI) header.getUI(); + if( ui.showTrailingVerticalLine ) + return false; + } + if( showTrailingVerticalLine ) return false; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderUI.java index 5f6f6637..d0b15bcf 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderUI.java @@ -26,7 +26,8 @@ import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; -import java.util.Objects; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLabel; @@ -40,6 +41,9 @@ import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTableHeaderUI; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.UIScale; /** @@ -64,32 +68,52 @@ import com.formdev.flatlaf.util.UIScale; * @uiDefault TableHeader.bottomSeparatorColor Color * @uiDefault TableHeader.showTrailingVerticalLine boolean * + * + * + * @uiDefault Component.arrowType String chevron (default) or triangle + * @uiDefault Table.sortIconColor Color + * * @author Karl Tauber */ public class FlatTableHeaderUI extends BasicTableHeaderUI + implements StyleableUI { - protected Color bottomSeparatorColor; - protected int height; - protected int sortIconPosition; + @Styleable protected Color bottomSeparatorColor; + @Styleable protected int height; + @Styleable(type=String.class) protected int sortIconPosition; + + // for FlatTableHeaderBorder + @Styleable protected Insets cellMargins; + @Styleable protected Color separatorColor; + /** @since 2 */ @Styleable protected boolean showTrailingVerticalLine; + + // for FlatAscendingSortIcon and FlatDescendingSortIcon + // (needs to be public because icon classes are in another package) + @Styleable public String arrowType; + @Styleable public Color sortIconColor; + + private PropertyChangeListener propertyChangeListener; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatTableHeaderUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); bottomSeparatorColor = UIManager.getColor( "TableHeader.bottomSeparatorColor" ); height = UIManager.getInt( "TableHeader.height" ); - switch( Objects.toString( UIManager.getString( "TableHeader.sortIconPosition" ), "right" ) ) { - default: - case "right": sortIconPosition = SwingConstants.RIGHT; break; - case "left": sortIconPosition = SwingConstants.LEFT; break; - case "top": sortIconPosition = SwingConstants.TOP; break; - case "bottom": sortIconPosition = SwingConstants.BOTTOM; break; - } + sortIconPosition = parseSortIconPosition( UIManager.getString( "TableHeader.sortIconPosition" ) ); } @Override @@ -97,6 +121,62 @@ public class FlatTableHeaderUI super.uninstallDefaults(); bottomSeparatorColor = null; + + oldStyleValues = null; + } + + @Override + protected void installListeners() { + super.installListeners(); + + propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( header, this::applyStyle, null ); + header.addPropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + } + + @Override + protected void uninstallListeners() { + super.uninstallListeners(); + + header.removePropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + propertyChangeListener = null; + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + if( key.equals( "sortIconPosition" ) && value instanceof String ) + value = parseSortIconPosition( (String) value ); + + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, header, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + + private static int parseSortIconPosition( String str ) { + if( str == null ) + str = ""; + + switch( str ) { + default: + case "right": return SwingConstants.RIGHT; + case "left": return SwingConstants.LEFT; + case "top": return SwingConstants.TOP; + case "bottom": return SwingConstants.BOTTOM; + } } @Override diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java index 8a3b7424..c45507d0 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java @@ -22,10 +22,12 @@ import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Insets; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JViewport; @@ -36,6 +38,8 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicTableUI; import javax.swing.table.JTableHeader; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; @@ -90,27 +94,41 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatTableUI extends BasicTableUI + implements StyleableUI { protected boolean showHorizontalLines; protected boolean showVerticalLines; - /** @since 1.6 */ protected boolean showTrailingVerticalLine; + /** @since 1.6 */ @Styleable protected boolean showTrailingVerticalLine; protected Dimension intercellSpacing; - protected Color selectionBackground; - protected Color selectionForeground; - protected Color selectionInactiveBackground; - protected Color selectionInactiveForeground; + @Styleable protected Color selectionBackground; + @Styleable protected Color selectionForeground; + @Styleable protected Color selectionInactiveBackground; + @Styleable protected Color selectionInactiveForeground; + + // for FlatTableCellBorder + @Styleable protected Insets cellMargins; + @Styleable protected Color cellFocusColor; + @Styleable protected boolean showCellFocusIndicator; private boolean oldShowHorizontalLines; private boolean oldShowVerticalLines; private Dimension oldIntercellSpacing; private PropertyChangeListener propertyChangeListener; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatTableUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -155,6 +173,8 @@ public class FlatTableUI selectionInactiveBackground = null; selectionInactiveForeground = null; + oldStyleValues = null; + // restore old show horizontal/vertical lines (if not modified) if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() ) table.setShowHorizontalLines( true ); @@ -171,8 +191,17 @@ public class FlatTableUI super.installListeners(); propertyChangeListener = e -> { - if( FlatClientProperties.COMPONENT_FOCUS_OWNER.equals( e.getPropertyName() ) ) - toggleSelectionColors(); + switch( e.getPropertyName() ) { + case FlatClientProperties.COMPONENT_FOCUS_OWNER: + toggleSelectionColors(); + break; + + case FlatClientProperties.STYLE: + applyStyle( e.getNewValue() ); + table.revalidate(); + table.repaint(); + break; + } }; table.addPropertyChangeListener( propertyChangeListener ); } @@ -206,6 +235,51 @@ public class FlatTableUI }; } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + Color oldSelectionBackground = selectionBackground; + Color oldSelectionForeground = selectionForeground; + Color oldSelectionInactiveBackground = selectionInactiveBackground; + Color oldSelectionInactiveForeground = selectionInactiveForeground; + + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + // update selection background + if( selectionBackground != oldSelectionBackground ) { + Color selBg = table.getSelectionBackground(); + if( selBg == oldSelectionBackground ) + table.setSelectionBackground( selectionBackground ); + else if( selBg == oldSelectionInactiveBackground ) + table.setSelectionBackground( selectionInactiveBackground ); + } + + // update selection foreground + if( selectionForeground != oldSelectionForeground ) { + Color selFg = table.getSelectionForeground(); + if( selFg == oldSelectionForeground ) + table.setSelectionForeground( selectionForeground ); + else if( selFg == oldSelectionInactiveForeground ) + table.setSelectionForeground( selectionInactiveForeground ); + } + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + /** * Toggle selection colors from focused to inactive and vice versa. * diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java index f188353f..14b3f04b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java @@ -23,13 +23,14 @@ import java.awt.Graphics2D; import java.awt.Insets; import java.awt.event.FocusListener; import java.beans.PropertyChangeEvent; +import java.util.Map; import javax.swing.JComponent; import javax.swing.JTextArea; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; -import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTextAreaUI; -import javax.swing.text.JTextComponent; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.HiDPIUtils; /** @@ -60,17 +61,22 @@ import com.formdev.flatlaf.util.HiDPIUtils; */ public class FlatTextAreaUI extends BasicTextAreaUI + implements StyleableUI { - protected int minimumWidth; + @Styleable protected int minimumWidth; protected boolean isIntelliJTheme; - protected Color background; - protected Color disabledBackground; - protected Color inactiveBackground; - protected Color focusedBackground; + private Color background; + @Styleable protected Color disabledBackground; + @Styleable protected Color inactiveBackground; + @Styleable protected Color focusedBackground; + + private Color oldDisabledBackground; + private Color oldInactiveBackground; private Insets defaultMargin; private FocusListener focusListener; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatTextAreaUI(); @@ -80,7 +86,7 @@ public class FlatTextAreaUI public void installUI( JComponent c ) { super.installUI( c ); - updateBackground(); + applyStyle( FlatStylingSupport.getStyle( c ) ); } @Override @@ -105,6 +111,11 @@ public class FlatTextAreaUI disabledBackground = null; inactiveBackground = null; focusedBackground = null; + + oldDisabledBackground = null; + oldInactiveBackground = null; + + oldStyleValues = null; } @Override @@ -126,38 +137,46 @@ public class FlatTextAreaUI @Override protected void propertyChange( PropertyChangeEvent e ) { - super.propertyChange( e ); - FlatEditorPaneUI.propertyChange( getComponent(), e ); + // invoke updateBackground() before super.propertyChange() + String propertyName = e.getPropertyName(); + if( "editable".equals( propertyName ) || "enabled".equals( propertyName ) ) + updateBackground(); - switch( e.getPropertyName() ) { - case "editable": - case "enabled": - updateBackground(); - break; - } + super.propertyChange( e ); + FlatEditorPaneUI.propertyChange( getComponent(), e, this::applyStyle ); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldDisabledBackground = disabledBackground; + oldInactiveBackground = inactiveBackground; + + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + updateBackground(); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); } private void updateBackground() { - JTextComponent c = getComponent(); - - Color background = c.getBackground(); - if( !(background instanceof UIResource) ) - return; - - // do not update background if it currently has a unknown color (assigned from outside) - if( background != this.background && - background != disabledBackground && - background != inactiveBackground ) - return; - - Color newBackground = !c.isEnabled() - ? disabledBackground - : (!c.isEditable() - ? inactiveBackground - : this.background); - - if( newBackground != background ) - c.setBackground( newBackground ); + FlatTextFieldUI.updateBackground( getComponent(), background, + disabledBackground, inactiveBackground, + oldDisabledBackground, oldInactiveBackground ); } @Override diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextBorder.java index b57a1088..e446e670 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextBorder.java @@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui; import java.awt.Component; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; /** * Border for various text components (e.g. {@link javax.swing.JTextField}). @@ -29,7 +30,7 @@ import javax.swing.UIManager; public class FlatTextBorder extends FlatBorder { - protected final int arc = UIManager.getInt( "TextComponent.arc" ); + @Styleable protected int arc = UIManager.getInt( "TextComponent.arc" ); @Override protected int getArc( Component c ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java index 5a02d059..f3fc6a58 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java @@ -27,7 +27,10 @@ import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.FocusListener; import java.beans.PropertyChangeEvent; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JSpinner; @@ -40,6 +43,8 @@ import javax.swing.plaf.basic.BasicTextFieldUI; import javax.swing.text.Caret; import javax.swing.text.JTextComponent; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.JavaCompatibility; import com.formdev.flatlaf.util.UIScale; @@ -75,20 +80,36 @@ import com.formdev.flatlaf.util.UIScale; */ public class FlatTextFieldUI extends BasicTextFieldUI + implements StyleableUI { - protected int minimumWidth; + @Styleable protected int minimumWidth; protected boolean isIntelliJTheme; - protected Color placeholderForeground; - protected Color focusedBackground; + private Color background; + @Styleable protected Color disabledBackground; + @Styleable protected Color inactiveBackground; + @Styleable protected Color placeholderForeground; + @Styleable protected Color focusedBackground; + + private Color oldDisabledBackground; + private Color oldInactiveBackground; private Insets defaultMargin; private FocusListener focusListener; + private Map oldStyleValues; + private AtomicBoolean borderShared; public static ComponentUI createUI( JComponent c ) { return new FlatTextFieldUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -96,6 +117,9 @@ public class FlatTextFieldUI String prefix = getPropertyPrefix(); minimumWidth = UIManager.getInt( "Component.minimumWidth" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); + background = UIManager.getColor( prefix + ".background" ); + disabledBackground = UIManager.getColor( prefix + ".disabledBackground" ); + inactiveBackground = UIManager.getColor( prefix + ".inactiveBackground" ); placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" ); focusedBackground = UIManager.getColor( prefix + ".focusedBackground" ); @@ -110,9 +134,18 @@ public class FlatTextFieldUI protected void uninstallDefaults() { super.uninstallDefaults(); + background = null; + disabledBackground = null; + inactiveBackground = null; placeholderForeground = null; focusedBackground = null; + oldDisabledBackground = null; + oldInactiveBackground = null; + + oldStyleValues = null; + borderShared = null; + MigLayoutVisualPadding.uninstall( getComponent() ); } @@ -141,11 +174,15 @@ public class FlatTextFieldUI @Override protected void propertyChange( PropertyChangeEvent e ) { - super.propertyChange( e ); - propertyChange( getComponent(), e ); + String propertyName = e.getPropertyName(); + if( "editable".equals( propertyName ) || "enabled".equals( propertyName ) ) + updateBackground(); + else + super.propertyChange( e ); + propertyChange( getComponent(), e, this::applyStyle ); } - static void propertyChange( JTextComponent c, PropertyChangeEvent e ) { + static void propertyChange( JTextComponent c, PropertyChangeEvent e, Consumer applyStyle ) { switch( e.getPropertyName() ) { case FlatClientProperties.PLACEHOLDER_TEXT: case FlatClientProperties.COMPONENT_ROUND_RECT: @@ -156,9 +193,77 @@ public class FlatTextFieldUI case FlatClientProperties.MINIMUM_WIDTH: c.revalidate(); break; + + case FlatClientProperties.STYLE: + applyStyle.accept( e.getNewValue() ); + c.revalidate(); + c.repaint(); + break; } } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldDisabledBackground = disabledBackground; + oldInactiveBackground = inactiveBackground; + + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + updateBackground(); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + if( borderShared == null ) + borderShared = new AtomicBoolean( true ); + return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, getComponent(), borderShared ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this, getComponent().getBorder() ); + } + + private void updateBackground() { + updateBackground( getComponent(), background, + disabledBackground, inactiveBackground, + oldDisabledBackground, oldInactiveBackground ); + } + + // same functionality as BasicTextUI.updateBackground() + static void updateBackground( JTextComponent c, Color background, + Color disabledBackground, Color inactiveBackground, + Color oldDisabledBackground, Color oldInactiveBackground ) + { + Color oldBackground = c.getBackground(); + if( !(oldBackground instanceof UIResource) ) + return; + + // do not update background if it currently has a unknown color (assigned from outside) + if( oldBackground != background && + oldBackground != disabledBackground && + oldBackground != inactiveBackground && + oldBackground != oldDisabledBackground && + oldBackground != oldInactiveBackground ) + return; + + Color newBackground = !c.isEnabled() + ? disabledBackground + : (!c.isEditable() + ? inactiveBackground + : background); + + if( newBackground != oldBackground ) + c.setBackground( newBackground ); + } + @Override protected void paintSafely( Graphics g ) { paintBackground( g, getComponent(), isIntelliJTheme, focusedBackground ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextPaneUI.java index b2962876..eadc6eda 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextPaneUI.java @@ -23,11 +23,14 @@ import java.awt.Graphics2D; import java.awt.Insets; import java.awt.event.FocusListener; import java.beans.PropertyChangeEvent; +import java.util.Map; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicTextPaneUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.HiDPIUtils; /** @@ -58,20 +61,35 @@ import com.formdev.flatlaf.util.HiDPIUtils; */ public class FlatTextPaneUI extends BasicTextPaneUI + implements StyleableUI { - protected int minimumWidth; + @Styleable protected int minimumWidth; protected boolean isIntelliJTheme; - protected Color focusedBackground; + private Color background; + @Styleable protected Color disabledBackground; + @Styleable protected Color inactiveBackground; + @Styleable protected Color focusedBackground; + + private Color oldDisabledBackground; + private Color oldInactiveBackground; private Insets defaultMargin; private Object oldHonorDisplayProperties; private FocusListener focusListener; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatTextPaneUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -79,6 +97,9 @@ public class FlatTextPaneUI String prefix = getPropertyPrefix(); minimumWidth = UIManager.getInt( "Component.minimumWidth" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); + background = UIManager.getColor( prefix + ".background" ); + disabledBackground = UIManager.getColor( prefix + ".disabledBackground" ); + inactiveBackground = UIManager.getColor( prefix + ".inactiveBackground" ); focusedBackground = UIManager.getColor( prefix + ".focusedBackground" ); defaultMargin = UIManager.getInsets( prefix + ".margin" ); @@ -92,8 +113,16 @@ public class FlatTextPaneUI protected void uninstallDefaults() { super.uninstallDefaults(); + background = null; + disabledBackground = null; + inactiveBackground = null; focusedBackground = null; + oldDisabledBackground = null; + oldInactiveBackground = null; + + oldStyleValues = null; + getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties ); } @@ -116,8 +145,46 @@ public class FlatTextPaneUI @Override protected void propertyChange( PropertyChangeEvent e ) { + // invoke updateBackground() before super.propertyChange() + String propertyName = e.getPropertyName(); + if( "editable".equals( propertyName ) || "enabled".equals( propertyName ) ) + updateBackground(); + super.propertyChange( e ); - FlatEditorPaneUI.propertyChange( getComponent(), e ); + FlatEditorPaneUI.propertyChange( getComponent(), e, this::applyStyle ); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldDisabledBackground = disabledBackground; + oldInactiveBackground = inactiveBackground; + + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + updateBackground(); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + + private void updateBackground() { + FlatTextFieldUI.updateBackground( getComponent(), background, + disabledBackground, inactiveBackground, + oldDisabledBackground, oldInactiveBackground ); } @Override 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 index 207b05b2..b7ca5baf 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java @@ -877,6 +877,10 @@ debug*/ protected class FlatTitleLabelUI extends FlatLabelUI { + protected FlatTitleLabelUI() { + super( false ); + } + @Override protected void paintEnabledText( JLabel l, Graphics g, String s, int textX, int textY ) { boolean hasEmbeddedMenuBar = hasVisibleEmbeddedMenuBar( rootPane.getJMenuBar() ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToggleButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToggleButtonUI.java index 2d1a5f24..ef87d6bd 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToggleButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToggleButtonUI.java @@ -21,11 +21,15 @@ import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.beans.PropertyChangeEvent; +import java.util.Iterator; +import java.util.Map; import javax.swing.AbstractButton; import javax.swing.JComponent; import javax.swing.JToggleButton; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.util.UIScale; /** @@ -73,17 +77,23 @@ import com.formdev.flatlaf.util.UIScale; public class FlatToggleButtonUI extends FlatButtonUI { - protected int tabUnderlineHeight; - protected Color tabUnderlineColor; - protected Color tabDisabledUnderlineColor; - protected Color tabSelectedBackground; - protected Color tabHoverBackground; - protected Color tabFocusBackground; + @Styleable(dot=true) protected int tabUnderlineHeight; + @Styleable(dot=true) protected Color tabUnderlineColor; + @Styleable(dot=true) protected Color tabDisabledUnderlineColor; + @Styleable(dot=true) protected Color tabSelectedBackground; + @Styleable(dot=true) protected Color tabHoverBackground; + @Styleable(dot=true) protected Color tabFocusBackground; private boolean defaults_initialized = false; public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.createSharedUI( FlatToggleButtonUI.class, FlatToggleButtonUI::new ); + return FlatUIUtils.canUseSharedUI( c ) + ? FlatUIUtils.createSharedUI( FlatToggleButtonUI.class, () -> new FlatToggleButtonUI( true ) ) + : new FlatToggleButtonUI( false ); + } + + protected FlatToggleButtonUI( boolean shared ) { + super( shared ); } @Override @@ -136,6 +146,31 @@ public class FlatToggleButtonUI } } + /** + * @since 2 + */ + @Override + protected Object applyStyleProperty( AbstractButton b, String key, Object value ) { + if( key.startsWith( "help." ) ) + throw new UnknownStyleException( key ); + + return super.applyStyleProperty( b, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + Map> infos = super.getStyleableInfos( c ); + Iterator it = infos.keySet().iterator(); + while( it.hasNext() ) { + if( it.next().startsWith( "help." ) ) + it.remove(); + } + return infos; + } + static boolean isTabButton( Component c ) { return c instanceof JToggleButton && clientPropertyEquals( (JToggleButton) c, BUTTON_TYPE, BUTTON_TYPE_TAB ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java index 5f7ffa57..68b9325d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarBorder.java @@ -22,9 +22,11 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Rectangle; +import java.util.function.Function; import javax.swing.JToolBar; import javax.swing.SwingConstants; import javax.swing.UIManager; +import javax.swing.plaf.ToolBarUI; import com.formdev.flatlaf.util.UIScale; /** @@ -42,7 +44,7 @@ public class FlatToolBarBorder private static final int DOT_SIZE = 2; private static final int GRIP_SIZE = DOT_SIZE * 3; - protected final Color gripColor = UIManager.getColor( "ToolBar.gripColor" ); + protected Color gripColor = UIManager.getColor( "ToolBar.gripColor" ); public FlatToolBarBorder() { super( UIManager.getInsets( "ToolBar.borderMargins" ) ); @@ -56,7 +58,8 @@ public class FlatToolBarBorder try { FlatUIUtils.setRenderingHints( g2 ); - g2.setColor( gripColor ); + Color color = getStyleFromToolBarUI( c, ui -> ui.gripColor ); + g2.setColor( (color != null) ? color : gripColor ); paintGrip( c, g2, x, y, width, height ); } finally { g2.dispose(); @@ -90,7 +93,14 @@ public class FlatToolBarBorder @Override public Insets getBorderInsets( Component c, Insets insets ) { - insets = super.getBorderInsets( c, insets ); + Insets m = getStyleFromToolBarUI( c, ui -> ui.borderMargins ); + if( m != null ) { + int t = top, l = left, b = bottom, r = right; + top = m.top; left = m.left; bottom = m.bottom; right = m.right; + insets = super.getBorderInsets( c, insets ); + top = t; left = l; bottom = b; right = r; + } else + insets = super.getBorderInsets( c, insets ); // add grip inset if floatable if( c instanceof JToolBar && ((JToolBar)c).isFloatable() ) { @@ -106,4 +116,17 @@ public class FlatToolBarBorder return insets; } + + /** + * Because this border is shared for all toolbars, + * get border specific style from FlatToolBarUI. + */ + static T getStyleFromToolBarUI( Component c, Function f ) { + if( c instanceof JToolBar ) { + ToolBarUI ui = ((JToolBar)c).getUI(); + if( ui instanceof FlatToolBarUI ) + return f.apply( (FlatToolBarUI) ui ); + } + return null; + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarSeparatorUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarSeparatorUI.java index d30eb30c..31881e2b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarSeparatorUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarSeparatorUI.java @@ -22,6 +22,8 @@ import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.JComponent; import javax.swing.JSeparator; import javax.swing.JToolBar; @@ -29,6 +31,9 @@ import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicToolBarSeparatorUI; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JToolBar.Separator}. @@ -42,16 +47,36 @@ import javax.swing.plaf.basic.BasicToolBarSeparatorUI; */ public class FlatToolBarSeparatorUI extends BasicToolBarSeparatorUI + implements StyleableUI { private static final int LINE_WIDTH = 1; - protected int separatorWidth; - protected Color separatorColor; + @Styleable protected int separatorWidth; + @Styleable protected Color separatorColor; + private final boolean shared; private boolean defaults_initialized = false; + private PropertyChangeListener propertyChangeListener; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { - return FlatUIUtils.createSharedUI( FlatToolBarSeparatorUI.class, FlatToolBarSeparatorUI::new ); + return FlatUIUtils.canUseSharedUI( c ) + ? FlatUIUtils.createSharedUI( FlatToolBarSeparatorUI.class, () -> new FlatToolBarSeparatorUI( true ) ) + : new FlatToolBarSeparatorUI( false ); + } + + /** + * @since 2 + */ + protected FlatToolBarSeparatorUI( boolean shared ) { + this.shared = shared; + } + + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); } @Override @@ -73,7 +98,59 @@ public class FlatToolBarSeparatorUI @Override protected void uninstallDefaults( JSeparator s ) { super.uninstallDefaults( s ); + defaults_initialized = false; + oldStyleValues = null; + } + + @Override + protected void installListeners( JSeparator s ) { + super.installListeners( s ); + + propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( + s, style -> applyStyle( s, this, style ), null ); + s.addPropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + } + + @Override + protected void uninstallListeners( JSeparator s ) { + super.uninstallListeners( s ); + + s.removePropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + propertyChangeListener = null; + } + + private static void applyStyle( JSeparator s, FlatToolBarSeparatorUI ui, Object style ) { + if( style != null && ui.shared ) { + // unshare component UI if necessary + // updateUI() invokes applyStyle() from installUI() + s.updateUI(); + } else + ui.applyStyle( style ); + s.revalidate(); + s.repaint(); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObject( this, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); } @Override diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java index 86af0906..da64ed28 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java @@ -16,16 +16,21 @@ package com.formdev.flatlaf.ui; +import java.awt.Color; import java.awt.Component; import java.awt.Insets; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.AbstractButton; import javax.swing.JComponent; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicToolBarUI; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JToolBar}. @@ -46,13 +51,25 @@ import javax.swing.plaf.basic.BasicToolBarUI; * * @uiDefault ToolBar.focusableButtons boolean * + * + * + * @uiDefault ToolBar.borderMargins Insets + * @uiDefault ToolBar.gripColor Color + * * @author Karl Tauber */ public class FlatToolBarUI extends BasicToolBarUI + implements StyleableUI { /** @since 1.4 */ - protected boolean focusableButtons; + @Styleable protected boolean focusableButtons; + + // for FlatToolBarBorder + @Styleable protected Insets borderMargins; + @Styleable protected Color gripColor; + + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatToolBarUI(); @@ -65,6 +82,8 @@ public class FlatToolBarUI // disable focusable state of buttons (when switching from another Laf) if( !focusableButtons ) setButtonsFocusable( false ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); } @Override @@ -74,6 +93,8 @@ public class FlatToolBarUI // re-enable focusable state of buttons (when switching to another Laf) if( !focusableButtons ) setButtonsFocusable( true ); + + oldStyleValues = null; } @Override @@ -110,6 +131,38 @@ public class FlatToolBarUI }; } + @Override + protected PropertyChangeListener createPropertyListener() { + return FlatStylingSupport.createPropertyChangeListener( toolBar, this::applyStyle, super.createPropertyListener() ); + } + + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + boolean oldFocusableButtons = focusableButtons; + + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + if( focusableButtons != oldFocusableButtons ) + setButtonsFocusable( focusableButtons ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, toolBar, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + /** * @since 1.4 */ diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java index ebdb2b40..0bc07516 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java @@ -26,6 +26,7 @@ import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.CellRendererPane; import javax.swing.Icon; import javax.swing.JComponent; @@ -39,6 +40,8 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreePath; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.UIScale; /** @@ -94,25 +97,68 @@ import com.formdev.flatlaf.util.UIScale; * @uiDefault Tree.wideSelection boolean * @uiDefault Tree.showCellFocusIndicator boolean * + * + * + * @uiDefault Component.arrowType String chevron (default) or triangle + * @uiDefault Tree.icon.expandedColor Color + * + * + * + * @uiDefault Component.arrowType String chevron (default) or triangle + * @uiDefault Tree.icon.collapsedColor Color + * + * + * + * @uiDefault Tree.icon.leafColor Color + * + * + * + * @uiDefault Tree.icon.closedColor Color + * + * + * + * @uiDefault Tree.icon.openColor Color + * * @author Karl Tauber */ public class FlatTreeUI extends BasicTreeUI + implements StyleableUI { - protected Color selectionBackground; - protected Color selectionForeground; - protected Color selectionInactiveBackground; - protected Color selectionInactiveForeground; - protected Color selectionBorderColor; - protected boolean wideSelection; - protected boolean showCellFocusIndicator; + @Styleable protected Color selectionBackground; + @Styleable protected Color selectionForeground; + @Styleable protected Color selectionInactiveBackground; + @Styleable protected Color selectionInactiveForeground; + @Styleable protected Color selectionBorderColor; + @Styleable protected boolean wideSelection; + @Styleable protected boolean showCellFocusIndicator; + + // for icons + // (needs to be public because icon classes are in another package) + @Styleable(dot=true) public String iconArrowType; + @Styleable(dot=true) public Color iconExpandedColor; + @Styleable(dot=true) public Color iconCollapsedColor; + @Styleable(dot=true) public Color iconLeafColor; + @Styleable(dot=true) public Color iconClosedColor; + @Styleable(dot=true) public Color iconOpenColor; private Color defaultCellNonSelectionBackground; + private Color defaultSelectionBackground; + private Color defaultSelectionForeground; + private Color defaultSelectionBorderColor; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatTreeUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStylingSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -128,6 +174,9 @@ public class FlatTreeUI showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" ); defaultCellNonSelectionBackground = UIManager.getColor( "Tree.textBackground" ); + defaultSelectionBackground = selectionBackground; + defaultSelectionForeground = selectionForeground; + defaultSelectionBorderColor = selectionBorderColor; // scale int rowHeight = FlatUIUtils.getUIInt( "Tree.rowHeight", 16 ); @@ -150,6 +199,10 @@ public class FlatTreeUI selectionBorderColor = null; defaultCellNonSelectionBackground = null; + defaultSelectionBackground = null; + defaultSelectionForeground = null; + defaultSelectionBorderColor = null; + oldStyleValues = null; } @Override @@ -216,6 +269,12 @@ public class FlatTreeUI repaintWideDropLocation( tree.getDropLocation() ); } break; + + case STYLE: + applyStyle( e.getNewValue() ); + tree.revalidate(); + tree.repaint(); + break; } } }; @@ -250,6 +309,28 @@ public class FlatTreeUI return bounds; } + /** + * @since 2 + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since 2 + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, tree, key, value ); + } + + /** + * @since 2 + */ + @Override + public Map> getStyleableInfos( JComponent c ) { + return FlatStylingSupport.getAnnotatedStyleableInfos( this ); + } + /** * Same as super.paintRow(), but supports wide selection and uses * inactive selection background/foreground if tree is not focused. @@ -289,35 +370,32 @@ public class FlatTreeUI Component rendererComponent = currentCellRenderer.getTreeCellRendererComponent( tree, path.getLastPathComponent(), isSelected, isExpanded, isLeaf, row, cellHasFocus ); - // apply inactive selection background/foreground if tree is not focused + // renderer background/foreground Color oldBackgroundSelectionColor = null; if( isSelected && !hasFocus && !isDropRow ) { - if( rendererComponent instanceof DefaultTreeCellRenderer ) { - DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent; - if( renderer.getBackgroundSelectionColor() == selectionBackground ) { - oldBackgroundSelectionColor = renderer.getBackgroundSelectionColor(); - renderer.setBackgroundSelectionColor( selectionInactiveBackground ); - } - } else { - if( rendererComponent.getBackground() == selectionBackground ) - rendererComponent.setBackground( selectionInactiveBackground ); - } + // apply inactive selection background/foreground if tree is not focused + oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionInactiveBackground ); + setRendererForeground( rendererComponent, selectionInactiveForeground ); - if( rendererComponent.getForeground() == selectionForeground ) - rendererComponent.setForeground( selectionInactiveForeground ); + } else if( isSelected ) { + // update background/foreground if set via style + if( selectionBackground != defaultSelectionBackground ) + oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionBackground ); + if( selectionForeground != defaultSelectionForeground ) + setRendererForeground( rendererComponent, selectionForeground ); } - // remove focus selection border if exactly one item is selected + // update focus selection border Color oldBorderSelectionColor = null; if( isSelected && hasFocus && - (!showCellFocusIndicator || tree.getMinSelectionRow() == tree.getMaxSelectionRow()) && - rendererComponent instanceof DefaultTreeCellRenderer ) + (!showCellFocusIndicator || tree.getMinSelectionRow() == tree.getMaxSelectionRow()) ) { - DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent; - if( renderer.getBorderSelectionColor() == selectionBorderColor ) { - oldBorderSelectionColor = renderer.getBorderSelectionColor(); - renderer.setBorderSelectionColor( null ); - } + // remove focus selection border if exactly one item is selected or if showCellFocusIndicator is false + oldBorderSelectionColor = setRendererBorderSelectionColor( rendererComponent, null ); + + } else if( hasFocus && selectionBorderColor != defaultSelectionBorderColor ) { + // update focus selection border if set via style + oldBorderSelectionColor = setRendererBorderSelectionColor( rendererComponent, selectionBorderColor ); } // paint selection background @@ -365,6 +443,42 @@ public class FlatTreeUI ((DefaultTreeCellRenderer)rendererComponent).setBorderSelectionColor( oldBorderSelectionColor ); } + private Color setRendererBackgroundSelectionColor( Component rendererComponent, Color color ) { + Color oldColor = null; + + if( rendererComponent instanceof DefaultTreeCellRenderer ) { + DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent; + if( renderer.getBackgroundSelectionColor() == defaultSelectionBackground ) { + oldColor = renderer.getBackgroundSelectionColor(); + renderer.setBackgroundSelectionColor( color ); + } + } else { + if( rendererComponent.getBackground() == defaultSelectionBackground ) + rendererComponent.setBackground( color ); + } + + return oldColor; + } + + private void setRendererForeground( Component rendererComponent, Color color ) { + if( rendererComponent.getForeground() == defaultSelectionForeground ) + rendererComponent.setForeground( color ); + } + + private Color setRendererBorderSelectionColor( Component rendererComponent, Color color ) { + Color oldColor = null; + + if( rendererComponent instanceof DefaultTreeCellRenderer ) { + DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent; + if( renderer.getBorderSelectionColor() == defaultSelectionBorderColor ) { + oldColor = renderer.getBorderSelectionColor(); + renderer.setBorderSelectionColor( color ); + } + } + + return oldColor; + } + private void paintWideSelection( Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java index b3a9407e..c0dae004 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java @@ -863,6 +863,14 @@ debug*/ .computeIfAbsent( key, k -> newInstanceSupplier.get() ); } + /** + * Returns whether the component UI for the given component can be shared + * with other components. This is only possible if it does not have styles. + */ + public static boolean canUseSharedUI( JComponent c ) { + return FlatStylingSupport.getStyle( c ) == null; + } + //---- class RepaintFocusListener ----------------------------------------- public static class RepaintFocusListener diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/MigLayoutVisualPadding.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/MigLayoutVisualPadding.java index 6148f718..7df37989 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/MigLayoutVisualPadding.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/MigLayoutVisualPadding.java @@ -21,6 +21,7 @@ import java.awt.Insets; import java.beans.PropertyChangeListener; import java.util.function.Function; import javax.swing.JComponent; +import com.formdev.flatlaf.FlatClientProperties; /** * Support for MigLayout visual paddings. @@ -80,7 +81,7 @@ public class MigLayoutVisualPadding return new Insets( focusWidth, focusWidth, focusWidth, focusWidth ); } else return null; - }, "border" ); + }, "border", FlatClientProperties.STYLE ); } /** diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java new file mode 100644 index 00000000..b940debe --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java @@ -0,0 +1,74 @@ +/* + * 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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Insets; +import org.junit.jupiter.api.Test; + +/** + * @author Karl Tauber + */ +public class TestUIDefaultsLoader +{ + @Test + void parseValue() { + assertEquals( null, UIDefaultsLoader.parseValue( "dummy", "null", null ) ); + assertEquals( false, UIDefaultsLoader.parseValue( "dummy", "false", null ) ); + assertEquals( true, UIDefaultsLoader.parseValue( "dummy", "true", null ) ); + + assertEquals( "hello", UIDefaultsLoader.parseValue( "dummy", "hello", null ) ); + assertEquals( "hello", UIDefaultsLoader.parseValue( "dummy", "\"hello\"", null ) ); + assertEquals( "null", UIDefaultsLoader.parseValue( "dummy", "\"null\"", null ) ); + + assertEquals( 'a', UIDefaultsLoader.parseValue( "dummyChar", "a", null ) ); + assertEquals( 123, UIDefaultsLoader.parseValue( "dummy", "123", null ) ); + assertEquals( 123, UIDefaultsLoader.parseValue( "dummyWidth", "123", null ) ); + assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", null ) ); + assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummyWidth", "{float}1.23", null ) ); + + assertEquals( new Insets( 2,2,2,2 ), UIDefaultsLoader.parseValue( "dummyInsets", "2,2,2,2", null ) ); + assertEquals( new Dimension( 2,2 ), UIDefaultsLoader.parseValue( "dummySize", "2,2", null ) ); + assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummy", "#f00", null ) ); + assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummyColor", "#f00", null ) ); + } + + @Test + void parseValueWithJavaType() { + assertEquals( null, UIDefaultsLoader.parseValue( "dummy", "null", String.class ) ); + assertEquals( false, UIDefaultsLoader.parseValue( "dummy", "false", boolean.class ) ); + assertEquals( true, UIDefaultsLoader.parseValue( "dummy", "true", Boolean.class ) ); + + assertEquals( "hello", UIDefaultsLoader.parseValue( "dummy", "hello", String.class ) ); + assertEquals( "hello", UIDefaultsLoader.parseValue( "dummy", "\"hello\"", String.class ) ); + assertEquals( "null", UIDefaultsLoader.parseValue( "dummy", "\"null\"", String.class ) ); + assertEquals( null, UIDefaultsLoader.parseValue( "dummy", "null", String.class ) ); + + assertEquals( 'a', UIDefaultsLoader.parseValue( "dummy", "a", char.class ) ); + assertEquals( 'a', UIDefaultsLoader.parseValue( "dummy", "a", Character.class ) ); + assertEquals( 123, UIDefaultsLoader.parseValue( "dummy", "123", int.class ) ); + assertEquals( 123, UIDefaultsLoader.parseValue( "dummy", "123", Integer.class ) ); + assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", float.class ) ); + assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", Float.class ) ); + + assertEquals( new Insets( 2,2,2,2 ), UIDefaultsLoader.parseValue( "dummy", "2,2,2,2", Insets.class ) ); + assertEquals( new Dimension( 2,2 ), UIDefaultsLoader.parseValue( "dummy", "2,2", Dimension.class ) ); + assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummy", "#f00", Color.class ) ); + } +} 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 new file mode 100644 index 00000000..24a0545a --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java @@ -0,0 +1,1126 @@ +/* + * 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.assertTrue; +import static com.formdev.flatlaf.ui.TestUtils.assertMapEquals; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Insets; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.swing.*; +import javax.swing.table.JTableHeader; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import com.formdev.flatlaf.icons.*; + +/** + * @author Karl Tauber + */ +public class TestFlatStyleableInfo +{ + @BeforeAll + static void setup() { + TestUtils.setup( false ); + } + + @AfterAll + static void cleanup() { + TestUtils.cleanup(); + } + + private Map> expectedMap( Object... keyValuePairs ) { + Map> map = new LinkedHashMap<>(); + expectedMap( map, keyValuePairs ); + return map; + } + + private void expectedMap( Map> map, Object... keyValuePairs ) { + for( int i = 0; i < keyValuePairs.length; i += 2 ) + map.put( (String) keyValuePairs[i], (Class) keyValuePairs[i+1] ); + } + + //---- components --------------------------------------------------------- + + @Test + void button() { + JButton b = new JButton(); + FlatButtonUI ui = (FlatButtonUI) b.getUI(); + + Map> expected = new LinkedHashMap<>(); + button( expected ); + + //---- FlatHelpButtonIcon ---- + + expectedMap( expected, + "help.focusWidth", int.class, + "help.focusColor", Color.class, + "help.innerFocusWidth", float.class, + "help.borderWidth", int.class, + + "help.borderColor", Color.class, + "help.disabledBorderColor", Color.class, + "help.focusedBorderColor", Color.class, + "help.hoverBorderColor", Color.class, + "help.background", Color.class, + "help.disabledBackground", Color.class, + "help.focusedBackground", Color.class, + "help.hoverBackground", Color.class, + "help.pressedBackground", Color.class, + "help.questionMarkColor", Color.class, + "help.disabledQuestionMarkColor", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( b ) ); + } + + private void button( Map> expected ) { + expectedMap( expected, + "minimumWidth", int.class, + + "focusedBackground", Color.class, + "hoverBackground", Color.class, + "pressedBackground", Color.class, + "selectedBackground", Color.class, + "selectedForeground", Color.class, + "disabledBackground", Color.class, + "disabledText", Color.class, + "disabledSelectedBackground", Color.class, + + "default.background", Color.class, + "default.foreground", Color.class, + "default.focusedBackground", Color.class, + "default.hoverBackground", Color.class, + "default.pressedBackground", Color.class, + "default.boldText", boolean.class, + + "paintShadow", boolean.class, + "shadowWidth", int.class, + "shadowColor", Color.class, + "default.shadowColor", Color.class, + + "toolbar.spacingInsets", Insets.class, + "toolbar.hoverBackground", Color.class, + "toolbar.pressedBackground", Color.class, + "toolbar.selectedBackground", Color.class + ); + + // border + flatButtonBorder( expected ); + } + + @Test + void checkBox() { + JCheckBox c = new JCheckBox(); + FlatCheckBoxUI ui = (FlatCheckBoxUI) c.getUI(); + + assertTrue( ui.getDefaultIcon() instanceof FlatCheckBoxIcon ); + + // FlatCheckBoxUI extends FlatRadioButtonUI + Map> expected = new LinkedHashMap<>(); + radioButton( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void comboBox() { + JComboBox c = new JComboBox<>(); + FlatComboBoxUI ui = (FlatComboBoxUI) c.getUI(); + + Map> expected = expectedMap( + "padding", Insets.class, + + "minimumWidth", int.class, + "editorColumns", int.class, + "buttonStyle", String.class, + "arrowType", String.class, + "borderColor", Color.class, + "disabledBorderColor", Color.class, + + "editableBackground", Color.class, + "focusedBackground", Color.class, + "disabledBackground", Color.class, + "disabledForeground", Color.class, + + "buttonBackground", Color.class, + "buttonFocusedBackground", Color.class, + "buttonEditableBackground", Color.class, + "buttonArrowColor", Color.class, + "buttonDisabledArrowColor", Color.class, + "buttonHoverArrowColor", Color.class, + "buttonPressedArrowColor", Color.class, + + "popupBackground", Color.class + ); + + // border + flatRoundBorder( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void editorPane() { + JEditorPane c = new JEditorPane(); + FlatEditorPaneUI ui = (FlatEditorPaneUI) c.getUI(); + + Map> expected = expectedMap( + "minimumWidth", int.class, + "disabledBackground", Color.class, + "inactiveBackground", Color.class, + "focusedBackground", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void formattedTextField() { + JFormattedTextField c = new JFormattedTextField(); + FlatFormattedTextFieldUI ui = (FlatFormattedTextFieldUI) c.getUI(); + + // FlatFormattedTextFieldUI extends FlatTextFieldUI + Map> expected = new LinkedHashMap<>(); + textField( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void internalFrame() { + JInternalFrame c = new JInternalFrame(); + FlatInternalFrameUI ui = (FlatInternalFrameUI) c.getUI(); + + Map> expected = expectedMap( + "activeBorderColor", Color.class, + "inactiveBorderColor", Color.class, + "borderLineWidth", int.class, + "dropShadowPainted", boolean.class, + "borderMargins", Insets.class, + + "activeDropShadowColor", Color.class, + "activeDropShadowInsets", Insets.class, + "activeDropShadowOpacity", float.class, + "inactiveDropShadowColor", Color.class, + "inactiveDropShadowInsets", Insets.class, + "inactiveDropShadowOpacity", float.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void label() { + JLabel c = new JLabel(); + FlatLabelUI ui = (FlatLabelUI) c.getUI(); + + Map> expected = expectedMap( + "disabledForeground", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void list() { + JList c = new JList<>(); + FlatListUI ui = (FlatListUI) c.getUI(); + + Map> expected = expectedMap( + "selectionBackground", Color.class, + "selectionForeground", Color.class, + "selectionInactiveBackground", Color.class, + "selectionInactiveForeground", Color.class, + + // FlatListCellBorder + "cellMargins", Insets.class, + "cellFocusColor", Color.class, + "showCellFocusIndicator", boolean.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void menuBar() { + JMenuBar c = new JMenuBar(); + FlatMenuBarUI ui = (FlatMenuBarUI) c.getUI(); + + Map> expected = expectedMap( + "itemMargins", Insets.class, + "hoverBackground", Color.class, + "underlineSelectionBackground", Color.class, + "underlineSelectionColor", Color.class, + "underlineSelectionHeight", int.class, + + // FlatMenuBarBorder + "borderColor", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void menu() { + JMenu c = new JMenu(); + FlatMenuUI ui = (FlatMenuUI) c.getUI(); + + Map> expected = new LinkedHashMap<>(); + menuItem( expected ); + menuItem_arrowIcon( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void menuItem() { + JMenuItem c = new JMenuItem(); + FlatMenuItemUI ui = (FlatMenuItemUI) c.getUI(); + + Map> expected = new LinkedHashMap<>(); + menuItem( expected ); + menuItem_arrowIcon( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void checkBoxMenuItem() { + JCheckBoxMenuItem c = new JCheckBoxMenuItem(); + FlatCheckBoxMenuItemUI ui = (FlatCheckBoxMenuItemUI) c.getUI(); + + Map> expected = new LinkedHashMap<>(); + menuItem( expected ); + menuItem_checkIcon( expected ); + menuItem_arrowIcon( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void radioButtonMenuItem() { + JRadioButtonMenuItem c = new JRadioButtonMenuItem(); + FlatRadioButtonMenuItemUI ui = (FlatRadioButtonMenuItemUI) c.getUI(); + + Map> expected = new LinkedHashMap<>(); + menuItem( expected ); + menuItem_checkIcon( expected ); + menuItem_arrowIcon( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + private void menuItem( Map> expected ) { + expectedMap( expected, + "selectionBackground", Color.class, + "selectionForeground", Color.class, + "disabledForeground", Color.class, + "acceleratorForeground", Color.class, + "acceleratorSelectionForeground", Color.class + ); + + menuItemRenderer( expected ); + } + + private void menuItemRenderer( Map> expected ) { + expectedMap( expected, + "minimumWidth", int.class, + "minimumIconSize", Dimension.class, + "textAcceleratorGap", int.class, + "textNoAcceleratorGap", int.class, + "acceleratorArrowGap", int.class, + + "checkBackground", Color.class, + "checkMargins", Insets.class, + + "underlineSelectionBackground", Color.class, + "underlineSelectionCheckBackground", Color.class, + "underlineSelectionColor", Color.class, + "underlineSelectionHeight", int.class + ); + } + + private void menuItem_checkIcon( Map> expected ) { + expectedMap( expected, + "icon.checkmarkColor", Color.class, + "icon.disabledCheckmarkColor", Color.class, + "selectionForeground", Color.class + ); + } + + private void menuItem_arrowIcon( Map> expected ) { + expectedMap( expected, + "icon.arrowType", String.class, + "icon.arrowColor", Color.class, + "icon.disabledArrowColor", Color.class, + "selectionForeground", Color.class + ); + } + + @Test + void passwordField() { + JPasswordField c = new JPasswordField(); + FlatPasswordFieldUI ui = (FlatPasswordFieldUI) c.getUI(); + + Map> expected = new LinkedHashMap<>(); + expectedMap( expected, + "showCapsLock", boolean.class + ); + + // FlatPasswordFieldUI extends FlatTextFieldUI + textField( expected ); + + expectedMap( expected, + // capsLockIcon + "capsLockIconColor", Color.class + ); + + // border + flatTextBorder( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void popupMenu() { + JPopupMenu c = new JPopupMenu(); + FlatPopupMenuUI ui = (FlatPopupMenuUI) c.getUI(); + + Map> expected = expectedMap( + "borderInsets", Insets.class, + "borderColor", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void popupMenuSeparator() { + JPopupMenu.Separator c = new JPopupMenu.Separator(); + FlatPopupMenuSeparatorUI ui = (FlatPopupMenuSeparatorUI) c.getUI(); + + Map> expected = new LinkedHashMap<>(); + + // FlatPopupMenuSeparatorUI extends FlatSeparatorUI + separator( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void progressBar() { + JProgressBar c = new JProgressBar(); + FlatProgressBarUI ui = (FlatProgressBarUI) c.getUI(); + + Map> expected = expectedMap( + "arc", int.class, + "horizontalSize", Dimension.class, + "verticalSize", Dimension.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void radioButton() { + JRadioButton c = new JRadioButton(); + FlatRadioButtonUI ui = (FlatRadioButtonUI) c.getUI(); + + assertTrue( ui.getDefaultIcon() instanceof FlatRadioButtonIcon ); + + Map> expected = new LinkedHashMap<>(); + radioButton( expected ); + + expectedMap( expected, + "icon.centerDiameter", int.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + private void radioButton( Map> expected ) { + expectedMap( expected, + "disabledText", Color.class, + + //---- icon ---- + + "icon.focusWidth", int.class, + "icon.focusColor", Color.class, + "icon.arc", int.class, + + // enabled + "icon.borderColor", Color.class, + "icon.background", Color.class, + "icon.selectedBorderColor", Color.class, + "icon.selectedBackground", Color.class, + "icon.checkmarkColor", Color.class, + + // disabled + "icon.disabledBorderColor", Color.class, + "icon.disabledBackground", Color.class, + "icon.disabledCheckmarkColor", Color.class, + + // focused + "icon.focusedBorderColor", Color.class, + "icon.focusedBackground", Color.class, + "icon.selectedFocusedBorderColor", Color.class, + "icon.selectedFocusedBackground", Color.class, + "icon.selectedFocusedCheckmarkColor", Color.class, + + // hover + "icon.hoverBorderColor", Color.class, + "icon.hoverBackground", Color.class, + "icon.selectedHoverBackground", Color.class, + + // pressed + "icon.pressedBackground", Color.class, + "icon.selectedPressedBackground", Color.class + ); + } + + @Test + void scrollBar() { + JScrollBar c = new JScrollBar(); + FlatScrollBarUI ui = (FlatScrollBarUI) c.getUI(); + + Map> expected = expectedMap( + "track", Color.class, + "thumb", Color.class, + "width", int.class, + "minimumThumbSize", Dimension.class, + "maximumThumbSize", Dimension.class, + "allowsAbsolutePositioning", boolean.class, + + "trackInsets", Insets.class, + "thumbInsets", Insets.class, + "trackArc", int.class, + "thumbArc", int.class, + "hoverTrackColor", Color.class, + "hoverThumbColor", Color.class, + "hoverThumbWithTrack", boolean.class, + "pressedTrackColor", Color.class, + "pressedThumbColor", Color.class, + "pressedThumbWithTrack", boolean.class, + + "showButtons", boolean.class, + "arrowType", String.class, + "buttonArrowColor", Color.class, + "buttonDisabledArrowColor", Color.class, + "hoverButtonBackground", Color.class, + "pressedButtonBackground", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void scrollPane() { + JScrollPane c = new JScrollPane(); + FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI(); + + Map> expected = new LinkedHashMap<>(); + + // border + flatBorder( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void separator() { + JSeparator c = new JSeparator(); + FlatSeparatorUI ui = (FlatSeparatorUI) c.getUI(); + + Map> expected = new LinkedHashMap<>(); + separator( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + private void separator( Map> expected ) { + expectedMap( expected, + "height", int.class, + "stripeWidth", int.class, + "stripeIndent", int.class + ); + } + + @Test + void slider() { + JSlider c = new JSlider(); + FlatSliderUI ui = (FlatSliderUI) c.getUI(); + + Map> expected = expectedMap( + "trackWidth", int.class, + "thumbSize", Dimension.class, + "focusWidth", int.class, + + "trackValueColor", Color.class, + "trackColor", Color.class, + "thumbColor", Color.class, + "thumbBorderColor", Color.class, + "focusedColor", Color.class, + "focusedThumbBorderColor", Color.class, + "hoverThumbColor", Color.class, + "pressedThumbColor", Color.class, + "disabledTrackColor", Color.class, + "disabledThumbColor", Color.class, + "disabledThumbBorderColor", Color.class, + "tickColor", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void spinner() { + JSpinner c = new JSpinner(); + FlatSpinnerUI ui = (FlatSpinnerUI) c.getUI(); + + Map> expected = expectedMap( + "minimumWidth", int.class, + "buttonStyle", String.class, + "arrowType", String.class, + "borderColor", Color.class, + "disabledBorderColor", Color.class, + "disabledBackground", Color.class, + "disabledForeground", Color.class, + "focusedBackground", Color.class, + "buttonBackground", Color.class, + "buttonArrowColor", Color.class, + "buttonDisabledArrowColor", Color.class, + "buttonHoverArrowColor", Color.class, + "buttonPressedArrowColor", Color.class, + "padding", Insets.class + ); + + // border + flatRoundBorder( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void splitPane() { + JSplitPane c = new JSplitPane(); + FlatSplitPaneUI ui = (FlatSplitPaneUI) c.getUI(); + + Map> expected = expectedMap( + "arrowType", String.class, + "oneTouchArrowColor", Color.class, + "oneTouchHoverArrowColor", Color.class, + "oneTouchPressedArrowColor", Color.class, + + "style", String.class, + "gripColor", Color.class, + "gripDotCount", int.class, + "gripDotSize", int.class, + "gripGap", int.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void tabbedPane() { + JTabbedPane c = new JTabbedPane(); + FlatTabbedPaneUI ui = (FlatTabbedPaneUI) c.getUI(); + + Map> expected = expectedMap( + "tabInsets", Insets.class, + "tabAreaInsets", Insets.class, + "textIconGap", int.class, + + "disabledForeground", Color.class, + + "selectedBackground", Color.class, + "selectedForeground", Color.class, + "underlineColor", Color.class, + "disabledUnderlineColor", Color.class, + "hoverColor", Color.class, + "focusColor", Color.class, + "tabSeparatorColor", Color.class, + "contentAreaColor", Color.class, + + "minimumTabWidth", int.class, + "maximumTabWidth", int.class, + "tabHeight", int.class, + "tabSelectionHeight", int.class, + "contentSeparatorHeight", int.class, + "showTabSeparators", boolean.class, + "tabSeparatorsFullHeight", boolean.class, + "hasFullBorder", boolean.class, + "tabsOpaque", boolean.class, + + "tabsPopupPolicy", String.class, + "scrollButtonsPolicy", String.class, + "scrollButtonsPlacement", String.class, + + "tabAreaAlignment", String.class, + "tabAlignment", String.class, + "tabWidthMode", String.class, + + "arrowType", String.class, + "buttonInsets", Insets.class, + "buttonArc", int.class, + "buttonHoverBackground", Color.class, + "buttonPressedBackground", Color.class, + + "moreTabsButtonToolTipText", String.class, + + // FlatTabbedPaneCloseIcon + "closeSize", Dimension.class, + "closeArc", int.class, + "closeCrossPlainSize", float.class, + "closeCrossFilledSize", float.class, + "closeCrossLineWidth", float.class, + "closeBackground", Color.class, + "closeForeground", Color.class, + "closeHoverBackground", Color.class, + "closeHoverForeground", Color.class, + "closePressedBackground", Color.class, + "closePressedForeground", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void table() { + JTable c = new JTable(); + FlatTableUI ui = (FlatTableUI) c.getUI(); + + Map> expected = expectedMap( + "showTrailingVerticalLine", boolean.class, + "selectionBackground", Color.class, + "selectionForeground", Color.class, + "selectionInactiveBackground", Color.class, + "selectionInactiveForeground", Color.class, + + // FlatTableCellBorder + "cellMargins", Insets.class, + "cellFocusColor", Color.class, + "showCellFocusIndicator", boolean.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void tableHeader() { + JTableHeader c = new JTableHeader(); + FlatTableHeaderUI ui = (FlatTableHeaderUI) c.getUI(); + + Map> expected = expectedMap( + "bottomSeparatorColor", Color.class, + "height", int.class, + "sortIconPosition", String.class, + + // FlatTableHeaderBorder + "cellMargins", Insets.class, + "separatorColor", Color.class, + "showTrailingVerticalLine", boolean.class, + + // FlatAscendingSortIcon and FlatDescendingSortIcon + "arrowType", String.class, + "sortIconColor", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void textArea() { + JTextArea c = new JTextArea(); + FlatTextAreaUI ui = (FlatTextAreaUI) c.getUI(); + + Map> expected = expectedMap( + "minimumWidth", int.class, + "disabledBackground", Color.class, + "inactiveBackground", Color.class, + "focusedBackground", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void textField() { + JTextField c = new JTextField(); + FlatTextFieldUI ui = (FlatTextFieldUI) c.getUI(); + + Map> expected = new LinkedHashMap<>(); + textField( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + private void textField( Map> expected ) { + expectedMap( expected, + "minimumWidth", int.class, + "disabledBackground", Color.class, + "inactiveBackground", Color.class, + "placeholderForeground", Color.class, + "focusedBackground", Color.class + ); + + // border + flatTextBorder( expected ); + } + + @Test + void textPane() { + JTextPane c = new JTextPane(); + FlatTextPaneUI ui = (FlatTextPaneUI) c.getUI(); + + Map> expected = expectedMap( + "minimumWidth", int.class, + "disabledBackground", Color.class, + "inactiveBackground", Color.class, + "focusedBackground", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void toggleButton() { + JToggleButton b = new JToggleButton(); + FlatToggleButtonUI ui = (FlatToggleButtonUI) b.getUI(); + + Map> expected = expectedMap( + "tab.underlineHeight", int.class, + "tab.underlineColor", Color.class, + "tab.disabledUnderlineColor", Color.class, + "tab.selectedBackground", Color.class, + "tab.hoverBackground", Color.class, + "tab.focusBackground", Color.class + ); + + // FlatToggleButtonUI extends FlatButtonUI + button( expected ); + + assertMapEquals( expected, ui.getStyleableInfos( b ) ); + } + + @Test + void toolBar() { + JToolBar c = new JToolBar(); + FlatToolBarUI ui = (FlatToolBarUI) c.getUI(); + + Map> expected = expectedMap( + "focusableButtons", boolean.class, + + "borderMargins", Insets.class, + "gripColor", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void toolBarSeparator() { + JToolBar.Separator c = new JToolBar.Separator(); + FlatToolBarSeparatorUI ui = (FlatToolBarSeparatorUI) c.getUI(); + + Map> expected = expectedMap( + "separatorWidth", int.class, + "separatorColor", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + @Test + void tree() { + JTree c = new JTree(); + FlatTreeUI ui = (FlatTreeUI) c.getUI(); + + Map> expected = expectedMap( + "selectionBackground", Color.class, + "selectionForeground", Color.class, + "selectionInactiveBackground", Color.class, + "selectionInactiveForeground", Color.class, + "selectionBorderColor", Color.class, + "wideSelection", boolean.class, + "showCellFocusIndicator", boolean.class, + + // icons + "icon.arrowType", String.class, + "icon.expandedColor", Color.class, + "icon.collapsedColor", Color.class, + "icon.leafColor", Color.class, + "icon.closedColor", Color.class, + "icon.openColor", Color.class + ); + + assertMapEquals( expected, ui.getStyleableInfos( c ) ); + } + + //---- component borders -------------------------------------------------- + + private void flatButtonBorder( Map> expected ) { + flatBorder( expected ); + + expectedMap( expected, + "borderColor", Color.class, + "disabledBorderColor", Color.class, + "focusedBorderColor", Color.class, + "hoverBorderColor", Color.class, + + "default.borderColor", Color.class, + "default.focusedBorderColor", Color.class, + "default.focusColor", Color.class, + "default.hoverBorderColor", Color.class, + "toolbar.focusColor", Color.class, + + "borderWidth", int.class, + "default.borderWidth", int.class, + "toolbar.margin", Insets.class, + "toolbar.spacingInsets", Insets.class, + "toolbar.focusWidth", float.class, + "arc", int.class + ); + } + + private void flatRoundBorder( Map> expected ) { + flatBorder( expected ); + + expectedMap( expected, + "arc", int.class + ); + } + + private void flatTextBorder( Map> expected ) { + flatBorder( expected ); + + expectedMap( expected, + "arc", int.class + ); + } + + private void flatBorder( Map> expected ) { + expectedMap( expected, + "focusWidth", int.class, + "innerFocusWidth", float.class, + "innerOutlineWidth", float.class, + "focusColor", Color.class, + "borderColor", Color.class, + "disabledBorderColor", Color.class, + "focusedBorderColor", Color.class, + + "error.borderColor", Color.class, + "error.focusedBorderColor", Color.class, + "warning.borderColor", Color.class, + "warning.focusedBorderColor", Color.class, + "custom.borderColor", Color.class + ); + } + + //---- borders ------------------------------------------------------------ + + @Test + void flatButtonBorder() { + FlatButtonBorder border = new FlatButtonBorder(); + + Map> expected = new LinkedHashMap<>(); + flatButtonBorder( expected ); + + assertMapEquals( expected, border.getStyleableInfos() ); + } + + @Test + void flatRoundBorder() { + FlatRoundBorder border = new FlatRoundBorder(); + + Map> expected = new LinkedHashMap<>(); + flatRoundBorder( expected ); + + assertMapEquals( expected, border.getStyleableInfos() ); + } + + @Test + void flatTextBorder() { + FlatTextBorder border = new FlatTextBorder(); + + Map> expected = new LinkedHashMap<>(); + flatTextBorder( expected ); + + assertMapEquals( expected, border.getStyleableInfos() ); + } + + @Test + void flatBorder() { + FlatBorder border = new FlatBorder(); + + Map> expected = new LinkedHashMap<>(); + flatBorder( expected ); + + assertMapEquals( expected, border.getStyleableInfos() ); + } + + //---- icons -------------------------------------------------------------- + + @Test + void flatCheckBoxIcon() { + FlatCheckBoxIcon icon = new FlatCheckBoxIcon(); + + Map> expected = new LinkedHashMap<>(); + flatCheckBoxIcon( expected ); + + assertMapEquals( expected, icon.getStyleableInfos() ); + } + + @Test + void flatRadioButtonIcon() { + FlatRadioButtonIcon icon = new FlatRadioButtonIcon(); + + // FlatRadioButtonIcon extends FlatCheckBoxIcon + Map> expected = new LinkedHashMap<>(); + flatCheckBoxIcon( expected ); + + expectedMap( expected, + "centerDiameter", int.class + ); + + assertMapEquals( expected, icon.getStyleableInfos() ); + } + + private void flatCheckBoxIcon( Map> expected ) { + expectedMap( expected, + "focusWidth", int.class, + "focusColor", Color.class, + "arc", int.class, + + // enabled + "borderColor", Color.class, + "background", Color.class, + "selectedBorderColor", Color.class, + "selectedBackground", Color.class, + "checkmarkColor", Color.class, + + // disabled + "disabledBorderColor", Color.class, + "disabledBackground", Color.class, + "disabledCheckmarkColor", Color.class, + + // focused + "focusedBorderColor", Color.class, + "focusedBackground", Color.class, + "selectedFocusedBorderColor", Color.class, + "selectedFocusedBackground", Color.class, + "selectedFocusedCheckmarkColor", Color.class, + + // hover + "hoverBorderColor", Color.class, + "hoverBackground", Color.class, + "selectedHoverBackground", Color.class, + + // pressed + "pressedBackground", Color.class, + "selectedPressedBackground", Color.class + ); + } + + @Test + void flatCheckBoxMenuItemIcon() { + FlatCheckBoxMenuItemIcon icon = new FlatCheckBoxMenuItemIcon(); + + Map> expected = new LinkedHashMap<>(); + flatCheckBoxMenuItemIcon( expected ); + + assertMapEquals( expected, icon.getStyleableInfos() ); + } + + @Test + void flatRadioButtonMenuItemIcon() { + FlatRadioButtonMenuItemIcon icon = new FlatRadioButtonMenuItemIcon(); + + // FlatRadioButtonMenuItemIcon extends FlatCheckBoxMenuItemIcon + Map> expected = new LinkedHashMap<>(); + flatCheckBoxMenuItemIcon( expected ); + + assertMapEquals( expected, icon.getStyleableInfos() ); + } + + private void flatCheckBoxMenuItemIcon( Map> expected ) { + expectedMap( expected, + "checkmarkColor", Color.class, + "disabledCheckmarkColor", Color.class, + "selectionForeground", Color.class + ); + } + + @Test + void flatMenuArrowIcon() { + FlatMenuArrowIcon icon = new FlatMenuArrowIcon(); + + Map> expected = new LinkedHashMap<>(); + flatMenuArrowIcon( expected ); + + assertMapEquals( expected, icon.getStyleableInfos() ); + } + + @Test + void flatMenuItemArrowIcon() { + FlatMenuItemArrowIcon icon = new FlatMenuItemArrowIcon(); + + // FlatMenuItemArrowIcon extends FlatMenuArrowIcon + Map> expected = new LinkedHashMap<>(); + flatMenuArrowIcon( expected ); + + assertMapEquals( expected, icon.getStyleableInfos() ); + } + + private void flatMenuArrowIcon( Map> expected ) { + expectedMap( expected, + "arrowType", String.class, + "arrowColor", Color.class, + "disabledArrowColor", Color.class, + "selectionForeground", Color.class + ); + } + + @Test + void flatHelpButtonIcon() { + FlatHelpButtonIcon icon = new FlatHelpButtonIcon(); + + Map> expected = expectedMap( + "focusWidth", int.class, + "focusColor", Color.class, + "innerFocusWidth", float.class, + "borderWidth", int.class, + + "borderColor", Color.class, + "disabledBorderColor", Color.class, + "focusedBorderColor", Color.class, + "hoverBorderColor", Color.class, + "background", Color.class, + "disabledBackground", Color.class, + "focusedBackground", Color.class, + "hoverBackground", Color.class, + "pressedBackground", Color.class, + "questionMarkColor", Color.class, + "disabledQuestionMarkColor", Color.class + ); + + assertMapEquals( expected, icon.getStyleableInfos() ); + } +} 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 new file mode 100644 index 00000000..8f458c99 --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java @@ -0,0 +1,1281 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertTrue; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Insets; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import javax.swing.*; +import javax.swing.table.JTableHeader; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import com.formdev.flatlaf.icons.*; + +/** + * @author Karl Tauber + */ +public class TestFlatStyling +{ + @BeforeAll + static void setup() { + TestUtils.setup( false ); + } + + @AfterAll + static void cleanup() { + TestUtils.cleanup(); + } + + @Test + void parse() { + assertEquals( null, FlatStylingSupport.parse( null ) ); + assertEquals( null, FlatStylingSupport.parse( "" ) ); + assertEquals( null, FlatStylingSupport.parse( " " ) ); + assertEquals( null, FlatStylingSupport.parse( ";" ) ); + assertEquals( null, FlatStylingSupport.parse( " ; ; " ) ); + + assertEquals( + expectedMap( "background", Color.WHITE ), + FlatStylingSupport.parse( "background: #fff" ) ); + assertEquals( + expectedMap( "background", Color.WHITE, "foreground", Color.BLACK ), + FlatStylingSupport.parse( "background: #fff; foreground: #000" ) ); + assertEquals( + expectedMap( "background", Color.WHITE, "foreground", Color.BLACK, "someWidth", 20 ), + FlatStylingSupport.parse( "background: #fff; foreground: #000; someWidth: 20" ) ); + } + + @Test + void parseColorFunctions() { + testColorStyle( 0x0c2238, "rgb(12,34,56)" ); + testColorStyle( 0x4e0c2238, "rgba(12,34,56,78)" ); + testColorStyle( 0xb57869, "hsl(12,34%,56%)" ); + testColorStyle( 0xc7b57869, "hsla(12,34%,56%,78%)" ); + + testColorStyle( 0xff6666, "lighten(#f00,20%)" ); + testColorStyle( 0x990000, "darken(#f00,20%)" ); + + testColorStyle( 0x9c3030, "saturate(#844,20%)" ); + testColorStyle( 0x745858, "desaturate(#844,20%)" ); + + testColorStyle( 0x4dff0000, "fadein(#ff000000,30%)" ); + testColorStyle( 0x99ff0000, "fadeout(#ff0000,40%)" ); + testColorStyle( 0x80ff0000, "fade(#ff0000,50%)" ); + + testColorStyle( 0xffaa00, "spin(#f00,40)" ); + testColorStyle( 0xff00aa, "spin(#f00,-40)" ); + + testColorStyle( 0x00ffff, "changeHue(#f00,180)" ); + testColorStyle( 0xbf4040, "changeSaturation(#f00,50%)" ); + testColorStyle( 0xff9999, "changeLightness(#f00,80%)" ); + testColorStyle( 0x80ff0000, "changeAlpha(#f00,50%)" ); + + testColorStyle( 0x1ae600, "mix(#f00,#0f0,10%)" ); + testColorStyle( 0x40bf00, "mix(#f00,#0f0,25%)" ); + testColorStyle( 0x808000, "mix(#f00,#0f0)" ); + testColorStyle( 0xbf4000, "mix(#f00,#0f0,75%)" ); + testColorStyle( 0xe61a00, "mix(#f00,#0f0,90%)" ); + + testColorStyle( 0xff40ff, "tint(#f0f,25%)" ); + testColorStyle( 0xff80ff, "tint(#f0f)" ); + testColorStyle( 0xffbfff, "tint(#f0f,75%)" ); + + testColorStyle( 0xbf00bf, "shade(#f0f,25%)" ); + testColorStyle( 0x800080, "shade(#f0f)" ); + testColorStyle( 0x400040, "shade(#f0f,75%)" ); + + // nested + testColorStyle( 0xd1c7c7, "saturate(darken(#fff,20%),10%)" ); + testColorStyle( 0xcf00cf, "shade(shade(#f0f,10%),10%)" ); + testColorStyle( 0xba00ba, "shade(shade(shade(#f0f,10%),10%),10%)" ); + } + + @Test + void parseReferences() { + assertEquals( Color.white, UIManager.getColor( "TextField.background" ) ); + + testColorStyle( 0xffffff, "$TextField.background" ); + testColorStyle( 0xcccccc, "darken($TextField.background,20%)" ); + testColorStyle( 0xd1c7c7, "saturate(darken($TextField.background,20%),10%)" ); + + testStyle( "hideMnemonics", true, "$Component.hideMnemonics" ); + testStyle( "arc", 6, "$Button.arc" ); + testStyle( "dropShadowOpacity", 0.15f, "$Popup.dropShadowOpacity" ); + testStyle( "margin", new Insets( 2, 14, 2, 14 ) , "$Button.margin" ); + testStyle( "iconSize", new Dimension( 64, 64 ), "$DesktopIcon.iconSize" ); + testStyle( "arrowType", "chevron", "$Component.arrowType" ); + } + + private void testColorStyle( int expectedRGB, String style ) { + testStyle( "background", new Color( expectedRGB, (expectedRGB & 0xff000000) != 0 ), style ); + } + + private void testStyle( String key, Object expected, String style ) { + assertEquals( + expectedMap( key, expected ), + FlatStylingSupport.parse( key + ": " + style ) ); + } + + private Map expectedMap( Object... keyValuePairs ) { + Map map = new HashMap<>(); + for( int i = 0; i < keyValuePairs.length; i += 2 ) + map.put( keyValuePairs[i], keyValuePairs[i+1] ); + return map; + } + + //---- components --------------------------------------------------------- + + @Test + void button() { + JButton b = new JButton(); + FlatButtonUI ui = (FlatButtonUI) b.getUI(); + + button( b, ui ); + + //---- FlatHelpButtonIcon ---- + + ui.applyStyle( b, "help.focusWidth: 2" ); + ui.applyStyle( b, "help.focusColor: #fff" ); + ui.applyStyle( b, "help.innerFocusWidth: {float}0.5" ); + ui.applyStyle( b, "help.borderWidth: 1" ); + + ui.applyStyle( b, "help.borderColor: #fff" ); + ui.applyStyle( b, "help.disabledBorderColor: #fff" ); + ui.applyStyle( b, "help.focusedBorderColor: #fff" ); + ui.applyStyle( b, "help.hoverBorderColor: #fff" ); + ui.applyStyle( b, "help.background: #fff" ); + ui.applyStyle( b, "help.disabledBackground: #fff" ); + ui.applyStyle( b, "help.focusedBackground: #fff" ); + ui.applyStyle( b, "help.hoverBackground: #fff" ); + ui.applyStyle( b, "help.pressedBackground: #fff" ); + ui.applyStyle( b, "help.questionMarkColor: #fff" ); + ui.applyStyle( b, "help.disabledQuestionMarkColor: #fff" ); + } + + private void button( AbstractButton b, FlatButtonUI ui ) { + ui.applyStyle( b, "minimumWidth: 100" ); + + ui.applyStyle( b, "focusedBackground: #fff" ); + ui.applyStyle( b, "hoverBackground: #fff" ); + ui.applyStyle( b, "pressedBackground: #fff" ); + ui.applyStyle( b, "selectedBackground: #fff" ); + ui.applyStyle( b, "selectedForeground: #fff" ); + ui.applyStyle( b, "disabledBackground: #fff" ); + ui.applyStyle( b, "disabledText: #fff" ); + ui.applyStyle( b, "disabledSelectedBackground: #fff" ); + + ui.applyStyle( b, "default.background: #fff" ); + ui.applyStyle( b, "default.foreground: #fff" ); + ui.applyStyle( b, "default.focusedBackground: #fff" ); + ui.applyStyle( b, "default.hoverBackground: #fff" ); + ui.applyStyle( b, "default.pressedBackground: #fff" ); + ui.applyStyle( b, "default.boldText: true" ); + + ui.applyStyle( b, "paintShadow: true" ); + ui.applyStyle( b, "shadowWidth: 2" ); + ui.applyStyle( b, "shadowColor: #fff" ); + ui.applyStyle( b, "default.shadowColor: #fff" ); + + ui.applyStyle( b, "toolbar.spacingInsets: 1,2,3,4" ); + ui.applyStyle( b, "toolbar.hoverBackground: #fff" ); + ui.applyStyle( b, "toolbar.pressedBackground: #fff" ); + ui.applyStyle( b, "toolbar.selectedBackground: #fff" ); + + // border + flatButtonBorder( style -> ui.applyStyle( b, style ) ); + + // JComponent properties + ui.applyStyle( b, "background: #fff" ); + ui.applyStyle( b, "foreground: #fff" ); + ui.applyStyle( b, "border: 2,2,2,2,#f00" ); + + // AbstractButton properties + ui.applyStyle( b, "margin: 2,2,2,2" ); + } + + @Test + void checkBox() { + JCheckBox c = new JCheckBox(); + FlatCheckBoxUI ui = (FlatCheckBoxUI) c.getUI(); + + assertTrue( ui.getDefaultIcon() instanceof FlatCheckBoxIcon ); + + // FlatCheckBoxUI extends FlatRadioButtonUI + radioButton( ui, c ); + } + + @Test + void comboBox() { + JComboBox c = new JComboBox<>(); + FlatComboBoxUI ui = (FlatComboBoxUI) c.getUI(); + + ui.applyStyle( "padding: 1,2,3,4" ); + + ui.applyStyle( "minimumWidth: 100" ); + ui.applyStyle( "editorColumns: 10" ); + ui.applyStyle( "buttonStyle: auto" ); + ui.applyStyle( "arrowType: chevron" ); + ui.applyStyle( "borderColor: #fff" ); + ui.applyStyle( "disabledBorderColor: #fff" ); + + ui.applyStyle( "editableBackground: #fff" ); + ui.applyStyle( "focusedBackground: #fff" ); + ui.applyStyle( "disabledBackground: #fff" ); + ui.applyStyle( "disabledForeground: #fff" ); + + ui.applyStyle( "buttonBackground: #fff" ); + ui.applyStyle( "buttonFocusedBackground: #fff" ); + ui.applyStyle( "buttonEditableBackground: #fff" ); + ui.applyStyle( "buttonArrowColor: #fff" ); + ui.applyStyle( "buttonDisabledArrowColor: #fff" ); + ui.applyStyle( "buttonHoverArrowColor: #fff" ); + ui.applyStyle( "buttonPressedArrowColor: #fff" ); + + ui.applyStyle( "popupBackground: #fff" ); + + // border + flatRoundBorder( style -> ui.applyStyle( style ) ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void editorPane() { + JEditorPane c = new JEditorPane(); + FlatEditorPaneUI ui = (FlatEditorPaneUI) c.getUI(); + + ui.applyStyle( "minimumWidth: 100" ); + ui.applyStyle( "disabledBackground: #fff" ); + ui.applyStyle( "inactiveBackground: #fff" ); + ui.applyStyle( "focusedBackground: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JTextComponent properties + ui.applyStyle( "caretColor: #fff" ); + ui.applyStyle( "selectionColor: #fff" ); + ui.applyStyle( "selectedTextColor: #fff" ); + ui.applyStyle( "disabledTextColor: #fff" ); + } + + @Test + void formattedTextField() { + JFormattedTextField c = new JFormattedTextField(); + FlatFormattedTextFieldUI ui = (FlatFormattedTextFieldUI) c.getUI(); + + // FlatFormattedTextFieldUI extends FlatTextFieldUI + textField( ui ); + } + + @Test + void internalFrame() { + JInternalFrame c = new JInternalFrame(); + FlatInternalFrameUI ui = (FlatInternalFrameUI) c.getUI(); + + ui.applyStyle( "activeBorderColor: #fff" ); + ui.applyStyle( "inactiveBorderColor: #fff" ); + ui.applyStyle( "borderLineWidth: 123" ); + ui.applyStyle( "dropShadowPainted: false" ); + ui.applyStyle( "borderMargins: 1,2,3,4" ); + + ui.applyStyle( "activeDropShadowColor: #fff" ); + ui.applyStyle( "activeDropShadowInsets: 1,2,3,4" ); + ui.applyStyle( "activeDropShadowOpacity: 0.5" ); + ui.applyStyle( "inactiveDropShadowColor: #fff" ); + ui.applyStyle( "inactiveDropShadowInsets: 1,2,3,4" ); + ui.applyStyle( "inactiveDropShadowOpacity: 0.5" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void label() { + JLabel c = new JLabel(); + FlatLabelUI ui = (FlatLabelUI) c.getUI(); + + ui.applyStyle( c, "disabledForeground: #fff" ); + + // JComponent properties + ui.applyStyle( c, "background: #fff" ); + ui.applyStyle( c, "foreground: #fff" ); + ui.applyStyle( c, "border: 2,2,2,2,#f00" ); + + // JLabel properties + ui.applyStyle( c, "icon: com.formdev.flatlaf.icons.FlatTreeExpandedIcon" ); + } + + @Test + void list() { + JList c = new JList<>(); + FlatListUI ui = (FlatListUI) c.getUI(); + + ui.applyStyle( "selectionBackground: #fff" ); + ui.applyStyle( "selectionForeground: #fff" ); + ui.applyStyle( "selectionInactiveBackground: #fff" ); + ui.applyStyle( "selectionInactiveForeground: #fff" ); + + // FlatListCellBorder + ui.applyStyle( "cellMargins: 1,2,3,4" ); + ui.applyStyle( "cellFocusColor: #fff" ); + ui.applyStyle( "showCellFocusIndicator: true" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JList properties + ui.applyStyle( "visibleRowCount: 20" ); + } + + @Test + void menuBar() { + JMenuBar c = new JMenuBar(); + FlatMenuBarUI ui = (FlatMenuBarUI) c.getUI(); + + ui.applyStyle( "itemMargins: 1,2,3,4" ); + ui.applyStyle( "hoverBackground: #fff" ); + ui.applyStyle( "underlineSelectionBackground: #fff" ); + ui.applyStyle( "underlineSelectionColor: #fff" ); + ui.applyStyle( "underlineSelectionHeight: 3" ); + + // FlatMenuBarBorder + ui.applyStyle( "borderColor: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void menu() { + JMenu c = new JMenu(); + FlatMenuUI ui = (FlatMenuUI) c.getUI(); + + Consumer applyStyle = style -> ui.applyStyle( style ); + menuItem( applyStyle ); + menuItem_arrowIcon( applyStyle ); + } + + @Test + void menuItem() { + JMenuItem c = new JMenuItem(); + FlatMenuItemUI ui = (FlatMenuItemUI) c.getUI(); + + Consumer applyStyle = style -> ui.applyStyle( style ); + menuItem( applyStyle ); + menuItem_arrowIcon( applyStyle ); + } + + @Test + void checkBoxMenuItem() { + JCheckBoxMenuItem c = new JCheckBoxMenuItem(); + FlatCheckBoxMenuItemUI ui = (FlatCheckBoxMenuItemUI) c.getUI(); + + Consumer applyStyle = style -> ui.applyStyle( style ); + menuItem( applyStyle ); + menuItem_checkIcon( applyStyle ); + menuItem_arrowIcon( applyStyle ); + } + + @Test + void radioButtonMenuItem() { + JRadioButtonMenuItem c = new JRadioButtonMenuItem(); + FlatRadioButtonMenuItemUI ui = (FlatRadioButtonMenuItemUI) c.getUI(); + + Consumer applyStyle = style -> ui.applyStyle( style ); + menuItem( applyStyle ); + menuItem_checkIcon( applyStyle ); + menuItem_arrowIcon( 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 ); + + // JComponent properties + applyStyle.accept( "background: #fff" ); + applyStyle.accept( "foreground: #fff" ); + applyStyle.accept( "border: 2,2,2,2,#f00" ); + + // AbstractButton properties + applyStyle.accept( "margin: 2,2,2,2" ); + } + + 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( "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() { + JPasswordField c = new JPasswordField(); + FlatPasswordFieldUI ui = (FlatPasswordFieldUI) c.getUI(); + + // FlatPasswordFieldUI extends FlatTextFieldUI + textField( ui ); + + ui.applyStyle( "showCapsLock: true" ); + + // capsLockIcon + ui.applyStyle( "capsLockIconColor: #fff" ); + + // border + flatTextBorder( style -> ui.applyStyle( style ) ); + } + + @Test + void popupMenu() { + JPopupMenu c = new JPopupMenu(); + FlatPopupMenuUI ui = (FlatPopupMenuUI) c.getUI(); + + ui.applyStyle( "borderInsets: 1,2,3,4" ); + ui.applyStyle( "borderColor: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void popupMenuSeparator() { + JPopupMenu.Separator c = new JPopupMenu.Separator(); + FlatPopupMenuSeparatorUI ui = (FlatPopupMenuSeparatorUI) c.getUI(); + + // FlatPopupMenuSeparatorUI extends FlatSeparatorUI + separator( ui, c ); + } + + @Test + void progressBar() { + JProgressBar c = new JProgressBar(); + FlatProgressBarUI ui = (FlatProgressBarUI) c.getUI(); + + ui.applyStyle( "arc: 5" ); + ui.applyStyle( "horizontalSize: 100,12" ); + ui.applyStyle( "verticalSize: 12,100" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void radioButton() { + JRadioButton c = new JRadioButton(); + FlatRadioButtonUI ui = (FlatRadioButtonUI) c.getUI(); + + assertTrue( ui.getDefaultIcon() instanceof FlatRadioButtonIcon ); + + radioButton( ui, c ); + + ui.applyStyle( c, "icon.centerDiameter: 8" ); + } + + private void radioButton( FlatRadioButtonUI ui, AbstractButton b ) { + ui.applyStyle( b, "disabledText: #fff" ); + + // JComponent properties + ui.applyStyle( b, "background: #fff" ); + ui.applyStyle( b, "foreground: #fff" ); + ui.applyStyle( b, "border: 2,2,2,2,#f00" ); + + // AbstractButton properties + ui.applyStyle( b, "margin: 2,2,2,2" ); + + //---- icon ---- + + ui.applyStyle( b, "icon.focusWidth: 2" ); + ui.applyStyle( b, "icon.focusColor: #fff" ); + ui.applyStyle( b, "icon.arc: 5" ); + + // enabled + ui.applyStyle( b, "icon.borderColor: #fff" ); + ui.applyStyle( b, "icon.background: #fff" ); + ui.applyStyle( b, "icon.selectedBorderColor: #fff" ); + ui.applyStyle( b, "icon.selectedBackground: #fff" ); + ui.applyStyle( b, "icon.checkmarkColor: #fff" ); + + // disabled + ui.applyStyle( b, "icon.disabledBorderColor: #fff" ); + ui.applyStyle( b, "icon.disabledBackground: #fff" ); + ui.applyStyle( b, "icon.disabledCheckmarkColor: #fff" ); + + // focused + ui.applyStyle( b, "icon.focusedBorderColor: #fff" ); + ui.applyStyle( b, "icon.focusedBackground: #fff" ); + ui.applyStyle( b, "icon.selectedFocusedBorderColor: #fff" ); + ui.applyStyle( b, "icon.selectedFocusedBackground: #fff" ); + ui.applyStyle( b, "icon.selectedFocusedCheckmarkColor: #fff" ); + + // hover + ui.applyStyle( b, "icon.hoverBorderColor: #fff" ); + ui.applyStyle( b, "icon.hoverBackground: #fff" ); + ui.applyStyle( b, "icon.selectedHoverBackground: #fff" ); + + // pressed + ui.applyStyle( b, "icon.pressedBackground: #fff" ); + ui.applyStyle( b, "icon.selectedPressedBackground: #fff" ); + } + + @Test + void scrollBar() { + JScrollBar c = new JScrollBar(); + FlatScrollBarUI ui = (FlatScrollBarUI) c.getUI(); + + ui.applyStyle( "track: #fff" ); + ui.applyStyle( "thumb: #fff" ); + ui.applyStyle( "width: 10" ); + ui.applyStyle( "minimumThumbSize: 1,2" ); + ui.applyStyle( "maximumThumbSize: 1,2" ); + ui.applyStyle( "allowsAbsolutePositioning: true" ); + + ui.applyStyle( "trackInsets: 1,2,3,4" ); + ui.applyStyle( "thumbInsets: 1,2,3,4" ); + ui.applyStyle( "trackArc: 5" ); + ui.applyStyle( "thumbArc: 10" ); + ui.applyStyle( "hoverTrackColor: #fff" ); + ui.applyStyle( "hoverThumbColor: #fff" ); + ui.applyStyle( "hoverThumbWithTrack: true" ); + ui.applyStyle( "pressedTrackColor: #fff" ); + ui.applyStyle( "pressedThumbColor: #fff" ); + ui.applyStyle( "pressedThumbWithTrack: true" ); + + ui.applyStyle( "showButtons: true" ); + ui.applyStyle( "arrowType: chevron" ); + ui.applyStyle( "buttonArrowColor: #fff" ); + ui.applyStyle( "buttonDisabledArrowColor: #fff" ); + ui.applyStyle( "hoverButtonBackground: #fff" ); + ui.applyStyle( "pressedButtonBackground: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void scrollPane() { + JScrollPane c = new JScrollPane(); + FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI(); + + // border + flatBorder( style -> ui.applyStyle( style ) ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void separator() { + JSeparator c = new JSeparator(); + FlatSeparatorUI ui = (FlatSeparatorUI) c.getUI(); + + separator( ui, c ); + } + + private void separator( FlatSeparatorUI ui, JSeparator c ) { + ui.applyStyle( c, "height: 6" ); + ui.applyStyle( c, "stripeWidth: 2" ); + ui.applyStyle( c, "stripeIndent: 10" ); + + // JComponent properties + ui.applyStyle( c, "background: #fff" ); + ui.applyStyle( c, "foreground: #fff" ); + ui.applyStyle( c, "border: 2,2,2,2,#f00" ); + } + + @Test + void slider() { + JSlider c = new JSlider(); + FlatSliderUI ui = (FlatSliderUI) c.getUI(); + + ui.applyStyle( "trackWidth: 2" ); + ui.applyStyle( "thumbSize: 12,12" ); + ui.applyStyle( "focusWidth: 4" ); + + ui.applyStyle( "trackValueColor: #fff" ); + ui.applyStyle( "trackColor: #fff" ); + ui.applyStyle( "thumbColor: #fff" ); + ui.applyStyle( "thumbBorderColor: #fff" ); + ui.applyStyle( "focusedColor: #fff" ); + ui.applyStyle( "focusedThumbBorderColor: #fff" ); + ui.applyStyle( "hoverThumbColor: #fff" ); + ui.applyStyle( "pressedThumbColor: #fff" ); + ui.applyStyle( "disabledTrackColor: #fff" ); + ui.applyStyle( "disabledThumbColor: #fff" ); + ui.applyStyle( "disabledThumbBorderColor: #fff" ); + ui.applyStyle( "tickColor: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JSlider properties + ui.applyStyle( "paintLabels: true" ); + ui.applyStyle( "paintTicks: true" ); + ui.applyStyle( "paintTrack: true" ); + ui.applyStyle( "snapToTicks: true" ); + } + + @Test + void spinner() { + JSpinner c = new JSpinner(); + FlatSpinnerUI ui = (FlatSpinnerUI) c.getUI(); + + ui.applyStyle( "minimumWidth: 100" ); + ui.applyStyle( "buttonStyle: button" ); + ui.applyStyle( "arrowType: chevron" ); + ui.applyStyle( "borderColor: #fff" ); + ui.applyStyle( "disabledBorderColor: #fff" ); + ui.applyStyle( "disabledBackground: #fff" ); + ui.applyStyle( "disabledForeground: #fff" ); + ui.applyStyle( "focusedBackground: #fff" ); + ui.applyStyle( "buttonBackground: #fff" ); + ui.applyStyle( "buttonArrowColor: #fff" ); + ui.applyStyle( "buttonDisabledArrowColor: #fff" ); + ui.applyStyle( "buttonHoverArrowColor: #fff" ); + ui.applyStyle( "buttonPressedArrowColor: #fff" ); + ui.applyStyle( "padding: 1,2,3,4" ); + + // border + flatRoundBorder( style -> ui.applyStyle( style ) ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void splitPane() { + JSplitPane c = new JSplitPane(); + FlatSplitPaneUI ui = (FlatSplitPaneUI) c.getUI(); + + ui.applyStyle( "arrowType: chevron" ); + ui.applyStyle( "oneTouchArrowColor: #fff" ); + ui.applyStyle( "oneTouchHoverArrowColor: #fff" ); + ui.applyStyle( "oneTouchPressedArrowColor: #fff" ); + + ui.applyStyle( "style: grip" ); + ui.applyStyle( "gripColor: #fff" ); + ui.applyStyle( "gripDotCount: 3" ); + ui.applyStyle( "gripDotSize: {integer}3" ); + ui.applyStyle( "gripGap: 2" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JSplitPane properties + ui.applyStyle( "dividerSize: {integer}20" ); + } + + @Test + void tabbedPane() { + JTabbedPane c = new JTabbedPane(); + FlatTabbedPaneUI ui = (FlatTabbedPaneUI) c.getUI(); + + ui.applyStyle( "tabInsets: 1,2,3,4" ); + ui.applyStyle( "tabAreaInsets: 1,2,3,4" ); + ui.applyStyle( "textIconGap: 4" ); + + ui.applyStyle( "disabledForeground: #fff" ); + + ui.applyStyle( "selectedBackground: #fff" ); + ui.applyStyle( "selectedForeground: #fff" ); + ui.applyStyle( "underlineColor: #fff" ); + ui.applyStyle( "disabledUnderlineColor: #fff" ); + ui.applyStyle( "hoverColor: #fff" ); + ui.applyStyle( "focusColor: #fff" ); + ui.applyStyle( "tabSeparatorColor: #fff" ); + ui.applyStyle( "contentAreaColor: #fff" ); + + ui.applyStyle( "minimumTabWidth: 50" ); + ui.applyStyle( "maximumTabWidth: 100" ); + ui.applyStyle( "tabHeight: 30" ); + ui.applyStyle( "tabSelectionHeight: 3" ); + ui.applyStyle( "contentSeparatorHeight: 1" ); + ui.applyStyle( "showTabSeparators: false" ); + ui.applyStyle( "tabSeparatorsFullHeight: false" ); + ui.applyStyle( "hasFullBorder: false" ); + ui.applyStyle( "tabsOpaque: false" ); + + ui.applyStyle( "tabsPopupPolicy: asNeeded" ); + ui.applyStyle( "scrollButtonsPolicy: asNeeded" ); + ui.applyStyle( "scrollButtonsPlacement: both" ); + + ui.applyStyle( "tabAreaAlignment: leading" ); + ui.applyStyle( "tabAlignment: center" ); + ui.applyStyle( "tabWidthMode: preferred" ); + + ui.applyStyle( "arrowType: chevron" ); + ui.applyStyle( "buttonInsets: 1,2,3,4" ); + ui.applyStyle( "buttonArc: 3" ); + ui.applyStyle( "buttonHoverBackground: #fff" ); + ui.applyStyle( "buttonPressedBackground: #fff" ); + + ui.applyStyle( "moreTabsButtonToolTipText: Gimme more" ); + + // FlatTabbedPaneCloseIcon + ui.applyStyle( "closeSize: 16,16" ); + ui.applyStyle( "closeArc: 4" ); + ui.applyStyle( "closeCrossPlainSize: {float}7.5" ); + ui.applyStyle( "closeCrossFilledSize: {float}7.5" ); + ui.applyStyle( "closeCrossLineWidth: {float}1" ); + ui.applyStyle( "closeBackground: #fff" ); + ui.applyStyle( "closeForeground: #fff" ); + ui.applyStyle( "closeHoverBackground: #fff" ); + ui.applyStyle( "closeHoverForeground: #fff" ); + ui.applyStyle( "closePressedBackground: #fff" ); + ui.applyStyle( "closePressedForeground: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void table() { + JTable c = new JTable(); + FlatTableUI ui = (FlatTableUI) c.getUI(); + + ui.applyStyle( "showTrailingVerticalLine: true" ); + ui.applyStyle( "selectionBackground: #fff" ); + ui.applyStyle( "selectionForeground: #fff" ); + ui.applyStyle( "selectionInactiveBackground: #fff" ); + ui.applyStyle( "selectionInactiveForeground: #fff" ); + + // FlatTableCellBorder + ui.applyStyle( "cellMargins: 1,2,3,4" ); + ui.applyStyle( "cellFocusColor: #fff" ); + ui.applyStyle( "showCellFocusIndicator: true" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JTable properties + ui.applyStyle( "fillsViewportHeight: true" ); + ui.applyStyle( "rowHeight: 30" ); + ui.applyStyle( "showHorizontalLines: true" ); + ui.applyStyle( "showVerticalLines: true" ); + ui.applyStyle( "intercellSpacing: {dimension}1,1" ); + } + + @Test + void tableHeader() { + JTableHeader c = new JTableHeader(); + FlatTableHeaderUI ui = (FlatTableHeaderUI) c.getUI(); + + ui.applyStyle( "bottomSeparatorColor: #fff" ); + ui.applyStyle( "height: 20" ); + ui.applyStyle( "sortIconPosition: top" ); + + // FlatTableHeaderBorder + ui.applyStyle( "cellMargins: 1,2,3,4" ); + ui.applyStyle( "separatorColor: #fff" ); + ui.applyStyle( "showTrailingVerticalLine: true" ); + + // FlatAscendingSortIcon and FlatDescendingSortIcon + ui.applyStyle( "arrowType: chevron" ); + ui.applyStyle( "sortIconColor: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + } + + @Test + void textArea() { + JTextArea c = new JTextArea(); + FlatTextAreaUI ui = (FlatTextAreaUI) c.getUI(); + + ui.applyStyle( "minimumWidth: 100" ); + ui.applyStyle( "disabledBackground: #fff" ); + ui.applyStyle( "inactiveBackground: #fff" ); + ui.applyStyle( "focusedBackground: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JTextComponent properties + ui.applyStyle( "caretColor: #fff" ); + ui.applyStyle( "selectionColor: #fff" ); + ui.applyStyle( "selectedTextColor: #fff" ); + ui.applyStyle( "disabledTextColor: #fff" ); + } + + @Test + void textField() { + JTextField c = new JTextField(); + FlatTextFieldUI ui = (FlatTextFieldUI) c.getUI(); + + textField( ui ); + } + + private void textField( FlatTextFieldUI ui ) { + ui.applyStyle( "minimumWidth: 100" ); + ui.applyStyle( "disabledBackground: #fff" ); + ui.applyStyle( "inactiveBackground: #fff" ); + ui.applyStyle( "placeholderForeground: #fff" ); + ui.applyStyle( "focusedBackground: #fff" ); + + // border + flatTextBorder( style -> ui.applyStyle( style ) ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JTextComponent properties + ui.applyStyle( "caretColor: #fff" ); + ui.applyStyle( "selectionColor: #fff" ); + ui.applyStyle( "selectedTextColor: #fff" ); + ui.applyStyle( "disabledTextColor: #fff" ); + } + + @Test + void textPane() { + JTextPane c = new JTextPane(); + FlatTextPaneUI ui = (FlatTextPaneUI) c.getUI(); + + ui.applyStyle( "minimumWidth: 100" ); + ui.applyStyle( "disabledBackground: #fff" ); + ui.applyStyle( "inactiveBackground: #fff" ); + ui.applyStyle( "focusedBackground: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JTextComponent properties + ui.applyStyle( "caretColor: #fff" ); + ui.applyStyle( "selectionColor: #fff" ); + ui.applyStyle( "selectedTextColor: #fff" ); + ui.applyStyle( "disabledTextColor: #fff" ); + } + + @Test + void toggleButton() { + JToggleButton b = new JToggleButton(); + FlatToggleButtonUI ui = (FlatToggleButtonUI) b.getUI(); + + // FlatToggleButtonUI extends FlatButtonUI + button( b, ui ); + + ui.applyStyle( b, "tab.underlineHeight: 3" ); + ui.applyStyle( b, "tab.underlineColor: #fff" ); + ui.applyStyle( b, "tab.disabledUnderlineColor: #fff" ); + ui.applyStyle( b, "tab.selectedBackground: #fff" ); + ui.applyStyle( b, "tab.hoverBackground: #fff" ); + ui.applyStyle( b, "tab.focusBackground: #fff" ); + } + + @Test + void toolBar() { + JToolBar c = new JToolBar(); + FlatToolBarUI ui = (FlatToolBarUI) c.getUI(); + + ui.applyStyle( "focusableButtons: true" ); + + ui.applyStyle( "borderMargins: 1,2,3,4" ); + ui.applyStyle( "gripColor: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JToolBar properties + ui.applyStyle( "borderPainted: true" ); + ui.applyStyle( "floatable: true" ); + ui.applyStyle( "margin: 2,2,2,2" ); + ui.applyStyle( "rollover: true" ); + } + + @Test + void toolBarSeparator() { + JToolBar.Separator c = new JToolBar.Separator(); + FlatToolBarSeparatorUI ui = (FlatToolBarSeparatorUI) c.getUI(); + + ui.applyStyle( "separatorWidth: 6" ); + ui.applyStyle( "separatorColor: #fff" ); + } + + @Test + void tree() { + JTree c = new JTree(); + FlatTreeUI ui = (FlatTreeUI) c.getUI(); + + ui.applyStyle( "selectionBackground: #fff" ); + ui.applyStyle( "selectionForeground: #fff" ); + ui.applyStyle( "selectionInactiveBackground: #fff" ); + ui.applyStyle( "selectionInactiveForeground: #fff" ); + ui.applyStyle( "selectionBorderColor: #fff" ); + ui.applyStyle( "wideSelection: true" ); + ui.applyStyle( "showCellFocusIndicator: true" ); + + // icons + ui.applyStyle( "icon.arrowType: chevron" ); + ui.applyStyle( "icon.expandedColor: #fff" ); + ui.applyStyle( "icon.collapsedColor: #fff" ); + ui.applyStyle( "icon.leafColor: #fff" ); + ui.applyStyle( "icon.closedColor: #fff" ); + ui.applyStyle( "icon.openColor: #fff" ); + + // JComponent properties + ui.applyStyle( "background: #fff" ); + ui.applyStyle( "foreground: #fff" ); + ui.applyStyle( "border: 2,2,2,2,#f00" ); + + // JTree properties + ui.applyStyle( "rootVisible: true" ); + ui.applyStyle( "rowHeight: 30" ); + ui.applyStyle( "scrollsOnExpand: true" ); + ui.applyStyle( "showsRootHandles: true" ); + ui.applyStyle( "visibleRowCount: 20" ); + } + + //---- component borders -------------------------------------------------- + + private void flatButtonBorder( Consumer applyStyle ) { + flatBorder( applyStyle ); + + applyStyle.accept( "borderColor: #fff" ); + applyStyle.accept( "disabledBorderColor: #fff" ); + applyStyle.accept( "focusedBorderColor: #fff" ); + applyStyle.accept( "hoverBorderColor: #fff" ); + + applyStyle.accept( "default.borderColor: #fff" ); + applyStyle.accept( "default.focusedBorderColor: #fff" ); + applyStyle.accept( "default.focusColor: #fff" ); + applyStyle.accept( "default.hoverBorderColor: #fff" ); + applyStyle.accept( "toolbar.focusColor: #fff" ); + + applyStyle.accept( "borderWidth: 1" ); + applyStyle.accept( "default.borderWidth: 2" ); + applyStyle.accept( "toolbar.margin: 1,2,3,4" ); + applyStyle.accept( "toolbar.spacingInsets: 1,2,3,4" ); + applyStyle.accept( "toolbar.focusWidth: {float}1.5" ); + applyStyle.accept( "arc: 6" ); + } + + private void flatRoundBorder( Consumer applyStyle ) { + flatBorder( applyStyle ); + + applyStyle.accept( "arc: 6" ); + } + + private void flatTextBorder( Consumer applyStyle ) { + flatBorder( applyStyle ); + + applyStyle.accept( "arc: 6" ); + } + + private void flatBorder( Consumer applyStyle ) { + applyStyle.accept( "focusWidth: 2" ); + applyStyle.accept( "innerFocusWidth: {float}0.5" ); + applyStyle.accept( "innerOutlineWidth: {float}1.5" ); + applyStyle.accept( "focusColor: #fff" ); + applyStyle.accept( "borderColor: #fff" ); + applyStyle.accept( "disabledBorderColor: #fff" ); + applyStyle.accept( "focusedBorderColor: #fff" ); + + applyStyle.accept( "error.borderColor: #fff" ); + applyStyle.accept( "error.focusedBorderColor: #fff" ); + applyStyle.accept( "warning.borderColor: #fff" ); + applyStyle.accept( "warning.focusedBorderColor: #fff" ); + applyStyle.accept( "custom.borderColor: desaturate(#f00,50%,relative derived noAutoInverse)" ); + } + + //---- borders ------------------------------------------------------------ + + @Test + void flatButtonBorder() { + FlatButtonBorder border = new FlatButtonBorder(); + + // FlatButtonBorder extends FlatBorder + flatBorder( border ); + + border.applyStyleProperty( "borderColor", Color.WHITE ); + border.applyStyleProperty( "disabledBorderColor", Color.WHITE ); + border.applyStyleProperty( "focusedBorderColor", Color.WHITE ); + border.applyStyleProperty( "hoverBorderColor", Color.WHITE ); + + border.applyStyleProperty( "default.borderColor", Color.WHITE ); + border.applyStyleProperty( "default.focusedBorderColor", Color.WHITE ); + border.applyStyleProperty( "default.focusColor", Color.WHITE ); + border.applyStyleProperty( "default.hoverBorderColor", Color.WHITE ); + border.applyStyleProperty( "toolbar.focusColor", Color.WHITE ); + + border.applyStyleProperty( "borderWidth", 1 ); + border.applyStyleProperty( "default.borderWidth", 2 ); + border.applyStyleProperty( "toolbar.margin", new Insets( 1, 2, 3, 4 ) ); + border.applyStyleProperty( "toolbar.spacingInsets", new Insets( 1, 2, 3, 4 ) ); + border.applyStyleProperty( "toolbar.focusWidth", 1.5f ); + border.applyStyleProperty( "arc", 6 ); + } + + @Test + void flatRoundBorder() { + FlatRoundBorder border = new FlatRoundBorder(); + + // FlatRoundBorder extends FlatBorder + flatBorder( border ); + + border.applyStyleProperty( "arc", 6 ); + } + + @Test + void flatTextBorder() { + FlatTextBorder border = new FlatTextBorder(); + + // FlatTextBorder extends FlatBorder + flatBorder( border ); + + border.applyStyleProperty( "arc", 6 ); + } + + @Test + void flatBorder() { + FlatBorder border = new FlatBorder(); + + flatBorder( border ); + } + + private void flatBorder( FlatBorder border ) { + border.applyStyleProperty( "focusWidth", 2 ); + border.applyStyleProperty( "innerFocusWidth", 0.5f ); + border.applyStyleProperty( "innerOutlineWidth", 1.5f ); + border.applyStyleProperty( "focusColor", Color.WHITE ); + border.applyStyleProperty( "borderColor", Color.WHITE ); + border.applyStyleProperty( "disabledBorderColor", Color.WHITE ); + border.applyStyleProperty( "focusedBorderColor", Color.WHITE ); + + border.applyStyleProperty( "error.borderColor", Color.WHITE ); + border.applyStyleProperty( "error.focusedBorderColor", Color.WHITE ); + border.applyStyleProperty( "warning.borderColor", Color.WHITE ); + border.applyStyleProperty( "warning.focusedBorderColor", Color.WHITE ); + border.applyStyleProperty( "custom.borderColor", Color.WHITE ); + } + + //---- icons -------------------------------------------------------------- + + @Test + void flatCheckBoxIcon() { + FlatCheckBoxIcon icon = new FlatCheckBoxIcon(); + + flatCheckBoxIcon( icon ); + } + + @Test + void flatRadioButtonIcon() { + FlatRadioButtonIcon icon = new FlatRadioButtonIcon(); + + // FlatRadioButtonIcon extends FlatCheckBoxIcon + flatCheckBoxIcon( icon ); + + icon.applyStyleProperty( "centerDiameter", 8 ); + } + + private void flatCheckBoxIcon( FlatCheckBoxIcon icon ) { + icon.applyStyleProperty( "focusWidth", 2 ); + icon.applyStyleProperty( "focusColor", Color.WHITE ); + icon.applyStyleProperty( "arc", 5 ); + + // enabled + icon.applyStyleProperty( "borderColor", Color.WHITE ); + icon.applyStyleProperty( "background", Color.WHITE ); + icon.applyStyleProperty( "selectedBorderColor", Color.WHITE ); + icon.applyStyleProperty( "selectedBackground", Color.WHITE ); + icon.applyStyleProperty( "checkmarkColor", Color.WHITE ); + + // disabled + icon.applyStyleProperty( "disabledBorderColor", Color.WHITE ); + icon.applyStyleProperty( "disabledBackground", Color.WHITE ); + icon.applyStyleProperty( "disabledCheckmarkColor", Color.WHITE ); + + // focused + icon.applyStyleProperty( "focusedBorderColor", Color.WHITE ); + icon.applyStyleProperty( "focusedBackground", Color.WHITE ); + icon.applyStyleProperty( "selectedFocusedBorderColor", Color.WHITE ); + icon.applyStyleProperty( "selectedFocusedBackground", Color.WHITE ); + icon.applyStyleProperty( "selectedFocusedCheckmarkColor", Color.WHITE ); + + // hover + icon.applyStyleProperty( "hoverBorderColor", Color.WHITE ); + icon.applyStyleProperty( "hoverBackground", Color.WHITE ); + icon.applyStyleProperty( "selectedHoverBackground", Color.WHITE ); + + // pressed + 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 ); + } + + @Test + void flatHelpButtonIcon() { + FlatHelpButtonIcon icon = new FlatHelpButtonIcon(); + + icon.applyStyleProperty( "focusWidth", 2 ); + icon.applyStyleProperty( "focusColor", Color.WHITE ); + icon.applyStyleProperty( "innerFocusWidth", 0.5f ); + icon.applyStyleProperty( "borderWidth", 1 ); + + icon.applyStyleProperty( "borderColor", Color.WHITE ); + icon.applyStyleProperty( "disabledBorderColor", Color.WHITE ); + icon.applyStyleProperty( "focusedBorderColor", Color.WHITE ); + icon.applyStyleProperty( "hoverBorderColor", Color.WHITE ); + icon.applyStyleProperty( "background", Color.WHITE ); + icon.applyStyleProperty( "disabledBackground", Color.WHITE ); + icon.applyStyleProperty( "focusedBackground", Color.WHITE ); + icon.applyStyleProperty( "hoverBackground", Color.WHITE ); + icon.applyStyleProperty( "pressedBackground", Color.WHITE ); + icon.applyStyleProperty( "questionMarkColor", Color.WHITE ); + icon.applyStyleProperty( "disabledQuestionMarkColor", Color.WHITE ); + } + + @Test + void flatClearIcon() { + FlatClearIcon icon = new FlatClearIcon(); + + icon.applyStyleProperty( "clearIconColor", Color.WHITE ); + icon.applyStyleProperty( "clearIconHoverColor", Color.WHITE ); + icon.applyStyleProperty( "clearIconPressedColor", Color.WHITE ); + } + + @Test + void flatSearchIcon() { + FlatSearchIcon icon = new FlatSearchIcon(); + + flatSearchIcon( icon ); + } + + @Test + void flatSearchWithHistoryIcon() { + FlatSearchWithHistoryIcon icon = new FlatSearchWithHistoryIcon(); + + flatSearchIcon( icon ); + } + + private void flatSearchIcon( FlatSearchIcon icon ) { + icon.applyStyleProperty( "searchIconColor", Color.WHITE ); + icon.applyStyleProperty( "searchIconHoverColor", Color.WHITE ); + icon.applyStyleProperty( "searchIconPressedColor", Color.WHITE ); + } +} diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatTextComponents.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatTextComponents.java new file mode 100644 index 00000000..7b70d134 --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatTextComponents.java @@ -0,0 +1,157 @@ +/* + * 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 static com.formdev.flatlaf.FlatClientProperties.STYLE; +import java.util.function.Supplier; +import javax.swing.JEditorPane; +import javax.swing.JFormattedTextField; +import javax.swing.JPasswordField; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.JTextPane; +import javax.swing.UIManager; +import javax.swing.plaf.ColorUIResource; +import javax.swing.plaf.basic.BasicTextFieldUI; +import javax.swing.text.JTextComponent; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author Karl Tauber + */ +public class TestFlatTextComponents +{ + @BeforeAll + static void setup() { + TestUtils.setup( false ); + } + + @AfterAll + static void cleanup() { + TestUtils.cleanup(); + } + + @Test + void editorPane_updateBackground() { + textComponent_updateBackground( "EditorPane", JEditorPane::new ); + } + + @Test + void formattedTextField_updateBackground() { + textComponent_updateBackground( "FormattedTextField", JFormattedTextField::new ); + } + + @Test + void passwordField_updateBackground() { + textComponent_updateBackground( "PasswordField", JPasswordField::new ); + } + + @Test + void textArea_updateBackground() { + textComponent_updateBackground( "TextArea", JTextArea::new ); + } + + @Test + void textField_updateBackground() { + textComponent_updateBackground( "TextField", JTextField::new ); + } + + @Test + void textPane_updateBackground() { + textComponent_updateBackground( "TextPane", JTextPane::new ); + } + + @Test + void basicTextField_updateBackground() { + textComponent_updateBackground( "TextField", () -> { + JTextField c = new JTextField(); + c.setUI( new BasicTextFieldUI() ); + return c; + } ); + } + + private void textComponent_updateBackground( String prefix, Supplier createTextComponent ) { + ColorUIResource background = new ColorUIResource( 0xff0000 ); + ColorUIResource inactiveBackground = new ColorUIResource( 0x00ff00 ); + ColorUIResource disabledBackground = new ColorUIResource( 0x0000ff ); + + UIManager.put( prefix + ".background", background ); + UIManager.put( prefix + ".inactiveBackground", inactiveBackground ); + UIManager.put( prefix + ".disabledBackground", disabledBackground ); + + JTextComponent c = createTextComponent.get(); + + // without styling + assertEquals( background, c.getBackground() ); + c.setEditable( false ); assertEquals( inactiveBackground, c.getBackground() ); + c.setEnabled( false ); assertEquals( disabledBackground, c.getBackground() ); + c.setEditable( true ); assertEquals( disabledBackground, c.getBackground() ); + c.setEnabled( true ); assertEquals( background, c.getBackground() ); + + + if( !c.getUI().getClass().getSimpleName().startsWith( "Flat" ) ) + return; + + + // with styling + + ColorUIResource inactiveBackground1 = new ColorUIResource( 0x00ee00 ); + ColorUIResource disabledBackground1 = new ColorUIResource( 0x0000ee ); + ColorUIResource inactiveBackground2 = new ColorUIResource( 0x00dd00 ); + ColorUIResource disabledBackground2 = new ColorUIResource( 0x0000dd ); + String style1 = "inactiveBackground: #00ee00; disabledBackground: #0000ee"; + String style2 = "inactiveBackground: #00dd00; disabledBackground: #0000dd"; + + c.putClientProperty( STYLE, style1 ); + + assertEquals( background, c.getBackground() ); + c.setEditable( false ); assertEquals( inactiveBackground1, c.getBackground() ); + c.setEnabled( false ); assertEquals( disabledBackground1, c.getBackground() ); + c.setEditable( true ); assertEquals( disabledBackground1, c.getBackground() ); + c.setEnabled( true ); assertEquals( background, c.getBackground() ); + + c.putClientProperty( STYLE, null ); + assertEquals( background, c.getBackground() ); + + c.setEditable( false ); + c.putClientProperty( STYLE, style1 ); + assertEquals( inactiveBackground1, c.getBackground() ); + + c.putClientProperty( STYLE, null ); + assertEquals( inactiveBackground, c.getBackground() ); + + c.setEnabled( false ); + c.putClientProperty( STYLE, style1 ); + assertEquals( disabledBackground1, c.getBackground() ); + + + // change from style1 to style2 + c.putClientProperty( STYLE, style2 ); + assertEquals( disabledBackground2, c.getBackground() ); + + c.setEnabled( true ); + assertEquals( inactiveBackground2, c.getBackground() ); + + + // remove style + c.putClientProperty( STYLE, null ); + assertEquals( inactiveBackground, c.getBackground() ); + } +} 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 index e4bead06..d9399383 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestUtils.java @@ -17,7 +17,10 @@ package com.formdev.flatlaf.ui; import java.awt.Font; +import java.util.Map; +import java.util.Objects; import javax.swing.UIManager; +import org.opentest4j.AssertionFailedError; import com.formdev.flatlaf.FlatIntelliJLaf; import com.formdev.flatlaf.FlatLightLaf; import com.formdev.flatlaf.FlatSystemProperties; @@ -50,4 +53,15 @@ public class TestUtils public static void resetFont() { UIManager.put( "defaultFont", null ); } + + public static void assertMapEquals( Map expected, Map actual ) { + if( !Objects.equals( expected, actual ) ) { + String expectedStr = String.valueOf( expected ).replace( ", ", ",\n" ); + String actualStr = String.valueOf( actual ).replace( ", ", ",\n" ); + String msg = String.format( "expected: <%s> but was: <%s>", expectedStr, actualStr ); + + // pass expected/actual strings to exception for nice diff in IDE + throw new AssertionFailedError( msg, expectedStr, actualStr ); + } + } } diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatButton.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatButton.java index 38434c28..4b290775 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatButton.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatButton.java @@ -27,7 +27,7 @@ import javax.swing.JButton; */ public class FlatButton extends JButton - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { // NOTE: enum names must be equal to allowed strings public enum ButtonType { none, square, roundRect, tab, help, toolBarButton, borderless } diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatCheckBox.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatCheckBox.java new file mode 100644 index 00000000..bfdc60b5 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatCheckBox.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.extras.components; + +import javax.swing.JCheckBox; + +/** + * Subclass of {@link JCheckBox} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatCheckBox + extends JCheckBox + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatCheckBoxMenuItem.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatCheckBoxMenuItem.java new file mode 100644 index 00000000..711d91e0 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatCheckBoxMenuItem.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.extras.components; + +import javax.swing.JCheckBoxMenuItem; + +/** + * Subclass of {@link JCheckBoxMenuItem} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatCheckBoxMenuItem + extends JCheckBoxMenuItem + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatComboBox.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatComboBox.java index 6b9581c7..79e44158 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatComboBox.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatComboBox.java @@ -27,7 +27,7 @@ import javax.swing.JComboBox; */ public class FlatComboBox extends JComboBox - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns the placeholder text that is only painted if the editable combo box is empty. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatEditorPane.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatEditorPane.java index 403ccb43..02be8eff 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatEditorPane.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatEditorPane.java @@ -26,7 +26,7 @@ import javax.swing.JEditorPane; */ public class FlatEditorPane extends JEditorPane - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns minimum width of a component. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java index 4ebc554d..fe030a95 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java @@ -29,7 +29,7 @@ import com.formdev.flatlaf.extras.components.FlatTextField.SelectAllOnFocusPolic */ public class FlatFormattedTextField extends JFormattedTextField - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns the placeholder text that is only painted if the text field is empty. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatInternalFrame.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatInternalFrame.java new file mode 100644 index 00000000..cdc09538 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatInternalFrame.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.extras.components; + +import javax.swing.JInternalFrame; + +/** + * Subclass of {@link JInternalFrame} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatInternalFrame + extends JInternalFrame + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatLabel.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatLabel.java new file mode 100644 index 00000000..2577b9a4 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatLabel.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.extras.components; + +import javax.swing.JLabel; + +/** + * Subclass of {@link JLabel} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatLabel + extends JLabel + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatList.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatList.java new file mode 100644 index 00000000..b667e907 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatList.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.extras.components; + +import javax.swing.JList; + +/** + * Subclass of {@link JList} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatList + extends JList + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatMenu.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatMenu.java new file mode 100644 index 00000000..407ad500 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatMenu.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.extras.components; + +import javax.swing.JMenu; + +/** + * Subclass of {@link JMenu} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatMenu + extends JMenu + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatMenuBar.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatMenuBar.java new file mode 100644 index 00000000..b7e2520b --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatMenuBar.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.extras.components; + +import javax.swing.JMenuBar; + +/** + * Subclass of {@link JMenuBar} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatMenuBar + extends JMenuBar + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatMenuItem.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatMenuItem.java new file mode 100644 index 00000000..5895a9aa --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatMenuItem.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.extras.components; + +import javax.swing.JMenuItem; + +/** + * Subclass of {@link JMenuItem} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatMenuItem + extends JMenuItem + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java index 86495da2..3a9b027a 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java @@ -29,7 +29,7 @@ import com.formdev.flatlaf.extras.components.FlatTextField.SelectAllOnFocusPolic */ public class FlatPasswordField extends JPasswordField - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns the placeholder text that is only painted if the text field is empty. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPopupMenu.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPopupMenu.java new file mode 100644 index 00000000..e16757e5 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPopupMenu.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.extras.components; + +import javax.swing.JPopupMenu; + +/** + * Subclass of {@link JPopupMenu} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatPopupMenu + extends JPopupMenu + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPopupMenuSeparator.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPopupMenuSeparator.java new file mode 100644 index 00000000..37f0abb2 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPopupMenuSeparator.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.extras.components; + +import javax.swing.JPopupMenu; + +/** + * Subclass of {@link JPopupMenu.Separator} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatPopupMenuSeparator + extends JPopupMenu.Separator + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatProgressBar.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatProgressBar.java index c4698374..d0ede426 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatProgressBar.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatProgressBar.java @@ -26,7 +26,7 @@ import javax.swing.JProgressBar; */ public class FlatProgressBar extends JProgressBar - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns whether the progress bar has always the larger height even if no string is painted. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatRadioButton.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatRadioButton.java new file mode 100644 index 00000000..f19b0a04 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatRadioButton.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.extras.components; + +import javax.swing.JRadioButton; + +/** + * Subclass of {@link JRadioButton} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatRadioButton + extends JRadioButton + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatRadioButtonMenuItem.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatRadioButtonMenuItem.java new file mode 100644 index 00000000..6185ba61 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatRadioButtonMenuItem.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.extras.components; + +import javax.swing.JRadioButtonMenuItem; + +/** + * Subclass of {@link JRadioButtonMenuItem} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatRadioButtonMenuItem + extends JRadioButtonMenuItem + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatScrollBar.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatScrollBar.java index eba79afe..7d0a2fd1 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatScrollBar.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatScrollBar.java @@ -26,7 +26,7 @@ import javax.swing.JScrollBar; */ public class FlatScrollBar extends JScrollBar - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns whether the decrease/increase arrow buttons of a scrollbar are shown. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatScrollPane.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatScrollPane.java index 159b477d..f99f0d3b 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatScrollPane.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatScrollPane.java @@ -27,7 +27,7 @@ import javax.swing.JScrollPane; */ public class FlatScrollPane extends JScrollPane - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns whether the decrease/increase arrow buttons of a scrollbar are shown. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSeparator.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSeparator.java new file mode 100644 index 00000000..aa8b9a56 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSeparator.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.extras.components; + +import javax.swing.JSeparator; + +/** + * Subclass of {@link JSeparator} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatSeparator + extends JSeparator + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSlider.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSlider.java new file mode 100644 index 00000000..aa9aa6e5 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSlider.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.extras.components; + +import javax.swing.JSlider; + +/** + * Subclass of {@link JSlider} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatSlider + extends JSlider + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSpinner.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSpinner.java index f4e162f0..d9567bad 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSpinner.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSpinner.java @@ -27,7 +27,7 @@ import javax.swing.JSpinner; */ public class FlatSpinner extends JSpinner - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns minimum width of a component. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSplitPane.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSplitPane.java new file mode 100644 index 00000000..846ad68e --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatSplitPane.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.extras.components; + +import javax.swing.JSplitPane; + +/** + * Subclass of {@link JSplitPane} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatSplitPane + extends JSplitPane + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatStyleableComponent.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatStyleableComponent.java new file mode 100644 index 00000000..d2b3c70c --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatStyleableComponent.java @@ -0,0 +1,88 @@ +/* + * 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.extras.components; + +import java.util.Map; +import javax.swing.JComponent; +import com.formdev.flatlaf.FlatClientProperties; + +/** + * Interface for all styleable FlatLaf components. + *

+ * If you already have custom subclasses of Swing components, you can add this interface + * to your components to add styling getter and setter methods to them. + * + * @author Karl Tauber + * @since 2 + */ +public interface FlatStyleableComponent +{ + /** + * Returns the style of a component as String in CSS syntax ("key1: value1; key2: value2; ...") + * or {@code null} if no style has been assigned. + */ + default String getStyle() { + return (String) getClientProperty( FlatClientProperties.STYLE ); + } + + /** + * Specifies the style of a component as String in CSS syntax ("key1: value1; key2: value2; ..."). + *

+ * The keys are the same as used in UI defaults, but without component type prefix. + * E.g. for UI default {@code Slider.thumbSize} use key {@code thumbSize}. + *

+ * The syntax of the CSS values is the same as used in FlatLaf properties files + * (https://www.formdev.com/flatlaf/properties-files/), + * but some features are not supported (e.g. variables). + */ + default void setStyle( String style ) { + putClientProperty( FlatClientProperties.STYLE, style ); + } + + + /** + * Returns the style of a component as {@link java.util.Map}<String, Object> + * or {@code null} if no style has been assigned. + */ + @SuppressWarnings( "unchecked" ) + default Map getStyleMap() { + return (Map) getClientProperty( FlatClientProperties.STYLE ); + } + + /** + * Specifies the style of a component as {@link java.util.Map}<String, Object> with binary values. + *

+ * The keys are the same as used in UI defaults, but without component type prefix. + * E.g. for UI default {@code Slider.thumbSize} use key {@code thumbSize}. + *

+ * The values are not parsed from a string. They must be binary. + */ + default void setStyleMap( Map styleMap ) { + putClientProperty( FlatClientProperties.STYLE, styleMap ); + } + + + /** + * Overrides {@link JComponent#getClientProperty(Object)}. + */ + Object getClientProperty( Object key ); + + /** + * Overrides {@link JComponent#putClientProperty(Object, Object)}. + */ + void putClientProperty( Object key, Object value ); +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTabbedPane.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTabbedPane.java index bd04f9b4..22181f90 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTabbedPane.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTabbedPane.java @@ -31,7 +31,7 @@ import javax.swing.SwingConstants; */ public class FlatTabbedPane extends JTabbedPane - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns whether separators are shown between tabs. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTable.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTable.java new file mode 100644 index 00000000..627517d0 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTable.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.extras.components; + +import javax.swing.JTable; + +/** + * Subclass of {@link JTable} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatTable + extends JTable + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTableHeader.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTableHeader.java new file mode 100644 index 00000000..37e0e209 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTableHeader.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.extras.components; + +import javax.swing.table.JTableHeader; + +/** + * Subclass of {@link JTableHeader} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatTableHeader + extends JTableHeader + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextArea.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextArea.java index a0568d97..5ba1f7ac 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextArea.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextArea.java @@ -26,7 +26,7 @@ import javax.swing.JTextArea; */ public class FlatTextArea extends JTextArea - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns minimum width of a component. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java index bfa22dd2..8c8cc56e 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java @@ -28,7 +28,7 @@ import javax.swing.JTextField; */ public class FlatTextField extends JTextField - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns the placeholder text that is only painted if the text field is empty. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextPane.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextPane.java index 4660068a..84897ee6 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextPane.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextPane.java @@ -26,7 +26,7 @@ import javax.swing.JTextPane; */ public class FlatTextPane extends JTextPane - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns minimum width of a component. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToggleButton.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToggleButton.java index a342f890..226acb12 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToggleButton.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToggleButton.java @@ -28,7 +28,7 @@ import com.formdev.flatlaf.extras.components.FlatButton.ButtonType; */ public class FlatToggleButton extends JToggleButton - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns type of a button. diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToolBar.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToolBar.java new file mode 100644 index 00000000..3caff752 --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToolBar.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.extras.components; + +import javax.swing.JToolBar; + +/** + * Subclass of {@link JToolBar} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatToolBar + extends JToolBar + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToolBarSeparator.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToolBarSeparator.java new file mode 100644 index 00000000..37300b5d --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatToolBarSeparator.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.extras.components; + +import javax.swing.JToolBar; + +/** + * Subclass of {@link JToolBar.Separator} that provides easy access to FlatLaf specific client properties. + * + * @author Karl Tauber + * @since 2 + */ +public class FlatToolBarSeparator + extends JToolBar.Separator + implements FlatStyleableComponent +{ +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTree.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTree.java index 730b91be..83c0075c 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTree.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTree.java @@ -22,10 +22,11 @@ import javax.swing.JTree; /** * Subclass of {@link JTree} that provides easy access to FlatLaf specific client properties. * + * @author Karl Tauber */ public class FlatTree extends JTree - implements FlatComponentExtension + implements FlatComponentExtension, FlatStyleableComponent { /** * Returns if the tree shows a wide selection diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/UIDefaultsLoaderAccessor.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/UIDefaultsLoaderAccessor.java index fb4698d1..d6b35f90 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/UIDefaultsLoaderAccessor.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/UIDefaultsLoaderAccessor.java @@ -56,7 +56,8 @@ public class UIDefaultsLoaderAccessor Function resolver ) { ValueType[] resultValueType2 = new ValueType[1]; - Object result = UIDefaultsLoader.parseValue( key, value, resultValueType2, resolver, Collections.emptyList() ); + Object result = UIDefaultsLoader.parseValue( key, value, null, + resultValueType2, resolver, Collections.emptyList() ); resultValueType[0] = resultValueType2[0]; return result; } diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java index 0ba5110f..e3ad1e27 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java @@ -130,6 +130,11 @@ class FlatThemeFileEditor if( UIManager.getLookAndFeel() instanceof FlatDarkLaf ) darkLafMenuItem.setSelected( true ); + // highlight selected tab + tabbedPane.setStyle( + "[light]selectedBackground: lighten($TabbedPane.background,5%);" + + " [dark]selectedBackground: darken($TabbedPane.background,5%)" ); + // add "+" button to tabbed pane newButton = new JButton( new FlatSVGIcon( "com/formdev/flatlaf/themeeditor/icons/add.svg" ) ); newButton.setToolTipText( "New Properties File" ); diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatDarkLaf.properties b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatDarkLaf.properties index 04e68e5d..d274c2e0 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatDarkLaf.properties +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatDarkLaf.properties @@ -14,11 +14,6 @@ # limitations under the License. # -#---- TabbedPane ---- - -#TabbedPane.selectedBackground = darken($TabbedPane.background,5%) - - #---- FlatThemeEditorPane ---- FlatThemeEditorPane.background = #2b2b2b diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLightLaf.properties b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLightLaf.properties index d84c0966..0678c339 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLightLaf.properties +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLightLaf.properties @@ -14,11 +14,6 @@ # limitations under the License. # -#---- TabbedPane ---- - -#TabbedPane.selectedBackground = lighten($TabbedPane.background,5%) - - #---- FlatThemeEditorPane ---- FlatThemeEditorPane.background = #fff