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; + } +}