diff --git a/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/EnhancedPasswordFieldApp.java b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/EnhancedPasswordFieldApp.java index 43a164e8..4e1faee1 100644 --- a/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/EnhancedPasswordFieldApp.java +++ b/gemsfx-demo/src/main/java/com/dlsc/gemsfx/demo/EnhancedPasswordFieldApp.java @@ -20,7 +20,7 @@ public class EnhancedPasswordFieldApp extends Application { @Override public void start(Stage primaryStage) throws Exception { - EnhancedPasswordField passwordField1 = EnhancedPasswordField.createSimplePasswordField(); + EnhancedPasswordField passwordField1 = new EnhancedPasswordField(); passwordField1.setPromptText("Enter your password"); passwordField1.setEchoChar('★'); passwordField1.setText("1234567890"); @@ -38,7 +38,7 @@ public void start(Stage primaryStage) throws Exception { passwordField2.setLeft(createIconNode(MaterialDesign.MDI_KEY)); passwordField2.setStyle("-fx-echo-char: '■';"); - EnhancedPasswordField passwordField3 = EnhancedPasswordField.createSimplePasswordField(); + EnhancedPasswordField passwordField3 = new EnhancedPasswordField(); passwordField3.setText("1234567890"); passwordField3.setShowPassword(true); diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/EnhancedPasswordField.java b/gemsfx/src/main/java/com/dlsc/gemsfx/EnhancedPasswordField.java index 1fb90e35..2d2f2cb7 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/EnhancedPasswordField.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/EnhancedPasswordField.java @@ -2,21 +2,22 @@ import com.dlsc.gemsfx.skins.EnhancedPasswordFieldSkin; import com.dlsc.gemsfx.util.EchoCharConverter; +import com.dlsc.gemsfx.util.UIUtil; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.css.CssMetaData; +import javafx.css.PseudoClass; import javafx.css.Styleable; import javafx.css.StyleableObjectProperty; import javafx.css.StyleableProperty; import javafx.scene.Node; import javafx.scene.control.PasswordField; import javafx.scene.control.Skin; -import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; -import org.kordamp.ikonli.javafx.FontIcon; -import org.kordamp.ikonli.materialdesign.MaterialDesign; import java.util.ArrayList; import java.util.Collections; @@ -54,45 +55,37 @@ public class EnhancedPasswordField extends PasswordField { public static final char DEFAULT_ECHO_CHAR = '●'; - private static final boolean DEFAULT_SHOW_PASSWORD = false; + private static final String DEFAULT_STYLE_CLASS = "enhanced-password-field"; + private static final boolean DEFAULT_SHOW_PASSWORD = false; + private static final PseudoClass SHOWING_PASSWORD_PSEUDO_CLASS = PseudoClass.getPseudoClass("showing-password"); + private final Logger LOG = Logger.getLogger(EnhancedPasswordField.class.getName()); public EnhancedPasswordField() { super(); getStyleClass().add(DEFAULT_STYLE_CLASS); - } - - public EnhancedPasswordField(String text) { - this(); - setText(text); - } - /** - * Creates a simple password field with an eye icon on the right side. - *

- * This method creates a simple password field with an eye icon on the right side. The eye icon - * can be clicked to toggle the visibility of the password. - * - * @return a simple password field with an eye icon on the right side - */ - public static EnhancedPasswordField createSimplePasswordField() { - EnhancedPasswordField passwordField = new EnhancedPasswordField(); + showPasswordProperty().subscribe(showing -> pseudoClassStateChanged(SHOWING_PASSWORD_PSEUDO_CLASS, showing)); //set right node - FontIcon fontIcon = new FontIcon(); - fontIcon.iconCodeProperty().bind(passwordField.showPasswordProperty().map(it -> it ? MaterialDesign.MDI_EYE : MaterialDesign.MDI_EYE_OFF)); - - StackPane right = new StackPane(fontIcon); - right.getStyleClass().add("right-icon-wrapper"); - right.setOnMouseClicked(event -> { - if (event.getButton() == MouseButton.PRIMARY) { - passwordField.setShowPassword(!passwordField.isShowPassword()); + Region rightIcon = new Region(); + rightIcon.getStyleClass().add("right-icon"); + + StackPane rightWrapper = new StackPane(rightIcon); + rightWrapper.getStyleClass().add("right-icon-wrapper"); + rightWrapper.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> { + if (UIUtil.clickOnNode(event)) { + setShowPassword(!isShowPassword()); + event.consume(); } }); + setRight(rightWrapper); + } - passwordField.setRight(right); - return passwordField; + public EnhancedPasswordField(String text) { + this(); + setText(text); } @Override diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/util/UIUtil.java b/gemsfx/src/main/java/com/dlsc/gemsfx/util/UIUtil.java index b9a43456..04d6345d 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/util/UIUtil.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/util/UIUtil.java @@ -84,7 +84,7 @@ public static void toggleClassOnCondition(Styleable node, String styleClass, boo * If the observable value is false, the style class is removed. * * @param node The node to toggle the style on. - * @param styleClass The style class to add or remove. + * @param styleClass The style class to add or remove. * @param booleanObservableValue The observable value that determines whether to add or remove the style. */ public static void toggleClassBasedOnObservable(Styleable node, String styleClass, ObservableValue booleanObservableValue) { @@ -194,13 +194,12 @@ public static void copyToClipboard(String copyContent) { } /** - * Determines if the given mouse event is a single primary button click + * Determines if the given mouse event is a primary button click * that hasn't moved since it was pressed. * *

This method checks if the mouse event satisfies the following conditions: *

* @@ -209,7 +208,27 @@ public static void copyToClipboard(String copyContent) { * @throws NullPointerException if the event is null. */ public static boolean clickOnNode(MouseEvent event) { - return event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1 && event.isStillSincePress(); + return clickOnNode(event, false); + } + + /** + * Determines if the given mouse event is a primary button click + * that hasn't moved since it was pressed. + * + *

This method checks if the mouse event satisfies the following conditions: + *

+ * + * @param event The mouse event to check. Must not be null. + * @param isSingleClick {@code true} if the event must be a single click, {@code false} otherwise. + * @return {@code true} if the event is a stable primary button click, {@code false} otherwise. + * @throws NullPointerException if the event is null. + */ + public static boolean clickOnNode(MouseEvent event, boolean isSingleClick) { + return event.getButton() == MouseButton.PRIMARY && event.isStillSincePress() && (!isSingleClick || event.getClickCount() == 1); } } \ No newline at end of file diff --git a/gemsfx/src/main/resources/com/dlsc/gemsfx/enhanced-password-field.css b/gemsfx/src/main/resources/com/dlsc/gemsfx/enhanced-password-field.css index d4334daa..5a003e73 100644 --- a/gemsfx/src/main/resources/com/dlsc/gemsfx/enhanced-password-field.css +++ b/gemsfx/src/main/resources/com/dlsc/gemsfx/enhanced-password-field.css @@ -1,8 +1,16 @@ .enhanced-password-field .right-icon-wrapper { - -fx-padding: 0 5px; -fx-cursor: hand; + -fx-max-height: 1.25em; + -fx-min-width: 1.25em; } -.enhanced-password-field .right-icon-wrapper .ikonli-font-icon { - -fx-icon-size: 15px; +.enhanced-password-field .right-icon-wrapper .right-icon { + -fx-shape: "M12,9c-0.8,0-1.5,0.3-2.1,0.9C9.3,10.5,9,11.2,9,12s0.3,1.5,0.8,2.1c0.6,0.6,1.3,0.8,2.1,0.8c0.8,0,1.5-0.3,2.1-0.8 c0.6-0.6,0.8-1.3,0.8-2.1c0-0.8-0.3-1.5-0.8-2.1C13.5,9.3,12.8,9,12,9z M12,17c-1.4,0-2.6-0.5-3.5-1.5C7.5,14.6,7,13.4,7,12 c0-1.4,0.5-2.6,1.5-3.5c1-1,2.1-1.5,3.5-1.5c1.4,0,2.6,0.5,3.5,1.5c1,0.9,1.5,2.1,1.5,3.5c0,1.4-0.5,2.6-1.5,3.5 C14.6,16.5,13.4,17,12,17z M12,4.5c-2.5,0-4.8,0.7-6.7,2.1C3.3,8,1.9,9.8,1,12c0.9,2.2,2.3,4,4.3,5.4c2,1.4,4.2,2.1,6.7,2.1 c2.5,0,4.8-0.7,6.7-2.1c2-1.4,3.4-3.2,4.3-5.4c-0.9-2.2-2.3-4-4.3-5.4C16.8,5.2,14.5,4.5,12,4.5z"; + -fx-max-height: 0.852em; + -fx-background-color: black; +} + +.enhanced-password-field:showing-password .right-icon-wrapper .right-icon { + -fx-shape: "M11.8,8.5l3.2,3.1v-0.1c0-0.8-0.3-1.5-0.9-2.1c-0.6-0.6-1.3-0.9-2.1-0.9H11.8z M7.5,9.3l1.5,1.5C9,11.1,9,11.3,9,11.5 c0,0.8,0.3,1.5,0.9,2.1c0.6,0.6,1.3,0.9,2.1,0.9c0.2,0,0.4,0,0.7-0.1l1.5,1.5c-0.7,0.3-1.4,0.5-2.2,0.6c-1.4,0-2.6-0.5-3.5-1.5 c-0.9-1-1.4-2.1-1.5-3.5C7,10.7,7.2,10,7.5,9.3z M2,3.8l2.3,2.3l0.5,0.4c-1.7,1.3-2.9,3-3.8,5c0.9,2.2,2.3,4,4.3,5.4 c2,1.4,4.2,2.1,6.7,2.1c1.6,0,3-0.3,4.4-0.8l3.4,3.3l1.3-1.3L3.3,2.5L2,3.8z M12,6.5c1.4,0,2.6,0.5,3.5,1.5c1,0.9,1.5,2.1,1.5,3.5 c0,0.7-0.1,1.3-0.4,1.8l2.9,2.9c1.5-1.3,2.7-2.9,3.5-4.7c-0.9-2.2-2.3-4-4.3-5.4C16.8,4.7,14.5,4,12,4c-1.4,0-2.7,0.2-4,0.7 l2.2,2.2C10.7,6.6,11.3,6.5,12,6.5z"; + -fx-max-height: 1.079em; } \ No newline at end of file