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 dde7fa14..8abd2ad3 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -98,6 +98,7 @@ public abstract class FlatLaf private static List customDefaultsSources; private static Map globalExtraDefaults; private Map extraDefaults; + private static Function systemColorGetter; private String desktopPropertyName; private String desktopPropertyName2; @@ -897,14 +898,14 @@ public abstract class FlatLaf * E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}. *

* The global extra defaults are useful for smaller additional defaults that may change. - * E.g. accent color. Otherwise, FlatLaf properties files should be used. + * Otherwise, FlatLaf properties files should be used. * See {@link #registerCustomDefaultsSource(String)}. *

* The keys and values are strings in same format as in FlatLaf properties files. *

- * Sample that setups "FlatLaf Light" theme with red accent color: + * Sample that setups "FlatLaf Light" theme with white background color: *

{@code
-	 * FlatLaf.setGlobalExtraDefaults( Collections.singletonMap( "@accentColor", "#f00" ) );
+	 * FlatLaf.setGlobalExtraDefaults( Collections.singletonMap( "@background", "#fff" ) );
 	 * FlatLightLaf.setup();
 	 * }
* @@ -929,15 +930,15 @@ public abstract class FlatLaf * E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}. *

* The extra defaults are useful for smaller additional defaults that may change. - * E.g. accent color. Otherwise, FlatLaf properties files should be used. + * Otherwise, FlatLaf properties files should be used. * See {@link #registerCustomDefaultsSource(String)}. *

* The keys and values are strings in same format as in FlatLaf properties files. *

- * Sample that setups "FlatLaf Light" theme with red accent color: + * Sample that setups "FlatLaf Light" theme with white background color: *

{@code
 	 * FlatLaf laf = new FlatLightLaf();
-	 * laf.setExtraDefaults( Collections.singletonMap( "@accentColor", "#f00" ) );
+	 * laf.setExtraDefaults( Collections.singletonMap( "@background", "#fff" ) );
 	 * FlatLaf.setup( laf );
 	 * }
* @@ -979,6 +980,36 @@ public abstract class FlatLaf return val; } + /** + * Returns the system color getter function, or {@code null}. + * + * @since 3 + */ + public static Function getSystemColorGetter() { + return systemColorGetter; + } + + /** + * Sets a system color getter function that is invoked when function + * {@code systemColor()} is used in FlatLaf properties files. + *

+ * The name of the system color is passed as parameter to the function. + * The function should return {@code null} for unknown system colors. + *

+ * Can be used to change the accent color: + *

{@code
+	 * FlatLaf.setSystemColorGetter( name -> {
+	 *     return name.equals( "accent" ) ? Color.red : null;
+	 * } );
+	 * FlatLightLaf.setup();
+	 * }
+ * + * @since 3 + */ + public static void setSystemColorGetter( Function systemColorGetter ) { + FlatLaf.systemColorGetter = systemColorGetter; + } + 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 aff6ba53..be07db40 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java @@ -302,7 +302,7 @@ public class IntelliJTheme for( Map.Entry e : colors.entrySet() ) { String value = e.getValue(); - ColorUIResource color = UIDefaultsLoader.parseColor( value ); + ColorUIResource color = parseColor( value ); if( color != null ) { String key = e.getKey(); namedColors.put( key, color ); @@ -448,7 +448,15 @@ public class IntelliJTheme ColorUIResource color = namedColors.get( value ); // parse color - return (color != null) ? color : UIDefaultsLoader.parseColor( value ); + return (color != null) ? color : parseColor( value ); + } + + private ColorUIResource parseColor( String value ) { + try { + return UIDefaultsLoader.parseColor( value ); + } catch( IllegalArgumentException ex ) { + return null; + } } /** @@ -540,7 +548,7 @@ public class IntelliJTheme // radioFocused.svg and radioSelectedFocused.svg // use opacity=".65" for the border // --> add alpha to focused border colors - String[] focusedBorderColorKeys = new String[] { + String[] focusedBorderColorKeys = { "CheckBox.icon.focusedBorderColor", "CheckBox.icon.focusedSelectedBorderColor", "CheckBox.icon[filled].focusedBorderColor", 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 294f753c..06c0e726 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -84,6 +84,7 @@ class UIDefaultsLoader private static int parseColorDepth; + private static Map systemColorCache; private static final SoftCache fontCache = new SoftCache<>(); static void loadDefaultsFromProperties( Class lookAndFeelClass, List addons, @@ -105,6 +106,10 @@ class UIDefaultsLoader Properties additionalDefaults, boolean dark, UIDefaults defaults ) { try { + // temporary cache system colors while loading defaults, + // which avoids that system color getter is invoked multiple times + systemColorCache = (FlatLaf.getSystemColorGetter() != null) ? new HashMap<>() : null; + // load core properties files Properties properties = new Properties(); for( Class lafClass : lafClasses ) { @@ -276,6 +281,9 @@ class UIDefaultsLoader // remember variables in defaults to allow using them in styles defaults.put( KEY_VARIABLES, variables ); + + // clear/disable system color cache + systemColorCache = null; } catch( IOException ex ) { LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load properties files.", ex ); } @@ -525,14 +533,14 @@ class UIDefaultsLoader case STRING: return value; case BOOLEAN: return parseBoolean( value ); case CHARACTER: return parseCharacter( value ); - case INTEGER: return parseInteger( value, true ); - case INTEGERORFLOAT:return parseIntegerOrFloat( value, true ); - case FLOAT: return parseFloat( value, true ); + case INTEGER: return parseInteger( value ); + case INTEGERORFLOAT:return parseIntegerOrFloat( value ); + case FLOAT: return parseFloat( value ); case BORDER: return parseBorder( value, resolver, addonClassLoaders ); case ICON: return parseInstance( value, addonClassLoaders ); case INSETS: return parseInsets( value ); case DIMENSION: return parseDimension( value ); - case COLOR: return parseColorOrFunction( value, resolver, true ); + case COLOR: return parseColorOrFunction( value, resolver ); case FONT: return parseFont( value ); case SCALEDINTEGER: return parseScaledInteger( value ); case SCALEDFLOAT: return parseScaledFloat( value ); @@ -550,24 +558,34 @@ class UIDefaultsLoader } // colors - Object color = parseColorOrFunction( value, resolver, false ); - if( color != null ) { - resultValueType[0] = ValueType.COLOR; + if( value.startsWith( "#" ) || value.endsWith( ")" ) ) { + Object color = parseColorOrFunction( value, resolver ); + resultValueType[0] = (color != null) ? ValueType.COLOR : ValueType.NULL; return color; } - // integer - Integer integer = parseInteger( value, false ); - if( integer != null ) { - resultValueType[0] = ValueType.INTEGER; - return integer; - } + // integer or float + char firstChar = value.charAt( 0 ); + if( (firstChar >= '0' && firstChar <= '9') || + firstChar == '-' || firstChar == '+' || firstChar == '.' ) + { + // integer + try { + Integer integer = parseInteger( value ); + resultValueType[0] = ValueType.INTEGER; + return integer; + } catch( NumberFormatException ex ) { + // ignore + } - // float - Float f = parseFloat( value, false ); - if( f != null ) { - resultValueType[0] = ValueType.FLOAT; - return f; + // float + try { + Float f = parseFloat( value ); + resultValueType[0] = ValueType.FLOAT; + return f; + } catch( NumberFormatException ex ) { + // ignore + } } // string @@ -596,10 +614,10 @@ class UIDefaultsLoader List parts = splitFunctionParams( value, ',' ); Insets insets = parseInsets( value ); ColorUIResource lineColor = (parts.size() >= 5) - ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true ) + ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver ) : null; - float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ), true ) : 1f; - int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ), true ) : 0; + float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ) ) : 1f; + int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ) ) : 0; return (LazyValue) t -> { return (lineColor != null) @@ -674,30 +692,24 @@ class UIDefaultsLoader } } - private static Object parseColorOrFunction( String value, Function resolver, boolean reportError ) { + private static Object parseColorOrFunction( String value, Function resolver ) { if( value.endsWith( ")" ) ) - return parseColorFunctions( value, resolver, reportError ); + return parseColorFunctions( value, resolver ); - return parseColor( value, reportError ); + return parseColor( value ); } + /** + * Parses a hex color in {@code #RGB}, {@code #RGBA}, {@code #RRGGBB} or {@code #RRGGBBAA} + * format and returns it as color object. + * + * @throws IllegalArgumentException + */ static ColorUIResource parseColor( String value ) { - return parseColor( value, false ); - } - - private static ColorUIResource parseColor( String value, boolean reportError ) { - try { - int rgba = parseColorRGBA( value ); - return ((rgba & 0xff000000) == 0xff000000) - ? new ColorUIResource( rgba ) - : new ColorUIResource( new Color( rgba, true ) ); - } catch( IllegalArgumentException ex ) { - if( reportError ) - throw new IllegalArgumentException( "invalid color '" + value + "'" ); - - // not a color --> ignore - } - return null; + int rgba = parseColorRGBA( value ); + return ((rgba & 0xff000000) == 0xff000000) + ? new ColorUIResource( rgba ) + : new ColorUIResource( new Color( rgba, true ) ); } /** @@ -710,7 +722,7 @@ class UIDefaultsLoader static int parseColorRGBA( String value ) { int len = value.length(); if( (len != 4 && len != 5 && len != 7 && len != 9) || value.charAt( 0 ) != '#' ) - throw new IllegalArgumentException(); + throw newInvalidColorException( value ); // parse hex int n = 0; @@ -725,7 +737,7 @@ class UIDefaultsLoader else if( ch >= 'A' && ch <= 'F' ) digit = ch - 'A' + 10; else - throw new IllegalArgumentException(); + throw newInvalidColorException( value ); n = (n << 4) | digit; } @@ -744,13 +756,14 @@ class UIDefaultsLoader : (((n >> 8) & 0xffffff) | ((n & 0xff) << 24)); // move alpha from lowest to highest byte } - private static Object parseColorFunctions( String value, Function resolver, boolean reportError ) { + private static IllegalArgumentException newInvalidColorException( String value ) { + return new IllegalArgumentException( "invalid color '" + value + "'" ); + } + + private static Object parseColorFunctions( String value, Function resolver ) { int paramsStart = value.indexOf( '(' ); - if( paramsStart < 0 ) { - if( reportError ) - throw new IllegalArgumentException( "missing opening parenthesis in function '" + value + "'" ); - return null; - } + if( paramsStart < 0 ) + throw new IllegalArgumentException( "missing opening parenthesis in function '" + value + "'" ); String function = StringUtils.substringTrimmed( value, 0, paramsStart ); List params = splitFunctionParams( value.substring( paramsStart + 1, value.length() - 1 ), ',' ); @@ -763,28 +776,29 @@ class UIDefaultsLoader parseColorDepth++; try { switch( function ) { - case "if": return parseColorIf( value, params, resolver, reportError ); - case "rgb": return parseColorRgbOrRgba( false, params, resolver, reportError ); - case "rgba": return parseColorRgbOrRgba( true, params, resolver, reportError ); + case "if": return parseColorIf( value, params, resolver ); + case "systemColor": return parseColorSystemColor( value, params, resolver ); + case "rgb": return parseColorRgbOrRgba( false, params, resolver ); + case "rgba": return parseColorRgbOrRgba( true, params, resolver ); case "hsl": return parseColorHslOrHsla( false, params ); case "hsla": return parseColorHslOrHsla( true, params ); - case "lighten": return parseColorHSLIncreaseDecrease( 2, true, params, resolver, reportError ); - case "darken": return parseColorHSLIncreaseDecrease( 2, false, params, resolver, reportError ); - case "saturate": return parseColorHSLIncreaseDecrease( 1, true, params, resolver, reportError ); - case "desaturate": return parseColorHSLIncreaseDecrease( 1, false, params, resolver, reportError ); - case "fadein": return parseColorHSLIncreaseDecrease( 3, true, params, resolver, reportError ); - case "fadeout": return parseColorHSLIncreaseDecrease( 3, false, params, resolver, reportError ); - case "fade": return parseColorFade( params, resolver, reportError ); - case "spin": return parseColorSpin( params, resolver, reportError ); - case "changeHue": return parseColorChange( 0, params, resolver, reportError ); - case "changeSaturation":return parseColorChange( 1, params, resolver, reportError ); - case "changeLightness": return parseColorChange( 2, params, resolver, reportError ); - case "changeAlpha": return parseColorChange( 3, params, resolver, reportError ); - case "mix": return parseColorMix( null, params, resolver, reportError ); - case "tint": return parseColorMix( "#fff", params, resolver, reportError ); - case "shade": return parseColorMix( "#000", params, resolver, reportError ); - case "contrast": return parseColorContrast( params, resolver, reportError ); - case "over": return parseColorOver( params, resolver, reportError ); + case "lighten": return parseColorHSLIncreaseDecrease( 2, true, params, resolver ); + case "darken": return parseColorHSLIncreaseDecrease( 2, false, params, resolver ); + case "saturate": return parseColorHSLIncreaseDecrease( 1, true, params, resolver ); + case "desaturate": return parseColorHSLIncreaseDecrease( 1, false, params, resolver ); + case "fadein": return parseColorHSLIncreaseDecrease( 3, true, params, resolver ); + case "fadeout": return parseColorHSLIncreaseDecrease( 3, false, params, resolver ); + case "fade": return parseColorFade( params, resolver ); + case "spin": return parseColorSpin( params, resolver ); + case "changeHue": return parseColorChange( 0, params, resolver ); + case "changeSaturation":return parseColorChange( 1, params, resolver ); + case "changeLightness": return parseColorChange( 2, params, resolver ); + case "changeAlpha": return parseColorChange( 3, params, resolver ); + case "mix": return parseColorMix( null, params, resolver ); + case "tint": return parseColorMix( "#fff", params, resolver ); + case "shade": return parseColorMix( "#000", params, resolver ); + case "contrast": return parseColorContrast( params, resolver ); + case "over": return parseColorOver( params, resolver ); } } finally { parseColorDepth--; @@ -799,13 +813,51 @@ class UIDefaultsLoader * This "if" function is only used if the "if" is passed as parameter to another * color function. Otherwise, the general "if" function is used. */ - private static Object parseColorIf( String value, List params, Function resolver, boolean reportError ) { + private static Object parseColorIf( String value, List params, Function resolver ) { if( params.size() != 3 ) throwMissingParametersException( value ); boolean ifCondition = parseCondition( params.get( 0 ), resolver, Collections.emptyList() ); String ifValue = params.get( ifCondition ? 1 : 2 ); - return parseColorOrFunction( resolver.apply( ifValue ), resolver, reportError ); + return parseColorOrFunction( resolver.apply( ifValue ), resolver ); + } + + /** + * Syntax: systemColor(name[,defaultValue]) + * - name: system color name + * - defaultValue: default color value used if system color is not available + */ + private static Object parseColorSystemColor( String value, List params, Function resolver ) { + if( params.size() < 1 ) + throwMissingParametersException( value ); + + ColorUIResource systemColor = getSystemColor( params.get( 0 ) ); + if( systemColor != null ) + return systemColor; + + String defaultValue = (params.size() > 1) ? params.get( 1 ) : ""; + if( defaultValue.equals( "null" ) || defaultValue.isEmpty() ) + return null; + + return parseColorOrFunction( resolver.apply( defaultValue ), resolver ); + } + + private static ColorUIResource getSystemColor( String name ) { + Function systemColorGetter = FlatLaf.getSystemColorGetter(); + if( systemColorGetter == null ) + return null; + + // use containsKey() because value may be null + if( systemColorCache != null && systemColorCache.containsKey( name ) ) + return systemColorCache.get( name ); + + Color color = systemColorGetter.apply( name ); + ColorUIResource uiColor = (color != null) ? new ColorUIResource( color ) : null; + + if( systemColorCache != null ) + systemColorCache.put( name, uiColor ); + + return uiColor; } /** @@ -816,7 +868,7 @@ class UIDefaultsLoader * - alpha: an integer 0-255 or a percentage 0-100% */ private static ColorUIResource parseColorRgbOrRgba( boolean hasAlpha, List params, - Function resolver, boolean reportError ) + Function resolver ) { if( hasAlpha && params.size() == 2 ) { // syntax rgba(color,alpha), which allows adding alpha to any color @@ -825,7 +877,7 @@ class UIDefaultsLoader String colorStr = params.get( 0 ); int alpha = parseInteger( params.get( 1 ), 0, 255, true ); - ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver, reportError ); + ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver ); return new ColorUIResource( new Color( ((alpha & 0xff) << 24) | (color.getRGB() & 0xffffff), true ) ); } @@ -865,7 +917,7 @@ class UIDefaultsLoader * - options: [relative] [autoInverse] [noAutoInverse] [lazy] [derived] */ private static Object parseColorHSLIncreaseDecrease( int hslIndex, boolean increase, - List params, Function resolver, boolean reportError ) + List params, Function resolver ) { String colorStr = params.get( 0 ); int amount = parsePercentage( params.get( 1 ) ); @@ -900,7 +952,7 @@ class UIDefaultsLoader } // parse base color, apply function and create derived color - return parseFunctionBaseColor( colorStr, function, derived, resolver, reportError ); + return parseFunctionBaseColor( colorStr, function, derived, resolver ); } /** @@ -909,7 +961,7 @@ class UIDefaultsLoader * - amount: percentage 0-100% * - options: [derived] [lazy] */ - private static Object parseColorFade( List params, Function resolver, boolean reportError ) { + private static Object parseColorFade( List params, Function resolver ) { String colorStr = params.get( 0 ); int amount = parsePercentage( params.get( 1 ) ); boolean derived = false; @@ -934,7 +986,7 @@ class UIDefaultsLoader } // parse base color, apply function and create derived color - return parseFunctionBaseColor( colorStr, function, derived, resolver, reportError ); + return parseFunctionBaseColor( colorStr, function, derived, resolver ); } /** @@ -943,9 +995,9 @@ class UIDefaultsLoader * - angle: number of degrees to rotate * - options: [derived] */ - private static Object parseColorSpin( List params, Function resolver, boolean reportError ) { + private static Object parseColorSpin( List params, Function resolver ) { String colorStr = params.get( 0 ); - int amount = parseInteger( params.get( 1 ), true ); + int amount = parseInteger( params.get( 1 ) ); boolean derived = false; if( params.size() > 2 ) { @@ -957,7 +1009,7 @@ class UIDefaultsLoader ColorFunction function = new ColorFunctions.HSLIncreaseDecrease( 0, true, amount, false, false ); // parse base color, apply function and create derived color - return parseFunctionBaseColor( colorStr, function, derived, resolver, reportError ); + return parseFunctionBaseColor( colorStr, function, derived, resolver ); } /** @@ -970,11 +1022,11 @@ class UIDefaultsLoader * - options: [derived] */ private static Object parseColorChange( int hslIndex, - List params, Function resolver, boolean reportError ) + List params, Function resolver ) { String colorStr = params.get( 0 ); int value = (hslIndex == 0) - ? parseInteger( params.get( 1 ), true ) + ? parseInteger( params.get( 1 ) ) : parsePercentage( params.get( 1 ) ); boolean derived = false; @@ -987,7 +1039,7 @@ class UIDefaultsLoader ColorFunction function = new ColorFunctions.HSLChange( hslIndex, value ); // parse base color, apply function and create derived color - return parseFunctionBaseColor( colorStr, function, derived, resolver, reportError ); + return parseFunctionBaseColor( colorStr, function, derived, resolver ); } /** @@ -999,7 +1051,7 @@ class UIDefaultsLoader * - weight: the weight (in range 0-100%) to mix the two colors * larger weight uses more of first color, smaller weight more of second color */ - private static Object parseColorMix( String color1Str, List params, Function resolver, boolean reportError ) { + private static Object parseColorMix( String color1Str, List params, Function resolver ) { int i = 0; if( color1Str == null ) color1Str = params.get( i++ ); @@ -1007,7 +1059,7 @@ class UIDefaultsLoader int weight = (params.size() > i) ? parsePercentage( params.get( i ) ) : 50; // parse second color - ColorUIResource color2 = (ColorUIResource) parseColorOrFunction( resolver.apply( color2Str ), resolver, reportError ); + ColorUIResource color2 = (ColorUIResource) parseColorOrFunction( resolver.apply( color2Str ), resolver ); if( color2 == null ) return null; @@ -1015,7 +1067,7 @@ class UIDefaultsLoader ColorFunction function = new ColorFunctions.Mix( color2, weight ); // parse first color, apply function and create mixed color - return parseFunctionBaseColor( color1Str, function, false, resolver, reportError ); + return parseFunctionBaseColor( color1Str, function, false, resolver ); } /** @@ -1026,14 +1078,14 @@ class UIDefaultsLoader * - threshold: the threshold (in range 0-100%) to specify where the transition * from "dark" to "light" is (default is 43%) */ - private static Object parseColorContrast( List params, Function resolver, boolean reportError ) { + private static Object parseColorContrast( List params, Function resolver ) { String colorStr = params.get( 0 ); String darkStr = params.get( 1 ); String lightStr = params.get( 2 ); int threshold = (params.size() > 3) ? parsePercentage( params.get( 3 ) ) : 43; // parse color to compare against - ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver, reportError ); + ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver ); if( color == null ) return null; @@ -1043,7 +1095,7 @@ class UIDefaultsLoader : darkStr; // parse dark or light color - return parseColorOrFunction( resolver.apply( darkOrLightColor ), resolver, reportError ); + return parseColorOrFunction( resolver.apply( darkOrLightColor ), resolver ); } /** @@ -1052,12 +1104,12 @@ class UIDefaultsLoader * the alpha of this color is used as weight to mix the two colors * - background: a background color (e.g. #f00) or a color function */ - private static ColorUIResource parseColorOver( List params, Function resolver, boolean reportError ) { + private static ColorUIResource parseColorOver( List params, Function resolver ) { String foregroundStr = params.get( 0 ); String backgroundStr = params.get( 1 ); // parse foreground color - ColorUIResource foreground = (ColorUIResource) parseColorOrFunction( resolver.apply( foregroundStr ), resolver, reportError ); + ColorUIResource foreground = (ColorUIResource) parseColorOrFunction( resolver.apply( foregroundStr ), resolver ); if( foreground == null || foreground.getAlpha() == 255 ) return foreground; @@ -1065,7 +1117,7 @@ class UIDefaultsLoader ColorUIResource foreground2 = new ColorUIResource( foreground.getRGB() ); // parse background color - ColorUIResource background = (ColorUIResource) parseColorOrFunction( resolver.apply( backgroundStr ), resolver, reportError ); + ColorUIResource background = (ColorUIResource) parseColorOrFunction( resolver.apply( backgroundStr ), resolver ); if( background == null ) return foreground2; @@ -1075,11 +1127,11 @@ class UIDefaultsLoader } private static Object parseFunctionBaseColor( String colorStr, ColorFunction function, - boolean derived, Function resolver, boolean reportError ) + boolean derived, Function resolver ) { // parse base color String resolvedColorStr = resolver.apply( colorStr ); - ColorUIResource baseColor = (ColorUIResource) parseColorOrFunction( resolvedColorStr, resolver, reportError ); + ColorUIResource baseColor = (ColorUIResource) parseColorOrFunction( resolvedColorStr, resolver ); if( baseColor == null ) return null; @@ -1163,11 +1215,11 @@ class UIDefaultsLoader throw new IllegalArgumentException( "size specified more than once in '" + value + "'" ); if( firstChar == '+' || firstChar == '-' ) - relativeSize = parseInteger( param, true ); + relativeSize = parseInteger( param ); else if( param.endsWith( "%" ) ) - scaleSize = parseInteger( param.substring( 0, param.length() - 1 ), true ) / 100f; + scaleSize = parseInteger( param.substring( 0, param.length() - 1 ) ) / 100f; else - absoluteSize = parseInteger( param, true ); + absoluteSize = parseInteger( param ); } else if( firstChar == '$' ) { // reference to base font if( baseFontKey != null ) @@ -1241,55 +1293,49 @@ class UIDefaultsLoader return (max * percent) / 100; } - Integer integer = parseInteger( value, true ); + Integer integer = parseInteger( value ); if( integer < min || integer > max ) throw new NumberFormatException( "integer '" + value + "' out of range (" + min + '-' + max + ')' ); return integer; } - private static Integer parseInteger( String value, boolean reportError ) { + private static Integer parseInteger( String value ) { try { return Integer.parseInt( value ); } catch( NumberFormatException ex ) { - if( reportError ) - throw new NumberFormatException( "invalid integer '" + value + "'" ); + throw new NumberFormatException( "invalid integer '" + value + "'" ); } - return null; } - private static Number parseIntegerOrFloat( String value, boolean reportError ) { + private static Number parseIntegerOrFloat( String value ) { try { return Integer.parseInt( value ); } catch( NumberFormatException ex ) { try { return Float.parseFloat( value ); } catch( NumberFormatException ex2 ) { - if( reportError ) - throw new NumberFormatException( "invalid integer or float '" + value + "'" ); + throw new NumberFormatException( "invalid integer or float '" + value + "'" ); } } - return null; } - private static Float parseFloat( String value, boolean reportError ) { + private static Float parseFloat( String value ) { try { return Float.parseFloat( value ); } catch( NumberFormatException ex ) { - if( reportError ) - throw new NumberFormatException( "invalid float '" + value + "'" ); + throw new NumberFormatException( "invalid float '" + value + "'" ); } - return null; } private static ActiveValue parseScaledInteger( String value ) { - int val = parseInteger( value, true ); + int val = parseInteger( value ); return t -> { return UIScale.scale( val ); }; } private static ActiveValue parseScaledFloat( String value ) { - float val = parseFloat( value, true ); + float val = parseFloat( value ); return t -> { return UIScale.scale( val ); }; @@ -1344,7 +1390,11 @@ class UIDefaultsLoader start = i + 1; } } - strs.add( StringUtils.substringTrimmed( str, start ) ); + + // last param + String s = StringUtils.substringTrimmed( str, start ); + if( !s.isEmpty() || !strs.isEmpty() ) + strs.add( s ); return strs; } diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties index 7ef63e19..4fe07ee4 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties @@ -63,7 +63,7 @@ # accent colors (blueish) # set @accentColor to use single accent color or # modify @accentBaseColor to use variations of accent base color -@accentColor = null +@accentColor = systemColor(accent,null) @accentBaseColor = #4B6EAF @accentBase2Color = lighten(saturate(spin(@accentBaseColor,-8),13%),5%) # accent color variations diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties index cad23984..98b064e4 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties @@ -63,7 +63,7 @@ # accent colors (blueish) # set @accentColor to use single accent color or # modify @accentBaseColor to use variations of accent base color -@accentColor = null +@accentColor = systemColor(accent,null) @accentBaseColor = #2675BF @accentBase2Color = lighten(saturate(@accentBaseColor,10%),6%) # accent color variations diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties index f07f64b3..d2f68486 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties @@ -88,12 +88,12 @@ @menuBackground = lighten(@background,8%) # selection -@selectionBackground = @nsSelectedContentBackgroundColor +@selectionBackground = if(systemColor(accent), shade(spin(systemColor(accent),4),20%), @nsSelectedContentBackgroundColor) @selectionForeground = @nsSelectedMenuItemTextColor @selectionInactiveBackground = @nsUnemphasizedSelectedContentBackgroundColor # text selection -@textSelectionBackground = @nsSelectedTextBackgroundColor +@textSelectionBackground = systemColor(highlight,if(systemColor(accent), darken(desaturate(systemColor(accent),60%),10%), @nsSelectedTextBackgroundColor)) @textSelectionForeground = @nsSelectedTextColor # menu @@ -101,8 +101,8 @@ @menuItemMargin = 3,11,3,11 # accent colors (blueish) -@accentColor = @nsControlAccentColor -@accentFocusColor = @nsKeyboardFocusIndicatorColor +@accentColor = systemColor(accent,@nsControlAccentColor) +@accentFocusColor = if(systemColor(accent), fade(spin(systemColor(accent),-10),50%), @nsKeyboardFocusIndicatorColor) #---- Button ---- diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacLightLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacLightLaf.properties index 67e382bf..5a411a85 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacLightLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacLightLaf.properties @@ -88,12 +88,12 @@ @menuBackground = darken(@background,4%) # selection -@selectionBackground = @nsSelectedContentBackgroundColor +@selectionBackground = if(systemColor(accent), shade(spin(systemColor(accent),4),10%), @nsSelectedContentBackgroundColor) @selectionForeground = @nsSelectedMenuItemTextColor @selectionInactiveBackground = @nsUnemphasizedSelectedContentBackgroundColor # text selection -@textSelectionBackground = @nsSelectedTextBackgroundColor +@textSelectionBackground = systemColor(highlight,if(systemColor(accent), tint(systemColor(accent),70%), @nsSelectedTextBackgroundColor)) @textSelectionForeground = @foreground # menu @@ -102,8 +102,8 @@ @menuItemMargin = 3,11,3,11 # accent colors (blueish) -@accentColor = @nsControlAccentColor -@accentFocusColor = @nsKeyboardFocusIndicatorColor +@accentColor = systemColor(accent,@nsControlAccentColor) +@accentFocusColor = if(systemColor(accent), fade(spin(systemColor(accent),4),50%), @nsKeyboardFocusIndicatorColor) #---- Button ---- diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java index be4bf174..f65def73 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java @@ -24,7 +24,6 @@ import java.net.URISyntaxException; import java.time.Year; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.prefs.Preferences; import javax.swing.*; import javax.swing.text.DefaultEditorKit; @@ -45,6 +44,8 @@ import com.formdev.flatlaf.extras.FlatUIDefaultsInspector; import com.formdev.flatlaf.extras.components.FlatButton; import com.formdev.flatlaf.extras.components.FlatButton.ButtonType; import com.formdev.flatlaf.icons.FlatAbstractIcon; +import com.formdev.flatlaf.themes.FlatMacDarkLaf; +import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.formdev.flatlaf.extras.FlatSVGUtils; import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.ui.JBRCustomDecorations; @@ -398,6 +399,7 @@ class DemoFrame }; private final JToggleButton[] accentColorButtons = new JToggleButton[accentColorKeys.length]; private JLabel accentColorLabel; + private Color accentColor; private void initAccentColors() { accentColorLabel = new JLabel( "Accent color: " ); @@ -416,6 +418,10 @@ class DemoFrame accentColorButtons[0].setSelected( true ); + FlatLaf.setSystemColorGetter( name -> { + return name.equals( "accent" ) ? accentColor : null; + } ); + UIManager.addPropertyChangeListener( e -> { if( "lookAndFeel".equals( e.getPropertyName() ) ) updateAccentColorButtons(); @@ -424,17 +430,17 @@ class DemoFrame } private void accentColorChanged( ActionEvent e ) { - String accentColor = accentColorKeys[0]; + String accentColorKey = null; for( int i = 0; i < accentColorButtons.length; i++ ) { if( accentColorButtons[i].isSelected() ) { - accentColor = accentColorKeys[i]; + accentColorKey = accentColorKeys[i]; break; } } - FlatLaf.setGlobalExtraDefaults( (accentColor != accentColorKeys[0]) - ? Collections.singletonMap( "@accentColor", "$" + accentColor ) - : null ); + accentColor = (accentColorKey != null && accentColorKey != accentColorKeys[0]) + ? UIManager.getColor( accentColorKey ) + : null; Class lafClass = UIManager.getLookAndFeel().getClass(); try { @@ -451,7 +457,9 @@ class DemoFrame lafClass == FlatLightLaf.class || lafClass == FlatDarkLaf.class || lafClass == FlatIntelliJLaf.class || - lafClass == FlatDarculaLaf.class; + lafClass == FlatDarculaLaf.class || + lafClass == FlatMacLightLaf.class || + lafClass == FlatMacDarkLaf.class; accentColorLabel.setVisible( isAccentColorSupported ); for( int i = 0; i < accentColorButtons.length; i++ ) diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatCompletionProvider.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatCompletionProvider.java index 71959f4c..164e8a89 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatCompletionProvider.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatCompletionProvider.java @@ -410,6 +410,10 @@ class FlatCompletionProvider addFunction( "lazy", "uiKey", "UI key (without leading '$')" ); + addFunction( "systemColor", + "name", "system color name", + "defaultValue", "default color value used if system color is not available" ); + addFunction( "rgb", "red", "0-255 or 0-100%", "green", "0-255 or 0-100%", diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeTokenMaker.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeTokenMaker.java index f9bbe12b..aed8b3a6 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeTokenMaker.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeTokenMaker.java @@ -56,6 +56,7 @@ public class FlatThemeTokenMaker tokenMap.put( "lazy", TOKEN_FUNCTION ); // color functions + tokenMap.put( "systemColor", TOKEN_FUNCTION ); tokenMap.put( "rgb", TOKEN_FUNCTION ); tokenMap.put( "rgba", TOKEN_FUNCTION ); tokenMap.put( "hsl", TOKEN_FUNCTION ); diff --git a/flatlaf-theme-editor/theme-editor-test.properties b/flatlaf-theme-editor/theme-editor-test.properties index 50d46d2a..eafb7b50 100644 --- a/flatlaf-theme-editor/theme-editor-test.properties +++ b/flatlaf-theme-editor/theme-editor-test.properties @@ -49,6 +49,9 @@ Prop.ifColor = lighten(if(#000,#0f0,#dfd), 10%) Prop.ifColorVar = lighten(if(@varTrue,@varTrueValue,@varFalseValue), 10%) Prop.lazy = lazy(Prop.string) +Prop.systemColor1 = systemColor(accent,null) +Prop.systemColor2 = systemColor(accent,#f00) + Prop.colorFunc1 = rgb(12,34,56) Prop.colorFunc2 = rgba(12,34,56,78) Prop.colorFunc3 = hsl(12,34%,56%)