diff --git a/base/src/main/java/atlantafx/base/controls/MaskTextField.java b/base/src/main/java/atlantafx/base/controls/MaskTextField.java index 5224dfe..6cdd654 100644 --- a/base/src/main/java/atlantafx/base/controls/MaskTextField.java +++ b/base/src/main/java/atlantafx/base/controls/MaskTextField.java @@ -30,28 +30,75 @@ package atlantafx.base.controls; import atlantafx.base.util.MaskChar; import atlantafx.base.util.MaskTextFormatter; import java.util.List; +import java.util.Objects; import javafx.beans.NamedArg; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import org.jetbrains.annotations.Nullable; /** - * This is a convenience wrapper for instantiating a {@link CustomTextField} - * with {@code MaskTextFormatter}. For additional info refer to the {@link MaskTextFormatter} + * This is a convenience wrapper for instantiating a {@link CustomTextField} with a + * {@code MaskTextFormatter}. For additional info refer to the {@link MaskTextFormatter} * docs. */ public class MaskTextField extends CustomTextField { - protected final MaskTextFormatter formatter; + /** + * The whole dancing around the editable mask property is solely due to SceneBuilder + * not works without no-arg constructor. It requires to make formatter value mutable + * as well, which is not really tested and never intended to be supported. Also, since + * the formatter property is not bound to the text field formatter property, setting the + * latter manually can lead to memory leak. + */ + protected final StringProperty mask = new SimpleStringProperty(this, "mask"); - public MaskTextField(@NamedArg("text") String mask) { + protected final ReadOnlyObjectWrapper formatter = + new ReadOnlyObjectWrapper<>(this, "formatter"); + + public MaskTextField() { + super(""); + init(); + } + + public MaskTextField(@NamedArg("mask") String mask) { this("", mask); } - public MaskTextField(@NamedArg("text") String text, @NamedArg("mask") String mask) { - super(text); - this.formatter = MaskTextFormatter.create(this, mask); + private MaskTextField(@NamedArg("text") String text, + @NamedArg("mask") String mask) { + super(Objects.requireNonNullElse(text, "")); + + formatter.set(MaskTextFormatter.create(this, mask)); + setMask(mask); // set mask only after creating a formatter, for validation + init(); } public MaskTextField(String text, List mask) { - super(text); - this.formatter = MaskTextFormatter.create(this, mask); + super(Objects.requireNonNullElse(text, "")); + + formatter.set(MaskTextFormatter.create(this, mask)); + setMask(null); + init(); + } + + protected void init() { + mask.addListener((obs, old, val) -> { + // this will replace the current text value with placeholder mask, + // so, neither text no prompt won't be shown in the SceneBuilder + formatter.set(val != null ? MaskTextFormatter.create(this, val) : null); + }); + } + + public StringProperty maskProperty() { + return mask; + } + + public @Nullable String getMask() { + return mask.get(); + } + + public void setMask(@Nullable String mask) { + this.mask.set(mask); } } diff --git a/base/src/main/java/atlantafx/base/controls/ModalPane.java b/base/src/main/java/atlantafx/base/controls/ModalPane.java index 59da403..16bb97d 100755 --- a/base/src/main/java/atlantafx/base/controls/ModalPane.java +++ b/base/src/main/java/atlantafx/base/controls/ModalPane.java @@ -6,6 +6,7 @@ import atlantafx.base.util.Animations; import java.util.Objects; import java.util.function.Function; import javafx.animation.Animation; +import javafx.beans.NamedArg; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -52,7 +53,7 @@ public class ModalPane extends Control { * @param topViewOrder the {@link #viewOrderProperty()} value to be set * to display the modal pane on top of the parent container. */ - public ModalPane(int topViewOrder) { + public ModalPane(@NamedArg("topViewOrder") int topViewOrder) { super(); this.topViewOrder = topViewOrder; } diff --git a/base/src/main/java/atlantafx/base/controls/PasswordTextField.java b/base/src/main/java/atlantafx/base/controls/PasswordTextField.java index 3fc9b26..15dee9a 100644 --- a/base/src/main/java/atlantafx/base/controls/PasswordTextField.java +++ b/base/src/main/java/atlantafx/base/controls/PasswordTextField.java @@ -30,58 +30,65 @@ package atlantafx.base.controls; import atlantafx.base.util.PasswordTextFormatter; import javafx.beans.NamedArg; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyStringProperty; /** * This is a convenience wrapper for instantiating a {@link CustomTextField} - * with {@code PasswordTextFormatter}. For additional info refer to the {@link PasswordTextFormatter} - * docs. + * with {@code PasswordTextFormatter}. For additional info refer to the + * {@link PasswordTextFormatter} docs. */ public class PasswordTextField extends CustomTextField { - protected final PasswordTextFormatter formatter; + protected final ReadOnlyObjectWrapper formatter + = new ReadOnlyObjectWrapper<>(this, "formatter"); + + public PasswordTextField() { + this("", PasswordTextFormatter.BULLET); + } public PasswordTextField(@NamedArg("text") String text) { this(text, PasswordTextFormatter.BULLET); } - public PasswordTextField(@NamedArg("text") String text, @NamedArg("bullet") char bullet) { + protected PasswordTextField(@NamedArg("text") String text, + @NamedArg("bullet") char bullet) { super(text); - this.formatter = PasswordTextFormatter.create(this, bullet); + formatter.set(PasswordTextFormatter.create(this, bullet)); } /** * See {@link PasswordTextFormatter#passwordProperty()}. */ public ReadOnlyStringProperty passwordProperty() { - return formatter.passwordProperty(); + return formatter.get().passwordProperty(); } /** * See {@link PasswordTextFormatter#getPassword()}. */ public String getPassword() { - return formatter.getPassword(); + return formatter.get().getPassword(); } /** * See {@link PasswordTextFormatter#revealPasswordProperty()}. */ public BooleanProperty revealPasswordProperty() { - return formatter.revealPasswordProperty(); + return formatter.get().revealPasswordProperty(); } /** - * See {@link PasswordTextFormatter#isRevealPassword()}. + * See {@link PasswordTextFormatter#getRevealPassword()}. */ - public boolean isRevealPassword() { - return formatter.isRevealPassword(); + public boolean getRevealPassword() { + return formatter.get().getRevealPassword(); } /** * See {@link PasswordTextFormatter#setRevealPassword(boolean)}. */ public void setRevealPassword(boolean reveal) { - formatter.setRevealPassword(reveal); + formatter.get().setRevealPassword(reveal); } } diff --git a/base/src/main/java/atlantafx/base/controls/RingProgressIndicator.java b/base/src/main/java/atlantafx/base/controls/RingProgressIndicator.java index d177e4f..1c56bbd 100755 --- a/base/src/main/java/atlantafx/base/controls/RingProgressIndicator.java +++ b/base/src/main/java/atlantafx/base/controls/RingProgressIndicator.java @@ -14,6 +14,7 @@ import javafx.util.StringConverter; public class RingProgressIndicator extends ProgressIndicator { public RingProgressIndicator() { + super(); } public RingProgressIndicator(double progress) { diff --git a/base/src/main/java/atlantafx/base/layout/ModalBox.java b/base/src/main/java/atlantafx/base/layout/ModalBox.java index b2a69d7..b07fa77 100644 --- a/base/src/main/java/atlantafx/base/layout/ModalBox.java +++ b/base/src/main/java/atlantafx/base/layout/ModalBox.java @@ -9,6 +9,8 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.event.Event; +import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.StackPane; @@ -131,7 +133,7 @@ public class ModalBox extends AnchorPane { // call user specified close handler if (onClose.get() != null) { - onClose.get().run(); + onClose.get().handle(new Event(Event.ANY)); } } @@ -145,18 +147,18 @@ public class ModalBox extends AnchorPane { * handler will be executed after the default close handler. Therefore, you * can use it to perform arbitrary actions on dialog close. */ - protected final ObjectProperty onClose = + protected final ObjectProperty> onClose = new SimpleObjectProperty<>(this, "onClose"); - public Runnable getOnClose() { + public EventHandler getOnClose() { return onClose.get(); } - public ObjectProperty onCloseProperty() { + public ObjectProperty> onCloseProperty() { return onClose; } - public void setOnClose(Runnable onClose) { + public void setOnClose(EventHandler onClose) { this.onClose.set(onClose); } diff --git a/base/src/main/java/atlantafx/base/util/MaskTextFormatter.java b/base/src/main/java/atlantafx/base/util/MaskTextFormatter.java index 58a3775..19b3425 100644 --- a/base/src/main/java/atlantafx/base/util/MaskTextFormatter.java +++ b/base/src/main/java/atlantafx/base/util/MaskTextFormatter.java @@ -62,11 +62,6 @@ public class MaskTextFormatter extends TextFormatter { this.filter = filter; } - public MaskTextFormatter(MaskTextFilter filter, TextField field) { - super(filter); - this.filter = filter; - } - /////////////////////////////////////////////////////////////////////////// // Factory Methods // /////////////////////////////////////////////////////////////////////////// diff --git a/base/src/main/java/atlantafx/base/util/PasswordTextFormatter.java b/base/src/main/java/atlantafx/base/util/PasswordTextFormatter.java index f2a99b5..75dfaed 100644 --- a/base/src/main/java/atlantafx/base/util/PasswordTextFormatter.java +++ b/base/src/main/java/atlantafx/base/util/PasswordTextFormatter.java @@ -81,7 +81,7 @@ public class PasswordTextFormatter extends TextFormatter { /** * See {@link #revealPasswordProperty}. */ - public boolean isRevealPassword() { + public boolean getRevealPassword() { return revealPasswordProperty().get(); } diff --git a/sampler/src/main/java/atlantafx/sampler/page/components/CustomTextFieldPage.java b/sampler/src/main/java/atlantafx/sampler/page/components/CustomTextFieldPage.java index 54b1ef8..e8e9115 100644 --- a/sampler/src/main/java/atlantafx/sampler/page/components/CustomTextFieldPage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/components/CustomTextFieldPage.java @@ -111,10 +111,10 @@ public final class CustomTextFieldPage extends OutlinePage { var icon = new FontIcon(Feather.EYE_OFF); icon.setCursor(Cursor.HAND); icon.setOnMouseClicked(e -> { - icon.setIconCode(tf.isRevealPassword() + icon.setIconCode(tf.getRevealPassword() ? Feather.EYE_OFF : Feather.EYE ); - tf.setRevealPassword(!tf.isRevealPassword()); + tf.setRevealPassword(!tf.getRevealPassword()); }); tf.setRight(icon); //snippet_3:end diff --git a/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java b/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java index 6796959..732f1d7 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/general/ThemePage.java @@ -94,7 +94,7 @@ public final class ThemePage extends OutlinePage { sceneBuilderDialog = new Lazy<>(() -> { var dialog = new SceneBuilderDialog(); dialog.setClearOnClose(true); - dialog.setOnClose(dialog::reset); + dialog.setOnClose(e -> dialog.reset()); return dialog; }); diff --git a/sampler/src/main/java/atlantafx/sampler/page/showcase/OverviewPage.java b/sampler/src/main/java/atlantafx/sampler/page/showcase/OverviewPage.java index 8f5d3ae..7f8f9ca 100755 --- a/sampler/src/main/java/atlantafx/sampler/page/showcase/OverviewPage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/showcase/OverviewPage.java @@ -4,13 +4,18 @@ package atlantafx.sampler.page.showcase; import static javafx.scene.control.ScrollPane.ScrollBarPolicy.AS_NEEDED; +import atlantafx.base.controls.MaskTextField; import atlantafx.base.theme.Styles; import atlantafx.sampler.Resources; import atlantafx.sampler.page.Page; import atlantafx.sampler.util.NodeUtils; import java.io.IOException; import java.net.URI; +import java.net.URL; +import java.util.ResourceBundle; +import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.Parent; @@ -79,4 +84,15 @@ public final class OverviewPage extends ScrollPane implements Page { @Override public void reset() { } + + /////////////////////////////////////////////////////////////////////////// + + public static class Controller implements Initializable { + public @FXML MaskTextField phoneTf; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + phoneTf.setText("(415) 273-91-64"); + } + } } diff --git a/sampler/src/main/resources/atlantafx/sampler/fxml/custom-controls.test.fxml b/sampler/src/main/resources/atlantafx/sampler/fxml/custom-controls.test.fxml new file mode 100644 index 0000000..3cf2de0 --- /dev/null +++ b/sampler/src/main/resources/atlantafx/sampler/fxml/custom-controls.test.fxml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sampler/src/main/resources/atlantafx/sampler/fxml/overview.fxml b/sampler/src/main/resources/atlantafx/sampler/fxml/overview.fxml index 288f972..14be928 100644 --- a/sampler/src/main/resources/atlantafx/sampler/fxml/overview.fxml +++ b/sampler/src/main/resources/atlantafx/sampler/fxml/overview.fxml @@ -2,6 +2,7 @@ + @@ -52,7 +53,7 @@ - + @@ -208,6 +209,7 @@ + @@ -223,6 +225,7 @@ +