IntelliJ Themes: basic support for loading and applying IntelliJ Platform themes in .theme.json format

This commit is contained in:
Karl Tauber
2019-11-11 16:25:37 +01:00
parent ebe1cd3367
commit e60db4ff90
3 changed files with 309 additions and 0 deletions

View File

@@ -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<String, String> colors;
private final Map<String, Object> ui;
private final Map<String, Object> 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<String, Object> json;
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 ) ) {
json = (Map<String, Object>) Json.parse( reader );
}
name = (String) json.get( "name" );
dark = Boolean.parseBoolean( (String) json.get( "dark" ) );
author = (String) json.get( "author" );
colors = (Map<String, String>) json.get( "colors" );
ui = (Map<String, Object>) json.get( "ui" );
icons = (Map<String, Object>) json.get( "icons" );
}
private void applyProperties( UIDefaults defaults ) {
Map<String, String> properties = convertToProperties();
applyProperties( properties, defaults );
}
private static void applyProperties( Map<String, String> properties, UIDefaults defaults ) {
// globals
ArrayList<Object> keys = Collections.list( defaults.keys() );
for( Map.Entry<String, String> 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<String, String> e : properties.entrySet() ) {
String key = e.getKey();
if( key.startsWith( "*." ) )
continue;
Object uiValue = UIDefaultsLoader.parseValue( key, e.getValue() );
defaults.put( key, uiValue );
}
}
private Map<String, String> convertToProperties() {
Map<String, String> properties = new LinkedHashMap<>();
if( ui == null )
return properties;
// convert json structure to properties map
for( Map.Entry<String, Object> e : ui.entrySet() )
addToProperties( e.getKey(), e.getValue(), properties );
return properties;
}
@SuppressWarnings( "unchecked" )
private void addToProperties( String key, Object value, Map<String, String> properties ) {
if( value instanceof Map ) {
for( Map.Entry<String, Object> e : ((Map<String, Object>)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;
}
}
}

View File

@@ -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<String, String> resolver ) {
value = value.trim();

View File

@@ -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<List<Object>, Map<String, Object>>
{
private Object value;
@Override
public List<Object> startArray() {
return new ArrayList<>();
}
@Override
public Map<String, Object> 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<Object> array ) {
value = array;
}
@Override
public void endObject( Map<String, Object> object ) {
value = object;
}
@Override
public void endArrayValue( List<Object> array ) {
array.add( value );
}
@Override
public void endObjectValue( Map<String, Object> object, String name ) {
object.put( name, value );
}
Object getValue() {
return value;
}
}
}