diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java new file mode 100644 index 00000000..bc078ac3 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java @@ -0,0 +1,205 @@ +/* + * Copyright 2019 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.swing.UIDefaults; +import com.formdev.flatlaf.json.Json; +import com.formdev.flatlaf.json.ParseException; + +/** + * @author Karl Tauber + */ +public class IntelliJTheme +{ + public final String name; + public final boolean dark; + public final String author; + + private final Map colors; + private final Map ui; + private final Map icons; + + public static boolean install( InputStream in ) + throws IOException, ParseException + { + try { + return FlatLaf.install( createLaf( in ) ); + } catch( Exception ex ) { + System.err.println( "Failed to initialize look and feel" ); + ex.printStackTrace(); + return false; + } + } + + public static FlatLaf createLaf( InputStream in ) + throws IOException, ParseException + { + return createLaf( new IntelliJTheme( in ) ); + } + + public static FlatLaf createLaf( IntelliJTheme theme ) { + FlatLaf laf = theme.dark + ? new DarkLaf( theme ) + : new LightLaf( theme ); + return laf; + } + + @SuppressWarnings( "unchecked" ) + public IntelliJTheme( InputStream in ) + throws IOException, ParseException + { + Map json; + try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 ) ) { + json = (Map) Json.parse( reader ); + } + + name = (String) json.get( "name" ); + dark = Boolean.parseBoolean( (String) json.get( "dark" ) ); + author = (String) json.get( "author" ); + + colors = (Map) json.get( "colors" ); + ui = (Map) json.get( "ui" ); + icons = (Map) json.get( "icons" ); + } + + private void applyProperties( UIDefaults defaults ) { + Map properties = convertToProperties(); + applyProperties( properties, defaults ); + } + + private static void applyProperties( Map properties, UIDefaults defaults ) { + // globals + ArrayList keys = Collections.list( defaults.keys() ); + for( Map.Entry e : properties.entrySet() ) { + String key = e.getKey(); + if( !key.startsWith( "*." ) ) + continue; + + String tail = key.substring( 1 ); + Object uiValue = UIDefaultsLoader.parseValue( key, e.getValue() ); + for( Object k : keys ) { + if( k instanceof String && ((String)k).endsWith( tail ) ) + defaults.put( k, uiValue ); + } + } + + // non-globals + for( Map.Entry e : properties.entrySet() ) { + String key = e.getKey(); + if( key.startsWith( "*." ) ) + continue; + + Object uiValue = UIDefaultsLoader.parseValue( key, e.getValue() ); + defaults.put( key, uiValue ); + } + } + + private Map convertToProperties() { + Map properties = new LinkedHashMap<>(); + if( ui == null ) + return properties; + + // convert json structure to properties map + for( Map.Entry e : ui.entrySet() ) + addToProperties( e.getKey(), e.getValue(), properties ); + + return properties; + } + + @SuppressWarnings( "unchecked" ) + private void addToProperties( String key, Object value, Map properties ) { + if( value instanceof Map ) { + for( Map.Entry e : ((Map)value).entrySet() ) + addToProperties( key + '.' + e.getKey(), e.getValue(), properties ); + } else { + String valueStr = value.toString(); + + // map colors + if( colors != null ) + valueStr = colors.getOrDefault( valueStr, valueStr ); + + properties.put( key, valueStr ); + } + } + + //---- class LightLaf ----------------------------------------------------- + + public static class LightLaf + extends FlatIntelliJLaf + { + private final IntelliJTheme theme; + + public LightLaf( IntelliJTheme theme ) { + this.theme = theme; + } + + @Override + public String getName() { + return theme.name; + } + + @Override + public String getDescription() { + return theme.name; + } + + @Override + public UIDefaults getDefaults() { + UIDefaults defaults = super.getDefaults(); + theme.applyProperties( defaults ); + return defaults; + } + } + + //---- class DarkLaf ------------------------------------------------------ + + public static class DarkLaf + extends FlatDarculaLaf + { + private final IntelliJTheme theme; + + public DarkLaf( IntelliJTheme theme ) { + this.theme = theme; + } + + @Override + public String getName() { + return theme.name; + } + + @Override + public String getDescription() { + return theme.name; + } + + @Override + public UIDefaults getDefaults() { + UIDefaults defaults = super.getDefaults(); + theme.applyProperties( defaults ); + return defaults; + } + } +} 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 e16e3ab8..acfc75d1 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -170,6 +170,10 @@ class UIDefaultsLoader private enum ValueType { UNKNOWN, STRING, INTEGER, BORDER, ICON, INSETS, SIZE, COLOR, SCALEDNUMBER } + static Object parseValue( String key, String value ) { + return parseValue( key, value, v -> v ); + } + private static Object parseValue( String key, String value, Function resolver ) { value = value.trim(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/json/Json.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/json/Json.java new file mode 100644 index 00000000..9a7b4b45 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/json/Json.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019 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 + * + * http://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.json; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Karl Tauber + */ +public class Json +{ + public static Object parse( Reader reader ) + throws IOException, ParseException + { + DefaultHandler handler = new DefaultHandler(); + new JsonParser( handler ).parse( reader ); + return handler.getValue(); + } + + //---- class DefaultHandler ----------------------------------------------- + + static class DefaultHandler + extends JsonHandler, Map> + { + private Object value; + + @Override + public List startArray() { + return new ArrayList<>(); + } + + @Override + public Map startObject() { + return new LinkedHashMap<>(); + } + + @Override + public void endNull() { + value = "null"; + } + + @Override + public void endBoolean( boolean bool ) { + value = bool ? "true" : "false"; + } + + @Override + public void endString( String string ) { + value = string; + } + + @Override + public void endNumber( String string ) { + value = string; + } + + @Override + public void endArray( List array ) { + value = array; + } + + @Override + public void endObject( Map object ) { + value = object; + } + + @Override + public void endArrayValue( List array ) { + array.add( value ); + } + + @Override + public void endObjectValue( Map object, String name ) { + object.put( name, value ); + } + + Object getValue() { + return value; + } + } +}