From f93d035e4ebf2ad58f8543814471b7471d67a1c6 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 3 Sep 2021 22:24:46 +0200 Subject: [PATCH] TextField: support leading and trailing components --- CHANGELOG.md | 5 +- .../formdev/flatlaf/FlatClientProperties.java | 48 ++++- .../formdev/flatlaf/ui/FlatTextFieldUI.java | 193 +++++++++++++++++- .../com/formdev/flatlaf/ui/FlatUIUtils.java | 26 +++ .../components/FlatFormattedTextField.java | 59 ++++++ .../extras/components/FlatPasswordField.java | 59 ++++++ .../extras/components/FlatTextField.java | 59 ++++++ .../testing/FlatTextComponentsTest.java | 63 +++++- .../testing/FlatTextComponentsTest.jfd | 22 +- 9 files changed, 515 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da6f25ae..435808d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,10 @@ FlatLaf Change Log (issue #416) - TextField, FormattedTextField and PasswordField: Support leading and trailing icons (set client property `JTextField.leadingIcon` or - `JTextField.trailingIcon` to an `Icon`). (PR #378; issue #368) + `JTextField.trailingIcon` to a `javax.swing.Icon`). (PR #378; issue #368) +- TextField, FormattedTextField and PasswordField: Support leading and trailing + components (set client property `JTextField.leadingComponent` or + `JTextField.trailingComponent` to a `java.awt.Component`). (PR #386) - TextComponents: Double/triple-click-and-drag now extends selection by whole words/lines. - Theming improvements: Reworks core themes to make it easier to create new 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 4af43c96..ed3f3acc 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -763,9 +763,9 @@ public interface FlatClientProperties /** * Specifies a component that will be placed at the leading edge of the tabs area. *

- * For top and bottom tab placement, the layed out component size will be + * For top and bottom tab placement, the laid out component size will be * the preferred component width and the tab area height.
- * For left and right tab placement, the layed out component size will be + * For left and right tab placement, the laid out component size will be * the tab area width and the preferred component height. *

* Component {@link javax.swing.JTabbedPane}
@@ -776,9 +776,9 @@ public interface FlatClientProperties /** * Specifies a component that will be placed at the trailing edge of the tabs area. *

- * For top and bottom tab placement, the layed out component size will be + * For top and bottom tab placement, the laid out component size will be * the available horizontal space (minimum is preferred component width) and the tab area height.
- * For left and right tab placement, the layed out component size will be + * For left and right tab placement, the laid out component size will be * the tab area width and the available vertical space (minimum is preferred component height). *

* Component {@link javax.swing.JTabbedPane}
@@ -863,6 +863,46 @@ public interface FlatClientProperties */ String TEXT_FIELD_TRAILING_ICON = "JTextField.trailingIcon"; + /** + * Specifies a component that will be placed at the leading edge of the text field. + *

+ * The component will be positioned inside and aligned to the visible text field border. + * There is no gap between the visible border and the component. + * The laid out component size will be the preferred component width + * and the inner text field height. + *

+ * The component should be not opaque because the text field border is painted + * slightly inside the usually visible border in some cases. + * E.g. when focused (in some themes) or when an outline color is specified + * (see {@link #OUTLINE}. + *

+ * Component {@link javax.swing.JTextField} (and subclasses)
+ * Value type {@link java.awt.Component} + * + * @since 2 + */ + String TEXT_FIELD_LEADING_COMPONENT = "JTextField.leadingComponent"; + + /** + * Specifies a component that will be placed at the trailing edge of the text field. + *

+ * The component will be positioned inside and aligned to the visible text field border. + * There is no gap between the visible border and the component. + * The laid out component size will be the preferred component width + * and the inner text field height. + *

+ * The component should be not opaque because the text field border is painted + * slightly inside the usually visible border in some cases. + * E.g. when focused (in some themes) or when an outline color is specified + * (see {@link #OUTLINE}. + *

+ * Component {@link javax.swing.JTextField} (and subclasses)
+ * Value type {@link java.awt.Component} + * + * @since 2 + */ + String TEXT_FIELD_TRAILING_COMPONENT = "JTextField.trailingComponent"; + //---- JToggleButton ------------------------------------------------------ /** 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 6814e7f8..60db8663 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 @@ -19,12 +19,15 @@ package com.formdev.flatlaf.ui; import static com.formdev.flatlaf.FlatClientProperties.*; import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Color; +import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.LayoutManager2; import java.awt.Rectangle; import java.awt.event.FocusListener; import java.beans.PropertyChangeEvent; @@ -94,6 +97,8 @@ public class FlatTextFieldUI /** @since 2 */ @Styleable protected Icon leadingIcon; /** @since 2 */ @Styleable protected Icon trailingIcon; + /** @since 2 */ protected Component leadingComponent; + /** @since 2 */ protected Component trailingComponent; private Color oldDisabledBackground; private Color oldInactiveBackground; @@ -115,11 +120,17 @@ public class FlatTextFieldUI leadingIcon = clientProperty( c, TEXT_FIELD_LEADING_ICON, null, Icon.class ); trailingIcon = clientProperty( c, TEXT_FIELD_TRAILING_ICON, null, Icon.class ); + installLeadingComponent(); + installTrailingComponent(); + installStyle(); } @Override public void uninstallUI( JComponent c ) { + uninstallLeadingComponent(); + uninstallTrailingComponent(); + super.uninstallUI( c ); leadingIcon = null; @@ -225,6 +236,20 @@ public class FlatTextFieldUI trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null; c.repaint(); break; + + case TEXT_FIELD_LEADING_COMPONENT: + uninstallLeadingComponent(); + installLeadingComponent(); + c.revalidate(); + c.repaint(); + break; + + case TEXT_FIELD_TRAILING_COMPONENT: + uninstallTrailingComponent(); + installTrailingComponent(); + c.revalidate(); + c.repaint(); + break; } } @@ -444,6 +469,12 @@ debug*/ // add width of leading and trailing icons size.width += getLeadingIconWidth() + getTrailingIconWidth(); + // add width of leading and trailing components + if( leadingComponent != null && leadingComponent.isVisible() ) + size.width += leadingComponent.getPreferredSize().width; + if( trailingComponent != null && trailingComponent.isVisible() ) + size.width += trailingComponent.getPreferredSize().width; + return size; } @@ -510,7 +541,8 @@ debug*/ /** * Returns the rectangle used to paint leading and trailing icons. * It invokes {@code super.getVisibleEditorRect()} and reduces left and/or - * right margin if the text field has leading or trailing icons. + * right margin if the text field has leading or trailing icons or components. + * Also the preferred widths of leading and trailing components are removed. * * @since 2 */ @@ -519,10 +551,24 @@ debug*/ if( r == null ) return null; - // if a leading/trailing icon is shown, then the left/right margin is reduced - // to the top margin, which places the icon nicely centered on left/right side boolean ltr = isLeftToRight(); - if( ltr ? hasLeadingIcon() : hasTrailingIcon() ) { + + // remove width of leading/trailing components + Component leftComponent = ltr ? leadingComponent : trailingComponent; + Component rightComponent = ltr ? trailingComponent : leadingComponent; + if( leftComponent != null ) { + int w = leftComponent.getPreferredSize().width; + r.x += w; + r.width -= w; + } + if( rightComponent != null ) + r.width -= rightComponent.getPreferredSize().width; + + // if a leading/trailing icons (or components) are shown, then the left/right margins are reduced + // to the top margin, which places the icon nicely centered on left/right side + if( (ltr ? hasLeadingIcon() : hasTrailingIcon()) || + (leftComponent != null && leftComponent.isVisible()) ) + { // reduce left margin Insets margin = getComponent().getMargin(); int newLeftMargin = Math.min( margin.left, margin.top ); @@ -532,7 +578,9 @@ debug*/ r.width += diff; } } - if( ltr ? hasTrailingIcon() : hasLeadingIcon() ) { + if( (ltr ? hasTrailingIcon() : hasLeadingIcon()) || + (rightComponent != null && rightComponent.isVisible()) ) + { // reduce right margin Insets margin = getComponent().getMargin(); int newRightMargin = Math.min( margin.right, margin.top ); @@ -540,6 +588,10 @@ debug*/ r.width += scale( margin.right - newRightMargin ); } + // make sure that width and height are not negative + r.width = Math.max( r.width, 0 ); + r.height = Math.max( r.height, 0 ); + return r; } @@ -578,4 +630,135 @@ debug*/ if( caret instanceof FlatCaret ) ((FlatCaret)caret).scrollCaretToVisible(); } + + /** @since 2 */ + protected void installLeadingComponent() { + JTextComponent c = getComponent(); + leadingComponent = clientProperty( c, TEXT_FIELD_LEADING_COMPONENT, null, Component.class ); + if( leadingComponent != null ) { + installLayout(); + c.add( leadingComponent ); + } + } + + /** @since 2 */ + protected void installTrailingComponent() { + JTextComponent c = getComponent(); + trailingComponent = clientProperty( c, TEXT_FIELD_TRAILING_COMPONENT, null, Component.class ); + if( trailingComponent != null ) { + installLayout(); + c.add( trailingComponent ); + } + } + + /** @since 2 */ + protected void uninstallLeadingComponent() { + if( leadingComponent != null ) { + getComponent().remove( leadingComponent ); + leadingComponent = null; + } + } + + /** @since 2 */ + protected void uninstallTrailingComponent() { + if( trailingComponent != null ) { + getComponent().remove( trailingComponent ); + trailingComponent = null; + } + } + + private void installLayout() { + JTextComponent c = getComponent(); + LayoutManager oldLayout = c.getLayout(); + if( !(oldLayout instanceof FlatTextFieldLayout) ) + c.setLayout( new FlatTextFieldLayout( oldLayout ) ); + } + + //---- class FlatTextFieldLayout ------------------------------------------ + + private class FlatTextFieldLayout + implements LayoutManager2, UIResource + { + private final LayoutManager delegate; + + FlatTextFieldLayout( LayoutManager delegate ) { + this.delegate = delegate; + } + + @Override + public void addLayoutComponent( String name, Component comp ) { + if( delegate != null ) + delegate.addLayoutComponent( name, comp ); + } + + @Override + public void removeLayoutComponent( Component comp ) { + if( delegate != null ) + delegate.removeLayoutComponent( comp ); + } + + @Override + public Dimension preferredLayoutSize( Container parent ) { + return (delegate != null) ? delegate.preferredLayoutSize( parent ) : null; + } + + @Override + public Dimension minimumLayoutSize( Container parent ) { + return (delegate != null) ? delegate.minimumLayoutSize( parent ) : null; + } + + @Override + public void layoutContainer( Container parent ) { + if( delegate != null ) + delegate.layoutContainer( parent ); + + if( leadingComponent == null && trailingComponent == null ) + return; + + int ow = FlatUIUtils.getBorderFocusAndLineWidth( getComponent() ); + int h = parent.getHeight() - ow - ow; + boolean ltr = isLeftToRight(); + Component leftComponent = ltr ? leadingComponent : trailingComponent; + Component rightComponent = ltr ? trailingComponent : leadingComponent; + + // layout left component + if( leftComponent != null && leftComponent.isVisible() ) { + int w = leftComponent.getPreferredSize().width; + leftComponent.setBounds( ow, ow, w, h ); + } + + // layout right component + if( rightComponent != null && rightComponent.isVisible() ) { + int w = rightComponent.getPreferredSize().width; + rightComponent.setBounds( parent.getWidth() - ow - w, ow, w, h ); + } + } + + @Override + public void addLayoutComponent( Component comp, Object constraints ) { + if( delegate instanceof LayoutManager2 ) + ((LayoutManager2)delegate).addLayoutComponent( comp, constraints ); + } + + @Override + public Dimension maximumLayoutSize( Container target ) { + return (delegate instanceof LayoutManager2) ? ((LayoutManager2)delegate).maximumLayoutSize( target ) : null; + } + + @Override + public float getLayoutAlignmentX( Container target ) { + return (delegate instanceof LayoutManager2) ? ((LayoutManager2)delegate).getLayoutAlignmentX( target ) : 0.5f; + } + + @Override + public float getLayoutAlignmentY( Container target ) { + return (delegate instanceof LayoutManager2) ? ((LayoutManager2)delegate).getLayoutAlignmentY( target ) : 0.5f; + } + + @Override + public void invalidateLayout( Container target ) { + if( delegate instanceof LayoutManager2 ) + ((LayoutManager2)delegate).invalidateLayout( target ); + } + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java index 2c5cd1b9..fda0c1c8 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java @@ -278,6 +278,32 @@ public class FlatUIUtils : 0; } + /** + * Returns the scaled line thickness used to compute the border insets. + * + * @since 2 + */ + public static float getBorderLineWidth( JComponent c ) { + FlatBorder border = getOutsideFlatBorder( c ); + return (border != null) + ? UIScale.scale( (float) border.getLineWidth( c ) ) + : 0; + } + + /** + * Returns the scaled thickness of the border. + * This includes the outer focus border and the actual component border. + * + * @since 2 + */ + public static int getBorderFocusAndLineWidth( JComponent c ) { + FlatBorder border = getOutsideFlatBorder( c ); + return (border != null) + ? Math.round( UIScale.scale( (float) border.getFocusWidth( c ) ) + + UIScale.scale( (float) border.getLineWidth( c ) ) ) + : 0; + } + /** * Returns the scaled arc diameter of the border for the given component. */ diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java index b8f1a270..54126d8d 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java @@ -18,6 +18,7 @@ package com.formdev.flatlaf.extras.components; import static com.formdev.flatlaf.FlatClientProperties.*; import java.awt.Color; +import java.awt.Component; import java.awt.Insets; import javax.swing.Icon; import javax.swing.JFormattedTextField; @@ -85,6 +86,64 @@ public class FlatFormattedTextField } + /** + * Returns a component that will be placed at the leading edge of the text field. + * + * @since 2 + */ + public Component getLeadingComponent() { + return (Component) getClientProperty( TEXT_FIELD_LEADING_COMPONENT ); + } + + /** + * Specifies a component that will be placed at the leading edge of the text field. + *

+ * The component will be positioned inside and aligned to the visible text field border. + * There is no gap between the visible border and the component. + * The laid out component size will be the preferred component width + * and the inner text field height. + *

+ * The component should be not opaque because the text field border is painted + * slightly inside the usually visible border in some cases. + * E.g. when focused (in some themes) or when an outline color is specified + * (see {@link #setOutline(Object)}. + * + * @since 2 + */ + public void setLeadingComponent( Component leadingComponent ) { + putClientProperty( TEXT_FIELD_LEADING_COMPONENT, leadingComponent ); + } + + + /** + * Returns a component that will be placed at the trailing edge of the text field. + * + * @since 2 + */ + public Component getTrailingComponent() { + return (Component) getClientProperty( TEXT_FIELD_TRAILING_COMPONENT ); + } + + /** + * Specifies a component that will be placed at the trailing edge of the text field. + *

+ * The component will be positioned inside and aligned to the visible text field border. + * There is no gap between the visible border and the component. + * The laid out component size will be the preferred component width + * and the inner text field height. + *

+ * The component should be not opaque because the text field border is painted + * slightly inside the usually visible border in some cases. + * E.g. when focused (in some themes) or when an outline color is specified + * (see {@link #setOutline(Object)}. + * + * @since 2 + */ + public void setTrailingComponent( Component trailingComponent ) { + putClientProperty( TEXT_FIELD_TRAILING_COMPONENT, trailingComponent ); + } + + /** * Returns whether all text is selected when the text component gains focus. */ diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java index 1078d368..392db28e 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java @@ -18,6 +18,7 @@ package com.formdev.flatlaf.extras.components; import static com.formdev.flatlaf.FlatClientProperties.*; import java.awt.Color; +import java.awt.Component; import java.awt.Insets; import javax.swing.Icon; import javax.swing.JPasswordField; @@ -85,6 +86,64 @@ public class FlatPasswordField } + /** + * Returns a component that will be placed at the leading edge of the text field. + * + * @since 2 + */ + public Component getLeadingComponent() { + return (Component) getClientProperty( TEXT_FIELD_LEADING_COMPONENT ); + } + + /** + * Specifies a component that will be placed at the leading edge of the text field. + *

+ * The component will be positioned inside and aligned to the visible text field border. + * There is no gap between the visible border and the component. + * The laid out component size will be the preferred component width + * and the inner text field height. + *

+ * The component should be not opaque because the text field border is painted + * slightly inside the usually visible border in some cases. + * E.g. when focused (in some themes) or when an outline color is specified + * (see {@link #setOutline(Object)}. + * + * @since 2 + */ + public void setLeadingComponent( Component leadingComponent ) { + putClientProperty( TEXT_FIELD_LEADING_COMPONENT, leadingComponent ); + } + + + /** + * Returns a component that will be placed at the trailing edge of the text field. + * + * @since 2 + */ + public Component getTrailingComponent() { + return (Component) getClientProperty( TEXT_FIELD_TRAILING_COMPONENT ); + } + + /** + * Specifies a component that will be placed at the trailing edge of the text field. + *

+ * The component will be positioned inside and aligned to the visible text field border. + * There is no gap between the visible border and the component. + * The laid out component size will be the preferred component width + * and the inner text field height. + *

+ * The component should be not opaque because the text field border is painted + * slightly inside the usually visible border in some cases. + * E.g. when focused (in some themes) or when an outline color is specified + * (see {@link #setOutline(Object)}. + * + * @since 2 + */ + public void setTrailingComponent( Component trailingComponent ) { + putClientProperty( TEXT_FIELD_TRAILING_COMPONENT, trailingComponent ); + } + + /** * Returns whether all text is selected when the text component gains focus. */ diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java index 31d8850d..9bd0f98a 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java @@ -18,6 +18,7 @@ package com.formdev.flatlaf.extras.components; import static com.formdev.flatlaf.FlatClientProperties.*; import java.awt.Color; +import java.awt.Component; import java.awt.Insets; import javax.swing.Icon; import javax.swing.JTextField; @@ -84,6 +85,64 @@ public class FlatTextField } + /** + * Returns a component that will be placed at the leading edge of the text field. + * + * @since 2 + */ + public Component getLeadingComponent() { + return (Component) getClientProperty( TEXT_FIELD_LEADING_COMPONENT ); + } + + /** + * Specifies a component that will be placed at the leading edge of the text field. + *

+ * The component will be positioned inside and aligned to the visible text field border. + * There is no gap between the visible border and the component. + * The laid out component size will be the preferred component width + * and the inner text field height. + *

+ * The component should be not opaque because the text field border is painted + * slightly inside the usually visible border in some cases. + * E.g. when focused (in some themes) or when an outline color is specified + * (see {@link #setOutline(Object)}. + * + * @since 2 + */ + public void setLeadingComponent( Component leadingComponent ) { + putClientProperty( TEXT_FIELD_LEADING_COMPONENT, leadingComponent ); + } + + + /** + * Returns a component that will be placed at the trailing edge of the text field. + * + * @since 2 + */ + public Component getTrailingComponent() { + return (Component) getClientProperty( TEXT_FIELD_TRAILING_COMPONENT ); + } + + /** + * Specifies a component that will be placed at the trailing edge of the text field. + *

+ * The component will be positioned inside and aligned to the visible text field border. + * There is no gap between the visible border and the component. + * The laid out component size will be the preferred component width + * and the inner text field height. + *

+ * The component should be not opaque because the text field border is painted + * slightly inside the usually visible border in some cases. + * E.g. when focused (in some themes) or when an outline color is specified + * (see {@link #setOutline(Object)}. + * + * @since 2 + */ + public void setTrailingComponent( Component trailingComponent ) { + putClientProperty( TEXT_FIELD_TRAILING_COMPONENT, trailingComponent ); + } + + // NOTE: enum names must be equal to allowed strings public enum SelectAllOnFocusPolicy { never, once, always }; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java index e72cb16b..1af9ec05 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java @@ -21,6 +21,7 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; +import java.util.function.Supplier; import javax.swing.*; import javax.swing.border.*; import javax.swing.text.DefaultEditorKit; @@ -58,26 +59,54 @@ public class FlatTextComponentsTest if( padding.equals( new Insets( 0, 0, 0, 0 ) ) ) padding = null; - for( Component c : getComponents() ) { - if( c instanceof JTextField ) - ((JTextField)c).putClientProperty( FlatClientProperties.TEXT_FIELD_PADDING, padding ); - } + putTextFieldClientProperty( FlatClientProperties.TEXT_FIELD_PADDING, padding ); } private void leadingIcon() { - applyIcon( FlatClientProperties.TEXT_FIELD_LEADING_ICON, leadingIconCheckBox.isSelected() + putTextFieldClientProperty( FlatClientProperties.TEXT_FIELD_LEADING_ICON, leadingIconCheckBox.isSelected() ? new TestIcon( 8, 16, Color.blue ) : null ); } private void trailingIcon() { - applyIcon( FlatClientProperties.TEXT_FIELD_TRAILING_ICON, trailingIconCheckBox.isSelected() + putTextFieldClientProperty( FlatClientProperties.TEXT_FIELD_TRAILING_ICON, trailingIconCheckBox.isSelected() ? new TestIcon( 24, 12, Color.magenta ) : null ); } - private void applyIcon( String key, Icon icon ) { + private void leadingComponent() { + putTextFieldClientProperty( FlatClientProperties.TEXT_FIELD_LEADING_COMPONENT, () -> { + if( !leadingComponentCheckBox.isSelected() ) + return null; + + JLabel l = new JLabel( "lead" ); + l.setOpaque( true ); + l.setBackground( Color.green ); + return l; + } ); + } + + private void trailingComponent() { + putTextFieldClientProperty( FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, () -> { + if( !trailingComponentCheckBox.isSelected() ) + return null; + + JLabel l = new JLabel( "tr" ); + l.setOpaque( true ); + l.setBackground( Color.magenta ); + return l; + } ); + } + + private void putTextFieldClientProperty( String key, Object value ) { for( Component c : getComponents() ) { if( c instanceof JTextField ) - ((JTextField)c).putClientProperty( key, icon ); + ((JTextField)c).putClientProperty( key, value ); + } + } + + private void putTextFieldClientProperty( String key, Supplier value ) { + for( Component c : getComponents() ) { + if( c instanceof JTextField ) + ((JTextField)c).putClientProperty( key, value.get() ); } } @@ -110,6 +139,8 @@ public class FlatTextComponentsTest bottomPaddingField = new JSpinner(); leadingIconCheckBox = new JCheckBox(); trailingIconCheckBox = new JCheckBox(); + leadingComponentCheckBox = new JCheckBox(); + trailingComponentCheckBox = new JCheckBox(); JLabel passwordFieldLabel = new JLabel(); JPasswordField passwordField1 = new JPasswordField(); JPasswordField passwordField3 = new JPasswordField(); @@ -256,7 +287,9 @@ public class FlatTextComponentsTest "[]" + "[]" + "[]" + + "[]0" + "[]" + + "[]0" + "[]")); //---- button1 ---- @@ -316,6 +349,18 @@ public class FlatTextComponentsTest trailingIconCheckBox.setName("trailingIconCheckBox"); trailingIconCheckBox.addActionListener(e -> trailingIcon()); panel1.add(trailingIconCheckBox, "cell 0 6 2 1,alignx left,growx 0"); + + //---- leadingComponentCheckBox ---- + leadingComponentCheckBox.setText("leading component"); + leadingComponentCheckBox.setName("leadingComponentCheckBox"); + leadingComponentCheckBox.addActionListener(e -> leadingComponent()); + panel1.add(leadingComponentCheckBox, "cell 0 7 2 1,alignx left,growx 0"); + + //---- trailingComponentCheckBox ---- + trailingComponentCheckBox.setText("trailing component"); + trailingComponentCheckBox.setName("trailingComponentCheckBox"); + trailingComponentCheckBox.addActionListener(e -> trailingComponent()); + panel1.add(trailingComponentCheckBox, "cell 0 8 2 1,alignx left,growx 0"); } add(panel1, "cell 4 0 1 6,aligny top,growy 0"); @@ -627,6 +672,8 @@ public class FlatTextComponentsTest private JSpinner bottomPaddingField; private JCheckBox leadingIconCheckBox; private JCheckBox trailingIconCheckBox; + private JCheckBox leadingComponentCheckBox; + private JCheckBox trailingComponentCheckBox; private JTextField textField; private JCheckBox dragEnabledCheckBox; private JTextArea textArea; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd index 5a340765..6eac474e 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd @@ -77,7 +77,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" "$columnConstraints": "[fill][fill]" - "$rowConstraints": "[][][][][][][]" + "$rowConstraints": "[][][][][][]0[][]0[]" } ) { name: "panel1" "border": new javax.swing.border.TitledBorder( "Control" ) @@ -168,6 +168,26 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 6 2 1,alignx left,growx 0" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "leadingComponentCheckBox" + "text": "leading component" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "leadingComponent", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 7 2 1,alignx left,growx 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "trailingComponentCheckBox" + "text": "trailing component" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "trailingComponent", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 8 2 1,alignx left,growx 0" + } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 4 0 1 6,aligny top,growy 0" } )