Add PasswordTextField

This commit is contained in:
mkpaz 2023-03-11 20:42:50 +04:00
parent d521f8ac4a
commit 5e8a4d3547
6 changed files with 130 additions and 30 deletions

@ -6,6 +6,7 @@
- (Base) New `DeckPane` component with swipe and slide transition support. - (Base) New `DeckPane` component with swipe and slide transition support.
- (Base) New `MaskTextField` (and `MaskTextFormatter`) component to support masked text input. - (Base) New `MaskTextField` (and `MaskTextFormatter`) component to support masked text input.
- (Base) New `PasswordTextField` component to simplify `PasswordTextFormatter` usage.
- (CSS) 🚀 New MacOS-like Cupertino theme in light and dark variants. - (CSS) 🚀 New MacOS-like Cupertino theme in light and dark variants.
- (CSS) 🚀 New [Dracula](https://ui.draculatheme.com/) theme. - (CSS) 🚀 New [Dracula](https://ui.draculatheme.com/) theme.
- (CSS) New `TabPane` style. There are three styles supported: default, floating and classic (new one). - (CSS) New `TabPane` style. There are three styles supported: default, floating and classic (new one).

@ -0,0 +1,87 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package atlantafx.base.controls;
import atlantafx.base.util.PasswordTextFormatter;
import javafx.beans.NamedArg;
import javafx.beans.property.BooleanProperty;
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.
*/
public class PasswordTextField extends CustomTextField {
protected final PasswordTextFormatter formatter;
public PasswordTextField(@NamedArg("text") String text) {
this(text, PasswordTextFormatter.BULLET);
}
public PasswordTextField(@NamedArg("text") String text, @NamedArg("bullet") char bullet) {
super(text);
this.formatter = PasswordTextFormatter.create(this, bullet);
}
/**
* See {@link PasswordTextFormatter#passwordProperty()}.
*/
public ReadOnlyStringProperty passwordProperty() {
return formatter.passwordProperty();
}
/**
* See {@link PasswordTextFormatter#getPassword()}.
*/
public String getPassword() {
return formatter.getPassword();
}
/**
* See {@link PasswordTextFormatter#revealPasswordProperty()}.
*/
public BooleanProperty revealPasswordProperty() {
return formatter.revealPasswordProperty();
}
/**
* See {@link PasswordTextFormatter#isRevealPassword()}.
*/
public boolean isRevealPassword() {
return formatter.isRevealPassword();
}
/**
* See {@link PasswordTextFormatter#setRevealPassword(boolean)}.
*/
public void setRevealPassword(boolean reveal) {
formatter.setRevealPassword(reveal);
}
}

@ -22,7 +22,7 @@ public class PasswordTextFormatter extends TextFormatter<String> {
protected PasswordTextFormatter(StringConverter<String> valueConverter, protected PasswordTextFormatter(StringConverter<String> valueConverter,
UnaryOperator<Change> filter, UnaryOperator<Change> filter,
TextField textField, TextField field,
char bullet) { char bullet) {
super(valueConverter, null, filter); super(valueConverter, null, filter);
@ -32,13 +32,13 @@ public class PasswordTextFormatter extends TextFormatter<String> {
if (filter == null) { if (filter == null) {
throw new NullPointerException("UnaryOperator cannot be null!"); throw new NullPointerException("UnaryOperator cannot be null!");
} }
if (textField == null) { if (field == null) {
throw new NullPointerException("TextField cannot be null!"); throw new NullPointerException("TextField cannot be null!");
} }
PasswordFilter passwordFilter = (PasswordFilter) getFilter(); PasswordFilter passwordFilter = (PasswordFilter) getFilter();
passwordFilter.setBullet(bullet); passwordFilter.setBullet(bullet);
passwordFilter.setInitialText(textField.getText()); passwordFilter.setInitialText(field.getText());
revealPasswordProperty().addListener((obs, old, val) -> { revealPasswordProperty().addListener((obs, old, val) -> {
if (val == null) { if (val == null) {
@ -48,41 +48,68 @@ public class PasswordTextFormatter extends TextFormatter<String> {
// Force text field update, because converter is only called on focus events by default. // Force text field update, because converter is only called on focus events by default.
// Also, reset caret first, because otherwise its position won't be correct due to // Also, reset caret first, because otherwise its position won't be correct due to
// #javafx-bug (https://bugs.openjdk.org/browse/JDK-8248914). // #javafx-bug (https://bugs.openjdk.org/browse/JDK-8248914).
textField.positionCaret(0); field.positionCaret(0);
textField.commitValue(); field.commitValue();
}); });
// force text field update on scene show // force text field update on scene show
Platform.runLater(textField::commitValue); Platform.runLater(field::commitValue);
} }
/**
* Always returns the unmasked password text regardless of
* the {@link #revealPasswordProperty} state.
*/
public ReadOnlyStringProperty passwordProperty() { public ReadOnlyStringProperty passwordProperty() {
return ((PasswordFilter) getFilter()).password.getReadOnlyProperty(); return ((PasswordFilter) getFilter()).password.getReadOnlyProperty();
} }
/**
* See {@link #passwordProperty()}.
*/
public String getPassword() { public String getPassword() {
return passwordProperty().get(); return passwordProperty().get();
} }
/**
* Specifies whether the unmasked password text is revealed or not.
*/
public BooleanProperty revealPasswordProperty() { public BooleanProperty revealPasswordProperty() {
return ((PasswordFilter) getFilter()).revealPassword; return ((PasswordFilter) getFilter()).revealPassword;
} }
/**
* See {@link #revealPasswordProperty}.
*/
public boolean isRevealPassword() { public boolean isRevealPassword() {
return revealPasswordProperty().get(); return revealPasswordProperty().get();
} }
/**
* See {@link #revealPasswordProperty}.
*/
public void setRevealPassword(boolean reveal) { public void setRevealPassword(boolean reveal) {
revealPasswordProperty().set(reveal); revealPasswordProperty().set(reveal);
} }
// Life would be easier if TextFormatter had the default constructor. /**
public static PasswordTextFormatter create(TextField textField, char bullet) { * Creates a new password text formatter with the provided mask character and
* applies itself to the specified text field.
*/
public static PasswordTextFormatter create(TextField field, char bullet) {
var filter = new PasswordFilter(); var filter = new PasswordFilter();
var converter = new PasswordStringConverter(filter); var converter = new PasswordStringConverter(filter);
return new PasswordTextFormatter(converter, filter, textField, bullet);
var formatter = new PasswordTextFormatter(converter, filter, field, bullet);
field.setTextFormatter(formatter);
return formatter;
} }
/**
* Creates a new password text formatter with the default mask character and
* applies itself to the specified text field.
*/
public static PasswordTextFormatter create(TextField textField) { public static PasswordTextFormatter create(TextField textField) {
return create(textField, BULLET); return create(textField, BULLET);
} }

@ -15,7 +15,6 @@ public class PasswordTextFormatterTest {
public void testTextIsMaskedByDefault() { public void testTextIsMaskedByDefault() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
assertEquals("+".repeat(3), field.getText()); assertEquals("+".repeat(3), field.getText());
@ -26,7 +25,6 @@ public class PasswordTextFormatterTest {
public void testTextCanBeRevealed() { public void testTextCanBeRevealed() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
fmt.setRevealPassword(true); fmt.setRevealPassword(true);
@ -38,7 +36,6 @@ public class PasswordTextFormatterTest {
public void testPrependText() { public void testPrependText() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
field.insertText(0, "456"); field.insertText(0, "456");
@ -50,7 +47,6 @@ public class PasswordTextFormatterTest {
public void testAppendText() { public void testAppendText() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
field.appendText("456"); field.appendText("456");
@ -62,7 +58,6 @@ public class PasswordTextFormatterTest {
public void testInsertText() { public void testInsertText() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
field.insertText(2, "456"); field.insertText(2, "456");
@ -74,7 +69,6 @@ public class PasswordTextFormatterTest {
public void testNoInitialText() { public void testNoInitialText() {
var field = new TextField(null); var field = new TextField(null);
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.appendText("456"); field.appendText("456");
assertEquals("+".repeat(3), field.getText()); assertEquals("+".repeat(3), field.getText());
@ -85,7 +79,6 @@ public class PasswordTextFormatterTest {
public void testDeleteSomeText() { public void testDeleteSomeText() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
field.deleteText(0, 2); field.deleteText(0, 2);
@ -97,7 +90,6 @@ public class PasswordTextFormatterTest {
public void testDeleteAllText() { public void testDeleteAllText() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
field.deleteText(0, field.getText().length()); field.deleteText(0, field.getText().length());
@ -109,7 +101,6 @@ public class PasswordTextFormatterTest {
public void testSetTextToNull() { public void testSetTextToNull() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
field.setText(null); field.setText(null);
@ -121,7 +112,6 @@ public class PasswordTextFormatterTest {
public void testReplaceSelection() { public void testReplaceSelection() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
field.selectRange(1, field.getText().length()); field.selectRange(1, field.getText().length());
@ -134,7 +124,6 @@ public class PasswordTextFormatterTest {
public void testReplaceAll() { public void testReplaceAll() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123"); field.setText("123");
field.selectRange(0, field.getText().length()); field.selectRange(0, field.getText().length());
@ -147,7 +136,6 @@ public class PasswordTextFormatterTest {
public void testCanContainBullets() { public void testCanContainBullets() {
var field = new TextField(); var field = new TextField();
var fmt = PasswordTextFormatter.create(field, '+'); var fmt = PasswordTextFormatter.create(field, '+');
field.setTextFormatter(fmt);
field.setText("123++"); field.setText("123++");
assertEquals("+".repeat(5), field.getText()); assertEquals("+".repeat(5), field.getText());

@ -236,7 +236,7 @@ class Sidebar extends StackPane {
navLink(CheckBoxPage.NAME, CheckBoxPage.class), navLink(CheckBoxPage.NAME, CheckBoxPage.class),
navLink(ColorPickerPage.NAME, ColorPickerPage.class), navLink(ColorPickerPage.NAME, ColorPickerPage.class),
navLink(ComboBoxPage.NAME, ComboBoxPage.class, "ChoiceBox"), navLink(ComboBoxPage.NAME, ComboBoxPage.class, "ChoiceBox"),
navLink(CustomTextFieldPage.NAME, CustomTextFieldPage.class, "MaskTextField"), navLink(CustomTextFieldPage.NAME, CustomTextFieldPage.class, "MaskTextField", "PasswordTextField"),
navLink(DatePickerPage.NAME, DatePickerPage.class), navLink(DatePickerPage.NAME, DatePickerPage.class),
navLink(DialogPage.NAME, DialogPage.class), navLink(DialogPage.NAME, DialogPage.class),
navLink(HtmlEditorPage.NAME, HtmlEditorPage.class), navLink(HtmlEditorPage.NAME, HtmlEditorPage.class),

@ -8,7 +8,7 @@ import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
import atlantafx.base.controls.CustomTextField; import atlantafx.base.controls.CustomTextField;
import atlantafx.base.controls.MaskTextField; import atlantafx.base.controls.MaskTextField;
import atlantafx.base.util.PasswordTextFormatter; import atlantafx.base.controls.PasswordTextField;
import atlantafx.sampler.page.AbstractPage; import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.page.SampleBlock; import atlantafx.sampler.page.SampleBlock;
import java.time.LocalTime; import java.time.LocalTime;
@ -89,20 +89,17 @@ public class CustomTextFieldPage extends AbstractPage {
} }
private SampleBlock passwordSample() { private SampleBlock passwordSample() {
var tf = new CustomTextField("qwerty"); var tf = new PasswordTextField("qwerty");
tf.setPrefWidth(PREF_WIDTH); tf.setPrefWidth(PREF_WIDTH);
var passwordFormatter = PasswordTextFormatter.create(tf);
tf.setTextFormatter(passwordFormatter);
var icon = new FontIcon(Feather.EYE_OFF); var icon = new FontIcon(Feather.EYE_OFF);
icon.setCursor(Cursor.HAND); icon.setCursor(Cursor.HAND);
icon.setOnMouseClicked(e -> { icon.setOnMouseClicked(e -> {
if (passwordFormatter.revealPasswordProperty().get()) { if (tf.revealPasswordProperty().get()) {
passwordFormatter.revealPasswordProperty().set(false); tf.revealPasswordProperty().set(false);
icon.setIconCode(Feather.EYE_OFF); icon.setIconCode(Feather.EYE_OFF);
} else { } else {
passwordFormatter.revealPasswordProperty().set(true); tf.revealPasswordProperty().set(true);
icon.setIconCode(Feather.EYE); icon.setIconCode(Feather.EYE);
} }
}); });