diff --git a/CHANGELOG.md b/CHANGELOG.md index b7545f86..1b91df39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,9 @@ FlatLaf Change Log `java.awt.Component`). (PR #386) - Support "clear" (or "cancel") button to empty text field. Only shown if text field is not empty, editable and enabled. (set client property - `JTextField.showClearButton` to `true`). (PR #TODO) + `JTextField.showClearButton` to `true`). (PR #442) +- PasswordField: Support reveal (or "eye") button to show password. (see UI + value `PasswordField.showRevealButton`) (PR #442; issue #173) - 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/icons/FlatRevealIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatRevealIcon.java new file mode 100644 index 00000000..8956e129 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatRevealIcon.java @@ -0,0 +1,56 @@ +/* + * 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.icons; + +import java.awt.Component; +import java.awt.Graphics2D; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; +import javax.swing.UIManager; + +/** + * "eye" icon for {@link javax.swing.JPasswordField}. + * + * @uiDefault PasswordField.revealIconColor Color + * + * @author Karl Tauber + * @since 2 + */ +public class FlatRevealIcon + extends FlatAbstractIcon +{ + public FlatRevealIcon() { + super( 16, 16, UIManager.getColor( "PasswordField.revealIconColor" ) ); + } + + @Override + protected void paintIcon( Component c, Graphics2D g ) { + Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD ); + path.append( new Ellipse2D.Float( 5.15f, 6.15f, 5.7f, 5.7f ), false ); + path.append( new Ellipse2D.Float( 6, 7, 4, 4 ), false ); + g.fill( path ); + + Path2D path2 = new Path2D.Float( Path2D.WIND_EVEN_ODD ); + path2.append( new Ellipse2D.Float( 2.15f, 4.15f, 11.7f, 11.7f ), false ); + path2.append( new Ellipse2D.Float( 3, 5, 10, 10 ), false ); + Area area = new Area( path2 ); + area.subtract( new Area( new Rectangle2D.Float( 0, 9.5f, 16, 16 ) ) ); + g.fill( area ); + } +} 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 69c93889..5d5fc82d 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 @@ -28,6 +28,7 @@ import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.Icon; import javax.swing.JComponent; +import javax.swing.JToggleButton; import javax.swing.LookAndFeel; import javax.swing.SwingUtilities; import javax.swing.UIManager; @@ -73,18 +74,25 @@ import com.formdev.flatlaf.util.UIScale; * * @uiDefault PasswordField.echoChar character * @uiDefault PasswordField.showCapsLock boolean + * @uiDefault PasswordField.showRevealButton boolean * @uiDefault PasswordField.capsLockIcon Icon + * @uiDefault PasswordField.revealIcon Icon * * @author Karl Tauber */ public class FlatPasswordFieldUI extends FlatTextFieldUI { + private Character echoChar; + @Styleable protected boolean showCapsLock; + /** @since 2 */ @Styleable protected boolean showRevealButton; protected Icon capsLockIcon; + /** @since 2 */ protected Icon revealIcon; private KeyListener capsLockListener; private boolean capsLockIconShared = true; + private JToggleButton revealButton; public static ComponentUI createUI( JComponent c ) { return new FlatPasswordFieldUI(); @@ -95,17 +103,33 @@ public class FlatPasswordFieldUI return "PasswordField"; } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + installRevealButton(); + } + + @Override + public void uninstallUI( JComponent c ) { + uninstallRevealButton(); + + super.uninstallUI( c ); + } + @Override protected void installDefaults() { super.installDefaults(); String prefix = getPropertyPrefix(); - Character echoChar = (Character) UIManager.get( prefix + ".echoChar" ); + echoChar = (Character) UIManager.get( prefix + ".echoChar" ); if( echoChar != null ) LookAndFeel.installProperty( getComponent(), "echoChar", echoChar ); showCapsLock = UIManager.getBoolean( "PasswordField.showCapsLock" ); + showRevealButton = UIManager.getBoolean( "PasswordField.showRevealButton" ); capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" ); + revealIcon = UIManager.getIcon( "PasswordField.revealIcon" ); capsLockIconShared = true; } @@ -114,6 +138,7 @@ public class FlatPasswordFieldUI super.uninstallDefaults(); capsLockIcon = null; + revealIcon = null; } @Override @@ -168,6 +193,18 @@ public class FlatPasswordFieldUI return "PasswordField"; } + @Override + protected void applyStyle( Object style ) { + boolean oldShowRevealButton = showRevealButton; + + super.applyStyle( style ); + + if( showRevealButton != oldShowRevealButton ) { + uninstallRevealButton(); + installRevealButton(); + } + } + /** @since 2 */ @Override protected Object applyStyleProperty( String key, Object value ) { @@ -236,4 +273,39 @@ public class FlatPasswordFieldUI return FlatUIUtils.isPermanentFocusOwner( c ) && Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK ); } + + /** @since 2 */ + protected void installRevealButton() { + JTextComponent c = getComponent(); + if( showRevealButton ) { + revealButton = createRevealButton(); + installLayout(); + c.add( revealButton ); + } + } + + /** @since 2 */ + protected JToggleButton createRevealButton() { + JToggleButton button = new JToggleButton( revealIcon ); + prepareLeadingOrTrailingComponent( button ); + button.addActionListener( e -> { + LookAndFeel.installProperty( getComponent(), "echoChar", button.isSelected() + ? '\0' + : (echoChar != null ? echoChar : '*')); + } ); + return button; + } + + /** @since 2 */ + protected void uninstallRevealButton() { + if( revealButton != null ) { + getComponent().remove( revealButton ); + revealButton = null; + } + } + + @Override + protected JComponent[] getTrailingComponents() { + return new JComponent[] { trailingComponent, revealButton, clearButton }; + } } diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties index f8ba150b..e4d4dbfc 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -484,7 +484,10 @@ PasswordField.placeholderForeground = @disabledForeground PasswordField.iconTextGap = 4 PasswordField.echoChar = \u2022 PasswordField.showCapsLock = true +PasswordField.showRevealButton = false PasswordField.capsLockIcon = com.formdev.flatlaf.icons.FlatCapsLockIcon +PasswordField.revealIcon = com.formdev.flatlaf.icons.FlatRevealIcon +PasswordField.revealIconColor = lazy(Actions.Grey) #---- Popup ---- diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java index 7842df5d..80900989 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java @@ -398,7 +398,8 @@ public class TestFlatStyleableInfo Map> expected = new LinkedHashMap<>(); expectedMap( expected, - "showCapsLock", boolean.class + "showCapsLock", boolean.class, + "showRevealButton", boolean.class ); // FlatPasswordFieldUI extends FlatTextFieldUI diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java index 700ced80..77654ca9 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java @@ -551,6 +551,7 @@ public class TestFlatStyling textField( ui ); ui.applyStyle( "showCapsLock: true" ); + ui.applyStyle( "showRevealButton: true" ); // capsLockIcon ui.applyStyle( "capsLockIconColor: #fff" ); 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 a43d40a5..6c2e9f23 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 @@ -38,6 +38,11 @@ class BasicComponentsPanel BasicComponentsPanel() { initComponents(); + // show reveal button for password field + // to enable this for all password fields use: + // UIManager.put( "PasswordField.showRevealButton", true ); + passwordField1.putClientProperty( FlatClientProperties.STYLE, "showRevealButton: true" ); + // search history button JButton searchHistoryButton = new JButton( new FlatSearchWithHistoryIcon( true ) ); searchHistoryButton.setToolTipText( "Search History" ); @@ -128,7 +133,7 @@ class BasicComponentsPanel JFormattedTextField formattedTextField4 = new JFormattedTextField(); JFormattedTextField formattedTextField5 = new JFormattedTextField(); JLabel passwordFieldLabel = new JLabel(); - JPasswordField passwordField1 = new JPasswordField(); + passwordField1 = new JPasswordField(); JPasswordField passwordField2 = new JPasswordField(); JPasswordField passwordField3 = new JPasswordField(); JPasswordField passwordField4 = new JPasswordField(); @@ -913,6 +918,7 @@ class BasicComponentsPanel } // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JPasswordField passwordField1; private JTextField compsTextField; private JTextField clearTextField; // JFormDesigner - End of variables declaration //GEN-END:variables 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 b6d9a4b9..b2735da8 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 @@ -375,6 +375,9 @@ new FormModel { add( new FormComponent( "javax.swing.JPasswordField" ) { name: "passwordField1" "text": "Editable" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 8,growx" } ) diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt index c1741a44..a3040e7a 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt @@ -711,9 +711,12 @@ PasswordField.inactiveBackground #3c3f41 HSL 204 4 25 javax.swing.plaf.Co PasswordField.inactiveForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI] PasswordField.margin 2,6,2,6 javax.swing.plaf.InsetsUIResource [UI] PasswordField.placeholderForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI] +PasswordField.revealIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatRevealIcon [UI] +PasswordField.revealIconColor [lazy] #afb1b3 HSL 210 3 69 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionBackground #4b6eaf HSL 219 40 49 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI] PasswordField.showCapsLock true +PasswordField.showRevealButton false PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt index 00d5267e..fc4ffa67 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt @@ -716,9 +716,12 @@ PasswordField.inactiveBackground #f2f2f2 HSL 0 0 95 javax.swing.plaf.Co PasswordField.inactiveForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI] PasswordField.margin 2,6,2,6 javax.swing.plaf.InsetsUIResource [UI] PasswordField.placeholderForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI] +PasswordField.revealIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatRevealIcon [UI] +PasswordField.revealIconColor [lazy] #6e6e6e HSL 0 0 43 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionBackground #2675bf HSL 209 67 45 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI] PasswordField.showCapsLock true +PasswordField.showRevealButton false PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt index 204b1cd8..7dcba7ad 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt @@ -726,9 +726,12 @@ PasswordField.inactiveBackground #f0f0f0 HSL 0 0 94 javax.swing.plaf.Co PasswordField.inactiveForeground #000088 HSL 240 100 27 javax.swing.plaf.ColorUIResource [UI] PasswordField.margin 2,6,2,6 javax.swing.plaf.InsetsUIResource [UI] PasswordField.placeholderForeground #000088 HSL 240 100 27 javax.swing.plaf.ColorUIResource [UI] +PasswordField.revealIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatRevealIcon [UI] +PasswordField.revealIconColor [lazy] #6e6e6e HSL 0 0 43 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionBackground #00aa00 HSL 120 100 33 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionForeground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI] PasswordField.showCapsLock true +PasswordField.showRevealButton false PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI 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 e3bfa84b..0a3a2ad2 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 @@ -126,6 +126,14 @@ public class FlatTextComponentsTest showClearButtonCheckBox.isSelected() ); } + private void showRevealButton() { + for( Component c : getComponents() ) { + if( c instanceof JPasswordField ) + ((JPasswordField)c).putClientProperty(FlatClientProperties.STYLE, + showRevealButtonCheckBox.isSelected() ? "showRevealButton: true" : null ); + } + } + private void putTextFieldClientProperty( String key, Object value ) { for( Component c : getComponents() ) { if( c instanceof JTextField ) @@ -174,6 +182,7 @@ public class FlatTextComponentsTest leadingComponentVisibleCheckBox = new JCheckBox(); trailingComponentVisibleCheckBox = new JCheckBox(); showClearButtonCheckBox = new JCheckBox(); + showRevealButtonCheckBox = new JCheckBox(); JLabel passwordFieldLabel = new JLabel(); JPasswordField passwordField1 = new JPasswordField(); JPasswordField passwordField3 = new JPasswordField(); @@ -326,6 +335,7 @@ public class FlatTextComponentsTest "[]" + "[]0" + "[]" + + "[]" + "[]")); //---- button1 ---- @@ -417,6 +427,12 @@ public class FlatTextComponentsTest showClearButtonCheckBox.setName("showClearButtonCheckBox"); showClearButtonCheckBox.addActionListener(e -> showClearButton()); panel1.add(showClearButtonCheckBox, "cell 0 11 2 1,alignx left,growx 0"); + + //---- showRevealButtonCheckBox ---- + showRevealButtonCheckBox.setText("password reveal button"); + showRevealButtonCheckBox.setName("showRevealButtonCheckBox"); + showRevealButtonCheckBox.addActionListener(e -> showRevealButton()); + panel1.add(showRevealButtonCheckBox, "cell 0 12 2 1,alignx left,growx 0"); } add(panel1, "cell 4 0 1 10,aligny top,growy 0"); @@ -733,6 +749,7 @@ public class FlatTextComponentsTest private JCheckBox leadingComponentVisibleCheckBox; private JCheckBox trailingComponentVisibleCheckBox; private JCheckBox showClearButtonCheckBox; + private JCheckBox showRevealButtonCheckBox; 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 06681160..0d8d6b31 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": "[][][][][][]0[][]0[][]0[][]" + "$rowConstraints": "[][][][][][]0[][]0[][]0[][][]" } ) { name: "panel1" "border": new javax.swing.border.TitledBorder( "Control" ) @@ -220,6 +220,16 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 11 2 1,alignx left,growx 0" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "showRevealButtonCheckBox" + "text": "password reveal button" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showRevealButton", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 12 2 1,alignx left,growx 0" + } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 4 0 1 10,aligny top,growy 0" } ) diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt index b0340b10..e719971f 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt @@ -557,9 +557,12 @@ PasswordField.inactiveBackground PasswordField.inactiveForeground PasswordField.margin PasswordField.placeholderForeground +PasswordField.revealIcon +PasswordField.revealIconColor PasswordField.selectionBackground PasswordField.selectionForeground PasswordField.showCapsLock +PasswordField.showRevealButton PasswordFieldUI Popup.dropShadowColor Popup.dropShadowInsets