diff --git a/CHANGELOG.md b/CHANGELOG.md index ece1ccd4..8cd7be2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ FlatLaf Change Log - Added drop shadows to popup menus, combobox popups, tooltips and internal frames. (issue #94) +- Support different component border colors to indicate errors, warnings or + custom state (set client property `JComponent.outline` to `error`, `warning` + or any `java.awt.Color`). - Button and ToggleButton: Support round button style (set client property `JButton.buttonType` to `roundRect`). - ComboBox, Spinner and TextField: Support round border style (set client 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 66a2646f..281849a2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -103,6 +103,34 @@ public interface FlatClientProperties */ String MINIMUM_HEIGHT = "JComponent.minimumHeight"; + /** + * Specifies the outline color of the component border. + *
+ * Components {@link javax.swing.JButton}, {@link javax.swing.JComboBox},
+ * {@link javax.swing.JFormattedTextField}, {@link javax.swing.JPasswordField},
+ * {@link javax.swing.JScrollPane}, {@link javax.swing.JSpinner},
+ * {@link javax.swing.JTextField} and {@link javax.swing.JToggleButton}
+ * Value type {@link java.lang.String} or {@link java.awt.Color} or {@link java.awt.Color}[2]
+ * Allowed Values {@link #OUTLINE_ERROR}, {@link #OUTLINE_WARNING},
+ * any color (type {@link java.awt.Color}) or an array of two colors (type {@link java.awt.Color}[2])
+ * where the first color is for focused state and the second for unfocused state
+ */
+ String OUTLINE = "JComponent.outline";
+
+ /**
+ * Paint the component border in another color (usually reddish) to indicate an error.
+ *
+ * @see #OUTLINE
+ */
+ String OUTLINE_ERROR = "error";
+
+ /**
+ * Paint the component border in another color (usually yellowish) to indicate a warning.
+ *
+ * @see #OUTLINE
+ */
+ String OUTLINE_WARNING = "warning";
+
/**
* Paint the component with round edges.
*
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java
index e10aa8a5..5c5119ff 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java
@@ -467,6 +467,12 @@ public class IntelliJTheme
uiKeyMapping.put( "ComboBox.ArrowButton.iconColor", "ComboBox.buttonArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.nonEditableBackground", "ComboBox.buttonBackground" );
+ // Component
+ uiKeyMapping.put( "Component.inactiveErrorFocusColor", "Component.error.borderColor" );
+ uiKeyMapping.put( "Component.errorFocusColor", "Component.error.focusedBorderColor" );
+ uiKeyMapping.put( "Component.inactiveWarningFocusColor", "Component.warning.borderColor" );
+ uiKeyMapping.put( "Component.warningFocusColor", "Component.warning.focusedBorderColor" );
+
// Link
uiKeyMapping.put( "Link.activeForeground", "Component.linkColor" );
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 b04102cf..075a76ef 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
@@ -36,6 +36,8 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicBorders;
import javax.swing.text.JTextComponent;
+import com.formdev.flatlaf.FlatClientProperties;
+import com.formdev.flatlaf.util.DerivedColor;
/**
* Border for various components (e.g. {@link javax.swing.JTextField}).
@@ -54,6 +56,12 @@ import javax.swing.text.JTextComponent;
* @uiDefault Component.disabledBorderColor Color
* @uiDefault Component.focusedBorderColor Color
*
+ * @uiDefault Component.error.borderColor Color
+ * @uiDefault Component.error.focusedBorderColor Color
+ * @uiDefault Component.warning.borderColor Color
+ * @uiDefault Component.warning.focusedBorderColor Color
+ * @uiDefault Component.custom.borderColor Color
+ *
* @author Karl Tauber
*/
public class FlatBorder
@@ -66,6 +74,12 @@ public class FlatBorder
protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
protected final 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" );
+
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create();
@@ -76,22 +90,48 @@ public class FlatBorder
float focusWidth = isCellEditor ? 0 : scale( (float) getFocusWidth( c ) );
float borderWidth = scale( (float) getBorderWidth( c ) );
float arc = isCellEditor ? 0 : scale( (float) getArc( c ) );
+ Color outlineColor = getOutlineColor( c );
- if( isFocused( c ) ) {
+ if( outlineColor != null || isFocused( c ) ) {
float innerFocusWidth = !(c instanceof JScrollPane) ? this.innerFocusWidth : 0;
- g2.setColor( getFocusColor( c ) );
+ g2.setColor( (outlineColor != null) ? outlineColor : getFocusColor( c ) );
FlatUIUtils.paintComponentOuterBorder( g2, x, y, width, height, focusWidth,
scale( (float) getLineWidth( c ) ) + scale( innerFocusWidth ), arc );
}
- g2.setPaint( getBorderColor( c ) );
+ g2.setPaint( (outlineColor != null) ? outlineColor : getBorderColor( c ) );
FlatUIUtils.paintComponentBorder( g2, x, y, width, height, focusWidth, borderWidth, arc );
} finally {
g2.dispose();
}
}
+ protected Color getOutlineColor( Component c ) {
+ if( !(c instanceof JComponent) )
+ return null;
+
+ Object outline = ((JComponent)c).getClientProperty( FlatClientProperties.OUTLINE );
+ if( outline instanceof String ) {
+ switch( (String) outline ) {
+ case FlatClientProperties.OUTLINE_ERROR:
+ return isFocused( c ) ? errorFocusedBorderColor : errorBorderColor;
+
+ case FlatClientProperties.OUTLINE_WARNING:
+ return isFocused( c ) ? warningFocusedBorderColor : warningBorderColor;
+ }
+ } else if( outline instanceof Color ) {
+ Color color = (Color) outline;
+ // use color functions to compute color for unfocused state
+ if( !isFocused( c ) && customBorderColor instanceof DerivedColor )
+ color = ((DerivedColor)customBorderColor).derive( color );
+ return color;
+ } else if( outline instanceof Color[] && ((Color[])outline).length >= 2 )
+ return ((Color[])outline)[isFocused( c ) ? 0 : 1];
+
+ return null;
+ }
+
protected Color getFocusColor( Component c ) {
return focusColor;
}
diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties
index ae204cff..f72caaca 100644
--- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties
+++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties
@@ -130,6 +130,12 @@ Component.focusColor=#3d6185
Component.linkColor=#589df6
Component.grayFilter=-20,-70,100
+Component.error.borderColor=desaturate($Component.error.focusedBorderColor,25%)
+Component.error.focusedBorderColor=#8b3c3c
+Component.warning.borderColor=darken(desaturate($Component.warning.focusedBorderColor,20%),10%)
+Component.warning.focusedBorderColor=#ac7920
+Component.custom.borderColor=desaturate(#f00,50%,relative derived)
+
#---- Desktop ----
diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties
index b1dd9abe..19e8ddbb 100644
--- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties
+++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties
@@ -132,6 +132,12 @@ Component.focusColor=#97c3f3
Component.linkColor=#2470B3
Component.grayFilter=25,-25,100
+Component.error.borderColor=lighten(desaturate($Component.error.focusedBorderColor,20%),25%)
+Component.error.focusedBorderColor=#e53e4d
+Component.warning.borderColor=lighten(saturate($Component.warning.focusedBorderColor,25%),20%)
+Component.warning.focusedBorderColor=#e2a53a
+Component.custom.borderColor=lighten(desaturate(#f00,20%,derived),25%,derived)
+
#---- Desktop ----
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java
index ffefbd6c..d01e72cc 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java
@@ -114,6 +114,14 @@ class BasicComponentsPanel
JScrollPane scrollPane12 = new JScrollPane();
JTextPane textPane4 = new JTextPane();
JTextPane textPane5 = new JTextPane();
+ JLabel label3 = new JLabel();
+ JTextField textField5 = new JTextField();
+ JComboBox comboBox7 = new JComboBox();
+ JSpinner spinner3 = new JSpinner();
+ JLabel label4 = new JLabel();
+ JTextField textField7 = new JTextField();
+ JComboBox comboBox8 = new JComboBox();
+ JSpinner spinner4 = new JSpinner();
JPopupMenu popupMenu1 = new JPopupMenu();
JMenuItem cutMenuItem = new JMenuItem();
JMenuItem copyMenuItem = new JMenuItem();
@@ -141,6 +149,8 @@ class BasicComponentsPanel
"[]" +
"[]" +
"[]" +
+ "[]para" +
+ "[]" +
"[]"));
//---- labelLabel ----
@@ -596,6 +606,38 @@ class BasicComponentsPanel
textPane5.setText("no scroll pane");
add(textPane5, "cell 5 11,growx");
+ //---- label3 ----
+ label3.setText("Error:");
+ add(label3, "cell 0 12");
+
+ //---- textField5 ----
+ textField5.putClientProperty("JComponent.outline", "error");
+ add(textField5, "cell 1 12,growx");
+
+ //---- comboBox7 ----
+ comboBox7.putClientProperty("JComponent.outline", "error");
+ add(comboBox7, "cell 2 12,growx");
+
+ //---- spinner3 ----
+ spinner3.putClientProperty("JComponent.outline", "error");
+ add(spinner3, "cell 3 12,growx");
+
+ //---- label4 ----
+ label4.setText("Warning:");
+ add(label4, "cell 0 13");
+
+ //---- textField7 ----
+ textField7.putClientProperty("JComponent.outline", "warning");
+ add(textField7, "cell 1 13,growx");
+
+ //---- comboBox8 ----
+ comboBox8.putClientProperty("JComponent.outline", "warning");
+ add(comboBox8, "cell 2 13,growx");
+
+ //---- spinner4 ----
+ spinner4.putClientProperty("JComponent.outline", "warning");
+ add(spinner4, "cell 3 13,growx");
+
//======== popupMenu1 ========
{
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd
index 3b057d0d..486497c2 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd
@@ -9,7 +9,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3"
"$columnConstraints": "[][][][][][]"
- "$rowConstraints": "[][][][][][][][][][][][]"
+ "$rowConstraints": "[][][][][][][][][][][][]para[][]"
} ) {
name: "this"
add( new FormComponent( "javax.swing.JLabel" ) {
@@ -591,9 +591,57 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 11,growx"
} )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "label3"
+ "text": "Error:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 12"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "textField5"
+ "$client.JComponent.outline": "error"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 12,growx"
+ } )
+ add( new FormComponent( "javax.swing.JComboBox" ) {
+ name: "comboBox7"
+ "$client.JComponent.outline": "error"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 12,growx"
+ } )
+ add( new FormComponent( "javax.swing.JSpinner" ) {
+ name: "spinner3"
+ "$client.JComponent.outline": "error"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 3 12,growx"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "label4"
+ "text": "Warning:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 13"
+ } )
+ add( new FormComponent( "javax.swing.JTextField" ) {
+ name: "textField7"
+ "$client.JComponent.outline": "warning"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 13,growx"
+ } )
+ add( new FormComponent( "javax.swing.JComboBox" ) {
+ name: "comboBox8"
+ "$client.JComponent.outline": "warning"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 13,growx"
+ } )
+ add( new FormComponent( "javax.swing.JSpinner" ) {
+ name: "spinner4"
+ "$client.JComponent.outline": "warning"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 3 13,growx"
+ } )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
- "size": new java.awt.Dimension( 790, 440 )
+ "size": new java.awt.Dimension( 920, 440 )
} )
add( new FormContainer( "javax.swing.JPopupMenu", new FormLayoutManager( class javax.swing.JPopupMenu ) ) {
name: "popupMenu1"
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponentsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponentsTest.java
index 0d8d8055..713474a3 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponentsTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponentsTest.java
@@ -84,6 +84,26 @@ public class FlatComponentsTest
}
}
+ private void outlineChanged() {
+ FlatTestFrame frame = (FlatTestFrame) SwingUtilities.getAncestorOfClass( FlatTestFrame.class, this );
+ if( frame == null )
+ return;
+
+ Object outline = errorOutlineRadioButton.isSelected() ? "error"
+ : warningOutlineRadioButton.isSelected() ? "warning"
+ : magentaOutlineRadioButton.isSelected() ? Color.magenta
+ : magentaCyanOutlineRadioButton.isSelected() ? new Color[] { Color.magenta, Color.cyan }
+ : null;
+
+ frame.updateComponentsRecur( this, (c, type) -> {
+ if( c instanceof JComponent )
+ ((JComponent)c).putClientProperty( FlatClientProperties.OUTLINE, outline );
+ } );
+
+ frame.repaint();
+ textField1.requestFocusInWindow();
+ }
+
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JLabel labelLabel = new JLabel();
@@ -146,7 +166,7 @@ public class FlatComponentsTest
JComboBox