From 7ed90cddf8062ac6435889faa37ef27d9e535f5d Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 7 Jul 2020 14:03:39 +0200 Subject: [PATCH] Theme Editor: support color preview for color functions UIDefaultsLoader: made some private methods package private and return parsed valued type --- .../com/formdev/flatlaf/UIDefaultsLoader.java | 44 +++-- flatlaf-theme-editor/build.gradle.kts | 2 +- .../flatlaf/UIDefaultsLoaderAccessor.java | 39 +++++ .../themeeditor/FlatSyntaxTextArea.java | 1 + .../themeeditor/FlatThemeEditorOverlay.java | 13 +- .../FlatThemePropertiesSupport.java | 156 ++++++++++++++++++ .../theme-editor-test.properties | 4 +- 7 files changed, 242 insertions(+), 17 deletions(-) create mode 100644 flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemePropertiesSupport.java 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 9a80178e..b1501355 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -167,7 +167,8 @@ class UIDefaultsLoader String value = resolveValue( properties, (String) e.getValue() ); try { - globals.put( key.substring( GLOBAL_PREFIX.length() ), parseValue( key, value, resolver, addonClassLoaders ) ); + globals.put( key.substring( GLOBAL_PREFIX.length() ), + parseValue( key, value, null, resolver, addonClassLoaders ) ); } catch( RuntimeException ex ) { logParseError( Level.SEVERE, key, value, ex ); } @@ -192,7 +193,7 @@ class UIDefaultsLoader String value = resolveValue( properties, (String) e.getValue() ); try { - defaults.put( key, parseValue( key, value, resolver, addonClassLoaders ) ); + defaults.put( key, parseValue( key, value, null, resolver, addonClassLoaders ) ); } catch( RuntimeException ex ) { logParseError( Level.SEVERE, key, value, ex ); } @@ -206,7 +207,7 @@ class UIDefaultsLoader FlatLaf.LOG.log( level, "FlatLaf: Failed to parse: '" + key + '=' + value + '\'', ex ); } - private static String resolveValue( Properties properties, String value ) { + static String resolveValue( Properties properties, String value ) { if( value.startsWith( PROPERTY_PREFIX ) ) value = value.substring( PROPERTY_PREFIX.length() ); else if( !value.startsWith( VARIABLE_PREFIX ) ) @@ -229,26 +230,34 @@ class UIDefaultsLoader return resolveValue( properties, newValue ); } - private enum ValueType { UNKNOWN, STRING, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, - SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER } + 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]; static Object parseValue( String key, String value ) { - return parseValue( key, value, v -> v, Collections.emptyList() ); + return parseValue( key, value, null, v -> v, Collections.emptyList() ); } - private static Object parseValue( String key, String value, Function resolver, List addonClassLoaders ) { + static Object parseValue( String key, String value, ValueType[] resultValueType, + Function resolver, List addonClassLoaders ) + { + if( resultValueType == null ) + resultValueType = tempResultValueType; + value = value.trim(); // null, false, true switch( value ) { - case "null": return null; - case "false": return false; - case "true": return true; + 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 ); @@ -301,6 +310,8 @@ class UIDefaultsLoader valueType = ValueType.GRAYFILTER; } + resultValueType[0] = valueType; + // parse value switch( valueType ) { case STRING: return value; @@ -323,20 +334,27 @@ class UIDefaultsLoader default: // colors Object color = parseColorOrFunction( value, resolver, false ); - if( color != null ) + if( color != null ) { + resultValueType[0] = ValueType.COLOR; return color; + } // integer Integer integer = parseInteger( value, false ); - if( integer != null ) + if( integer != null ) { + resultValueType[0] = ValueType.INTEGER; return integer; + } // float Float f = parseFloat( value, false ); - if( f != null ) + if( f != null ) { + resultValueType[0] = ValueType.FLOAT; return f; + } // string + resultValueType[0] = ValueType.STRING; return value; } } diff --git a/flatlaf-theme-editor/build.gradle.kts b/flatlaf-theme-editor/build.gradle.kts index e40fb7bc..05f1ccc3 100644 --- a/flatlaf-theme-editor/build.gradle.kts +++ b/flatlaf-theme-editor/build.gradle.kts @@ -21,5 +21,5 @@ plugins { dependencies { implementation( project( ":flatlaf-core" ) ) - implementation( "com.fifesoft:rsyntaxtextarea:3.1.0" ) + implementation( "com.fifesoft:rsyntaxtextarea:3.1.1" ) } 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 3c5c8b5b..3e1c1fc5 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 @@ -16,6 +16,11 @@ package com.formdev.flatlaf; +import java.util.Collections; +import java.util.Properties; +import java.util.function.Function; +import com.formdev.flatlaf.UIDefaultsLoader.ValueType; + /** * Enable accessing package private methods of {@link UIDefaultsLoader}. * @@ -23,6 +28,40 @@ package com.formdev.flatlaf; */ public class UIDefaultsLoaderAccessor { + public static Object UNKNOWN = ValueType.UNKNOWN; + public static Object STRING = ValueType.STRING; + public static Object BOOLEAN = ValueType.BOOLEAN; + public static Object CHARACTER = ValueType.CHARACTER; + public static Object INTEGER = ValueType.INTEGER; + public static Object FLOAT = ValueType.FLOAT; + public static Object BORDER = ValueType.BORDER; + public static Object ICON = ValueType.ICON; + public static Object INSETS = ValueType.INSETS; + public static Object DIMENSION = ValueType.DIMENSION; + public static Object COLOR = ValueType.COLOR; + public static Object SCALEDINTEGER = ValueType.SCALEDINTEGER; + public static Object SCALEDFLOAT = ValueType.SCALEDFLOAT; + public static Object SCALEDINSETS = ValueType.SCALEDINSETS; + public static Object SCALEDDIMENSION = ValueType.SCALEDDIMENSION; + public static Object INSTANCE = ValueType.INSTANCE; + public static Object CLASS = ValueType.CLASS; + public static Object GRAYFILTER = ValueType.GRAYFILTER; + public static Object NULL = ValueType.NULL; + public static Object LAZY = ValueType.LAZY; + + public static String resolveValue( Properties properties, String value ) { + return UIDefaultsLoader.resolveValue( properties, value ); + } + + public static Object parseValue( String key, String value, Object[] resultValueType, + Function resolver ) + { + ValueType[] resultValueType2 = new ValueType[1]; + Object result = UIDefaultsLoader.parseValue( key, value, resultValueType2, resolver, Collections.emptyList() ); + resultValueType[0] = resultValueType2[0]; + return result; + } + public static int parseColorRGBA( String value ) { return UIDefaultsLoader.parseColorRGBA( value ); } diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextArea.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextArea.java index 34760888..4f629012 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextArea.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextArea.java @@ -34,6 +34,7 @@ class FlatSyntaxTextArea { private boolean useColorOfColorTokens; + final FlatThemePropertiesSupport propertiesSupport = new FlatThemePropertiesSupport( this ); private final Map parsedColorsMap = new HashMap<>(); FlatSyntaxTextArea() { diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorOverlay.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorOverlay.java index 9ec3f0eb..987e4f4d 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorOverlay.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorOverlay.java @@ -103,11 +103,18 @@ class FlatThemeEditorOverlay g.setColor( color ); g.fillRect( px, r.y, pw, r.height ); + // if color is semi-transparent paint also none-transparent color + int alpha = color.getAlpha(); + if( alpha != 255 && pw > r.height * 2 ) { + g.setColor( new Color( color.getRGB() ) ); + g.fillRect( px + pw - r.height, r.y, r.height, r.height ); + } + // paint text int textX = px - maxTextWidth; if( textX > r.x + gap) { float[] hsl = HSLColor.fromRGB( color ); - String hslStr = String.format( "HSL %d %d %d", + String hslStr = String.format( "HSL %3d %2d %2d", Math.round( hsl[0] ), Math.round( hsl[1] ), Math.round( hsl[2] ) ); g.setColor( textArea.getForeground() ); FlatUIUtils.drawString( textArea, g, hslStr, textX, @@ -120,6 +127,10 @@ class FlatThemeEditorOverlay } private Color getColorInLine( FlatSyntaxTextArea textArea, int line ) { + Object value = textArea.propertiesSupport.getParsedValueAtLine( line ); + if( value instanceof Color ) + return (Color) value; + Token token = textArea.getTokenListForLine( line ); for( Token t = token; t != null && t.isPaintable(); t = t.getNextToken() ) { if( t.getType() == FlatThemeTokenMaker.TOKEN_COLOR ) { diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemePropertiesSupport.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemePropertiesSupport.java new file mode 100644 index 00000000..442f0ae1 --- /dev/null +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemePropertiesSupport.java @@ -0,0 +1,156 @@ +/* + * Copyright 2020 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.themeeditor; + +import java.awt.Color; +import java.io.IOException; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.function.Function; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import com.formdev.flatlaf.UIDefaultsLoaderAccessor; + +/** + * Supports parsing content of text area in FlatLaf properties syntax. + * + * @author Karl Tauber + */ +class FlatThemePropertiesSupport + implements DocumentListener +{ + private final FlatSyntaxTextArea textArea; + private final Function resolver; + private Properties propertiesCache; + private final Map parsedValueCache = new HashMap<>(); + + FlatThemePropertiesSupport( FlatSyntaxTextArea textArea ) { + this.textArea = textArea; + + resolver = v -> { + return resolveValue( v ); + }; + + textArea.getDocument().addDocumentListener( this ); + } + + private String resolveValue( String value ) { + return UIDefaultsLoaderAccessor.resolveValue( getProperties(), value ); + } + + Object getParsedValueAtLine( int line ) { + Integer lineKey = line; + Object parsedValue = parsedValueCache.get( lineKey ); + if( parsedValue != null ) + return !(parsedValue instanceof Exception) ? parsedValue : null; + + KeyValue keyValue = getKeyValueAtLine( line ); + if( keyValue == null ) + return null; + + try { + Object[] resultValueType = new Object[1]; + String value = resolveValue( keyValue.value ); + parsedValue = UIDefaultsLoaderAccessor.parseValue( keyValue.key, value, resultValueType, resolver ); + parsedValueCache.put( lineKey, parsedValue ); + return parsedValue; + } catch( Exception ex ) { + System.out.println( ex.getMessage() ); //TODO + parsedValueCache.put( lineKey, ex ); + return null; + } + } + + private KeyValue getKeyValueAtLine( int line ) { + try { + int startOffset = textArea.getLineStartOffset( line ); + int endOffset = textArea.getLineEndOffset( line ); + String text = textArea.getText( startOffset, endOffset - startOffset ); + + Properties properties = new Properties(); + properties.load( new StringReader( text ) ); + if( properties.isEmpty() ) + return null; + + String key = (String) properties.keys().nextElement(); + String value = properties.getProperty( key ); + return new KeyValue( key, value ); + } catch( BadLocationException | IOException ex ) { + // ignore + return null; + } + } + + private Properties getProperties() { + if( propertiesCache != null ) + return propertiesCache; + + propertiesCache = new Properties(); + try { + propertiesCache.load( new StringReader( textArea.getText() ) ); + } catch( IOException ex ) { + ex.printStackTrace(); //TODO + } + return propertiesCache; + } + + private void clearCache() { + propertiesCache = null; + parsedValueCache.clear(); + } + + //---- interface DocumentListener ---- + + @Override + public void insertUpdate( DocumentEvent e ) { + clearCache(); + } + + @Override + public void removeUpdate( DocumentEvent e ) { + clearCache(); + } + + @Override + public void changedUpdate( DocumentEvent e ) { + } + + //---- class KeyValue ----------------------------------------------------- + + static class CacheLineInfo { + Object parsedValue; + Object valueType; + Exception parseError; + + Color origColor; + } + + //---- class KeyValue ----------------------------------------------------- + + static class KeyValue { + final String key; + final String value; + + KeyValue( String key, String value ) { + this.key = key; + this.value = value; + } + } +} diff --git a/flatlaf-theme-editor/theme-editor-test.properties b/flatlaf-theme-editor/theme-editor-test.properties index 4fe494bb..889569e1 100644 --- a/flatlaf-theme-editor/theme-editor-test.properties +++ b/flatlaf-theme-editor/theme-editor-test.properties @@ -33,8 +33,8 @@ Prop.lazy=lazy(Prop.string) Prop.colorFunc1=rgb(12,34,56) Prop.colorFunc2=rgba(12,34,56,78) -Prop.colorFunc3=hsl(12,34,56) -Prop.colorFunc4=hsla(12,34,56,78) +Prop.colorFunc3=hsl(12,34%,56%) +Prop.colorFunc4=hsla(12,34%,56%,78%) Prop.colorFunc5=lighten(#fe1289,20%) Prop.colorFunc6=darken(#fe1289,20%)