diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java
index ff6a5aa5..5318773d 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java
@@ -126,6 +126,16 @@ public interface FlatClientProperties
//---- JComponent ---------------------------------------------------------
+ /**
+ * Specifies the style of a component in CSS syntax ("key1: value1; key2: value2; ...").
+ *
+ * Components {@link javax.swing.JComponent}
+ * Value type {@link java.lang.String}
+ *
+ * @since TODO
+ */
+ String COMPONENT_STYLE = "JComponent.style";
+
/**
* Specifies minimum width of a component.
*
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java
index f408aaa5..c98ebf42 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java
@@ -726,6 +726,21 @@ public abstract class FlatLaf
customDefaultsSources.remove( folder );
}
+ /**
+ * Parses a UI defaults value string and converts it into a binary object.
+ *
+ * See: https://www.formdev.com/flatlaf/properties-files/
+ *
+ * @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();
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java
index b44f769f..2c0b31ba 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java
@@ -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 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();
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java
new file mode 100644
index 00000000..0c94dccf
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java
@@ -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 parse( Map oldStyleValues,
+ String style, BiFunction applyProperty ) throws IllegalArgumentException
+ {
+ // restore previous values
+ if( oldStyleValues != null ) {
+ for( Entry e : oldStyleValues.entrySet() )
+ applyProperty.apply( e.getKey(), e.getValue() );
+ }
+
+ // ignore empty style
+ if( style == null || style.trim().isEmpty() )
+ return null;
+
+ Map 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;
+ }
+}