Styling: basic implementation of styling support using client property JComponent.style and CSS syntax

only for JSlider (at the moment)

e.g. `mySlider.putClientProperty( "JComponent.style", "trackValueColor: #00f; trackColor: #f00; thumbColor: #0f0; trackWidth: 6; thumbSize: 40,20; focusWidth: 20" );`

(issues #117 and #340)
This commit is contained in:
Karl Tauber
2021-06-15 14:35:26 +02:00
parent 8a72b30cbc
commit edade93054
4 changed files with 214 additions and 0 deletions

View File

@@ -126,6 +126,16 @@ public interface FlatClientProperties
//---- JComponent ---------------------------------------------------------
/**
* Specifies the style of a component in CSS syntax ("key1: value1; key2: value2; ...").
* <p>
* <strong>Components</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
*
* @since TODO
*/
String COMPONENT_STYLE = "JComponent.style";
/**
* Specifies minimum width of a component.
* <p>

View File

@@ -726,6 +726,21 @@ public abstract class FlatLaf
customDefaultsSources.remove( folder );
}
/**
* Parses a UI defaults value string and converts it into a binary object.
* <p>
* See: <a href="https://www.formdev.com/flatlaf/properties-files/">https://www.formdev.com/flatlaf/properties-files/</a>
*
* @param key the key, which is used to determine the value type
* @param value the value string
* @return the binary value
* @throws IllegalArgumentException on syntax errors
* @since TODO
*/
public static Object parseDefaultsValue( String key, String value ) throws IllegalArgumentException {
return UIDefaultsLoader.parseValue( key, value );
}
private static void reSetLookAndFeel() {
EventQueue.invokeLater( () -> {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();

View File

@@ -27,6 +27,8 @@ import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JSlider;
import javax.swing.LookAndFeel;
@@ -34,6 +36,8 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSliderUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
@@ -90,6 +94,7 @@ public class FlatSliderUI
protected Color disabledTrackColor;
protected Color disabledThumbColor;
protected Color disabledThumbBorderColor;
protected Color tickColor;
private Color defaultBackground;
private Color defaultForeground;
@@ -98,6 +103,7 @@ public class FlatSliderUI
protected boolean thumbPressed;
private Object[] oldRenderingHints;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatSliderUI();
@@ -107,6 +113,13 @@ public class FlatSliderUI
super( null );
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
applyStyle( FlatStyleSupport.getStyle( slider ) );
}
@Override
protected void installDefaults( JSlider slider ) {
super.installDefaults( slider );
@@ -134,6 +147,7 @@ public class FlatSliderUI
disabledTrackColor = UIManager.getColor( "Slider.disabledTrackColor" );
disabledThumbColor = UIManager.getColor( "Slider.disabledThumbColor" );
disabledThumbBorderColor = FlatUIUtils.getUIColor( "Slider.disabledThumbBorderColor", "Component.disabledBorderColor" );
tickColor = FlatUIUtils.getUIColor( "Slider.tickColor", Color.BLACK ); // see BasicSliderUI.paintTicks()
defaultBackground = UIManager.getColor( "Slider.background" );
defaultForeground = UIManager.getColor( "Slider.foreground" );
@@ -155,9 +169,12 @@ public class FlatSliderUI
disabledTrackColor = null;
disabledThumbColor = null;
disabledThumbBorderColor = null;
tickColor = null;
defaultBackground = null;
defaultForeground = null;
oldStyleValues = null;
}
@Override
@@ -165,6 +182,57 @@ public class FlatSliderUI
return new FlatTrackListener();
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) {
PropertyChangeListener superListener = super.createPropertyChangeListener( slider );
return e -> {
superListener.propertyChange( e );
switch( e.getPropertyName() ) {
case FlatClientProperties.COMPONENT_STYLE:
applyStyle( FlatStyleSupport.toString( e.getNewValue() ) );
slider.revalidate();
slider.repaint();
break;
}
};
}
/**
* @since TODO
*/
protected void applyStyle( String style ) {
oldStyleValues = FlatStyleSupport.parse( oldStyleValues, style, this::applyStyleProperty );
}
/**
* @since TODO
*/
protected Object applyStyleProperty( String key, Object value ) {
Object oldValue;
switch( key ) {
case "trackWidth": oldValue = trackWidth; trackWidth = (int) value; break;
case "thumbSize": oldValue = thumbSize; thumbSize = (Dimension) value; break;
case "focusWidth": oldValue = focusWidth; focusWidth = (int) value; break;
case "trackValueColor": oldValue = trackValueColor; trackValueColor = (Color) value; break;
case "trackColor": oldValue = trackColor; trackColor = (Color) value; break;
case "thumbColor": oldValue = thumbColor; thumbColor = (Color) value; break;
case "thumbBorderColor": oldValue = thumbBorderColor; thumbBorderColor = (Color) value; break;
case "focusedColor": oldValue = focusedColor; focusedColor = (Color) value; break;
case "focusedThumbBorderColor": oldValue = focusedThumbBorderColor; focusedThumbBorderColor = (Color) value; break;
case "hoverThumbColor": oldValue = hoverThumbColor; hoverThumbColor = (Color) value; break;
case "pressedThumbColor": oldValue = pressedThumbColor; pressedThumbColor = (Color) value; break;
case "disabledTrackColor": oldValue = disabledTrackColor; disabledTrackColor = (Color) value; break;
case "disabledThumbColor": oldValue = disabledThumbColor; disabledThumbColor = (Color) value; break;
case "disabledThumbBorderColor": oldValue = disabledThumbBorderColor; disabledThumbBorderColor = (Color) value; break;
case "tickColor": oldValue = tickColor; tickColor = (Color) value; break;
default: throw new IllegalArgumentException( "unknown style '" + key + "'" );
}
return oldValue;
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
if( c == null )
@@ -306,6 +374,19 @@ debug*/
((Graphics2D)g).fill( track );
}
@Override
public void paintTicks( Graphics g ) {
// because BasicSliderUI.paintTicks() always uses
// g.setColor( UIManager.getColor("Slider.tickColor") )
// we override this method and use our tickColor field to allow styling
super.paintTicks( new Graphics2DProxy( (Graphics2D) g ) {
@Override
public void setColor( Color c ) {
super.setColor( tickColor );
}
} );
}
@Override
public void paintThumb( Graphics g ) {
Color thumbColor = getThumbColor();

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2021 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.ui;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BiFunction;
import javax.swing.JComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.StringUtils;
/**
* Support for styling components in CSS syntax.
*
* @author Karl Tauber
* @since TODO
*/
public class FlatStyleSupport
{
/**
* Parses styles in CSS syntax ("key1: value1; key2: value2; ..."),
* converts the value strings into binary and invokes the given function
* to apply the properties.
*
* @param oldStyleValues map of old values modified by the previous invocation, or {@code null}
* @param style the style in CSS syntax
* @param applyProperty function that is invoked to apply the properties;
* first parameter is the key, second the binary value;
* the function must return the old value
* @return map of old values modified by the given style, or {@code null}
* @throws IllegalArgumentException on syntax errors
*/
public static Map<String, Object> parse( Map<String, Object> oldStyleValues,
String style, BiFunction<String, Object, Object> applyProperty ) throws IllegalArgumentException
{
// restore previous values
if( oldStyleValues != null ) {
for( Entry<String, Object> e : oldStyleValues.entrySet() )
applyProperty.apply( e.getKey(), e.getValue() );
}
// ignore empty style
if( style == null || style.trim().isEmpty() )
return null;
Map<String, Object> oldValues = null;
// split style into parts and process them
for( String part : StringUtils.split( style, ';' ) ) {
// ignore empty parts
part = part.trim();
if( part.isEmpty() )
continue;
// find separator colon
int sepIndex = part.indexOf( ':' );
if( sepIndex < 0 )
throw new IllegalArgumentException( "missing colon in '" + part + "'" );
// split into key and value
String key = part.substring( 0, sepIndex ).trim();
String value = part.substring( sepIndex + 1 ).trim();
if( key.isEmpty() )
throw new IllegalArgumentException( "missing key in '" + part + "'" );
if( value.isEmpty() )
throw new IllegalArgumentException( "missing value in '" + part + "'" );
// parse value string and convert it into binary value
Object val = FlatLaf.parseDefaultsValue( key, value );
Object oldValue = applyProperty.apply( key, val );
// remember previous value
if( oldValues == null )
oldValues = new HashMap<>();
oldValues.put( key, oldValue );
}
return oldValues;
}
public static boolean hasStyle( JComponent c ) {
return getStyle( c ) != null;
}
public static String getStyle( JComponent c ) {
return toString( c.getClientProperty( FlatClientProperties.COMPONENT_STYLE ) );
}
static String toString( Object style ) {
return (style instanceof String) ? (String) style : null;
}
}