diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java index 9675479c..8448f128 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatAbstractIcon.java @@ -39,7 +39,7 @@ public abstract class FlatAbstractIcon { protected final int width; protected final int height; - protected final Color color; + protected Color color; public FlatAbstractIcon( int width, int height, Color color ) { this.width = width; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCapsLockIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCapsLockIcon.java index 2ceab38f..345e6184 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCapsLockIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCapsLockIcon.java @@ -16,12 +16,14 @@ package com.formdev.flatlaf.icons; +import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStyleSupport.UnknownStyleException; import com.formdev.flatlaf.ui.FlatUIUtils; /** @@ -38,6 +40,17 @@ public class FlatCapsLockIcon super( 16, 16, UIManager.getColor( "PasswordField.capsLockIconColor" ) ); } + /** + * @since TODO + */ + public Object applyStyleProperty( String key, Object value ) { + Object oldValue; + switch( key ) { + case "capsLockIconColor": oldValue = color; color = (Color) value; return oldValue; + default: throw new UnknownStyleException( key ); + } + } + @Override protected void paintIcon( Component c, Graphics2D g ) { /* diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java index 27c8d424..eb7f93bd 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java @@ -31,6 +31,7 @@ import javax.swing.JViewport; import javax.swing.UIManager; import javax.swing.plaf.basic.BasicBorders; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; import com.formdev.flatlaf.util.DerivedColor; /** @@ -61,19 +62,35 @@ import com.formdev.flatlaf.util.DerivedColor; public class FlatBorder extends BasicBorders.MarginBorder { - protected final int focusWidth = UIManager.getInt( "Component.focusWidth" ); - protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ); - protected final float innerOutlineWidth = FlatUIUtils.getUIFloat( "Component.innerOutlineWidth", 0 ); - protected final Color focusColor = UIManager.getColor( "Component.focusColor" ); - protected final Color borderColor = UIManager.getColor( "Component.borderColor" ); - protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" ); - protected final Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" ); + @Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" ); + @Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ); + @Styleable protected float innerOutlineWidth = FlatUIUtils.getUIFloat( "Component.innerOutlineWidth", 0 ); + @Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" ); + @Styleable protected Color borderColor = UIManager.getColor( "Component.borderColor" ); + @Styleable protected Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" ); + @Styleable protected Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" ); - protected final Color errorBorderColor = UIManager.getColor( "Component.error.borderColor" ); - protected final Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" ); - protected final Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" ); - protected final Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" ); - protected final Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" ); + protected Color errorBorderColor = UIManager.getColor( "Component.error.borderColor" ); + protected Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" ); + protected Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" ); + protected Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" ); + protected Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" ); + + /** + * @since TODO + */ + public Object applyStyleProperty( String key, Object value ) { + Object oldValue; + switch( key ) { + case "error.borderColor": oldValue = errorBorderColor; errorBorderColor = (Color) value; return oldValue; + case "error.focusedBorderColor": oldValue = errorFocusedBorderColor; errorFocusedBorderColor = (Color) value; return oldValue; + case "warning.borderColor": oldValue = warningBorderColor; warningBorderColor = (Color) value; return oldValue; + case "warning.focusedBorderColor": oldValue = warningFocusedBorderColor; warningFocusedBorderColor = (Color) value; return oldValue; + case "custom.borderColor": oldValue = customBorderColor; customBorderColor = (Color) value; return oldValue; + } + + return FlatStyleSupport.applyToAnnotatedObject( this, key, value ); + } @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java index d338aa53..d7d9e1bb 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java @@ -26,14 +26,19 @@ import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.beans.PropertyChangeEvent; +import java.util.Map; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.LookAndFeel; import javax.swing.UIManager; +import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicPasswordFieldUI; import javax.swing.text.Caret; import javax.swing.text.JTextComponent; +import com.formdev.flatlaf.icons.FlatCapsLockIcon; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStyleSupport.UnknownStyleException; import com.formdev.flatlaf.util.HiDPIUtils; /** @@ -71,20 +76,30 @@ import com.formdev.flatlaf.util.HiDPIUtils; public class FlatPasswordFieldUI extends BasicPasswordFieldUI { - protected int minimumWidth; + @Styleable protected int minimumWidth; protected boolean isIntelliJTheme; - protected Color placeholderForeground; - protected Color focusedBackground; - protected boolean showCapsLock; + @Styleable protected Color placeholderForeground; + @Styleable protected Color focusedBackground; + @Styleable protected boolean showCapsLock; protected Icon capsLockIcon; private FocusListener focusListener; private KeyListener capsLockListener; + private Map oldStyleValues; + private boolean borderShared = true; + private boolean capsLockIconShared = true; public static ComponentUI createUI( JComponent c ) { return new FlatPasswordFieldUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStyleSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -159,7 +174,47 @@ public class FlatPasswordFieldUI @Override protected void propertyChange( PropertyChangeEvent e ) { super.propertyChange( e ); - FlatTextFieldUI.propertyChange( getComponent(), e ); + FlatTextFieldUI.propertyChange( getComponent(), e, this::applyStyle ); + } + + /** + * @since TODO + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStyleSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon ) { + if( capsLockIconShared ) { + capsLockIcon = FlatStyleSupport.cloneIcon( capsLockIcon ); + capsLockIconShared = false; + } + return ((FlatCapsLockIcon)capsLockIcon).applyStyleProperty( key, value ); + } + + try { + return FlatStyleSupport.applyToAnnotatedObject( this, key, value ); + } catch( UnknownStyleException ex ) { + Border border = getComponent().getBorder(); + if( border instanceof FlatBorder ) { + if( borderShared ) { + border = FlatStyleSupport.cloneBorder( border ); + getComponent().setBorder( border ); + borderShared = false; + } + + try { + return ((FlatBorder)border).applyStyleProperty( key, value ); + } catch( UnknownStyleException ex2 ) { + // ignore + } + } + throw ex; + } } @Override diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java index 27da0e49..b812ffd8 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java @@ -68,7 +68,7 @@ public class FlatRadioButtonUI private Color defaultBackground; private final boolean shared; - private boolean iconShared; + private boolean iconShared = true; private boolean defaults_initialized = false; private Map oldStyleValues; @@ -83,7 +83,6 @@ public class FlatRadioButtonUI */ protected FlatRadioButtonUI( boolean shared ) { this.shared = shared; - iconShared = true; } @Override @@ -169,11 +168,7 @@ public class FlatRadioButtonUI return null; if( iconShared ) { - try { - icon = icon.getClass().getDeclaredConstructor().newInstance(); - } catch( Exception ex ) { - throw new IllegalArgumentException( "failed to clone icon '" + icon.getClass().getName() + "'" ); - } + icon = FlatStyleSupport.cloneIcon( icon ); iconShared = false; } 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 index 6f37a993..8fc4459c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java @@ -26,8 +26,10 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.BiFunction; +import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.UIManager; +import javax.swing.border.Border; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.util.StringUtils; @@ -217,12 +219,30 @@ public class FlatStyleSupport return c.getClientProperty( FlatClientProperties.COMPONENT_STYLE ); } + static Border cloneBorder( Border border ) { + Class borderClass = border.getClass(); + try { + return borderClass.getDeclaredConstructor().newInstance(); + } catch( Exception ex ) { + throw new IllegalArgumentException( "failed to clone border '" + borderClass.getName() + "'" ); + } + } + + static Icon cloneIcon( Icon icon ) { + Class iconClass = icon.getClass(); + try { + return iconClass.getDeclaredConstructor().newInstance(); + } catch( Exception ex ) { + throw new IllegalArgumentException( "failed to clone icon '" + iconClass.getName() + "'" ); + } + } + //---- class UnknownStyleException ---------------------------------------- public static class UnknownStyleException extends IllegalArgumentException { - UnknownStyleException( String key ) { + public UnknownStyleException( String key ) { super( key ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextBorder.java index b57a1088..9ad7a5b4 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextBorder.java @@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui; import java.awt.Component; import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; /** * Border for various text components (e.g. {@link javax.swing.JTextField}). @@ -29,7 +30,7 @@ import javax.swing.UIManager; public class FlatTextBorder extends FlatBorder { - protected final int arc = UIManager.getInt( "TextComponent.arc" ); + @Styleable protected int arc = UIManager.getInt( "TextComponent.arc" ); @Override protected int getArc( Component c ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java index d80ac851..ff33eb85 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java @@ -26,18 +26,23 @@ import java.awt.Graphics2D; import java.awt.Insets; import java.awt.event.FocusListener; import java.beans.PropertyChangeEvent; +import java.util.Map; +import java.util.function.Consumer; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.LookAndFeel; import javax.swing.UIManager; +import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTextFieldUI; import javax.swing.text.Caret; import javax.swing.text.JTextComponent; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; +import com.formdev.flatlaf.ui.FlatStyleSupport.UnknownStyleException; import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.JavaCompatibility; @@ -73,17 +78,26 @@ import com.formdev.flatlaf.util.JavaCompatibility; public class FlatTextFieldUI extends BasicTextFieldUI { - protected int minimumWidth; + @Styleable protected int minimumWidth; protected boolean isIntelliJTheme; - protected Color placeholderForeground; - protected Color focusedBackground; + @Styleable protected Color placeholderForeground; + @Styleable protected Color focusedBackground; private FocusListener focusListener; + private Map oldStyleValues; + private boolean borderShared = true; public static ComponentUI createUI( JComponent c ) { return new FlatTextFieldUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStyleSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -135,10 +149,10 @@ public class FlatTextFieldUI @Override protected void propertyChange( PropertyChangeEvent e ) { super.propertyChange( e ); - propertyChange( getComponent(), e ); + propertyChange( getComponent(), e, this::applyStyle ); } - static void propertyChange( JTextComponent c, PropertyChangeEvent e ) { + static void propertyChange( JTextComponent c, PropertyChangeEvent e, Consumer applyStyle ) { switch( e.getPropertyName() ) { case FlatClientProperties.PLACEHOLDER_TEXT: case FlatClientProperties.COMPONENT_ROUND_RECT: @@ -148,6 +162,44 @@ public class FlatTextFieldUI case FlatClientProperties.MINIMUM_WIDTH: c.revalidate(); break; + + case FlatClientProperties.COMPONENT_STYLE: + applyStyle.accept( e.getNewValue() ); + c.revalidate(); + c.repaint(); + break; + } + } + + /** + * @since TODO + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStyleSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + try { + return FlatStyleSupport.applyToAnnotatedObject( this, key, value ); + } catch( UnknownStyleException ex ) { + Border border = getComponent().getBorder(); + if( border instanceof FlatBorder ) { + if( borderShared ) { + border = FlatStyleSupport.cloneBorder( border ); + getComponent().setBorder( border ); + borderShared = false; + } + + try { + return ((FlatBorder)border).applyStyleProperty( key, value ); + } catch( UnknownStyleException ex2 ) { + // ignore + } + } + throw ex; } } diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java index 23780402..2d99d784 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java @@ -21,11 +21,16 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.awt.Color; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; import javax.swing.JCheckBox; +import javax.swing.JFormattedTextField; +import javax.swing.JPasswordField; import javax.swing.JRadioButton; import javax.swing.JSplitPane; +import javax.swing.JTextField; import javax.swing.UIManager; import org.junit.jupiter.api.Test; +import com.formdev.flatlaf.icons.FlatCapsLockIcon; import com.formdev.flatlaf.icons.FlatCheckBoxIcon; import com.formdev.flatlaf.icons.FlatRadioButtonIcon; @@ -75,6 +80,39 @@ public class FlatStylingTests radioButton( ui ); } + @Test + void formattedTextField() { + FlatFormattedTextFieldUI ui = new FlatFormattedTextFieldUI(); + + // create border + UIManager.put( "FormattedTextField.border", new FlatTextBorder() ); + ui.installUI( new JFormattedTextField() ); + + // FlatFormattedTextFieldUI extends FlatTextFieldUI + textField( ui ); + } + + @Test + void passwordField() { + FlatPasswordFieldUI ui = new FlatPasswordFieldUI(); + + // create border and capsLockIcon + UIManager.put( "PasswordField.border", new FlatTextBorder() ); + UIManager.put( "PasswordField.capsLockIcon", new FlatCapsLockIcon() ); + ui.installUI( new JPasswordField() ); + + ui.applyStyle( "minimumWidth: 100" ); + ui.applyStyle( "placeholderForeground: #fff" ); + ui.applyStyle( "focusedBackground: #fff" ); + ui.applyStyle( "showCapsLock: true" ); + + // capsLockIcon + ui.applyStyle( "capsLockIconColor: #fff" ); + + // border + flatTextBorder( style -> ui.applyStyle( style ) ); + } + @Test void popupMenuSeparator() { FlatPopupMenuSeparatorUI ui = new FlatPopupMenuSeparatorUI( false ); @@ -228,26 +266,105 @@ public class FlatStylingTests ui.applyStyle( "gripGap: 2" ); } - //---- icons -------------------------------------------------------------- + @Test + void textField() { + FlatTextFieldUI ui = new FlatTextFieldUI(); + + // create border + UIManager.put( "TextField.border", new FlatTextBorder() ); + ui.installUI( new JTextField() ); + + textField( ui ); + } + + private void textField( FlatTextFieldUI ui ) { + ui.applyStyle( "minimumWidth: 100" ); + ui.applyStyle( "placeholderForeground: #fff" ); + ui.applyStyle( "focusedBackground: #fff" ); + + // border + flatTextBorder( style -> ui.applyStyle( style ) ); + } + + //---- component borders -------------------------------------------------- + + private void flatTextBorder( Consumer applyStyle ) { + flatBorder( applyStyle ); + + applyStyle.accept( "arc: 6" ); + } + + private void flatBorder( Consumer applyStyle ) { + applyStyle.accept( "focusWidth: 2" ); + applyStyle.accept( "innerFocusWidth: {float}0.5" ); + applyStyle.accept( "innerOutlineWidth: {float}1.5" ); + applyStyle.accept( "focusColor: #fff" ); + applyStyle.accept( "borderColor: #fff" ); + applyStyle.accept( "disabledBorderColor: #fff" ); + applyStyle.accept( "focusedBorderColor: #fff" ); + + applyStyle.accept( "error.borderColor: #fff" ); + applyStyle.accept( "error.focusedBorderColor: #fff" ); + applyStyle.accept( "warning.borderColor: #fff" ); + applyStyle.accept( "warning.focusedBorderColor: #fff" ); + applyStyle.accept( "custom.borderColor: desaturate(#f00,50%,relative derived noAutoInverse)" ); + } + + //---- borders ------------------------------------------------------------ @Test - void checkBoxIcon() { - FlatCheckBoxIcon icon = new FlatCheckBoxIcon(); + void flatTextBorder() { + FlatTextBorder border = new FlatTextBorder(); - checkBoxIcon( icon ); + // FlatTextBorder extends FlatBorder + flatBorder( border ); + + border.applyStyleProperty( "arc", 6 ); } @Test - void radioButtonIcon() { + void flatBorder() { + FlatBorder border = new FlatBorder(); + + flatBorder( border ); + } + + private void flatBorder( FlatBorder border ) { + border.applyStyleProperty( "focusWidth", 2 ); + border.applyStyleProperty( "innerFocusWidth", 0.5f ); + border.applyStyleProperty( "innerOutlineWidth", 1.5f ); + border.applyStyleProperty( "focusColor", Color.WHITE ); + border.applyStyleProperty( "borderColor", Color.WHITE ); + border.applyStyleProperty( "disabledBorderColor", Color.WHITE ); + border.applyStyleProperty( "focusedBorderColor", Color.WHITE ); + + border.applyStyleProperty( "error.borderColor", Color.WHITE ); + border.applyStyleProperty( "error.focusedBorderColor", Color.WHITE ); + border.applyStyleProperty( "warning.borderColor", Color.WHITE ); + border.applyStyleProperty( "warning.focusedBorderColor", Color.WHITE ); + border.applyStyleProperty( "custom.borderColor", Color.WHITE ); + } + + //---- icons -------------------------------------------------------------- + + @Test + void flatCheckBoxIcon() { + FlatCheckBoxIcon icon = new FlatCheckBoxIcon(); + + flatCheckBoxIcon( icon ); + } + + @Test + void flatRadioButtonIcon() { FlatRadioButtonIcon icon = new FlatRadioButtonIcon(); // FlatRadioButtonIcon extends FlatCheckBoxIcon - checkBoxIcon( icon ); + flatCheckBoxIcon( icon ); icon.applyStyleProperty( "centerDiameter", 8 ); } - private void checkBoxIcon( FlatCheckBoxIcon icon ) { + private void flatCheckBoxIcon( FlatCheckBoxIcon icon ) { icon.applyStyleProperty( "focusWidth", 2 ); icon.applyStyleProperty( "focusColor", Color.WHITE ); icon.applyStyleProperty( "arc", 5 );