Make controls more FXML-friendly

This commit is contained in:
mkpaz 2023-05-28 16:40:41 +04:00
parent 2956ef7558
commit 998bd69334
12 changed files with 176 additions and 37 deletions

@ -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<MaskTextFormatter> 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<MaskChar> 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);
}
}

@ -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;
}

@ -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<PasswordTextFormatter> 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);
}
}

@ -14,6 +14,7 @@ import javafx.util.StringConverter;
public class RingProgressIndicator extends ProgressIndicator {
public RingProgressIndicator() {
super();
}
public RingProgressIndicator(double progress) {

@ -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<Runnable> onClose =
protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose");
public Runnable getOnClose() {
public EventHandler<? super Event> getOnClose() {
return onClose.get();
}
public ObjectProperty<Runnable> onCloseProperty() {
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
public void setOnClose(Runnable onClose) {
public void setOnClose(EventHandler<? super Event> onClose) {
this.onClose.set(onClose);
}

@ -62,11 +62,6 @@ public class MaskTextFormatter extends TextFormatter<String> {
this.filter = filter;
}
public MaskTextFormatter(MaskTextFilter filter, TextField field) {
super(filter);
this.filter = filter;
}
///////////////////////////////////////////////////////////////////////////
// Factory Methods //
///////////////////////////////////////////////////////////////////////////

@ -81,7 +81,7 @@ public class PasswordTextFormatter extends TextFormatter<String> {
/**
* See {@link #revealPasswordProperty}.
*/
public boolean isRevealPassword() {
public boolean getRevealPassword() {
return revealPasswordProperty().get();
}

@ -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

@ -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;
});

@ -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");
}
}
}

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import atlantafx.base.controls.Breadcrumbs?>
<?import atlantafx.base.controls.Calendar?>
<?import atlantafx.base.controls.Card?>
<?import atlantafx.base.controls.CustomTextField?>
<?import atlantafx.base.controls.PasswordTextField?>
<?import atlantafx.base.controls.RingProgressIndicator?>
<?import atlantafx.base.controls.Spacer?>
<?import atlantafx.base.controls.Tile?>
<?import atlantafx.base.controls.ToggleSwitch?>
<?import atlantafx.base.layout.DeckPane?>
<?import atlantafx.base.layout.InputGroup?>
<?import atlantafx.base.layout.ModalBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="897.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label layoutX="14.0" layoutY="14.0" style="-fx-text-fill: green;" text="Breadcrumbs" />
<Breadcrumbs layoutX="14.0" layoutY="45.0" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="45.0" prefWidth="280.0" style="-fx-border-color: red;" />
<Calendar layoutX="14.0" layoutY="107.0" />
<CustomTextField layoutX="14.0" layoutY="470.0" prefHeight="36.0" prefWidth="264.0" text="Text" />
<Label layoutX="14.0" layoutY="442.0" style="-fx-text-fill: green;" text="CustomTextField" />
<ToggleSwitch layoutX="411.0" layoutY="33.0" />
<Separator layoutX="295.0" layoutY="15.0" orientation="VERTICAL" prefHeight="759.0" prefWidth="25.0" />
<PasswordTextField layoutX="14.0" layoutY="546.0" prefHeight="36.0" prefWidth="264.0" text="password" />
<Label layoutX="14.0" layoutY="517.0" style="-fx-text-fill: green;" text="PasswordTextField" />
<RingProgressIndicator layoutX="326.0" layoutY="25.0" progress="0.35" />
<Tile layoutX="320.0" layoutY="209.0" prefHeight="73.0" prefWidth="483.0" subTitle="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla fermentum, quam eu pretium euismod, ipsum mauris interdum massa, at scelerisque nulla augue a nunc. Vivamus vehicula rhoncus est, ut placerat nulla pellentesque vel. Duis ac mattis sapien. " title="Title" />
<InputGroup layoutX="526.0" layoutY="57.0">
<children>
<ToggleButton mnemonicParsing="false" text="Toggle1" />
<ToggleButton mnemonicParsing="false" text="Toggle2" />
<ToggleButton layoutX="124.0" layoutY="10.0" mnemonicParsing="false" text="Toggle3" />
</children>
</InputGroup>
<Label layoutX="527.0" layoutY="23.0" style="-fx-text-fill: green;" text="InputGroup" />
<HBox alignment="CENTER_LEFT" layoutX="527.0" layoutY="137.0" prefHeight="56.0" prefWidth="280.0" style="-fx-border-color: red;">
<children>
<Label text="Left" />
<Spacer prefHeight="73.0" prefWidth="219.0" />
<Label layoutX="10.0" layoutY="10.0" text="Right" />
</children>
</HBox>
<Label layoutX="526.0" layoutY="108.0" style="-fx-text-fill: green;" text="Spacer" />
<VBox layoutX="863.0" layoutY="128.0" prefHeight="200.0" prefWidth="100.0" style="-fx-border-color: red;">
<children>
<Label text="Top" />
<Spacer />
<Label layoutX="10.0" layoutY="10.0" text="Bottom" />
</children>
</VBox>
<Label layoutX="336.0" layoutY="183.0" style="-fx-text-fill: green;" text="Tile" />
<ModalBox layoutX="326.0" layoutY="459.0" prefHeight="175.0" prefWidth="264.0" style="-fx-border-color: red;" />
<Label layoutX="325.0" layoutY="432.0" style="-fx-text-fill: green;" text="ModalBox" />
<Card layoutX="641.0" layoutY="457.0" prefHeight="229.0" prefWidth="309.0" styleClass="elevated-2" />
<Label layoutX="642.0" layoutY="432.0" style="-fx-text-fill: green;" text="Card" />
<DeckPane layoutX="326.0" layoutY="695.0" prefHeight="127.0" prefWidth="264.0" style="-fx-border-color: red;" />
<Label layoutX="325.0" layoutY="665.0" style="-fx-text-fill: green;" text="DeckPane" />
</children>
</AnchorPane>

@ -2,6 +2,7 @@
<?import atlantafx.base.controls.Calendar?>
<?import atlantafx.base.controls.CustomTextField?>
<?import atlantafx.base.controls.MaskTextField?>
<?import atlantafx.base.controls.PasswordTextField?>
<?import atlantafx.base.controls.RingProgressIndicator?>
<?import atlantafx.base.controls.Tile?>
@ -52,7 +53,7 @@
<?import javafx.scene.paint.Color?>
<?import org.kordamp.ikonli.javafx.FontIcon?>
<GridPane hgap="40.0" vgap="20.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
<GridPane hgap="40.0" vgap="20.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atlantafx.sampler.page.showcase.OverviewPage$Controller">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" />
<ColumnConstraints hgrow="NEVER" />
@ -208,6 +209,7 @@
<RowConstraints vgrow="NEVER" />
<RowConstraints vgrow="NEVER" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
</rowConstraints>
<children>
<TextField promptText="Prompt" GridPane.columnIndex="1" />
@ -223,6 +225,7 @@
<PasswordTextField text="password" GridPane.rowIndex="2" />
<TextField editable="false" promptText="Readonly" GridPane.rowIndex="1" />
<TextField disable="true" promptText="Disabled" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<MaskTextField fx:id="phoneTf" mask="(NNN) NNN-NN-NN" GridPane.rowIndex="3" />
</children>
</GridPane>
<GridPane hgap="10.0" layoutX="30.0" layoutY="30.0" vgap="10.0" GridPane.rowIndex="2">