Styling: support TextField, FormattedTextField and PasswordField

This commit is contained in:
Karl Tauber
2021-06-18 13:02:40 +02:00
parent 1938cb586d
commit 53abbbbe56
9 changed files with 309 additions and 39 deletions

View File

@@ -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;

View File

@@ -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 ) {
/*

View File

@@ -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 ) {

View File

@@ -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<String, Object> 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

View File

@@ -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<String, Object> 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;
}

View File

@@ -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<? extends Border> 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<? extends Icon> 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 );
}

View File

@@ -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 ) {

View File

@@ -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<String, Object> 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<Object> 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;
}
}

View File

@@ -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<String> applyStyle ) {
flatBorder( applyStyle );
applyStyle.accept( "arc: 6" );
}
private void flatBorder( Consumer<String> 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 );