From a4ea88f4be50348ac357588dcfd51d13c594e91d Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 26 Sep 2021 23:54:06 +0200 Subject: [PATCH] UIDefaultsLoader: added if() function (inspired by Less CSS) --- .../com/formdev/flatlaf/UIDefaultsLoader.java | 54 ++++++++++++++++++- .../themeeditor/FlatCompletionProvider.java | 7 +++ .../themeeditor/FlatThemeTokenMaker.java | 7 ++- .../theme-editor-test.properties | 18 +++++++ 4 files changed, 83 insertions(+), 3 deletions(-) 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 a63fd6bd..723ca6f9 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -312,6 +312,24 @@ class UIDefaultsLoader case "true": resultValueType[0] = ValueType.BOOLEAN; return true; } + // check for function "if" + // Syntax: if(condition,trueValue,falseValue) + // - condition: evaluates to true if: + // - is not "null" + // - is not "false" + // - is not an integer with zero value + // - trueValue: used if condition is true + // - falseValue: used if condition is false + if( value.startsWith( "if(" ) && value.endsWith( ")" ) ) { + List params = splitFunctionParams( value.substring( 3, value.length() - 1 ), ',' ); + if( params.size() != 3 ) + throwMissingParametersException( value ); + + boolean ifCondition = parseCondition( params.get( 0 ), resolver, addonClassLoaders ); + String ifValue = params.get( ifCondition ? 1 : 2 ); + return parseValue( key, resolver.apply( ifValue ), resultValueType, resolver, addonClassLoaders ); + } + // check for function "lazy" // Syntax: lazy(uiKey) if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) { @@ -420,6 +438,20 @@ class UIDefaultsLoader } } + private static boolean parseCondition( String condition, + Function resolver, List addonClassLoaders ) + { + try { + Object conditionValue = parseValue( "", resolver.apply( condition ), null, resolver, addonClassLoaders ); + return (conditionValue != null && + !conditionValue.equals( false ) && + !conditionValue.equals( 0 ) ); + } catch( IllegalArgumentException ex ) { + // ignore errors (e.g. variable or property not found) and evaluate to false + return false; + } + } + private static Object parseBorder( String value, Function resolver, List addonClassLoaders ) { if( value.indexOf( ',' ) >= 0 ) { // top,left,bottom,right[,lineColor[,lineThickness]] @@ -584,7 +616,7 @@ class UIDefaultsLoader String function = value.substring( 0, paramsStart ).trim(); List params = splitFunctionParams( value.substring( paramsStart + 1, value.length() - 1 ), ',' ); if( params.isEmpty() ) - throw new IllegalArgumentException( "missing parameters in function '" + value + "'" ); + throwMissingParametersException( value ); if( parseColorDepth > 100 ) throw new IllegalArgumentException( "endless recursion in color function '" + value + "'" ); @@ -592,6 +624,7 @@ 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 "hsl": return parseColorHslOrHsla( false, params ); @@ -620,6 +653,21 @@ class UIDefaultsLoader throw new IllegalArgumentException( "unknown color function '" + value + "'" ); } + /** + * Syntax: if(condition,trueValue,falseValue) + *

+ * 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 ) { + 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 ); + } + /** * Syntax: rgb(red,green,blue) or rgba(red,green,blue,alpha) * - red: an integer 0-255 or a percentage 0-100% @@ -1029,4 +1077,8 @@ class UIDefaultsLoader LoggingFacade.INSTANCE.logSevere( "FlatLaf: '" + uiKey + "' not found in UI defaults.", null ); return value; } + + private static void throwMissingParametersException( String value ) { + throw new IllegalArgumentException( "missing parameters in function '" + value + "'" ); + } } 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 2f333a13..ae22afca 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 @@ -405,6 +405,13 @@ class FlatCompletionProvider setParameterizedCompletionParams( '(', ",", ')' ); setParameterChoicesProvider( this ); + addFunction( "if", + "condition", "evaluates to true if: is not \"null\" and is not \"false\" and is not an integer with zero value", + "trueValue", "used if condition is true", + "falseValue", "used if condition is false" ); + addFunction( "lazy", + "uiKey", "UI key (without leading '$')" ); + 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 f8dbcea9..b8eac587 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 @@ -51,7 +51,11 @@ public class FlatThemeTokenMaker tokenMap.put( "false", Token.LITERAL_BOOLEAN ); tokenMap.put( "true", Token.LITERAL_BOOLEAN ); - // functions + // general functions + tokenMap.put( "if", TOKEN_FUNCTION ); + tokenMap.put( "lazy", TOKEN_FUNCTION ); + + // color functions tokenMap.put( "rgb", TOKEN_FUNCTION ); tokenMap.put( "rgba", TOKEN_FUNCTION ); tokenMap.put( "hsl", TOKEN_FUNCTION ); @@ -72,7 +76,6 @@ public class FlatThemeTokenMaker tokenMap.put( "tint", TOKEN_FUNCTION ); tokenMap.put( "shade", TOKEN_FUNCTION ); tokenMap.put( "contrast", TOKEN_FUNCTION ); - tokenMap.put( "lazy", TOKEN_FUNCTION ); // function options tokenMap.put( "relative", Token.RESERVED_WORD ); diff --git a/flatlaf-theme-editor/theme-editor-test.properties b/flatlaf-theme-editor/theme-editor-test.properties index 46a84560..7d75455b 100644 --- a/flatlaf-theme-editor/theme-editor-test.properties +++ b/flatlaf-theme-editor/theme-editor-test.properties @@ -29,6 +29,24 @@ Prop.true = true Prop.var = @var1 Prop.ref = $Prop.string +Prop.ifNotNull = if(#000,#0f0,#dfd) +Prop.ifNull = if(null,#0f0,#dfd) +Prop.ifTrue = if(true,#0f0,#dfd) +Prop.ifFalse = if(false,#0f0,#dfd) +Prop.ifOne = if(1,#0f0,#dfd) +Prop.ifZero = if(0,#0f0,#dfd) +@varTrue = true +@varFalse = false +@varTrueValue = #0f0 +@varFalseValue = #dfd +Prop.ifVarTrue = if(@varTrue,@varTrueValue,@varFalseValue) +Prop.ifVarFalse = if(@varFalse,@varTrueValue,@varFalseValue) +Prop.ifTrueColorFunc = if(true,lighten(#f00,20%),darken(#f00,20%)) +Prop.ifFalseColorFunc = if(false,lighten(#f00,20%),darken(#f00,20%)) +Prop.ifUndefinedVar = if(@undefinedVar,#0f0,#dfd) +Prop.ifUndefinedProp = if($undefinedProp,#0f0,#dfd) +Prop.ifColor = lighten(if(#000,#0f0,#dfd), 10%) +Prop.ifColorVar = lighten(if(@varTrue,@varTrueValue,@varFalseValue), 10%) Prop.lazy = lazy(Prop.string) Prop.colorFunc1 = rgb(12,34,56)