Merge PR #607: systemColor() function and support changing accent color in macOS themes

This commit is contained in:
Karl Tauber
2022-11-14 12:05:47 +01:00
11 changed files with 243 additions and 138 deletions

View File

@@ -98,6 +98,7 @@ public abstract class FlatLaf
private static List<Object> customDefaultsSources; private static List<Object> customDefaultsSources;
private static Map<String, String> globalExtraDefaults; private static Map<String, String> globalExtraDefaults;
private Map<String, String> extraDefaults; private Map<String, String> extraDefaults;
private static Function<String, Color> systemColorGetter;
private String desktopPropertyName; private String desktopPropertyName;
private String desktopPropertyName2; private String desktopPropertyName2;
@@ -897,14 +898,14 @@ public abstract class FlatLaf
* E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}. * E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}.
* <p> * <p>
* The global extra defaults are useful for smaller additional defaults that may change. * 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)}. * See {@link #registerCustomDefaultsSource(String)}.
* <p> * <p>
* The keys and values are strings in same format as in FlatLaf properties files. * The keys and values are strings in same format as in FlatLaf properties files.
* <p> * <p>
* Sample that setups "FlatLaf Light" theme with red accent color: * Sample that setups "FlatLaf Light" theme with white background color:
* <pre>{@code * <pre>{@code
* FlatLaf.setGlobalExtraDefaults( Collections.singletonMap( "@accentColor", "#f00" ) ); * FlatLaf.setGlobalExtraDefaults( Collections.singletonMap( "@background", "#fff" ) );
* FlatLightLaf.setup(); * FlatLightLaf.setup();
* }</pre> * }</pre>
* *
@@ -929,15 +930,15 @@ public abstract class FlatLaf
* E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}. * E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}.
* <p> * <p>
* The extra defaults are useful for smaller additional defaults that may change. * 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)}. * See {@link #registerCustomDefaultsSource(String)}.
* <p> * <p>
* The keys and values are strings in same format as in FlatLaf properties files. * The keys and values are strings in same format as in FlatLaf properties files.
* <p> * <p>
* Sample that setups "FlatLaf Light" theme with red accent color: * Sample that setups "FlatLaf Light" theme with white background color:
* <pre>{@code * <pre>{@code
* FlatLaf laf = new FlatLightLaf(); * FlatLaf laf = new FlatLightLaf();
* laf.setExtraDefaults( Collections.singletonMap( "@accentColor", "#f00" ) ); * laf.setExtraDefaults( Collections.singletonMap( "@background", "#fff" ) );
* FlatLaf.setup( laf ); * FlatLaf.setup( laf );
* }</pre> * }</pre>
* *
@@ -979,6 +980,36 @@ public abstract class FlatLaf
return val; return val;
} }
/**
* Returns the system color getter function, or {@code null}.
*
* @since 3
*/
public static Function<String, Color> getSystemColorGetter() {
return systemColorGetter;
}
/**
* Sets a system color getter function that is invoked when function
* {@code systemColor()} is used in FlatLaf properties files.
* <p>
* The name of the system color is passed as parameter to the function.
* The function should return {@code null} for unknown system colors.
* <p>
* Can be used to change the accent color:
* <pre>{@code
* FlatLaf.setSystemColorGetter( name -> {
* return name.equals( "accent" ) ? Color.red : null;
* } );
* FlatLightLaf.setup();
* }</pre>
*
* @since 3
*/
public static void setSystemColorGetter( Function<String, Color> systemColorGetter ) {
FlatLaf.systemColorGetter = systemColorGetter;
}
private static void reSetLookAndFeel() { private static void reSetLookAndFeel() {
EventQueue.invokeLater( () -> { EventQueue.invokeLater( () -> {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel(); LookAndFeel lookAndFeel = UIManager.getLookAndFeel();

View File

@@ -302,7 +302,7 @@ public class IntelliJTheme
for( Map.Entry<String, String> e : colors.entrySet() ) { for( Map.Entry<String, String> e : colors.entrySet() ) {
String value = e.getValue(); String value = e.getValue();
ColorUIResource color = UIDefaultsLoader.parseColor( value ); ColorUIResource color = parseColor( value );
if( color != null ) { if( color != null ) {
String key = e.getKey(); String key = e.getKey();
namedColors.put( key, color ); namedColors.put( key, color );
@@ -448,7 +448,15 @@ public class IntelliJTheme
ColorUIResource color = namedColors.get( value ); ColorUIResource color = namedColors.get( value );
// parse color // 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 // radioFocused.svg and radioSelectedFocused.svg
// use opacity=".65" for the border // use opacity=".65" for the border
// --> add alpha to focused border colors // --> add alpha to focused border colors
String[] focusedBorderColorKeys = new String[] { String[] focusedBorderColorKeys = {
"CheckBox.icon.focusedBorderColor", "CheckBox.icon.focusedBorderColor",
"CheckBox.icon.focusedSelectedBorderColor", "CheckBox.icon.focusedSelectedBorderColor",
"CheckBox.icon[filled].focusedBorderColor", "CheckBox.icon[filled].focusedBorderColor",

View File

@@ -84,6 +84,7 @@ class UIDefaultsLoader
private static int parseColorDepth; private static int parseColorDepth;
private static Map<String, ColorUIResource> systemColorCache;
private static final SoftCache<String, Object> fontCache = new SoftCache<>(); private static final SoftCache<String, Object> fontCache = new SoftCache<>();
static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, List<FlatDefaultsAddon> addons, static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, List<FlatDefaultsAddon> addons,
@@ -105,6 +106,10 @@ class UIDefaultsLoader
Properties additionalDefaults, boolean dark, UIDefaults defaults ) Properties additionalDefaults, boolean dark, UIDefaults defaults )
{ {
try { 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 // load core properties files
Properties properties = new Properties(); Properties properties = new Properties();
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
@@ -276,6 +281,9 @@ class UIDefaultsLoader
// remember variables in defaults to allow using them in styles // remember variables in defaults to allow using them in styles
defaults.put( KEY_VARIABLES, variables ); defaults.put( KEY_VARIABLES, variables );
// clear/disable system color cache
systemColorCache = null;
} catch( IOException ex ) { } catch( IOException ex ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load properties files.", ex ); LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load properties files.", ex );
} }
@@ -525,14 +533,14 @@ class UIDefaultsLoader
case STRING: return value; case STRING: return value;
case BOOLEAN: return parseBoolean( value ); case BOOLEAN: return parseBoolean( value );
case CHARACTER: return parseCharacter( value ); case CHARACTER: return parseCharacter( value );
case INTEGER: return parseInteger( value, true ); case INTEGER: return parseInteger( value );
case INTEGERORFLOAT:return parseIntegerOrFloat( value, true ); case INTEGERORFLOAT:return parseIntegerOrFloat( value );
case FLOAT: return parseFloat( value, true ); case FLOAT: return parseFloat( value );
case BORDER: return parseBorder( value, resolver, addonClassLoaders ); case BORDER: return parseBorder( value, resolver, addonClassLoaders );
case ICON: return parseInstance( value, addonClassLoaders ); case ICON: return parseInstance( value, addonClassLoaders );
case INSETS: return parseInsets( value ); case INSETS: return parseInsets( value );
case DIMENSION: return parseDimension( 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 FONT: return parseFont( value );
case SCALEDINTEGER: return parseScaledInteger( value ); case SCALEDINTEGER: return parseScaledInteger( value );
case SCALEDFLOAT: return parseScaledFloat( value ); case SCALEDFLOAT: return parseScaledFloat( value );
@@ -550,24 +558,34 @@ class UIDefaultsLoader
} }
// colors // colors
Object color = parseColorOrFunction( value, resolver, false ); if( value.startsWith( "#" ) || value.endsWith( ")" ) ) {
if( color != null ) { Object color = parseColorOrFunction( value, resolver );
resultValueType[0] = ValueType.COLOR; resultValueType[0] = (color != null) ? ValueType.COLOR : ValueType.NULL;
return color; return color;
} }
// integer // integer or float
Integer integer = parseInteger( value, false ); char firstChar = value.charAt( 0 );
if( integer != null ) { if( (firstChar >= '0' && firstChar <= '9') ||
resultValueType[0] = ValueType.INTEGER; firstChar == '-' || firstChar == '+' || firstChar == '.' )
return integer; {
} // integer
try {
Integer integer = parseInteger( value );
resultValueType[0] = ValueType.INTEGER;
return integer;
} catch( NumberFormatException ex ) {
// ignore
}
// float // float
Float f = parseFloat( value, false ); try {
if( f != null ) { Float f = parseFloat( value );
resultValueType[0] = ValueType.FLOAT; resultValueType[0] = ValueType.FLOAT;
return f; return f;
} catch( NumberFormatException ex ) {
// ignore
}
} }
// string // string
@@ -596,10 +614,10 @@ class UIDefaultsLoader
List<String> parts = splitFunctionParams( value, ',' ); List<String> parts = splitFunctionParams( value, ',' );
Insets insets = parseInsets( value ); Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5) ColorUIResource lineColor = (parts.size() >= 5)
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true ) ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
: null; : null;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ), true ) : 1f; float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ) ) : 1f;
int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ), true ) : 0; int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ) ) : 0;
return (LazyValue) t -> { return (LazyValue) t -> {
return (lineColor != null) return (lineColor != null)
@@ -674,30 +692,24 @@ class UIDefaultsLoader
} }
} }
private static Object parseColorOrFunction( String value, Function<String, String> resolver, boolean reportError ) { private static Object parseColorOrFunction( String value, Function<String, String> resolver ) {
if( value.endsWith( ")" ) ) 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 ) { static ColorUIResource parseColor( String value ) {
return parseColor( value, false ); int rgba = parseColorRGBA( value );
} return ((rgba & 0xff000000) == 0xff000000)
? new ColorUIResource( rgba )
private static ColorUIResource parseColor( String value, boolean reportError ) { : new ColorUIResource( new Color( rgba, true ) );
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;
} }
/** /**
@@ -710,7 +722,7 @@ class UIDefaultsLoader
static int parseColorRGBA( String value ) { static int parseColorRGBA( String value ) {
int len = value.length(); int len = value.length();
if( (len != 4 && len != 5 && len != 7 && len != 9) || value.charAt( 0 ) != '#' ) if( (len != 4 && len != 5 && len != 7 && len != 9) || value.charAt( 0 ) != '#' )
throw new IllegalArgumentException(); throw newInvalidColorException( value );
// parse hex // parse hex
int n = 0; int n = 0;
@@ -725,7 +737,7 @@ class UIDefaultsLoader
else if( ch >= 'A' && ch <= 'F' ) else if( ch >= 'A' && ch <= 'F' )
digit = ch - 'A' + 10; digit = ch - 'A' + 10;
else else
throw new IllegalArgumentException(); throw newInvalidColorException( value );
n = (n << 4) | digit; n = (n << 4) | digit;
} }
@@ -744,13 +756,14 @@ class UIDefaultsLoader
: (((n >> 8) & 0xffffff) | ((n & 0xff) << 24)); // move alpha from lowest to highest byte : (((n >> 8) & 0xffffff) | ((n & 0xff) << 24)); // move alpha from lowest to highest byte
} }
private static Object parseColorFunctions( String value, Function<String, String> resolver, boolean reportError ) { private static IllegalArgumentException newInvalidColorException( String value ) {
return new IllegalArgumentException( "invalid color '" + value + "'" );
}
private static Object parseColorFunctions( String value, Function<String, String> resolver ) {
int paramsStart = value.indexOf( '(' ); int paramsStart = value.indexOf( '(' );
if( paramsStart < 0 ) { if( paramsStart < 0 )
if( reportError ) throw new IllegalArgumentException( "missing opening parenthesis in function '" + value + "'" );
throw new IllegalArgumentException( "missing opening parenthesis in function '" + value + "'" );
return null;
}
String function = StringUtils.substringTrimmed( value, 0, paramsStart ); String function = StringUtils.substringTrimmed( value, 0, paramsStart );
List<String> params = splitFunctionParams( value.substring( paramsStart + 1, value.length() - 1 ), ',' ); List<String> params = splitFunctionParams( value.substring( paramsStart + 1, value.length() - 1 ), ',' );
@@ -763,28 +776,29 @@ class UIDefaultsLoader
parseColorDepth++; parseColorDepth++;
try { try {
switch( function ) { switch( function ) {
case "if": return parseColorIf( value, params, resolver, reportError ); case "if": return parseColorIf( value, params, resolver );
case "rgb": return parseColorRgbOrRgba( false, params, resolver, reportError ); case "systemColor": return parseColorSystemColor( value, params, resolver );
case "rgba": return parseColorRgbOrRgba( true, params, resolver, reportError ); case "rgb": return parseColorRgbOrRgba( false, params, resolver );
case "rgba": return parseColorRgbOrRgba( true, params, resolver );
case "hsl": return parseColorHslOrHsla( false, params ); case "hsl": return parseColorHslOrHsla( false, params );
case "hsla": return parseColorHslOrHsla( true, params ); case "hsla": return parseColorHslOrHsla( true, params );
case "lighten": return parseColorHSLIncreaseDecrease( 2, true, params, resolver, reportError ); case "lighten": return parseColorHSLIncreaseDecrease( 2, true, params, resolver );
case "darken": return parseColorHSLIncreaseDecrease( 2, false, params, resolver, reportError ); case "darken": return parseColorHSLIncreaseDecrease( 2, false, params, resolver );
case "saturate": return parseColorHSLIncreaseDecrease( 1, true, params, resolver, reportError ); case "saturate": return parseColorHSLIncreaseDecrease( 1, true, params, resolver );
case "desaturate": return parseColorHSLIncreaseDecrease( 1, false, params, resolver, reportError ); case "desaturate": return parseColorHSLIncreaseDecrease( 1, false, params, resolver );
case "fadein": return parseColorHSLIncreaseDecrease( 3, true, params, resolver, reportError ); case "fadein": return parseColorHSLIncreaseDecrease( 3, true, params, resolver );
case "fadeout": return parseColorHSLIncreaseDecrease( 3, false, params, resolver, reportError ); case "fadeout": return parseColorHSLIncreaseDecrease( 3, false, params, resolver );
case "fade": return parseColorFade( params, resolver, reportError ); case "fade": return parseColorFade( params, resolver );
case "spin": return parseColorSpin( params, resolver, reportError ); case "spin": return parseColorSpin( params, resolver );
case "changeHue": return parseColorChange( 0, params, resolver, reportError ); case "changeHue": return parseColorChange( 0, params, resolver );
case "changeSaturation":return parseColorChange( 1, params, resolver, reportError ); case "changeSaturation":return parseColorChange( 1, params, resolver );
case "changeLightness": return parseColorChange( 2, params, resolver, reportError ); case "changeLightness": return parseColorChange( 2, params, resolver );
case "changeAlpha": return parseColorChange( 3, params, resolver, reportError ); case "changeAlpha": return parseColorChange( 3, params, resolver );
case "mix": return parseColorMix( null, params, resolver, reportError ); case "mix": return parseColorMix( null, params, resolver );
case "tint": return parseColorMix( "#fff", params, resolver, reportError ); case "tint": return parseColorMix( "#fff", params, resolver );
case "shade": return parseColorMix( "#000", params, resolver, reportError ); case "shade": return parseColorMix( "#000", params, resolver );
case "contrast": return parseColorContrast( params, resolver, reportError ); case "contrast": return parseColorContrast( params, resolver );
case "over": return parseColorOver( params, resolver, reportError ); case "over": return parseColorOver( params, resolver );
} }
} finally { } finally {
parseColorDepth--; parseColorDepth--;
@@ -799,13 +813,51 @@ class UIDefaultsLoader
* This "if" function is only used if the "if" is passed as parameter to another * This "if" function is only used if the "if" is passed as parameter to another
* color function. Otherwise, the general "if" function is used. * color function. Otherwise, the general "if" function is used.
*/ */
private static Object parseColorIf( String value, List<String> params, Function<String, String> resolver, boolean reportError ) { private static Object parseColorIf( String value, List<String> params, Function<String, String> resolver ) {
if( params.size() != 3 ) if( params.size() != 3 )
throwMissingParametersException( value ); throwMissingParametersException( value );
boolean ifCondition = parseCondition( params.get( 0 ), resolver, Collections.emptyList() ); boolean ifCondition = parseCondition( params.get( 0 ), resolver, Collections.emptyList() );
String ifValue = params.get( ifCondition ? 1 : 2 ); 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<String> params, Function<String, String> 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<String, Color> 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% * - alpha: an integer 0-255 or a percentage 0-100%
*/ */
private static ColorUIResource parseColorRgbOrRgba( boolean hasAlpha, List<String> params, private static ColorUIResource parseColorRgbOrRgba( boolean hasAlpha, List<String> params,
Function<String, String> resolver, boolean reportError ) Function<String, String> resolver )
{ {
if( hasAlpha && params.size() == 2 ) { if( hasAlpha && params.size() == 2 ) {
// syntax rgba(color,alpha), which allows adding alpha to any color // syntax rgba(color,alpha), which allows adding alpha to any color
@@ -825,7 +877,7 @@ class UIDefaultsLoader
String colorStr = params.get( 0 ); String colorStr = params.get( 0 );
int alpha = parseInteger( params.get( 1 ), 0, 255, true ); 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 ) ); return new ColorUIResource( new Color( ((alpha & 0xff) << 24) | (color.getRGB() & 0xffffff), true ) );
} }
@@ -865,7 +917,7 @@ class UIDefaultsLoader
* - options: [relative] [autoInverse] [noAutoInverse] [lazy] [derived] * - options: [relative] [autoInverse] [noAutoInverse] [lazy] [derived]
*/ */
private static Object parseColorHSLIncreaseDecrease( int hslIndex, boolean increase, private static Object parseColorHSLIncreaseDecrease( int hslIndex, boolean increase,
List<String> params, Function<String, String> resolver, boolean reportError ) List<String> params, Function<String, String> resolver )
{ {
String colorStr = params.get( 0 ); String colorStr = params.get( 0 );
int amount = parsePercentage( params.get( 1 ) ); int amount = parsePercentage( params.get( 1 ) );
@@ -900,7 +952,7 @@ class UIDefaultsLoader
} }
// parse base color, apply function and create derived color // 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% * - amount: percentage 0-100%
* - options: [derived] [lazy] * - options: [derived] [lazy]
*/ */
private static Object parseColorFade( List<String> params, Function<String, String> resolver, boolean reportError ) { private static Object parseColorFade( List<String> params, Function<String, String> resolver ) {
String colorStr = params.get( 0 ); String colorStr = params.get( 0 );
int amount = parsePercentage( params.get( 1 ) ); int amount = parsePercentage( params.get( 1 ) );
boolean derived = false; boolean derived = false;
@@ -934,7 +986,7 @@ class UIDefaultsLoader
} }
// parse base color, apply function and create derived color // 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 * - angle: number of degrees to rotate
* - options: [derived] * - options: [derived]
*/ */
private static Object parseColorSpin( List<String> params, Function<String, String> resolver, boolean reportError ) { private static Object parseColorSpin( List<String> params, Function<String, String> resolver ) {
String colorStr = params.get( 0 ); String colorStr = params.get( 0 );
int amount = parseInteger( params.get( 1 ), true ); int amount = parseInteger( params.get( 1 ) );
boolean derived = false; boolean derived = false;
if( params.size() > 2 ) { if( params.size() > 2 ) {
@@ -957,7 +1009,7 @@ class UIDefaultsLoader
ColorFunction function = new ColorFunctions.HSLIncreaseDecrease( 0, true, amount, false, false ); ColorFunction function = new ColorFunctions.HSLIncreaseDecrease( 0, true, amount, false, false );
// parse base color, apply function and create derived color // 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] * - options: [derived]
*/ */
private static Object parseColorChange( int hslIndex, private static Object parseColorChange( int hslIndex,
List<String> params, Function<String, String> resolver, boolean reportError ) List<String> params, Function<String, String> resolver )
{ {
String colorStr = params.get( 0 ); String colorStr = params.get( 0 );
int value = (hslIndex == 0) int value = (hslIndex == 0)
? parseInteger( params.get( 1 ), true ) ? parseInteger( params.get( 1 ) )
: parsePercentage( params.get( 1 ) ); : parsePercentage( params.get( 1 ) );
boolean derived = false; boolean derived = false;
@@ -987,7 +1039,7 @@ class UIDefaultsLoader
ColorFunction function = new ColorFunctions.HSLChange( hslIndex, value ); ColorFunction function = new ColorFunctions.HSLChange( hslIndex, value );
// parse base color, apply function and create derived color // 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 * - 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 * larger weight uses more of first color, smaller weight more of second color
*/ */
private static Object parseColorMix( String color1Str, List<String> params, Function<String, String> resolver, boolean reportError ) { private static Object parseColorMix( String color1Str, List<String> params, Function<String, String> resolver ) {
int i = 0; int i = 0;
if( color1Str == null ) if( color1Str == null )
color1Str = params.get( i++ ); color1Str = params.get( i++ );
@@ -1007,7 +1059,7 @@ class UIDefaultsLoader
int weight = (params.size() > i) ? parsePercentage( params.get( i ) ) : 50; int weight = (params.size() > i) ? parsePercentage( params.get( i ) ) : 50;
// parse second color // parse second color
ColorUIResource color2 = (ColorUIResource) parseColorOrFunction( resolver.apply( color2Str ), resolver, reportError ); ColorUIResource color2 = (ColorUIResource) parseColorOrFunction( resolver.apply( color2Str ), resolver );
if( color2 == null ) if( color2 == null )
return null; return null;
@@ -1015,7 +1067,7 @@ class UIDefaultsLoader
ColorFunction function = new ColorFunctions.Mix( color2, weight ); ColorFunction function = new ColorFunctions.Mix( color2, weight );
// parse first color, apply function and create mixed color // 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 * - threshold: the threshold (in range 0-100%) to specify where the transition
* from "dark" to "light" is (default is 43%) * from "dark" to "light" is (default is 43%)
*/ */
private static Object parseColorContrast( List<String> params, Function<String, String> resolver, boolean reportError ) { private static Object parseColorContrast( List<String> params, Function<String, String> resolver ) {
String colorStr = params.get( 0 ); String colorStr = params.get( 0 );
String darkStr = params.get( 1 ); String darkStr = params.get( 1 );
String lightStr = params.get( 2 ); String lightStr = params.get( 2 );
int threshold = (params.size() > 3) ? parsePercentage( params.get( 3 ) ) : 43; int threshold = (params.size() > 3) ? parsePercentage( params.get( 3 ) ) : 43;
// parse color to compare against // 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 ) if( color == null )
return null; return null;
@@ -1043,7 +1095,7 @@ class UIDefaultsLoader
: darkStr; : darkStr;
// parse dark or light color // 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 * 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 * - background: a background color (e.g. #f00) or a color function
*/ */
private static ColorUIResource parseColorOver( List<String> params, Function<String, String> resolver, boolean reportError ) { private static ColorUIResource parseColorOver( List<String> params, Function<String, String> resolver ) {
String foregroundStr = params.get( 0 ); String foregroundStr = params.get( 0 );
String backgroundStr = params.get( 1 ); String backgroundStr = params.get( 1 );
// parse foreground color // 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 ) if( foreground == null || foreground.getAlpha() == 255 )
return foreground; return foreground;
@@ -1065,7 +1117,7 @@ class UIDefaultsLoader
ColorUIResource foreground2 = new ColorUIResource( foreground.getRGB() ); ColorUIResource foreground2 = new ColorUIResource( foreground.getRGB() );
// parse background color // parse background color
ColorUIResource background = (ColorUIResource) parseColorOrFunction( resolver.apply( backgroundStr ), resolver, reportError ); ColorUIResource background = (ColorUIResource) parseColorOrFunction( resolver.apply( backgroundStr ), resolver );
if( background == null ) if( background == null )
return foreground2; return foreground2;
@@ -1075,11 +1127,11 @@ class UIDefaultsLoader
} }
private static Object parseFunctionBaseColor( String colorStr, ColorFunction function, private static Object parseFunctionBaseColor( String colorStr, ColorFunction function,
boolean derived, Function<String, String> resolver, boolean reportError ) boolean derived, Function<String, String> resolver )
{ {
// parse base color // parse base color
String resolvedColorStr = resolver.apply( colorStr ); String resolvedColorStr = resolver.apply( colorStr );
ColorUIResource baseColor = (ColorUIResource) parseColorOrFunction( resolvedColorStr, resolver, reportError ); ColorUIResource baseColor = (ColorUIResource) parseColorOrFunction( resolvedColorStr, resolver );
if( baseColor == null ) if( baseColor == null )
return null; return null;
@@ -1163,11 +1215,11 @@ class UIDefaultsLoader
throw new IllegalArgumentException( "size specified more than once in '" + value + "'" ); throw new IllegalArgumentException( "size specified more than once in '" + value + "'" );
if( firstChar == '+' || firstChar == '-' ) if( firstChar == '+' || firstChar == '-' )
relativeSize = parseInteger( param, true ); relativeSize = parseInteger( param );
else if( param.endsWith( "%" ) ) else if( param.endsWith( "%" ) )
scaleSize = parseInteger( param.substring( 0, param.length() - 1 ), true ) / 100f; scaleSize = parseInteger( param.substring( 0, param.length() - 1 ) ) / 100f;
else else
absoluteSize = parseInteger( param, true ); absoluteSize = parseInteger( param );
} else if( firstChar == '$' ) { } else if( firstChar == '$' ) {
// reference to base font // reference to base font
if( baseFontKey != null ) if( baseFontKey != null )
@@ -1241,55 +1293,49 @@ class UIDefaultsLoader
return (max * percent) / 100; return (max * percent) / 100;
} }
Integer integer = parseInteger( value, true ); Integer integer = parseInteger( value );
if( integer < min || integer > max ) if( integer < min || integer > max )
throw new NumberFormatException( "integer '" + value + "' out of range (" + min + '-' + max + ')' ); throw new NumberFormatException( "integer '" + value + "' out of range (" + min + '-' + max + ')' );
return integer; return integer;
} }
private static Integer parseInteger( String value, boolean reportError ) { private static Integer parseInteger( String value ) {
try { try {
return Integer.parseInt( value ); return Integer.parseInt( value );
} catch( NumberFormatException ex ) { } 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 { try {
return Integer.parseInt( value ); return Integer.parseInt( value );
} catch( NumberFormatException ex ) { } catch( NumberFormatException ex ) {
try { try {
return Float.parseFloat( value ); return Float.parseFloat( value );
} catch( NumberFormatException ex2 ) { } 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 { try {
return Float.parseFloat( value ); return Float.parseFloat( value );
} catch( NumberFormatException ex ) { } 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 ) { private static ActiveValue parseScaledInteger( String value ) {
int val = parseInteger( value, true ); int val = parseInteger( value );
return t -> { return t -> {
return UIScale.scale( val ); return UIScale.scale( val );
}; };
} }
private static ActiveValue parseScaledFloat( String value ) { private static ActiveValue parseScaledFloat( String value ) {
float val = parseFloat( value, true ); float val = parseFloat( value );
return t -> { return t -> {
return UIScale.scale( val ); return UIScale.scale( val );
}; };
@@ -1344,7 +1390,11 @@ class UIDefaultsLoader
start = i + 1; 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; return strs;
} }

View File

@@ -63,7 +63,7 @@
# accent colors (blueish) # accent colors (blueish)
# set @accentColor to use single accent color or # set @accentColor to use single accent color or
# modify @accentBaseColor to use variations of accent base color # modify @accentBaseColor to use variations of accent base color
@accentColor = null @accentColor = systemColor(accent,null)
@accentBaseColor = #4B6EAF @accentBaseColor = #4B6EAF
@accentBase2Color = lighten(saturate(spin(@accentBaseColor,-8),13%),5%) @accentBase2Color = lighten(saturate(spin(@accentBaseColor,-8),13%),5%)
# accent color variations # accent color variations

View File

@@ -63,7 +63,7 @@
# accent colors (blueish) # accent colors (blueish)
# set @accentColor to use single accent color or # set @accentColor to use single accent color or
# modify @accentBaseColor to use variations of accent base color # modify @accentBaseColor to use variations of accent base color
@accentColor = null @accentColor = systemColor(accent,null)
@accentBaseColor = #2675BF @accentBaseColor = #2675BF
@accentBase2Color = lighten(saturate(@accentBaseColor,10%),6%) @accentBase2Color = lighten(saturate(@accentBaseColor,10%),6%)
# accent color variations # accent color variations

View File

@@ -88,12 +88,12 @@
@menuBackground = lighten(@background,8%) @menuBackground = lighten(@background,8%)
# selection # selection
@selectionBackground = @nsSelectedContentBackgroundColor @selectionBackground = if(systemColor(accent), shade(spin(systemColor(accent),4),20%), @nsSelectedContentBackgroundColor)
@selectionForeground = @nsSelectedMenuItemTextColor @selectionForeground = @nsSelectedMenuItemTextColor
@selectionInactiveBackground = @nsUnemphasizedSelectedContentBackgroundColor @selectionInactiveBackground = @nsUnemphasizedSelectedContentBackgroundColor
# text selection # text selection
@textSelectionBackground = @nsSelectedTextBackgroundColor @textSelectionBackground = systemColor(highlight,if(systemColor(accent), darken(desaturate(systemColor(accent),60%),10%), @nsSelectedTextBackgroundColor))
@textSelectionForeground = @nsSelectedTextColor @textSelectionForeground = @nsSelectedTextColor
# menu # menu
@@ -101,8 +101,8 @@
@menuItemMargin = 3,11,3,11 @menuItemMargin = 3,11,3,11
# accent colors (blueish) # accent colors (blueish)
@accentColor = @nsControlAccentColor @accentColor = systemColor(accent,@nsControlAccentColor)
@accentFocusColor = @nsKeyboardFocusIndicatorColor @accentFocusColor = if(systemColor(accent), fade(spin(systemColor(accent),-10),50%), @nsKeyboardFocusIndicatorColor)
#---- Button ---- #---- Button ----

View File

@@ -88,12 +88,12 @@
@menuBackground = darken(@background,4%) @menuBackground = darken(@background,4%)
# selection # selection
@selectionBackground = @nsSelectedContentBackgroundColor @selectionBackground = if(systemColor(accent), shade(spin(systemColor(accent),4),10%), @nsSelectedContentBackgroundColor)
@selectionForeground = @nsSelectedMenuItemTextColor @selectionForeground = @nsSelectedMenuItemTextColor
@selectionInactiveBackground = @nsUnemphasizedSelectedContentBackgroundColor @selectionInactiveBackground = @nsUnemphasizedSelectedContentBackgroundColor
# text selection # text selection
@textSelectionBackground = @nsSelectedTextBackgroundColor @textSelectionBackground = systemColor(highlight,if(systemColor(accent), tint(systemColor(accent),70%), @nsSelectedTextBackgroundColor))
@textSelectionForeground = @foreground @textSelectionForeground = @foreground
# menu # menu
@@ -102,8 +102,8 @@
@menuItemMargin = 3,11,3,11 @menuItemMargin = 3,11,3,11
# accent colors (blueish) # accent colors (blueish)
@accentColor = @nsControlAccentColor @accentColor = systemColor(accent,@nsControlAccentColor)
@accentFocusColor = @nsKeyboardFocusIndicatorColor @accentFocusColor = if(systemColor(accent), fade(spin(systemColor(accent),4),50%), @nsKeyboardFocusIndicatorColor)
#---- Button ---- #---- Button ----

View File

@@ -24,7 +24,6 @@ import java.net.URISyntaxException;
import java.time.Year; import java.time.Year;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import javax.swing.*; import javax.swing.*;
import javax.swing.text.DefaultEditorKit; 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;
import com.formdev.flatlaf.extras.components.FlatButton.ButtonType; import com.formdev.flatlaf.extras.components.FlatButton.ButtonType;
import com.formdev.flatlaf.icons.FlatAbstractIcon; 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.extras.FlatSVGUtils;
import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.JBRCustomDecorations; import com.formdev.flatlaf.ui.JBRCustomDecorations;
@@ -398,6 +399,7 @@ class DemoFrame
}; };
private final JToggleButton[] accentColorButtons = new JToggleButton[accentColorKeys.length]; private final JToggleButton[] accentColorButtons = new JToggleButton[accentColorKeys.length];
private JLabel accentColorLabel; private JLabel accentColorLabel;
private Color accentColor;
private void initAccentColors() { private void initAccentColors() {
accentColorLabel = new JLabel( "Accent color: " ); accentColorLabel = new JLabel( "Accent color: " );
@@ -416,6 +418,10 @@ class DemoFrame
accentColorButtons[0].setSelected( true ); accentColorButtons[0].setSelected( true );
FlatLaf.setSystemColorGetter( name -> {
return name.equals( "accent" ) ? accentColor : null;
} );
UIManager.addPropertyChangeListener( e -> { UIManager.addPropertyChangeListener( e -> {
if( "lookAndFeel".equals( e.getPropertyName() ) ) if( "lookAndFeel".equals( e.getPropertyName() ) )
updateAccentColorButtons(); updateAccentColorButtons();
@@ -424,17 +430,17 @@ class DemoFrame
} }
private void accentColorChanged( ActionEvent e ) { private void accentColorChanged( ActionEvent e ) {
String accentColor = accentColorKeys[0]; String accentColorKey = null;
for( int i = 0; i < accentColorButtons.length; i++ ) { for( int i = 0; i < accentColorButtons.length; i++ ) {
if( accentColorButtons[i].isSelected() ) { if( accentColorButtons[i].isSelected() ) {
accentColor = accentColorKeys[i]; accentColorKey = accentColorKeys[i];
break; break;
} }
} }
FlatLaf.setGlobalExtraDefaults( (accentColor != accentColorKeys[0]) accentColor = (accentColorKey != null && accentColorKey != accentColorKeys[0])
? Collections.singletonMap( "@accentColor", "$" + accentColor ) ? UIManager.getColor( accentColorKey )
: null ); : null;
Class<? extends LookAndFeel> lafClass = UIManager.getLookAndFeel().getClass(); Class<? extends LookAndFeel> lafClass = UIManager.getLookAndFeel().getClass();
try { try {
@@ -451,7 +457,9 @@ class DemoFrame
lafClass == FlatLightLaf.class || lafClass == FlatLightLaf.class ||
lafClass == FlatDarkLaf.class || lafClass == FlatDarkLaf.class ||
lafClass == FlatIntelliJLaf.class || lafClass == FlatIntelliJLaf.class ||
lafClass == FlatDarculaLaf.class; lafClass == FlatDarculaLaf.class ||
lafClass == FlatMacLightLaf.class ||
lafClass == FlatMacDarkLaf.class;
accentColorLabel.setVisible( isAccentColorSupported ); accentColorLabel.setVisible( isAccentColorSupported );
for( int i = 0; i < accentColorButtons.length; i++ ) for( int i = 0; i < accentColorButtons.length; i++ )

View File

@@ -410,6 +410,10 @@ class FlatCompletionProvider
addFunction( "lazy", addFunction( "lazy",
"uiKey", "UI key (without leading '$')" ); "uiKey", "UI key (without leading '$')" );
addFunction( "systemColor",
"name", "system color name",
"defaultValue", "default color value used if system color is not available" );
addFunction( "rgb", addFunction( "rgb",
"red", "0-255 or 0-100%", "red", "0-255 or 0-100%",
"green", "0-255 or 0-100%", "green", "0-255 or 0-100%",

View File

@@ -56,6 +56,7 @@ public class FlatThemeTokenMaker
tokenMap.put( "lazy", TOKEN_FUNCTION ); tokenMap.put( "lazy", TOKEN_FUNCTION );
// color functions // color functions
tokenMap.put( "systemColor", TOKEN_FUNCTION );
tokenMap.put( "rgb", TOKEN_FUNCTION ); tokenMap.put( "rgb", TOKEN_FUNCTION );
tokenMap.put( "rgba", TOKEN_FUNCTION ); tokenMap.put( "rgba", TOKEN_FUNCTION );
tokenMap.put( "hsl", TOKEN_FUNCTION ); tokenMap.put( "hsl", TOKEN_FUNCTION );

View File

@@ -49,6 +49,9 @@ Prop.ifColor = lighten(if(#000,#0f0,#dfd), 10%)
Prop.ifColorVar = lighten(if(@varTrue,@varTrueValue,@varFalseValue), 10%) Prop.ifColorVar = lighten(if(@varTrue,@varTrueValue,@varFalseValue), 10%)
Prop.lazy = lazy(Prop.string) Prop.lazy = lazy(Prop.string)
Prop.systemColor1 = systemColor(accent,null)
Prop.systemColor2 = systemColor(accent,#f00)
Prop.colorFunc1 = rgb(12,34,56) Prop.colorFunc1 = rgb(12,34,56)
Prop.colorFunc2 = rgba(12,34,56,78) Prop.colorFunc2 = rgba(12,34,56,78)
Prop.colorFunc3 = hsl(12,34%,56%) Prop.colorFunc3 = hsl(12,34%,56%)