Write a Javadoc for all controls, layouts and utils

This commit is contained in:
mkpaz 2023-05-30 21:01:20 +04:00
parent 750ed00e11
commit 357f31da64
55 changed files with 1422 additions and 872 deletions

@ -1,24 +1,59 @@
/* SPDX-License-Identifier: MIT */
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package atlantafx.base.controls;
import javafx.scene.control.Control;
import javafx.scene.control.SkinBase;
public abstract class BehaviorBase<C extends Control, S extends SkinBase<C>> {
/**
* Encapsulates behavior interaction logic for a skin. The main functionality
* in BehaviorBase revolves around infrastructure for resolving events into
* function calls. A BehaviorBase implementation will usually contain logic for
* handling key events based on the host platform, as well as view-specific
* functions for handling mouse and key and other input events.
*
* <p>Although BehaviorBase is typically used as a base class, it is not abstract and
* several skins instantiate an instance of BehaviorBase directly.
*
* <p>Note: This is an excerpt of the private Behavior API from the JavaFX codebase.
* It serves as a compatibility layer for implementing certain controls, although it
* can also be useful for new controls.
*/
public class BehaviorBase<C extends Control, S extends SkinBase<C>> {
private C control;
private S skin;
/**
* Constructor for all BehaviorBase instances.
*
* @param control The control for which this Skin should attach to.
* @param skin The skin used by the control.
*/
protected BehaviorBase(C control, S skin) {
this.control = control;
this.skin = skin;
}
/**
* Gets the control associated with this behavior.
*
* @return The control for this behavior.
*/
public C getControl() {
return control;
}
/**
* Gets the skin associated with this behavior.
*
* @return The control for this behavior.
*/
public S getSkin() {
return skin;
}

@ -1,4 +1,9 @@
/* SPDX-License-Identifier: MIT */
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package atlantafx.base.controls;
@ -7,21 +12,48 @@ import javafx.beans.value.ObservableValue;
import javafx.scene.control.Control;
import javafx.scene.control.SkinBase;
/**
* Base implementation class for defining the visual representation of user
* interface controls that need to handle platform events and therefore can take
* advantage of using the Behavior API.
*
* <p>Note: This is an excerpt of the private Behavior API from the JavaFX codebase.
* It serves as a compatibility layer for implementing certain controls, although it
* can also be useful for new controls.
*/
public abstract class BehaviorSkinBase<C extends Control, B extends BehaviorBase<C, ?>> extends SkinBase<C> {
protected B behavior;
/**
* Constructor for all BehaviorSkinBase instances.
*
* @param control The control for which this Skin should attach to.
*/
protected BehaviorSkinBase(C control) {
super(control);
behavior = createDefaultBehavior();
}
/**
* An abstract method for creating the behavior instance to be used by this skin.
*/
public abstract B createDefaultBehavior();
/**
* Gets the control associated with this skin.
*
* @return The control for this Skin.
*/
public C getControl() {
return getSkinnable();
}
/**
* Gets the behavior associated with this skin.
*
* @return The behavior for this skin.
*/
public B getBehavior() {
return behavior;
}

@ -47,14 +47,21 @@ import javafx.scene.control.Skin;
import javafx.scene.control.TreeItem;
import javafx.scene.layout.Region;
import javafx.util.Callback;
import org.jetbrains.annotations.Nullable;
/**
* Represents a bread crumb bar. This control is useful to visualize and navigate
* A bread crumb bar. This control is useful to visualize and navigate
* a hierarchical path structure, such as file systems.
*
* <p>A breadcrumbs consist of two types of elements: a button (default is a hyperlink)
* and a divider (default is for Label). You can customize both by providing the
* corresponding control factory.
* <pre>{@code
* String[] list = {"Root", "Folder", "file.txt"};
* BreadCrumbItem<String> selectedCrumb = Breadcrumbs.buildTreeModel(list);
* Breadcrumbs<String> breadcrumbs = new Breadcrumbs<>(selectedCrumb);
* }</pre>
*
* <p>A breadcrumbs consist of two types of elements: a button (default is
* {@code Hyperlink}) and a divider (default is for {@code Label}). You can
* customize both by providing the corresponding factory.
*/
@SuppressWarnings("unused")
public class Breadcrumbs<T> extends Control {
@ -63,6 +70,7 @@ public class Breadcrumbs<T> extends Control {
protected final Callback<BreadCrumbItem<T>, ButtonBase> defaultCrumbNodeFactory =
item -> new Hyperlink(item.getStringValue());
protected final Callback<BreadCrumbItem<T>, ? extends Node> defaultDividerFactory =
item -> item != null && !item.isLast() ? new Label("/") : null;
@ -74,10 +82,12 @@ public class Breadcrumbs<T> extends Control {
}
/**
* Creates a bread crumb bar with the given BreadCrumbItem as the currently
* selected crumb.
* Creates a bread crumb bar with the given BreadCrumbItem as the
* currently {@link #selectedCrumbProperty()}.
*
* @param selectedCrumb The currently selected crumb.
*/
public Breadcrumbs(BreadCrumbItem<T> selectedCrumb) {
public Breadcrumbs(@Nullable BreadCrumbItem<T> selectedCrumb) {
getStyleClass().add(DEFAULT_STYLE_CLASS);
// breadcrumbs should be the size of its content
@ -100,8 +110,10 @@ public class Breadcrumbs<T> extends Control {
}
/**
* Construct a tree model from the flat list which then can be set
* as selectedCrumb node to be shown.
* Constructs a tree model from the flat list which then can be set
* as the {@code selectedCrumb} node to be shown.
*
* @param crumbs The flat list of values used to build the tree model
*/
@SafeVarargs
public static <T> BreadCrumbItem<T> buildTreeModel(T... crumbs) {
@ -128,7 +140,8 @@ public class Breadcrumbs<T> extends Control {
* <p>Consider the following hierarchy:
* [Root] &gt; [Folder] &gt; [SubFolder] &gt; [file.txt]
*
* <p>To show the above bread crumb bar, you have to set the [file.txt] tree-node as selected crumb.
* <p>To show the above bread crumb bar, you have to set the [file.txt]
* tree-node as selected crumb.
*/
public final ObjectProperty<BreadCrumbItem<T>> selectedCrumbProperty() {
return selectedCrumb;
@ -147,10 +160,9 @@ public class Breadcrumbs<T> extends Control {
/**
* Enables or disables auto navigation (default is enabled).
* If auto navigation is enabled, it will automatically navigate to the crumb which was
* clicked by the user.
*
* @return a {@link BooleanProperty}
* If auto navigation is enabled, it will automatically navigate to the
* crumb which was clicked by the user, meaning it will set the
* {@link #selectedCrumbProperty()} upon click.
*/
public final BooleanProperty autoNavigationEnabledProperty() {
return autoNavigation;
@ -168,14 +180,15 @@ public class Breadcrumbs<T> extends Control {
}
/**
* Crumb factory is used to create custom bread crumb instances.
* <code>null</code> is not allowed and will result in a fallback to the default factory.
* The crumb factory is used to create custom bread crumb instances.
* A null value is not allowed and will result in a fallback to the default factory.
* <ul>
* <li><code>BreadCrumbItem&lt;T&gt;</code> specifies the tree item for creating bread crumb.</li>
* <li><code>ButtonBase</code> stands for resulting bread crumb node.</li>
* </ul>
*
* <p><code>BreadCrumbItem&lt;T&gt;</code> specifies the tree item for creating bread crumb. Use
* {@link BreadCrumbItem#isFirst()} and {@link BreadCrumbItem#isLast()} to create bread crumb
* depending on item position.
*
* <p><code>ButtonBase</code> stands for resulting bread crumb node. It CAN NOT be <code>null</code>.
* <p>Use {@link BreadCrumbItem#isFirst()} and {@link BreadCrumbItem#isLast()} to
* create bread crumb depending on item position.
*/
public final ObjectProperty<Callback<BreadCrumbItem<T>, ButtonBase>> crumbFactoryProperty() {
return crumbFactory;
@ -196,16 +209,19 @@ public class Breadcrumbs<T> extends Control {
}
/**
* Divider factory is used to create custom divider instances.
* <code>null</code> is not allowed and will result in a fallback to the default factory.
* The divider factory is used to create custom instances of dividers.
* A null value is not allowed and will result in a fallback to the default factory.
*
* <p><code>BreadCrumbItem&lt;T&gt;</code> specifies the preceding tree item. It can be null, because this way
* you can insert divider before the first bread crumb, which can be used e.g. for creating a Unix path.
* Use {@link BreadCrumbItem#isFirst()} and {@link BreadCrumbItem#isLast()} to create divider
* depending on item position.
* <ul>
* <li><code>BreadCrumbItem&lt;T&gt;</code> specifies the preceding tree item.
* It can be null, which allows for inserting a divider before the first bread crumb,
* such as when creating a Unix path.</li>
* <li><code>? extends Node</code> stands for resulting divider node. It can also be null,
* indicating that there will be no divider inserted after the specified bread crumb.</li>
* </ul>
*
* <p><code>? extends Node</code> stands for resulting divider node. It CAN be <code>null</code>, which
* means there will be no divider inserted after the specified bread crumb.
* <p>Use {@link BreadCrumbItem#isFirst()} and {@link BreadCrumbItem#isLast()} to
* create bread crumb depending on item position.
*/
public final ObjectProperty<Callback<BreadCrumbItem<T>, ? extends Node>> dividerFactoryProperty() {
return dividerFactory;
@ -226,23 +242,12 @@ public class Breadcrumbs<T> extends Control {
}
/**
* The EventHandler is called when a user selects a bread crumb.
* Represents the EventHandler that is called when a user selects a bread crumb.
*/
public final ObjectProperty<EventHandler<BreadCrumbActionEvent<T>>> onCrumbActionProperty() {
return onCrumbAction;
}
/**
* Set a new EventHandler for when a user selects a crumb.
*/
public final void setOnCrumbAction(EventHandler<BreadCrumbActionEvent<T>> value) {
onCrumbActionProperty().set(value);
}
public final EventHandler<BreadCrumbActionEvent<T>> getOnCrumbAction() {
return onCrumbActionProperty().get();
}
protected final ObjectProperty<EventHandler<BreadCrumbActionEvent<T>>> onCrumbAction = new ObjectPropertyBase<>() {
@SuppressWarnings({ "unchecked", "rawtypes" })
@ -262,48 +267,79 @@ public class Breadcrumbs<T> extends Control {
}
};
public final void setOnCrumbAction(EventHandler<BreadCrumbActionEvent<T>> value) {
onCrumbActionProperty().set(value);
}
public final EventHandler<BreadCrumbActionEvent<T>> getOnCrumbAction() {
return onCrumbActionProperty().get();
}
///////////////////////////////////////////////////////////////////////////
/**
* {@code BreadCrumbItem} extends {@link TreeItem}, providing support
* for navigating hierarchical structures.
*
* @param <T> The type of the value property within BreadCrumbItem.
*/
public static class BreadCrumbItem<T> extends TreeItem<T> {
// setters must not be exposed to user
private boolean first;
private boolean last;
protected boolean first;
protected boolean last;
public BreadCrumbItem(T value) {
/**
* Creates a BreadCrumbItem with the value property set to the provided object.
*
* @param value The object to be stored as the value of this BreadCrumbItem.
*/
public BreadCrumbItem(@Nullable T value) {
super(value);
}
/**
* Use this method to determine if this item is at the beginning,
* so you can create bread crumbs accordingly.
*/
public boolean isFirst() {
return first;
}
void setFirst(boolean first) {
this.first = first;
}
/**
* Use this method to determine if this item is at the end,
* so you can create breadcrumbs accordingly.
*/
public boolean isLast() {
return last;
}
void setLast(boolean last) {
///////////////////////////////////////////////////
// package private //
///////////////////////////////////////////////////
protected void setFirst(boolean first) {
this.first = first;
}
protected void setLast(boolean last) {
this.last = last;
}
String getStringValue() {
protected String getStringValue() {
return getValue() != null ? getValue().toString() : "";
}
}
/**
* Represents an Event which is fired when a bread crumb was activated.
* An {@code Event} which is fired when a bread crumb was activated.
*/
public static class BreadCrumbActionEvent<T> extends Event {
/**
* The event type that should be listened to by people interested in
* knowing when the {@link Breadcrumbs#selectedCrumbProperty() selected crumb}
* has changed.
* Represents the event type that should be listened to by people who are
* interested in knowing when the selected crumb has changed. This is accomplished
* through listening to the {@link Breadcrumbs#selectedCrumbProperty() selected crumb
* property}.
*/
public static final EventType<BreadCrumbActionEvent<?>> CRUMB_ACTION
= new EventType<>("CRUMB_ACTION" + UUID.randomUUID());
@ -312,6 +348,8 @@ public class Breadcrumbs<T> extends Control {
/**
* Creates a new event that can subsequently be fired.
*
* @param selectedCrumb The currently selected crumb.
*/
public BreadCrumbActionEvent(BreadCrumbItem<T> selectedCrumb) {
super(CRUMB_ACTION);

@ -41,17 +41,23 @@ import javafx.scene.control.ButtonBase;
import javafx.scene.control.SkinBase;
import javafx.scene.control.TreeItem.TreeModificationEvent;
/**
* The default skin for the {@link Breadcrumbs} control.
*/
public class BreadcrumbsSkin<T> extends SkinBase<Breadcrumbs<T>> {
protected static final PseudoClass FIRST = PseudoClass.getPseudoClass("first");
protected static final PseudoClass LAST = PseudoClass.getPseudoClass("last");
protected final EventHandler<TreeModificationEvent<Object>> treeChildrenModifiedHandler = e -> updateBreadCrumbs();
protected final EventHandler<TreeModificationEvent<Object>> treeChildrenModifiedHandler =
e -> updateBreadCrumbs();
public BreadcrumbsSkin(final Breadcrumbs<T> control) {
super(control);
control.selectedCrumbProperty().addListener((obs, old, val) -> updateSelectedPath(old, val));
control.selectedCrumbProperty().addListener(
(obs, old, val) -> updateSelectedPath(old, val)
);
updateSelectedPath(getSkinnable().selectedCrumbProperty().get(), null);
}

@ -51,22 +51,23 @@ import javafx.scene.control.Control;
import javafx.scene.control.DateCell;
import javafx.scene.control.Skin;
import javafx.util.Callback;
import org.jetbrains.annotations.Nullable;
/**
* The DatePicker control allows the user to select a date. The calendar is based on either
* The Calendar control allows the user to select a date. The calendar is based on either
* the standard ISO-8601 chronology or any of the other chronology classes defined in the
* java.time.chrono package.
* <code>java.time.chrono</code> package.
*
* <p>The {@link #valueProperty() value} property represents the currently selected
* {@link LocalDate}. The default value is null.
*
* <p>The {@link #chronologyProperty() chronology} property specifies a calendar system to be used
* for parsing, displaying, and choosing dates.
*
* <p>The {@link #valueProperty() value} property is always defined in the ISO calendar system,
* <ul>
* <li>The {@link #valueProperty() value} property represents the currently selected
* {@link LocalDate}. The default value is null.</li>
* <li>The {@link #chronologyProperty() chronology} property specifies a calendar system to be used
* for parsing, displaying, and choosing dates.</li>
* <li>The {@link #valueProperty() value} property is always defined in the ISO calendar system,
* however, so applications based on a different chronology may use the conversion methods
* provided in the {@link java.time.chrono.Chronology} API to get or set the corresponding
* {@link java.time.chrono.ChronoLocalDate} value.
* {@link java.time.chrono.ChronoLocalDate} value.</li>
* </ul>
*/
public class Calendar extends Control {
@ -74,7 +75,8 @@ public class Calendar extends Control {
protected Chronology lastValidChronology = IsoChronology.INSTANCE;
/**
* Creates a default DatePicker instance with a <code>null</code> date value set.
* Creates a default Calendar instance with a <code>null</code>
* date value set.
*/
public Calendar() {
this(null);
@ -105,11 +107,12 @@ public class Calendar extends Control {
}
/**
* Creates a DatePicker instance and sets the {@link #valueProperty() value} to the given date.
* Creates a Calendar instance and sets the {@link #valueProperty() value}
* to the specified date.
*
* @param localDate to be set as the currently selected date in the DatePicker. Can be null.
* @param localDate The date to be set as the currently selected date in the Calendar.
*/
public Calendar(LocalDate localDate) {
public Calendar(@Nullable LocalDate localDate) {
setValue(localDate);
getStyleClass().add(DEFAULT_STYLE_CLASS);
}
@ -126,6 +129,13 @@ public class Calendar extends Control {
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the currently selected {@link LocalDate}. The default value is null.
*/
public ObjectProperty<LocalDate> valueProperty() {
return value;
}
private final ObjectProperty<LocalDate> value = new SimpleObjectProperty<>(this, "value");
public final LocalDate getValue() {
@ -136,10 +146,6 @@ public class Calendar extends Control {
valueProperty().set(value);
}
public ObjectProperty<LocalDate> valueProperty() {
return value;
}
/**
* A custom cell factory can be provided to customize individual day cells
* Refer to {@link DateCell} and {@link Cell} for more information on cell factories.
@ -162,8 +168,8 @@ public class Calendar extends Control {
}
/**
* The calendar system used for parsing, displaying, and choosing dates in the DatePicker
* control.
* The calendar system used for parsing, displaying, and choosing dates in the
* Calendar control.
*
* <p>The default is usually {@link IsoChronology} unless provided explicitly
* in the {@link Locale} by use of a Locale calendar extension.
@ -176,7 +182,8 @@ public class Calendar extends Control {
return chronology;
}
private final ObjectProperty<Chronology> chronology = new SimpleObjectProperty<>(this, "chronology", null);
private final ObjectProperty<Chronology> chronology
= new SimpleObjectProperty<>(this, "chronology", null);
@SuppressWarnings("CatchAndPrintStackTrace")
public final Chronology getChronology() {
@ -199,7 +206,7 @@ public class Calendar extends Control {
}
/**
* Whether the DatePicker popup should display a column showing week numbers.
* Whether the Calendar should display a column showing week numbers.
*
* <p>The default value is specified in a resource bundle, and depends on the country of the
* current locale.
@ -238,7 +245,15 @@ public class Calendar extends Control {
return showWeekNumbersProperty().getValue();
}
private final ObjectProperty<Node> topNode = new SimpleObjectProperty<>(this, "topNode", null);
/**
* Represents the custom node to be placed at the top of the Calendar above the month-year area.
*/
public ObjectProperty<Node> topNodeProperty() {
return topNode;
}
private final ObjectProperty<Node> topNode
= new SimpleObjectProperty<>(this, "topNode", null);
public final void setTopNode(Node value) {
topNode.setValue(value);
@ -248,11 +263,15 @@ public class Calendar extends Control {
return topNode.getValue();
}
public ObjectProperty<Node> topNodeProperty() {
return topNode;
/**
* Represents the custom node to be placed at the bottom of the Calendar below the day-cell grid.
*/
public ObjectProperty<Node> bottomNodeProperty() {
return bottomNode;
}
private final ObjectProperty<Node> bottomNode = new SimpleObjectProperty<>(this, "bottomNode", null);
private final ObjectProperty<Node> bottomNode
= new SimpleObjectProperty<>(this, "bottomNode", null);
public final void setBottomNode(Node value) {
bottomNode.setValue(value);
@ -262,10 +281,6 @@ public class Calendar extends Control {
return bottomNode.getValue();
}
public ObjectProperty<Node> bottomNodeProperty() {
return bottomNode;
}
///////////////////////////////////////////////////////////////////////////
// Stylesheet Handling //
///////////////////////////////////////////////////////////////////////////

@ -12,6 +12,9 @@ import java.time.ZoneId;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
/**
* The default behavior for the {@link Calendar} control.
*/
public class CalendarBehavior extends BehaviorBase<Calendar, CalendarSkin> {
public CalendarBehavior(Calendar control, CalendarSkin skin) {

@ -73,6 +73,9 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
/**
* The default skin for the {@link Calendar} control.
*/
public class CalendarSkin extends BehaviorSkinBase<Calendar, CalendarBehavior> {
// formatters
@ -100,7 +103,8 @@ public class CalendarSkin extends BehaviorSkinBase<Calendar, CalendarBehavior> {
protected DateCell lastFocusedDayCell = null;
protected final int daysPerWeek = getDaysPerWeek();
private final ObjectProperty<YearMonth> displayedYearMonth = new SimpleObjectProperty<>(this, "displayedYearMonth");
private final ObjectProperty<YearMonth> displayedYearMonth
= new SimpleObjectProperty<>(this, "displayedYearMonth");
public ObjectProperty<YearMonth> displayedYearMonthProperty() {
return displayedYearMonth;
@ -113,27 +117,27 @@ public class CalendarSkin extends BehaviorSkinBase<Calendar, CalendarBehavior> {
return firstDayOfMonth.get();
}
public CalendarSkin(Calendar datePicker) {
super(datePicker);
public CalendarSkin(Calendar control) {
super(control);
createUI();
registerChangeListener(datePicker.valueProperty(), e -> {
LocalDate date = datePicker.getValue();
registerChangeListener(control.valueProperty(), e -> {
LocalDate date = control.getValue();
displayedYearMonthProperty().set(
date != null ? YearMonth.from(date) : YearMonth.now(ZoneId.systemDefault())
);
updateValues();
datePicker.fireEvent(new ActionEvent());
control.fireEvent(new ActionEvent());
});
registerChangeListener(datePicker.showWeekNumbersProperty(), e -> {
registerChangeListener(control.showWeekNumbersProperty(), e -> {
updateGrid();
updateWeekNumberCells();
});
registerChangeListener(datePicker.topNodeProperty(), e -> {
Node node = datePicker.getTopNode();
registerChangeListener(control.topNodeProperty(), e -> {
Node node = control.getTopNode();
if (node == null) {
rootPane.getChildren().removeIf(c -> c.getStyleClass().contains("top-node"));
} else {
@ -144,8 +148,8 @@ public class CalendarSkin extends BehaviorSkinBase<Calendar, CalendarBehavior> {
}
});
registerChangeListener(datePicker.bottomNodeProperty(), e -> {
Node node = datePicker.getBottomNode();
registerChangeListener(control.bottomNodeProperty(), e -> {
Node node = control.getBottomNode();
if (node == null) {
rootPane.getChildren().removeIf(c -> c.getStyleClass().contains("bottom-node"));
} else {
@ -171,9 +175,7 @@ public class CalendarSkin extends BehaviorSkinBase<Calendar, CalendarBehavior> {
}
/**
* The primary chronology for display. This may be overridden to be different from the
* DatePicker chronology. For example DatePickerHijrahContent uses ISO as primary and Hijrah
* as a secondary chronology.
* The primary chronology for display.
*/
public Chronology getPrimaryChronology() {
return getControl().getChronology();

@ -2,20 +2,31 @@
package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.StringProperty;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("unused")
/**
* A MenuItem that is intended to contain a caption for a group of menu items
* that share a common purpose.
*/
public class CaptionMenuItem extends CustomMenuItem {
protected final Label title = new Label();
/**
* Creates an empty menu item.
*/
public CaptionMenuItem() {
this(null);
}
public CaptionMenuItem(String text) {
/**
* Creates a CaptionMenuItem with the specified text as the title.
*/
public CaptionMenuItem(@Nullable @NamedArg("text") String text) {
super();
setTitle(text);
@ -24,6 +35,13 @@ public class CaptionMenuItem extends CustomMenuItem {
getStyleClass().addAll("caption-menu-item");
}
/**
* Contains the title of the menu item.
*/
public StringProperty titleProperty() {
return title.textProperty();
}
public String getTitle() {
return title.getText();
}
@ -31,8 +49,4 @@ public class CaptionMenuItem extends CustomMenuItem {
public void setTitle(String text) {
title.setText(text);
}
public StringProperty titleProperty() {
return title.textProperty();
}
}

@ -9,7 +9,7 @@ import javafx.scene.control.Control;
import javafx.scene.control.Skin;
/**
* Aa versatile container that can used in various contexts such as headings,
* A versatile container that can be used in various contexts, such as headings,
* text, dialogs and more. It includes a header to provide a brief overview
* or context of the information. The sub-header and body sections provide
* more detailed content, while the footer may include additional actions or
@ -17,12 +17,17 @@ import javafx.scene.control.Skin;
*/
public class Card extends Control {
// Default constructor
/**
* Creates an empty Card.
*/
public Card() {
super();
getStyleClass().add("card");
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new CardSkin(this);
@ -33,69 +38,69 @@ public class Card extends Control {
///////////////////////////////////////////////////////////////////////////
/**
* The property representing the cards header node.
* Represents the cards header node.
*/
public ObjectProperty<Node> headerProperty() {
return header;
}
private final ObjectProperty<Node> header = new SimpleObjectProperty<>(this, "header");
public Node getHeader() {
return header.get();
}
public ObjectProperty<Node> headerProperty() {
return header;
}
public void setHeader(Node header) {
this.header.set(header);
}
/**
* The property representing the cards sub-header node.
* Represents the cards sub-header node.
*/
public final ObjectProperty<Node> subHeaderProperty() {
return subHeader;
}
private final ObjectProperty<Node> subHeader = new SimpleObjectProperty<>(this, "subHeader");
public Node getSubHeader() {
return subHeader.get();
}
public final ObjectProperty<Node> subHeaderProperty() {
return subHeader;
}
public void setSubHeader(Node subHeader) {
this.subHeader.set(subHeader);
}
/**
* The property representing the cards body node.
* Represents the cards body node.
*/
public ObjectProperty<Node> bodyProperty() {
return body;
}
private final ObjectProperty<Node> body = new SimpleObjectProperty<>(this, "body");
public Node getBody() {
return body.get();
}
public ObjectProperty<Node> bodyProperty() {
return body;
}
public void setBody(Node body) {
this.body.set(body);
}
/**
* The property representing the cards footer node.
* Represents the cards footer node.
*/
public ObjectProperty<Node> footerProperty() {
return footer;
}
private final ObjectProperty<Node> footer = new SimpleObjectProperty<>(this, "footer");
public Node getFooter() {
return footer.get();
}
public ObjectProperty<Node> footerProperty() {
return footer;
}
public void setFooter(Node footer) {
this.footer.set(footer);
}

@ -11,6 +11,9 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
/**
* The default skin for the {@link Card} control.
*/
public class CardSkin implements Skin<Card> {
protected static final PseudoClass HAS_HEADER = PseudoClass.getPseudoClass("has-header");

@ -36,79 +36,29 @@ import javafx.scene.control.Skin;
import javafx.scene.control.TextField;
/**
* A base class for people wanting to customize a {@link TextField} to contain nodes
* inside the text field itself, without being on top of the users typed-in text.
* A base class for people wanting to customize a {@link TextField}
* to contain nodes inside the text field itself, without being on top
* of the users typed-in text.
*/
@SuppressWarnings("unused")
public class CustomTextField extends TextField {
/**
* Instantiates a default CustomTextField.
* Creates an empty CustomTextField.
*/
public CustomTextField() {
getStyleClass().add("custom-text-field");
}
/**
* Creates a CustomTextField with initial text content.
*
* @param text A string for text content.
*/
public CustomTextField(String text) {
this();
setText(text);
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
private final ObjectProperty<Node> left = new SimpleObjectProperty<>(this, "left");
/**
* Returns an ObjectProperty wrapping the {@link Node} that is placed
* on the left of the text field.
*/
public final ObjectProperty<Node> leftProperty() {
return left;
}
/**
* Returns the {@link Node} that is placed on the left of the text field.
*/
public final Node getLeft() {
return left.get();
}
/**
* Sets the {@link Node} that is placed on the left of the text field.
*/
public final void setLeft(Node value) {
left.set(value);
}
private final ObjectProperty<Node> right = new SimpleObjectProperty<>(this, "right");
/**
* Property representing the {@link Node} that is placed on the right of the text field.
*/
public final ObjectProperty<Node> rightProperty() {
return right;
}
/**
* Returns the {@link Node} that is placed on the right of the text field.
*/
public final Node getRight() {
return right.get();
}
/**
* Sets the {@link Node} that is placed on the right of the text field.
*/
public final void setRight(Node value) {
right.set(value);
}
///////////////////////////////////////////////////////////////////////////
// Methods //
///////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@ -126,4 +76,54 @@ public class CustomTextField extends TextField {
}
};
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the {@link Node} that is placed on the left of the text field.
*/
public final ObjectProperty<Node> leftProperty() {
return left;
}
private final ObjectProperty<Node> left = new SimpleObjectProperty<>(this, "left");
/**
* Returns the {@link Node} that is placed on the left of the text field.
*/
public final Node getLeft() {
return left.get();
}
/**
* Sets the {@link Node} that is placed on the left of the text field.
*/
public final void setLeft(Node value) {
left.set(value);
}
/**
* Represents the {@link Node} that is placed on the right of the text field.
*/
public final ObjectProperty<Node> rightProperty() {
return right;
}
private final ObjectProperty<Node> right = new SimpleObjectProperty<>(this, "right");
/**
* Returns the {@link Node} that is placed on the right of the text field.
*/
public final Node getRight() {
return right.get();
}
/**
* Sets the {@link Node} that is placed on the right of the text field.
*/
public final void setRight(Node value) {
right.set(value);
}
}

@ -38,6 +38,9 @@ import javafx.scene.control.skin.TextFieldSkin;
import javafx.scene.layout.StackPane;
import javafx.scene.text.HitInfo;
/**
* The default skin for the {@link CustomTextField} control.
*/
public abstract class CustomTextFieldSkin extends TextFieldSkin {
private static final PseudoClass HAS_NO_SIDE_NODE = PseudoClass.getPseudoClass("no-side-nodes");

@ -29,6 +29,7 @@ package atlantafx.base.controls;
import atlantafx.base.util.MaskChar;
import atlantafx.base.util.MaskTextFormatter;
import atlantafx.base.util.SimpleMaskChar;
import java.util.List;
import java.util.Objects;
import javafx.beans.NamedArg;
@ -38,9 +39,9 @@ import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.Nullable;
/**
* This is a convenience wrapper for instantiating a {@link CustomTextField} with a
* {@code MaskTextFormatter}. For additional info refer to the {@link MaskTextFormatter}
* docs.
* A convenience wrapper for instantiating a {@link CustomTextField} with a
* {@code MaskTextFormatter}. For additional info refer to the
* {@link MaskTextFormatter} documentation.
*/
public class MaskTextField extends CustomTextField {
@ -56,15 +57,35 @@ public class MaskTextField extends CustomTextField {
protected final ReadOnlyObjectWrapper<MaskTextFormatter> formatter =
new ReadOnlyObjectWrapper<>(this, "formatter");
/**
* Creates an empty MaskTextField.
*/
public MaskTextField() {
super("");
init();
}
/**
* Creates an empty MaskTextField with the specified input mask.
*
* <p>The input mask is specified as a string that must follow the
* rules described in the {@link MaskTextFormatter} documentation.
*
* @param mask The input mask.
*/
public MaskTextField(@NamedArg("mask") String mask) {
this("", mask);
}
/**
* Creates a MaskTextField with initial text content and the specified input mask.
*
* <p>The input mask is specified as a string that must follow the
* rules described in the {@link MaskTextFormatter} documentation.
*
* @param text A string for text content.
* @param mask An input mask.
*/
private MaskTextField(@NamedArg("text") String text,
@NamedArg("mask") String mask) {
super(Objects.requireNonNullElse(text, ""));
@ -74,6 +95,15 @@ public class MaskTextField extends CustomTextField {
init();
}
/**
* Creates a MaskTextField with initial text content and the specified input mask.
*
* <p>The input mask is specified as a list of {@code MaskChar}. You can use
* the {@link SimpleMaskChar} as the default implementation.
*
* @param text A string for text content.
* @param mask An input mask.
*/
public MaskTextField(String text, List<MaskChar> mask) {
super(Objects.requireNonNullElse(text, ""));
@ -90,6 +120,18 @@ public class MaskTextField extends CustomTextField {
});
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the input mask.
*
* <p>Note that the MaskTextField allows for specifying the input mask as either a string
* or a list of {@code MaskChar}. These formats cannot be converted to one another. Therefore,
* if the input mask was specified as a list of {@code MaskChar}, this property will return
* null value.
*/
public StringProperty maskProperty() {
return mask;
}

@ -12,21 +12,24 @@ import javafx.scene.control.Skin;
import org.jetbrains.annotations.Nullable;
/**
* The Message is a component for displaying notifications or alerts
* and is specifically designed to grab the users attention.
* It is based on the Tile layout and shares its structure.
* A control for displaying banners or alerts that is specifically
* designed to grab the users attention. It is based on the {@link Tile}
* layout and shares its structure.
*/
public class Message extends TileBase {
/**
* See {@link Tile#Tile()}.
* Creates an empty Message.
*/
public Message() {
this(null, null, null);
}
/**
* See {@link Tile#Tile(String, String)}.
* Creates a new Message with an initial title and description.
*
* @param title A string for the title.
* @param description A string for the description.
*/
public Message(@Nullable @NamedArg("title") String title,
@Nullable @NamedArg("description") String description) {
@ -34,7 +37,11 @@ public class Message extends TileBase {
}
/**
* See {@link Tile#Tile(String, String, Node)}.
* Creates a new Message with an initial title, description and graphic.
*
* @param title A string for the title.
* @param description A string for the description.
* @param graphic A graphic or icon.
*/
public Message(@Nullable String title,
@Nullable String description,
@ -43,6 +50,9 @@ public class Message extends TileBase {
getStyleClass().add("message");
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new MessageSkin(this);
@ -53,27 +63,33 @@ public class Message extends TileBase {
///////////////////////////////////////////////////////////////////////////
/**
* The property representing the messages action handler. Setting an action handler
* makes the message interactive or clickable. When a user clicks on the interactive
* Represents the messages action handler. Setting an action handler makes the
* message interactive (or clickable). When a user clicks on the interactive
* message, the specified action handler will be called.
*/
public ObjectProperty<Runnable> actionHandlerProperty() {
return actionHandler;
}
private final ObjectProperty<Runnable> actionHandler = new SimpleObjectProperty<>(this, "actionHandler");
public Runnable getActionHandler() {
return actionHandler.get();
}
public ObjectProperty<Runnable> actionHandlerProperty() {
return actionHandler;
}
public void setActionHandler(Runnable actionHandler) {
this.actionHandler.set(actionHandler);
}
/**
* The property representing the user specified close handler.
* Represents the user-specified close handler, which is intended to be used to close
* or dismiss the message. When a user clicks on the message's close button, the specified
* close handler will be called.
*/
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose");
@ -81,10 +97,6 @@ public class Message extends TileBase {
return onClose.get();
}
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
public void setOnClose(EventHandler<? super Event> onClose) {
this.onClose.set(onClose);
}

@ -9,6 +9,9 @@ import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.layout.StackPane;
/**
* The default skin for the {@link Message} control.
*/
public class MessageSkin extends TileSkinBase<Message> {
protected static final PseudoClass CLOSEABLE = PseudoClass.getPseudoClass("closeable");

@ -16,53 +16,93 @@ import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.jetbrains.annotations.Nullable;
/**
* A container for displaying application dialogs ot top of the current scene
* without opening a modal {@link javafx.stage.Stage}. It's a translucent (glass) pane
* that can hold arbitrary content as well as animate its appearance.<br/><br/>
* without opening a modal {@link Stage}. It's a translucent (glass) pane
* that can hold arbitrary content as well as animate its appearance.
*
* <p>When {@link #displayProperty()} value is changed the modal pane modifies own
* {@link #viewOrderProperty()} value accordingly, thus moving itself on top of the
* parent container or vise versa. You can change the target view order value via the
* constructor param. This also means that one must not change modal pane {@link #viewOrderProperty()}
* property manually.
* constructor param. This also means that one <b>must not</b> change the modal pane
* {@link #viewOrderProperty()} property manually.
*
* <p>Example:
*
* <pre>{@code
* ModalPane modalPane = new ModalPane();
*
* Label content = new Label("Content");
* content.setSize(450, 450);
*
* Button openBtn = new Button("Open");
* openBtn.setOnAction(evt -> modalPane.show(content));
* }</pre>
*/
public class ModalPane extends Control {
protected static final int Z_FRONT = -10;
protected static final int Z_BACK = 10;
/**
* The default value that is set to the modal pane
* when it must be on top of other nodes.
*/
public static final int Z_FRONT = -10;
/**
* The default value that is set to the modal pane
* when it must be below of other nodes.
*/
public static final int Z_BACK = 10;
/**
* The default animation duration that is applied to the modal content
* when it enters the user view.
*/
public static final Duration DEFAULT_DURATION_IN = Duration.millis(200);
/**
* The default animation duration that is applied to the modal content
* when it leaves the user view.
*/
public static final Duration DEFAULT_DURATION_OUT = Duration.millis(100);
private final int topViewOrder;
/**
* See {@link #ModalPane(int)}.
* Creates a new modal pane with the default {@code topViewOrder}
* property value.
*/
public ModalPane() {
this(Z_FRONT);
}
/**
* Creates a new modal pane.
* Creates a new modal pane with the specified {@code topViewOrder} property.
*
* @param topViewOrder the {@link #viewOrderProperty()} value to be set
* to display the modal pane on top of the parent container.
* @param topViewOrder The {@link #viewOrderProperty()} value to be set in order
* to display the ModalPane on top of the parent container.
*/
public ModalPane(@NamedArg("topViewOrder") int topViewOrder) {
super();
this.topViewOrder = topViewOrder;
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new ModalPaneSkin(this);
}
/**
* Returns the value of {@link #viewOrderProperty()} to be set in order to display
* the ModalPane on top of its parent container. This is a constructor parameter
* that cannot be changed after the ModalPane has been instantiated.
*/
public int getTopViewOrder() {
return topViewOrder;
}
@ -71,6 +111,13 @@ public class ModalPane extends Control {
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Specifies the content node to display inside the modal pane.
*/
public ObjectProperty<Node> contentProperty() {
return content;
}
protected final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content", null);
public Node getContent() {
@ -82,14 +129,14 @@ public class ModalPane extends Control {
}
/**
* The content node to display inside the modal pane.
* Indicates whether the modal pane is set to be on top or not.
* When changed, the {@link #viewOrderProperty()} value will be modified accordingly.
* See the {@link #getTopViewOrder()}.
*/
public ObjectProperty<Node> contentProperty() {
return content;
public BooleanProperty displayProperty() {
return display;
}
// ~
protected final BooleanProperty display = new SimpleBooleanProperty(this, "display", false);
public boolean isDisplay() {
@ -101,15 +148,12 @@ public class ModalPane extends Control {
}
/**
* Whether the modal pane is set to top or not.
* When changed the pane {@link #viewOrderProperty()} value will be modified accordingly.
* Specifies the alignment of the content node.
*/
public BooleanProperty displayProperty() {
return display;
public ObjectProperty<Pos> alignmentProperty() {
return alignment;
}
// ~
protected final ObjectProperty<Pos> alignment = new SimpleObjectProperty<>(this, "alignment", Pos.CENTER);
public Pos getAlignment() {
@ -121,14 +165,13 @@ public class ModalPane extends Control {
}
/**
* Content alignment.
* The factory that provides a transition to be played on content appearance,
* i.e. when {@link #displayProperty()} is set to 'true'.
*/
public ObjectProperty<Pos> alignmentProperty() {
return alignment;
public ObjectProperty<Function<Node, Animation>> inTransitionFactoryProperty() {
return inTransitionFactory;
}
// ~
protected final ObjectProperty<Function<Node, Animation>> inTransitionFactory = new SimpleObjectProperty<>(
this, "inTransitionFactory", node -> Animations.zoomIn(node, DEFAULT_DURATION_IN)
);
@ -142,15 +185,13 @@ public class ModalPane extends Control {
}
/**
* The factory that provides a transition to be played on content appearance,
* i.e. when {@link #displayProperty()} is set to 'true'.
* The factory that provides a transition to be played on content disappearance,
* i.e. when {@link #displayProperty()} is set to 'false'.
*/
public ObjectProperty<Function<Node, Animation>> inTransitionFactoryProperty() {
return inTransitionFactory;
public ObjectProperty<Function<Node, Animation>> outTransitionFactoryProperty() {
return outTransitionFactory;
}
// ~
protected final ObjectProperty<Function<Node, Animation>> outTransitionFactory = new SimpleObjectProperty<>(
this, "outTransitionFactory", node -> Animations.zoomOut(node, DEFAULT_DURATION_OUT)
);
@ -164,15 +205,16 @@ public class ModalPane extends Control {
}
/**
* The factory that provides a transition to be played on content disappearance,
* i.e. when {@link #displayProperty()} is set to 'false'.
* Specifies whether the content should be treated as persistent or not.
*
* <p>By default, the modal pane exits when the ESC button is pressed or when
* the mouse is clicked outside the content area. This property prevents
* this behavior and plays a bouncing animation instead.
*/
public ObjectProperty<Function<Node, Animation>> outTransitionFactoryProperty() {
return outTransitionFactory;
public BooleanProperty persistentProperty() {
return persistent;
}
// ~
protected final BooleanProperty persistent = new SimpleBooleanProperty(this, "persistent", false);
public boolean getPersistent() {
@ -183,23 +225,13 @@ public class ModalPane extends Control {
this.persistent.set(persistent);
}
/**
* Specifies whether content should be treated as persistent or not.
* By default, the modal pane exits when the ESC button is pressed or when
* the mouse is clicked outside the content area. This property prevents
* this behavior and plays bouncing animation instead.
*/
public BooleanProperty persistentProperty() {
return persistent;
}
///////////////////////////////////////////////////////////////////////////
// Public API //
///////////////////////////////////////////////////////////////////////////
/**
* A convenience method for setting the content of the modal pane content
* and triggering display state at the same time.
* and triggering its display state at the same time.
*/
public void show(Node node) {
// calling show method with no content specified doesn't make any sense
@ -208,6 +240,17 @@ public class ModalPane extends Control {
setDisplay(true);
}
/**
* A convenience method for clearing the content of the modal pane content
* and triggering its display state at the same time.
*/
public void hide(boolean clear) {
setDisplay(false);
if (clear) {
setContent(null);
}
}
/**
* See {@link #hide(boolean)}.
*/
@ -216,24 +259,16 @@ public class ModalPane extends Control {
}
/**
* A convenience method for clearing the content of the modal pane content
* and triggering display state at the same time.
* See {@link #usePredefinedTransitionFactories(Side, Duration, Duration)}.
*/
public void hide(boolean clear) {
setDisplay(false);
if (clear) {
setContent(null);
}
public void usePredefinedTransitionFactories(@Nullable Side side) {
usePredefinedTransitionFactories(side, DEFAULT_DURATION_IN, DEFAULT_DURATION_OUT);
}
/**
* Sets the predefined factory for both {@link #inTransitionFactoryProperty()} and
* {@link #outTransitionFactoryProperty()} based on content position.
*/
public void usePredefinedTransitionFactories(@Nullable Side side) {
usePredefinedTransitionFactories(side, DEFAULT_DURATION_IN, DEFAULT_DURATION_OUT);
}
public void usePredefinedTransitionFactories(@Nullable Side side,
@Nullable Duration inDuration,
@Nullable Duration outDuration) {

@ -22,6 +22,9 @@ import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import org.jetbrains.annotations.Nullable;
/**
* The default skin for the {@link ModalPane} control.
*/
public class ModalPaneSkin extends SkinBase<ModalPane> {
protected ModalPane control;

@ -15,6 +15,7 @@ import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.Control;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Skin;
@ -22,23 +23,34 @@ import javafx.scene.layout.Region;
import org.jetbrains.annotations.Nullable;
/**
* The Notification control is intended for displaying alerts and messages
* to users as pop-ups. It's customizable with different colors and icons,
* can contain a graphic or image, along with the message and additional actions
* for users to take. The purpose of this control is to immediately notify users
* of significant events and provide them with quick access to related actions without
* interrupting their workflow.
* A control that is intended for displaying notifications to users as pop-ups.
* It is customizable with different colors and icons, can contain a graphic or image,
* along with the message and additional actions for users to take.
*/
public class Notification extends Control {
/**
* Creates an empty Notification.
*/
public Notification() {
this(null, null);
}
/**
* Creates a Notification with initial message text.
*
* @param message A string for the notification message.
*/
public Notification(@Nullable @NamedArg("message") String message) {
this(message, null);
}
/**
* Creates a Notification with initial message text and graphic.
*
* @param message A string for the notification message.
* @param graphic A graphic or icon.
*/
public Notification(@Nullable @NamedArg("message") String message,
@Nullable @NamedArg("graphic") Node graphic) {
super();
@ -66,16 +78,16 @@ public class Notification extends Control {
* Represents an optional graphical component that can be displayed alongside
* the notification message.
*/
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic");
public Node getGraphic() {
return graphic.get();
}
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
public void setGraphic(Node graphic) {
this.graphic.set(graphic);
}
@ -84,16 +96,16 @@ public class Notification extends Control {
* Stores a short text message that will be displayed to users when the
* notification appears. This property doesn't support the formatted text.
*/
public StringProperty messageProperty() {
return message;
}
private final StringProperty message = new SimpleStringProperty(this, "message");
public String getMessage() {
return message.get();
}
public StringProperty messageProperty() {
return message;
}
public void setMessage(String message) {
this.message.set(message);
}
@ -102,8 +114,14 @@ public class Notification extends Control {
* Specifies the primary actions associated with this notification.
*
* <p>This property is used to store one or more action buttons that will
* be displayed at the bottom of the notification when it appears.
* be displayed at the bottom of the notification when it appears. These
* buttons will be placed inside the {@link ButtonBar} and use the alignment
* that is described in the ButtonBar documentation.
*/
public ReadOnlyObjectProperty<ObservableList<Button>> primaryActionsProperty() {
return primaryActions.getReadOnlyProperty();
}
private final ReadOnlyObjectWrapper<ObservableList<Button>> primaryActions =
new ReadOnlyObjectWrapper<>(this, "primaryActions", FXCollections.observableArrayList());
@ -111,10 +129,6 @@ public class Notification extends Control {
return primaryActions.get();
}
public ReadOnlyObjectProperty<ObservableList<Button>> primaryActionsProperty() {
return primaryActions.getReadOnlyProperty();
}
public void setPrimaryActions(ObservableList<Button> buttons) {
this.primaryActions.set(buttons);
}
@ -128,7 +142,13 @@ public class Notification extends Control {
*
* <p>This property is used to store one or more menu items that will be displayed
* as a dropdown menu at the top corner of the notification when it appears.
*
* <p>The dropdown menu button will not appear if the list is empty.
*/
public ReadOnlyObjectProperty<ObservableList<MenuItem>> secondaryActionsProperty() {
return secondaryActions.getReadOnlyProperty();
}
private final ReadOnlyObjectWrapper<ObservableList<MenuItem>> secondaryActions =
new ReadOnlyObjectWrapper<>(this, "secondaryActions", FXCollections.observableArrayList());
@ -136,10 +156,6 @@ public class Notification extends Control {
return secondaryActions.get();
}
public ReadOnlyObjectProperty<ObservableList<MenuItem>> secondaryActionsProperty() {
return secondaryActions.getReadOnlyProperty();
}
public void setSecondaryActions(ObservableList<MenuItem> items) {
this.secondaryActions.set(items);
}
@ -150,7 +166,13 @@ public class Notification extends Control {
/**
* Specifies the close handler used to dismiss this notification.
*
* <p>The close button will not appear if the handler is not set for it.
*/
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose");
@ -158,10 +180,6 @@ public class Notification extends Control {
return onClose.get();
}
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
public void setOnClose(EventHandler<? super Event> onClose) {
this.onClose.set(onClose);
}

@ -19,6 +19,9 @@ import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
/**
* The default skin for the {@link Notification} control.
*/
public class NotificationSkin extends SkinBase<Notification> {
protected final VBox container = new VBox();

@ -34,29 +34,47 @@ 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.
* A convenience wrapper for instantiating a {@link CustomTextField}
* with a {@code PasswordTextFormatter}. For additional info refer to the
* {@link PasswordTextFormatter} documentation.
*/
public class PasswordTextField extends CustomTextField {
protected final ReadOnlyObjectWrapper<PasswordTextFormatter> formatter
= new ReadOnlyObjectWrapper<>(this, "formatter");
/**
* Creates an empty PasswordTextField.
*/
public PasswordTextField() {
this("", PasswordTextFormatter.BULLET);
}
/**
* Creates a PasswordTextField with initial text content.
*
* @param text A string for text content.
*/
public PasswordTextField(@NamedArg("text") String text) {
this(text, PasswordTextFormatter.BULLET);
}
/**
* Creates a PasswordTextField with initial text content and bullet character.
*
* @param text A string for text content.
* @param bullet A bullet character to mask the password string.
*/
protected PasswordTextField(@NamedArg("text") String text,
@NamedArg("bullet") char bullet) {
super(text);
formatter.set(PasswordTextFormatter.create(this, bullet));
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* See {@link PasswordTextFormatter#passwordProperty()}.
*/

@ -62,29 +62,38 @@ import javafx.stage.WindowEvent;
import javafx.util.Duration;
/**
* The Popover control provides detailed information about an owning node in a
* popup window. The popup window has a very lightweight appearance (no default
* window decorations) and an arrow pointing at the owner. Due to the nature of
* popup windows the Popover will move around with the parent window when the
* user drags it.
* A control that is intended to provide detailed information about
* an owning node in a popup window. The popup window has a lightweight
* appearance (no default window decorations) and an arrow pointing at the owner.
* Due to the nature of popup windows the Popover will move around with the parent
* window when the user drags it.
*
* <p>The Popover can be detached from the owning node by dragging it away from the
* owner. It stops displaying an arrow and starts displaying a title and a close
* icon.
*
* <p>Example
*
* <pre>{@code
* var textFlow = new TextFlow(new Text("Some content"));
* textFlow.setPrefWidth(300);
*
* var popover = new Popover(textFlow);
* popover.setTitle("Title");
*
* var ownerLink = new Hyperlink("Show popover");
* ownerLink.setOnAction(e -> popover.show(ownerLink));
* }</pre>
*/
@SuppressWarnings("unused")
public class Popover extends PopupControl {
private static final String DEFAULT_STYLE_CLASS = "popover";
private static final Duration DEFAULT_FADE_DURATION = Duration.seconds(.2);
private final StackPane root = new StackPane();
private double targetX;
private double targetY;
private final SimpleBooleanProperty animated = new SimpleBooleanProperty(true);
private final ObjectProperty<Duration> fadeInDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION);
private final ObjectProperty<Duration> fadeOutDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION);
/**
* Creates a popover with a label as the content node.
*/
@ -96,7 +105,7 @@ public class Popover extends PopupControl {
setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
setOnHiding(evt -> setDetached(false));
/* Create some initial content */
// create some initial content
Label label = new Label("No Content");
label.setPrefSize(200, 200);
label.setPadding(new Insets(4));
@ -124,31 +133,31 @@ public class Popover extends PopupControl {
/**
* Creates a popover with the given node as the content node.
*
* @param content The content shown by the popover
* @param content The content shown by the popover.
*/
public Popover(Node content) {
this();
setContentNode(content);
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new PopoverSkin(this);
}
private final StackPane root = new StackPane();
/**
* The root pane stores the content node of the popover. It is accessible
* via this method in order to support proper styling.
*
* <p>Example:
*
* <pre>
* <pre>{@code
* Popover popOver = new Popover();
* popOver.getRoot().getStylesheets().add(...);
* </pre>
* }</pre>
*
* @return the root pane
*/
@ -156,44 +165,9 @@ public class Popover extends PopupControl {
return root;
}
private final ObjectProperty<Node> contentNode = new SimpleObjectProperty<>(this, "contentNode") {
@Override
public void setValue(Node node) {
if (node == null) {
throw new NullPointerException("Node cannot be null!");
}
this.set(node);
}
};
/**
* Returns the content shown by the popover.
*
* @return the content node property
*/
public final ObjectProperty<Node> contentNodeProperty() {
return contentNode;
}
/**
* Returns the value of the content property.
*
* @return the content node
* @see #contentNodeProperty()
*/
public final Node getContentNode() {
return contentNodeProperty().get();
}
/**
* Sets the value of the content property.
*
* @param content the new content node value
* @see #contentNodeProperty()
*/
public final void setContentNode(Node content) {
contentNodeProperty().set(content);
}
///////////////////////////////////////////////////////////////////////////
// Listeners //
///////////////////////////////////////////////////////////////////////////
private final InvalidationListener hideListener = observable -> {
if (!isDetached()) {
@ -220,10 +194,17 @@ public class Popover extends PopupControl {
private final WeakChangeListener<Number> weakYListener = new WeakChangeListener<>(yListener);
private Window ownerWindow;
private final EventHandler<WindowEvent> closePopoverOnOwnerWindowCloseLambda = event -> ownerWindowHiding();
private final EventHandler<WindowEvent> closePopoverOnOwnerWindowCloseLambda
= event -> ownerWindowHiding();
private final WeakEventHandler<WindowEvent> closePopoverOnOwnerWindowClose =
new WeakEventHandler<>(closePopoverOnOwnerWindowCloseLambda);
///////////////////////////////////////////////////////////////////////////
// API //
///////////////////////////////////////////////////////////////////////////
/**
* Shows the popover in a position relative to the edges of the given owner
* node. The position is dependent on the arrow location. If the arrow is
@ -232,7 +213,7 @@ public class Popover extends PopupControl {
* below the given owner node. The arrow will slightly overlap with the
* owner node.
*
* @param owner the owner of the popover
* @param owner The owner of the popover.
*/
public final void show(Node owner) {
show(owner, 4);
@ -245,10 +226,10 @@ public class Popover extends PopupControl {
* given owner. If the arrow points up then the popover will be placed
* below the given owner node.
*
* @param owner the owner of the popover
* @param offset if negative specifies the distance to the owner node or when
* @param owner The owner of the popover.
* @param offset If negative specifies the distance to the owner node or when
* positive specifies the number of pixels that the arrow will
* overlap with the owner node (positive values are recommended)
* overlap with the owner node (positive values are recommended).
*/
public final void show(Node owner, double offset) {
Objects.requireNonNull(owner, "Owner node cannot be null!");
@ -311,9 +292,9 @@ public class Popover extends PopupControl {
* the given owner node. The x and y coordinate will be the target location
* of the arrow of the popover and not the location of the window.
*
* @param owner the owning node
* @param x the x coordinate for the popover arrow tip
* @param y the y coordinate for the popover arrow tip
* @param owner The owning node.
* @param x The x coordinate for the popover arrow tip.
* @param y The y coordinate for the popover arrow tip.
*/
@Override
public final void show(Node owner, double x, double y) {
@ -325,17 +306,16 @@ public class Popover extends PopupControl {
* the given owner node. The x and y coordinate will be the target location
* of the arrow of the popover and not the location of the window.
*
* @param owner the owning node
* @param x the x coordinate for the popover arrow tip
* @param y the y coordinate for the popover arrow tip
* @param fadeInDuration the time it takes for the popover to be fully visible.
* @param owner The owning node.
* @param x The x coordinate for the popover arrow tip.
* @param y The y coordinate for the popover arrow tip.
* @param fadeInDuration The time it takes for the popover to be fully visible.
* This duration takes precedence over the fade-in property without setting.
*/
public final void show(Node owner, double x, double y, Duration fadeInDuration) {
/*
* Calling show() a second time without first closing the popover
* causes it to be placed at the wrong location.
*/
// Calling show() a second time without first closing the popover
// causes it to be placed at the wrong location.
if (ownerWindow != null && isShowing()) {
super.hide();
}
@ -433,8 +413,8 @@ public class Popover extends PopupControl {
/**
* Hides the popover by quickly changing its opacity to 0.
*
* @param fadeOutDuration the duration of the fade transition that is being used to
* change the opacity of the popover
* @param fadeOutDuration The duration of the fade transition that is being used to
* change the opacity of the popover.
*/
public final void hide(Duration fadeOutDuration) {
if (fadeOutDuration == null) {
@ -521,7 +501,46 @@ public class Popover extends PopupControl {
}
}
private final BooleanProperty headerAlwaysVisible = new SimpleBooleanProperty(this, "headerAlwaysVisible");
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Specifies the content shown by the popover.
*/
public final ObjectProperty<Node> contentNodeProperty() {
return contentNode;
}
private final ObjectProperty<Node> contentNode = new SimpleObjectProperty<>(this, "contentNode") {
@Override
public void setValue(Node node) {
if (node == null) {
throw new NullPointerException("Node cannot be null!");
}
this.set(node);
}
};
/**
* Returns the value of the content property.
*
* @return the content node.
* @see #contentNodeProperty()
*/
public final Node getContentNode() {
return contentNodeProperty().get();
}
/**
* Sets the value of the content property.
*
* @param content The new content node value.
* @see #contentNodeProperty()
*/
public final void setContentNode(Node content) {
contentNodeProperty().set(content);
}
/**
* Determines whether the {@link Popover} header should remain visible or not,
@ -531,10 +550,12 @@ public class Popover extends PopupControl {
return headerAlwaysVisible;
}
private final BooleanProperty headerAlwaysVisible = new SimpleBooleanProperty(this, "headerAlwaysVisible");
/**
* Sets the value of the headerAlwaysVisible property.
*
* @param visible if true, then the header is visible even while attached
* @param visible If "true", then the header is visible even while attached.
* @see #headerAlwaysVisibleProperty()
*/
public final void setHeaderAlwaysVisible(boolean visible) {
@ -544,15 +565,13 @@ public class Popover extends PopupControl {
/**
* Returns the value of the detachable property.
*
* @return true if the header is visible even while attached
* @return "true" if the header is visible even while attached
* @see #headerAlwaysVisibleProperty()
*/
public final boolean isHeaderAlwaysVisible() {
return headerAlwaysVisible.getValue();
}
private final BooleanProperty closeButtonEnabled = new SimpleBooleanProperty(this, "closeButtonEnabled", true);
/**
* Determines whether the header's close button should be available or not.
*/
@ -560,10 +579,12 @@ public class Popover extends PopupControl {
return closeButtonEnabled;
}
private final BooleanProperty closeButtonEnabled = new SimpleBooleanProperty(this, "closeButtonEnabled", true);
/**
* Sets the value of the closeButtonEnabled property.
*
* @param enabled if false, the popover will not be closeable by the header's close button
* @param enabled If "false", the popover will not be closeable by the header's close button.
* @see #closeButtonEnabledProperty()
*/
public final void setCloseButtonEnabled(boolean enabled) {
@ -573,15 +594,13 @@ public class Popover extends PopupControl {
/**
* Returns the value of the closeButtonEnabled property.
*
* @return true if the header's close button is enabled
* @return "true" if the header's close button is enabled
* @see #closeButtonEnabledProperty()
*/
public final boolean isCloseButtonEnabled() {
return closeButtonEnabled.getValue();
}
private final BooleanProperty detachable = new SimpleBooleanProperty(this, "detachable", true);
/**
* Determines if the popover is detachable at all.
*/
@ -589,10 +608,12 @@ public class Popover extends PopupControl {
return detachable;
}
private final BooleanProperty detachable = new SimpleBooleanProperty(this, "detachable", true);
/**
* Sets the value of the detachable property.
*
* @param detachable if true then the user can detach / tear off the popover
* @param detachable If "true" then the user can detach / tear off the popover.
* @see #detachableProperty()
*/
public final void setDetachable(boolean detachable) {
@ -602,31 +623,28 @@ public class Popover extends PopupControl {
/**
* Returns the value of the detachable property.
*
* @return true if the user is allowed to detach / tear off the popover
* @return "true" if the user is allowed to detach / tear off the popover
* @see #detachableProperty()
*/
public final boolean isDetachable() {
return detachableProperty().get();
}
private final BooleanProperty detached = new SimpleBooleanProperty(this, "detached", false);
/**
* Determines whether the popover is detached from the owning node or not.
* A detached popover no longer shows an arrow pointing at the owner and
* features its own title bar.
*
* @return the detached property
*/
public final BooleanProperty detachedProperty() {
return detached;
}
private final BooleanProperty detached = new SimpleBooleanProperty(this, "detached", false);
/**
* Sets the value of the detached property.
*
* @param detached if true the popover will change its appearance to "detached"
* mode
* @param detached If "true" the popover will change its appearance to "detached" mode.
* @see #detachedProperty()
*/
public final void setDetached(boolean detached) {
@ -636,24 +654,23 @@ public class Popover extends PopupControl {
/**
* Returns the value of the detached property.
*
* @return true if the popover is currently detached.
* @return "true" if the popover is currently detached
* @see #detachedProperty()
*/
public final boolean isDetached() {
return detachedProperty().get();
}
private final DoubleProperty arrowSize = new SimpleDoubleProperty(this, "arrowSize", 12);
/**
* Controls the size of the arrow. Default value is 12.
*
* @return the arrow size property
* Controls the size of the arrow.
* Default value is "12".
*/
public final DoubleProperty arrowSizeProperty() {
return arrowSize;
}
private final DoubleProperty arrowSize = new SimpleDoubleProperty(this, "arrowSize", 12);
/**
* Returns the value of the arrow size property.
*
@ -667,25 +684,23 @@ public class Popover extends PopupControl {
/**
* Sets the value of the arrow size property.
*
* @param size the new value of the arrow size property
* @param size The new value of the arrow size property.
* @see #arrowSizeProperty()
*/
public final void setArrowSize(double size) {
arrowSizeProperty().set(size);
}
private final DoubleProperty arrowIndent = new SimpleDoubleProperty(this, "arrowIndent", 12);
/**
* Controls the distance between the arrow and the corners of the popover.
* The default value is 12.
*
* @return the arrow indent property
* Default value is "12".
*/
public final DoubleProperty arrowIndentProperty() {
return arrowIndent;
}
private final DoubleProperty arrowIndent = new SimpleDoubleProperty(this, "arrowIndent", 12);
/**
* Returns the value of the arrow indent property.
*
@ -699,24 +714,23 @@ public class Popover extends PopupControl {
/**
* Sets the value of the arrow indent property.
*
* @param size the arrow indent value
* @param size The arrow indent value.
* @see #arrowIndentProperty()
*/
public final void setArrowIndent(double size) {
arrowIndentProperty().set(size);
}
private final DoubleProperty cornerRadius = new SimpleDoubleProperty(this, "cornerRadius", 6);
/**
* Returns the corner radius property for the popover.
*
* @return the corner radius property (default is 6)
* Default value is "6".
*/
public final DoubleProperty cornerRadiusProperty() {
return cornerRadius;
}
private final DoubleProperty cornerRadius = new SimpleDoubleProperty(this, "cornerRadius", 6);
/**
* Returns the value of the corner radius property.
*
@ -730,24 +744,22 @@ public class Popover extends PopupControl {
/**
* Sets the value of the corner radius property.
*
* @param radius the corner radius
* @param radius The corner radius.
* @see #cornerRadiusProperty()
*/
public final void setCornerRadius(double radius) {
cornerRadiusProperty().set(radius);
}
private final StringProperty title = new SimpleStringProperty(this, "title", "Info");
/**
* Stores the title to display in the Popover's header.
*
* @return the title property
*/
public final StringProperty titleProperty() {
return title;
}
private final StringProperty title = new SimpleStringProperty(this, "title", "Info");
/**
* Returns the value of the title property.
*
@ -761,7 +773,7 @@ public class Popover extends PopupControl {
/**
* Sets the value of the title property.
*
* @param title the title to use when detached
* @param title The title to use when detached.
* @see #titleProperty()
*/
public final void setTitle(String title) {
@ -771,24 +783,23 @@ public class Popover extends PopupControl {
titleProperty().set(title);
}
private final ObjectProperty<ArrowLocation> arrowLocation =
new SimpleObjectProperty<>(this, "arrowLocation", ArrowLocation.LEFT_TOP);
/**
* Stores the preferred arrow location. This might not be the actual
* location of the arrow if auto fix is enabled.
*
* @return the arrow location property
* @see #setAutoFix(boolean)
*/
public final ObjectProperty<ArrowLocation> arrowLocationProperty() {
return arrowLocation;
}
private final ObjectProperty<ArrowLocation> arrowLocation =
new SimpleObjectProperty<>(this, "arrowLocation", ArrowLocation.LEFT_TOP);
/**
* Sets the value of the arrow location property.
*
* @param location the requested location
* @param location The requested location.
* @see #arrowLocationProperty()
*/
public final void setArrowLocation(ArrowLocation location) {
@ -824,22 +835,13 @@ public class Popover extends PopupControl {
}
/**
* Stores the fade-in duration. This should be set before calling Popover.show(..).
*
* @return the fade-in duration property
* Stores the fade-in duration. This should be set before calling <code>Popover.show(..)</code>.
*/
public final ObjectProperty<Duration> fadeInDurationProperty() {
return fadeInDuration;
}
/**
* Stores the fade-out duration.
*
* @return the fade-out duration property
*/
public final ObjectProperty<Duration> fadeOutDurationProperty() {
return fadeOutDuration;
}
private final ObjectProperty<Duration> fadeInDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION);
/**
* Returns the value of the fade-in duration property.
@ -855,13 +857,22 @@ public class Popover extends PopupControl {
* Sets the value of the fade-in duration property. This should be set before calling
* Popover.show(..).
*
* @param duration the requested fade-in duration
* @param duration The requested fade-in duration.
* @see #fadeInDurationProperty()
*/
public final void setFadeInDuration(Duration duration) {
fadeInDurationProperty().setValue(duration);
}
/**
* Stores the fade-out duration.
*/
public final ObjectProperty<Duration> fadeOutDurationProperty() {
return fadeOutDuration;
}
private final ObjectProperty<Duration> fadeOutDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION);
/**
* Returns the value of the fade-out duration property.
*
@ -875,7 +886,7 @@ public class Popover extends PopupControl {
/**
* Sets the value of the fade-out duration property.
*
* @param duration the requested fade-out duration
* @param duration The requested fade-out duration.
* @see #fadeOutDurationProperty()
*/
public final void setFadeOutDuration(Duration duration) {
@ -885,17 +896,17 @@ public class Popover extends PopupControl {
/**
* Stores the "animated" flag. If true then the Popover will be shown / hidden with a short
* fade in / out animation.
*
* @return the "animated" property
*/
public final BooleanProperty animatedProperty() {
return animated;
}
private final SimpleBooleanProperty animated = new SimpleBooleanProperty(true);
/**
* Returns the value of the "animated" property.
*
* @return true if the Popover will be shown and hidden with a short fade animation
* @return "true" if the Popover will be shown and hidden with a short fade animation
* @see #animatedProperty()
*/
public final boolean isAnimated() {
@ -905,7 +916,7 @@ public class Popover extends PopupControl {
/**
* Sets the value of the "animated" property.
*
* @param animated if true the Popover will be shown and hidden with a short fade animation
* @param animated If "true" the Popover will be shown and hidden with a short fade animation.
* @see #animatedProperty()
*/
public final void setAnimated(boolean animated) {

@ -62,6 +62,9 @@ import javafx.scene.shape.QuadCurveTo;
import javafx.scene.shape.VLineTo;
import javafx.stage.Window;
/**
* The default skin for the {@link Popover} control.
*/
public class PopoverSkin implements Skin<Popover> {
private static final String DETACHED_STYLE_CLASS = "detached";

@ -8,7 +8,7 @@ import javafx.scene.control.skin.SliderSkin;
import javafx.scene.layout.StackPane;
/**
* {@link Slider} skin that supports progress color.
* A {@link Slider} skin that supports progress color indication.
*/
public class ProgressSliderSkin extends SliderSkin {

@ -2,6 +2,7 @@
package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
@ -11,21 +12,43 @@ import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.Skin;
import javafx.util.StringConverter;
/**
* A ProgressIndicator that displays progress value as a ring that gradually
* empties out as a task is completed.
*/
public class RingProgressIndicator extends ProgressIndicator {
/**
* Creates a new indeterminate ProgressIndicator.
*/
public RingProgressIndicator() {
super();
}
public RingProgressIndicator(double progress) {
/**
* Creates a new ProgressIndicator with the given progress value.
*
* @param progress The progress, represented as a value between 0 and 1.
*/
public RingProgressIndicator(@NamedArg("progress") double progress) {
this(progress, false);
}
public RingProgressIndicator(double progress, boolean reverse) {
/**
* Creates a new ProgressIndicator with the given progress value and type.
*
* @param progress The progress, represented as a value between 0 and 1.
* @param reverse A flag to indicate whether the indicator is reversed or not.
*/
public RingProgressIndicator(@NamedArg("progress") double progress,
@NamedArg("reverse") boolean reverse) {
super(progress);
this.reverse.set(reverse);
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new RingProgressIndicatorSkin(this);
@ -35,6 +58,14 @@ public class RingProgressIndicator extends ProgressIndicator {
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the node to be displayed within the progress indicator. If null,
* it will fall back to the Label with an integer progress value from 1 to 100.
*/
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
protected final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic", null);
public Node getGraphic() {
@ -46,15 +77,15 @@ public class RingProgressIndicator extends ProgressIndicator {
}
/**
* Any node to be displayed within the progress indicator. If null,
* it will fall back to the Label with integer progress value from 1 to 100.
* Represents an optional converter to transform the progress value to a string.
* It is only used if a custom graphic node is not set.
*
* @see #graphicProperty()
*/
public ObjectProperty<Node> graphicProperty() {
return graphic;
public ObjectProperty<StringConverter<Double>> stringConverterProperty() {
return stringConverter;
}
// ~
protected final ObjectProperty<StringConverter<Double>> stringConverter =
new SimpleObjectProperty<>(this, "converter", null);
@ -67,25 +98,16 @@ public class RingProgressIndicator extends ProgressIndicator {
}
/**
* Optional converter to transform progress value to string.
*/
public ObjectProperty<StringConverter<Double>> stringConverterProperty() {
return stringConverter;
}
// ~
private final ReadOnlyBooleanWrapper reverse = new ReadOnlyBooleanWrapper(this, "reverse", false);
public boolean isReverse() {
return reverse.get();
}
/**
* Reverse progress indicator scale. For indeterminate variant
* Reverses the progress indicator scale. For the indeterminate variant,
* this means it will be rotated counterclockwise.
*/
public ReadOnlyBooleanProperty reverseProperty() {
return reverse.getReadOnlyProperty();
}
protected final ReadOnlyBooleanWrapper reverse = new ReadOnlyBooleanWrapper(this, "reverse", false);
public boolean isReverse() {
return reverse.get();
}
}

@ -24,6 +24,9 @@ import javafx.scene.shape.Arc;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
/**
* The default skin for the {@link RingProgressIndicator} control.
*/
public class RingProgressIndicatorSkin extends SkinBase<RingProgressIndicator> {
protected static final double DEFAULT_ANIMATION_TIME = 3;

@ -11,6 +11,15 @@ import javafx.scene.Node;
import javafx.scene.layout.Pane;
import org.jetbrains.annotations.Nullable;
/**
* An internal convenience class for implementing slot-based approach.
*
* <p>It is intended to be used for controls that allow custom user nodes
* to be placed inside their skins. his class automatically adds or removes
* an updated <code>ObservableValue<? extends Node></code> value to/from the
* given container and also maintains the <code>:filled</code> pseudo-class
* state to indicate whether the corresponding slot is empty or not.
*/
final class SlotListener implements ChangeListener<Node> {
private static final PseudoClass FILLED = PseudoClass.getPseudoClass("filled");
@ -18,10 +27,24 @@ final class SlotListener implements ChangeListener<Node> {
private final Pane slot;
private final @Nullable BiConsumer<Node, Boolean> onContentUpdate;
/**
* Creates a new listener and binds it to the specified container.
*
* @param slot The container for user-specified node.
*/
public SlotListener(Pane slot) {
this(slot, null);
}
/**
* Creates a new listener and binds it to the specified container.
* Also, it registers the custom callback handler that will be notified
* upon the container content changed.
*
* @param slot The container for user-specified node.
* @param onContentUpdate The callback handler to be notified upon
* the container content changing.
*/
public SlotListener(Node slot, @Nullable BiConsumer<Node, Boolean> onContentUpdate) {
Objects.requireNonNull(slot, "Slot cannot be null.");

@ -8,12 +8,38 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
/**
* A spacing component used to distribute remaining width between
* a parent's child components.
*
* <p>When placing a single Spacer before or after the child components,
* the components will be pushed to the right and left of its container
* for horizontally oriented Spacer, or to the top and bottom for vertically
* oriented Spacer.
*
* <p>You can also specify the `Spacer` size. In this case, it will not be
* extended and will work like a gap with the given size between sibling components.
*
* <p>Note that this control is not intended to be used in FXML unless SceneBuilder
* supports constructor arguments, because none of the properties mentioned above are
* observable.
*/
public class Spacer extends Region {
/**
* Creates a new horizontally oriented Spacer that expands
* to fill remaining space.
*/
public Spacer() {
this(Orientation.HORIZONTAL);
}
/**
* Creates a new Spacer with the given orientation that expands
* to fill remaining space.
*
* @param orientation The orientation of the spacer.
*/
public Spacer(Orientation orientation) {
super();
@ -23,10 +49,21 @@ public class Spacer extends Region {
}
}
/**
* Creates a new Spacer with the fixed size.
*
* @param size The size of the spacer.
*/
public Spacer(double size) {
this(size, Orientation.HORIZONTAL);
}
/**
* Creates a new Spacer with the fixed size and orientation.
*
* @param size The size of the spacer.
* @param orientation The orientation of the spacer.
*/
public Spacer(double size, Orientation orientation) {
super();

@ -10,21 +10,37 @@ import javafx.scene.control.Skin;
import org.jetbrains.annotations.Nullable;
/**
* A versatile container that can used in various contexts such as dialog headers,
* list items, and cards. It can contain a graphic, a title, description, and optional
* actions.
* A versatile container that can used in various contexts such as dialog
* headers, list items, and cards. It can contain a graphic, a title, description,
* and optional actions.
*/
public class Tile extends TileBase {
/**
* Creates a new empty Tile.
*/
public Tile() {
this(null, null, null);
}
/**
* Creates a new Tile with an initial title and description.
*
* @param title A string for the title.
* @param description A string for the description.
*/
public Tile(@Nullable @NamedArg("title") String title,
@Nullable @NamedArg("description") String description) {
this(title, description, null);
}
/**
* Creates a new Tile with an initial title, description and graphic.
*
* @param title A string for the title.
* @param description A string for the description.
* @param graphic A graphic or icon.
*/
public Tile(@Nullable String title,
@Nullable String description,
@Nullable Node graphic) {
@ -32,6 +48,9 @@ public class Tile extends TileBase {
getStyleClass().add("tile");
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new TileSkin(this);
@ -42,38 +61,40 @@ public class Tile extends TileBase {
///////////////////////////////////////////////////////////////////////////
/**
* The property representing the tiles action node. It is commonly used
* to place an action controls that are associated with the tile.
* Represents the node to be placed in the tiles action slot. It is commonly
* used to place action controls that are associated with the tile.
*/
public ObjectProperty<Node> actionProperty() {
return action;
}
private final ObjectProperty<Node> action = new SimpleObjectProperty<>(this, "action");
public Node getAction() {
return action.get();
}
public ObjectProperty<Node> actionProperty() {
return action;
}
public void setAction(Node action) {
this.action.set(action);
}
/**
* The property representing the tiles action handler. Setting an action handler
* makes the tile interactive or clickable. When a user clicks on the interactive
* tile, the specified action handler will be called.
* Represents the tiles action handler.
*
* <p>Setting an action handler makes the tile interactive (or clickable).
* When a user clicks on the interactive tile, the specified action handler will be called.
*/
private final ObjectProperty<Runnable> actionHandler = new SimpleObjectProperty<>(this, "actionHandler");
public ObjectProperty<Runnable> actionHandlerProperty() {
return actionHandler;
}
private final ObjectProperty<Runnable> actionHandler
= new SimpleObjectProperty<>(this, "actionHandler");
public Runnable getActionHandler() {
return actionHandler.get();
}
public ObjectProperty<Runnable> actionHandlerProperty() {
return actionHandler;
}
public void setActionHandler(Runnable actionHandler) {
this.actionHandler.set(actionHandler);
}

@ -2,7 +2,7 @@
package atlantafx.base.controls;
import javafx.beans.NamedArg;
import atlantafx.base.util.BBCodeParser;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
@ -11,14 +11,18 @@ import javafx.scene.Node;
import javafx.scene.control.Control;
import org.jetbrains.annotations.Nullable;
/**
* A common class for implementing tile-based controls, specifically the
* {@link Message} and the {@link Tile}.
*/
public abstract class TileBase extends Control {
public TileBase() {
this(null, null, null);
}
public TileBase(@Nullable @NamedArg("title") String title,
@Nullable @NamedArg("description") String description) {
public TileBase(@Nullable String title,
@Nullable String description) {
this(title, description, null);
}
@ -38,54 +42,56 @@ public abstract class TileBase extends Control {
///////////////////////////////////////////////////////////////////////////
/**
* The property representing the tiles graphic node. It is commonly used
* to add images or icons that are associated with the tile.
* Represents the tiles graphic node. It is commonly used to add images or icons
* that are associated with the tile.
*/
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic");
public Node getGraphic() {
return graphic.get();
}
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
public void setGraphic(Node graphic) {
this.graphic.set(graphic);
}
/**
* The property representing the tiles title. Although it is not mandatory,
* you typically would not want to have a tile without a title.
* Represents the tiles title (or header).
*/
public StringProperty titleProperty() {
return title;
}
private final StringProperty title = new SimpleStringProperty(this, "title");
public String getTitle() {
return title.get();
}
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
/**
* The property representing the tiles description.
* Represents the tiles description (or optional text).
*
* <p>This property supports BBCode formatted text. Refer to the {@link BBCodeParser}
* for more information.
*/
public StringProperty descriptionProperty() {
return description;
}
private final StringProperty description = new SimpleStringProperty(this, "description");
public String getDescription() {
return description.get();
}
public StringProperty descriptionProperty() {
return description;
}
public void setDescription(String description) {
this.description.set(description);
}

@ -4,6 +4,9 @@ package atlantafx.base.controls;
import atlantafx.base.theme.Styles;
/**
* The default skin for the {@link Tile} control.
*/
public class TileSkin extends TileSkinBase<Tile> {
public TileSkin(Tile control) {

@ -15,6 +15,10 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextFlow;
/**
* A common skin for implementing tile-based controls, specifically the
* {@link MessageSkin} and the {@link TileSkin}.
*/
public abstract class TileSkinBase<T extends TileBase> extends SkinBase<T> {
protected static final PseudoClass HAS_GRAPHIC = PseudoClass.getPseudoClass("has-graphic");

@ -51,7 +51,11 @@ import javafx.scene.control.Skin;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
@SuppressWarnings("unused")
/**
* A control that provides users with the ability to choose between two distinct values.
* It is functionally similar, though aesthetically different, from the RadioButton
* and Checkbox.
*/
public class ToggleSwitch extends Labeled implements Toggle {
protected static final String DEFAULT_STYLE_CLASS = "toggle-switch";
@ -68,13 +72,21 @@ public class ToggleSwitch extends Labeled implements Toggle {
/**
* Creates a toggle switch with the specified label.
*
* @param text The label string of the control
* @param text The label string of the control.
*/
public ToggleSwitch(String text) {
super(text);
initialize();
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new ToggleSwitchSkin(this);
}
private void initialize() {
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
}
@ -83,18 +95,6 @@ public class ToggleSwitch extends Labeled implements Toggle {
// Properties //
///////////////////////////////////////////////////////////////////////////
private BooleanProperty selected;
@Override
public final void setSelected(boolean value) {
selectedProperty().set(value);
}
@Override
public final boolean isSelected() {
return selected != null && selected.get();
}
/**
* Returns whether this Toggle Switch is selected.
*/
@ -151,24 +151,23 @@ public class ToggleSwitch extends Labeled implements Toggle {
return selected;
}
private BooleanProperty selected;
@Override
public final void setSelected(boolean value) {
selectedProperty().set(value);
}
@Override
public final boolean isSelected() {
return selected != null && selected.get();
}
/**
* The {@link ToggleGroup} to which this {@code ToggleSwitch} belongs.
* A {@code ToggleSwitch} can only be in one group at any one time. If the
* group is changed, then the button is removed from the old group prior to
* being added to the new group.
* The {@link ToggleGroup} to which this ToggleSwitch belongs. A toggle can only
* be in one group at any one time. If the group is changed, then the toggle is
* removed from the old group prior to being added to the new group.
*/
private ObjectProperty<ToggleGroup> toggleGroup;
@Override
public final void setToggleGroup(ToggleGroup value) {
toggleGroupProperty().set(value);
}
@Override
public final ToggleGroup getToggleGroup() {
return toggleGroup == null ? null : toggleGroup.get();
}
@Override
public final ObjectProperty<ToggleGroup> toggleGroupProperty() {
if (toggleGroup == null) {
@ -204,23 +203,20 @@ public class ToggleSwitch extends Labeled implements Toggle {
return toggleGroup;
}
// ~
private ObjectProperty<ToggleGroup> toggleGroup;
private ObjectProperty<HorizontalDirection> labelPosition;
@Override
public final void setToggleGroup(ToggleGroup value) {
toggleGroupProperty().set(value);
}
public final void setLabelPosition(HorizontalDirection pos) {
labelPositionProperty().setValue(pos);
@Override
public final ToggleGroup getToggleGroup() {
return toggleGroup == null ? null : toggleGroup.get();
}
/**
* Returns whether this Toggle Switch is selected.
*/
public final HorizontalDirection getLabelPosition() {
return labelPosition == null ? HorizontalDirection.LEFT : labelPosition.getValue();
}
/**
* Specifies the side where {@link #textProperty()} values should be placed.
* Specifies the side where {@link #textProperty()} value should be placed.
* Default is {@link HorizontalDirection#LEFT}.
*/
public final ObjectProperty<HorizontalDirection> labelPositionProperty() {
@ -253,13 +249,22 @@ public class ToggleSwitch extends Labeled implements Toggle {
return labelPosition;
}
private ObjectProperty<HorizontalDirection> labelPosition;
public final void setLabelPosition(HorizontalDirection pos) {
labelPositionProperty().setValue(pos);
}
public final HorizontalDirection getLabelPosition() {
return labelPosition == null ? HorizontalDirection.LEFT : labelPosition.getValue();
}
///////////////////////////////////////////////////////////////////////////
// Methods //
///////////////////////////////////////////////////////////////////////////
/**
* Toggles the state of the {@code Switch}. The {@code Switch} will cycle
* through the selected and unselected states.
* Toggles the state of the switch, cycling through the selected and unselected states.
*/
public void fire() {
if (!isDisabled()) {
@ -268,13 +273,9 @@ public class ToggleSwitch extends Labeled implements Toggle {
}
}
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new ToggleSwitchSkin(this);
}
///////////////////////////////////////////////////////////////////////////
// Styleable Properties //
///////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
@ -284,10 +285,6 @@ public class ToggleSwitch extends Labeled implements Toggle {
return StyleableProperties.STYLEABLES;
}
///////////////////////////////////////////////////////////////////////////
// Styleable Properties //
///////////////////////////////////////////////////////////////////////////
private static class StyleableProperties {
private static final CssMetaData<ToggleSwitch, HorizontalDirection> LABEL_POSITION = new CssMetaData<>(

@ -48,6 +48,9 @@ import javafx.scene.control.SkinBase;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
/**
* The default skin for the {@link ToggleSwitch} control.
*/
public class ToggleSwitchSkin extends SkinBase<ToggleSwitch> {
protected static final Duration DEFAULT_ANIMATION_TIME = Duration.millis(200);

@ -0,0 +1,5 @@
/**
* Defines the UI controls and their corresponding skins.
*/
package atlantafx.base.controls;

@ -22,15 +22,15 @@ import javafx.util.Duration;
import org.jetbrains.annotations.Nullable;
/**
* DeckPane represents a pane that displays all of its child nodes in a deck,
* where only one node can be visible at a time. It does not maintain any sequence
* (model), but only cares about the top node, which can be changed by various
* transition effects.<p/>
* Represents a pane that displays all of its child nodes in a deck,
* where only one node can be visible at a time. It <b>does not maintain any
* sequence</b> (model), but only cares about the top node, which can be changed
* by various transition effects.<p/>
*
* <h3>View Order</h3>
*
* <p>DeckPane manages {@link Node#viewOrderProperty()} of its children. Topmost
* visible node always has a higher view order value, while the rest of the nodes
* visible node always has the highest view order value, while the rest of the nodes
* have the default value, which is zero. Following that logic, one must not set
* child nodes view order manually, because it will break the contract.
*
@ -53,6 +53,9 @@ public class DeckPane extends AnchorPane {
// the rest of the nodes
protected static final int Z_DEFAULT = 0;
/**
* Creates a new empty DeckPane.
*/
public DeckPane() {
super();
@ -66,14 +69,19 @@ public class DeckPane extends AnchorPane {
});
}
/**
* Creates an DeckPane with the given children.
*
* @param children The initial set of children for this pane.
*/
public DeckPane(Node... children) {
this();
getChildren().addAll(children);
}
/**
* Returns the node with the higher view order value or the last node
* if all child nodes have the same view order value.
* Returns the node with the highest view order value, or the
* last node if all child nodes have the same view order value.
*/
public @Nullable Node getTopNode() {
var size = getChildren().size();
@ -93,6 +101,8 @@ public class DeckPane extends AnchorPane {
/**
* Sets given node on top without playing any transition.
* Does nothing if that node isn't added to the pane.
*
* @param target The node to be set on top.
*/
public void setTopNode(Node target) {
if (!getChildren().contains(target)) {
@ -120,16 +130,19 @@ public class DeckPane extends AnchorPane {
}
/**
* Adds given nodes to the pane and binds them to the pane edges
* using the provided offsets. See {@link AnchorPane#setTopAnchor(Node, Double)}
* Adds the given nodes to the pane and binds them to the pane edges
* using the provided offset. See {@link AnchorPane#setTopAnchor(Node, Double)}
* for the reference.
*
* @param offset The offset values for each othe the specified nodes.
* @param nodes The array of the nodes to be added.
*/
public void addChildren(Insets offsets, Node... nodes) {
public void addChildren(Insets offset, Node... nodes) {
for (var node : nodes) {
AnchorPane.setTopAnchor(node, offsets.getTop());
AnchorPane.setRightAnchor(node, offsets.getRight());
AnchorPane.setBottomAnchor(node, offsets.getBottom());
AnchorPane.setLeftAnchor(node, offsets.getLeft());
AnchorPane.setTopAnchor(node, offset.getTop());
AnchorPane.setRightAnchor(node, offset.getRight());
AnchorPane.setBottomAnchor(node, offset.getBottom());
AnchorPane.setLeftAnchor(node, offset.getLeft());
}
getChildren().addAll(nodes);
}
@ -138,6 +151,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the
* swipe transition from bottom to top. If the pane doesn't contain
* that node, it will be added to the end before playing transition.
*
* @param target The node to be set on top.
*/
public void swipeUp(Node target) {
var topNode = getTopNode();
@ -164,6 +179,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the
* swipe transition from top to bottom. If the pane doesn't contain
* that node, it will be added to the end before playing transition.
*
* @param target The node to be set on top.
*/
public void swipeDown(Node target) {
var topNode = getTopNode();
@ -190,6 +207,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the
* swipe transition from right to left. If the pane doesn't contain
* that node, it will be added to the end before playing transition.
*
* @param target The node to be set on top.
*/
public void swipeLeft(Node target) {
var topNode = getTopNode();
@ -216,6 +235,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the
* swipe transition from left to right. If the pane doesn't contain
* that node, it will be added to the end before playing transition.
*
* @param target The node to be set on top.
*/
public void swipeRight(Node target) {
var topNode = getTopNode();
@ -242,6 +263,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the
* slide transition from bottom to top. If the pane doesn't contain
* that node, it will be added to the end before playing transition.
*
* @param target The node to be set on top.
*/
public void slideUp(Node target) {
var topNode = getTopNode();
@ -265,6 +288,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the
* slide transition from top to bottom. If the pane doesn't contain
* that node, it will be added to the end before playing transition.
*
* @param target The node to be set on top.
*/
public void slideDown(Node target) {
var topNode = getTopNode();
@ -288,6 +313,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the
* slide transition from right to left. If the pane doesn't contain
* that node, it will be added to the end before playing transition.
*
* @param target The node to be set on top.
*/
public void slideLeft(Node target) {
var topNode = getTopNode();
@ -311,6 +338,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the
* slide transition from left to right. If the pane doesn't contain
* that node, it will be added to the end before playing transition.
*
* @param target The node to be set on top.
*/
public void slideRight(Node target) {
var topNode = getTopNode();
@ -334,6 +363,13 @@ public class DeckPane extends AnchorPane {
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents the duration of the transition effect that is played when changing the top node.
*/
public ObjectProperty<Duration> animationDurationProperty() {
return animationDuration;
}
protected final ObjectProperty<Duration> animationDuration =
new SimpleObjectProperty<>(this, "animationDuration", Duration.seconds(1));
@ -341,18 +377,17 @@ public class DeckPane extends AnchorPane {
return animationDuration.get();
}
/**
* Duration of the transition effect which is played when changing the top node.
*/
public ObjectProperty<Duration> animationDurationProperty() {
return animationDuration;
}
public void setAnimationDuration(@Nullable Duration animationDuration) {
this.animationDuration.set(Objects.requireNonNullElse(animationDuration, Duration.ZERO));
}
// ~
/**
* Indicates whether the transition is in progress. Subscribe to this property
* to be notified when the animation starts or finishes.
*/
public ReadOnlyBooleanProperty animationActiveProperty() {
return animationActive.getReadOnlyProperty();
}
protected final ReadOnlyBooleanWrapper animationActive =
new ReadOnlyBooleanWrapper(this, "animationActive");
@ -361,19 +396,16 @@ public class DeckPane extends AnchorPane {
return animationActive.get();
}
/**
* Returns whether transition is in progress. Subscribe to be notified
* when animation started or finished.
*/
public ReadOnlyBooleanProperty animationActiveProperty() {
return animationActive.getReadOnlyProperty();
}
protected void setAnimationActive(boolean animationActive) {
this.animationActive.set(animationActive);
}
// ~
/**
* Sets the callback action to be called before setting a node at the top of the DeckPane.
*/
public ObjectProperty<Consumer<Node>> beforeShowCallbackProperty() {
return beforeShowCallback;
}
protected final ObjectProperty<Consumer<Node>> beforeShowCallback =
new SimpleObjectProperty<>(this, "beforeShowCallback");
@ -382,13 +414,6 @@ public class DeckPane extends AnchorPane {
return beforeShowCallback.get();
}
/**
* Callback action to be called before setting a node at the top of the deck.
*/
public ObjectProperty<Consumer<Node>> beforeShowCallbackProperty() {
return beforeShowCallback;
}
public void setBeforeShowCallback(@Nullable Consumer<Node> callback) {
this.beforeShowCallback.set(callback);
}
@ -399,7 +424,12 @@ public class DeckPane extends AnchorPane {
}
}
// ~
/**
* Sets the callback action to be called after removing the top node from the top of the DeckPane.
*/
public ObjectProperty<Consumer<Node>> afterHideCallbackProperty() {
return afterHideCallback;
}
protected final ObjectProperty<Consumer<Node>> afterHideCallback =
new SimpleObjectProperty<>(this, "afterHideCallback");
@ -408,13 +438,6 @@ public class DeckPane extends AnchorPane {
return afterHideCallback.get();
}
/**
* Callback action to be called after removing the top node from the top of the deck.
*/
public ObjectProperty<Consumer<Node>> afterHideCallbackProperty() {
return afterHideCallback;
}
public void setAfterHideCallback(@Nullable Consumer<Node> callback) {
this.afterHideCallback.set(callback);
}

@ -9,16 +9,18 @@ import javafx.scene.Node;
import javafx.scene.layout.HBox;
/**
* InputGroup is a layout that helps combine multiple controls into a group
* that looks like a single control. Without it, you would have to manually
* add the "left-pill", "center-pill," and "right-pill" styles classes to
* each control in such combination. The InputGroup removes this ceremony.
* Since it inherits from HBox, you can use the same API.
* A layout that helps combine multiple controls into a group that looks
* like a single control.
*
* <p>Without it, you would have to manually add the ".left-pill", ".center-pill"
* and ".right-pill" styles classes to each control in such combination.
* The InputGroup removes this ceremony. Since it inherits from HBox, you can use
* the same API.
*/
public class InputGroup extends HBox {
/**
* See {@link HBox#HBox()}.
* Creates a new empty InputGroup.
*/
public InputGroup() {
super();
@ -26,7 +28,9 @@ public class InputGroup extends HBox {
}
/**
* See {@link HBox#HBox(Node...)}.
* Creates an InputGroup with the given children.
*
* @param children The initial set of children for this pane.
*/
public InputGroup(Node... children) {
super(children);

@ -17,11 +17,12 @@ import javafx.scene.layout.StackPane;
import org.jetbrains.annotations.Nullable;
/**
* The ModalBox is a specialized control or layout designed to hold the
* {@link ModalPane} dialog content. It includes the close button out-of-the-box
* and allows for the addition of arbitrary children. The ModalBox is derived
* from the {@link AnchorPane}, so it inherits the same API. Just be sure that
* you haven't removed the close button while using it.
* A specialized control (or layout) designed to hold the {@link ModalPane}
* dialog content. It includes the close button out-of-the-box and allows for the
* addition of arbitrary children.
*
* <p>The ModalBox is derived from the {@link AnchorPane}, so it inherits the same API.
* Just be sure that you haven't removed the close button while using it.
*/
public class ModalBox extends AnchorPane {
@ -40,7 +41,7 @@ public class ModalBox extends AnchorPane {
/**
* Creates a ModalBox layout with the given children.
*
* @param children the initial set of children for this pane
* @param children The initial set of children for this pane.
*/
public ModalBox(Node... children) {
this((String) null, children);
@ -52,8 +53,8 @@ public class ModalBox extends AnchorPane {
* on the close button, it performs a ModalPane lookup via the specified
* selector and calls the {@link ModalPane#hide()} method automatically.
*
* @param selector the ModalPane pane CSS selector
* @param children the initial set of children for this pane
* @param selector The ModalPane pane CSS selector.
* @param children The initial set of children for this pane.
*/
public ModalBox(@Nullable @NamedArg("selector") String selector, Node... children) {
super(children);
@ -69,8 +70,8 @@ public class ModalBox extends AnchorPane {
* the close handler to a ModalPane. When user clicks on the close button,
* it calls the {@link ModalPane#hide()} method automatically.
*
* @param modalPane the ModalPane pane CSS selector
* @param children the initial set of children for this pane
* @param modalPane The ModalPane pane CSS selector.
* @param children The initial set of children for this pane.
*/
public ModalBox(@Nullable ModalPane modalPane, Node... children) {
super(children);
@ -88,7 +89,7 @@ public class ModalBox extends AnchorPane {
* and {@link Node#isMouseTransparent()} is false, then the close button
* will not receive mouse events and therefore will not be clickable.
*
* @param node the node to be added
* @param node The node to be added.
*/
public void addContent(Node node) {
Objects.requireNonNull(node, "Node cannot be null.");
@ -142,11 +143,16 @@ public class ModalBox extends AnchorPane {
///////////////////////////////////////////////////////////////////////////
/**
* The property representing the user specified close handler. Note that
* if you have also specified the ModalPane instance or CSS selector, this
* handler will be executed after the default close handler. Therefore, you
* The property representing the user specified close handler.
*
* <p>Note that if you have also specified the ModalPane instance or CSS selector,
* this handler will be executed after the default close handler. Therefore, you
* can use it to perform arbitrary actions on dialog close.
*/
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose");
@ -154,27 +160,25 @@ public class ModalBox extends AnchorPane {
return onClose.get();
}
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
public void setOnClose(EventHandler<? super Event> onClose) {
this.onClose.set(onClose);
}
/**
* See {@link ModalPane#hide(boolean)}.
* Specifies whether to remove (clear) the ModalPane content after it's closed.
*
* @see ModalPane#hide(boolean).
*/
public BooleanProperty clearOnCloseProperty() {
return clearOnClose;
}
protected final BooleanProperty clearOnClose = new SimpleBooleanProperty(this, "clearOnClose");
public boolean isClearOnClose() {
return clearOnClose.get();
}
public BooleanProperty clearOnCloseProperty() {
return clearOnClose;
}
public void setClearOnClose(boolean clearOnClose) {
this.clearOnClose.set(clearOnClose);
}

@ -0,0 +1,5 @@
/**
* Defines additional layouts and layout helpers.
*/
package atlantafx.base.layout;

@ -11,8 +11,8 @@ import javafx.scene.Node;
import javafx.scene.control.TabPane;
/**
* A set of constants and utility methods that simplifies adding
* CSS classes programmatically.
* A set of constants and utility methods that simplifies adding CSS
* classes programmatically.
*/
@SuppressWarnings("unused")
public final class Styles {
@ -132,11 +132,11 @@ public final class Styles {
}
/**
* Adds given style class to the node if it's not present, otherwise
* removes it.
* Adds the given style class to the node if it's not present,
* otherwise removes it.
*
* @param node the target node
* @param styleClass the style class to be toggled
* @param node The target node.
* @param styleClass The style class to be toggled.
* @throws NullPointerException if node or style class is null
*/
public static void toggleStyleClass(Node node, String styleClass) {
@ -156,13 +156,13 @@ public final class Styles {
}
/**
* Adds given style class to the node and removes the excluded classes.
* Adds the given style class to the node and removes the excluded classes.
* This method is supposed to be used when only one from a set of classes
* have to be present at once.
*
* @param node the target node
* @param styleClass the style class to be toggled
* @param excludes the style classes to be excluded
* @param node The target node.
* @param styleClass The style class to be toggled.
* @param excludes The style classes to be excluded.
* @throws NullPointerException if node or styleClass is null
*/
public static void addStyleClass(Node node, String styleClass, String... excludes) {
@ -187,9 +187,9 @@ public final class Styles {
* This method is supposed to be used when only one from a set of pseudo-classes
* have to be present at once.
*
* @param node the node to activate the pseudo-class on
* @param pseudoClass the pseudo-class to be activated
* @param excludes the pseudo-classes to be deactivated
* @param node The node to activate the pseudo-class on.
* @param pseudoClass The pseudo-class to be activated.
* @param excludes The pseudo-classes to be deactivated.
* @throws NullPointerException if node or pseudo-class is null
*/
public static void activatePseudoClass(Node node, PseudoClass pseudoClass, PseudoClass... excludes) {
@ -213,9 +213,9 @@ public final class Styles {
* There's no check for duplicates, so the CSS declarations with the same property
* name can be appended multiple times.
*
* @param node the node to append the new style declaration
* @param prop CSS property name
* @param value CSS property value
* @param node The node to append the new style declaration.
* @param prop The CSS property name.
* @param value The CSS property value.
* @throws NullPointerException if node is null
*/
public static void appendStyle(Node node, String prop, String value) {
@ -239,8 +239,8 @@ public final class Styles {
/**
* Removes the specified CSS style declaration from the specified node.
*
* @param node the node to remove the style from
* @param prop the name of the style property to remove
* @param node The node to remove the style from.
* @param prop The name of the style property to remove.
* @throws NullPointerException if node is null
*/
@SuppressWarnings("StringSplitter")
@ -284,8 +284,8 @@ public final class Styles {
* node.getStylesheets().remove(dataUri);
* </pre>
*
* @param css the CSS string to encode
* @return the resulting data URI string
* @param css The CSS string to encode.
* @return The resulting data URI string.
*/
public static String toDataURI(String css) {
if (css == null) {

@ -9,7 +9,7 @@ import javafx.application.Application;
import org.jetbrains.annotations.Nullable;
/**
* Basic theme interface.
* The basic theme interface.
*/
public interface Theme {
@ -39,7 +39,7 @@ public interface Theme {
boolean isDarkMode();
/**
* Simple factory method for instantiating a new theme.
* A simple factory method for instantiating a new theme.
*/
static Theme of(final String name, final String userAgentStylesheet, final boolean darkMode) {
if (name == null) {

@ -11,10 +11,21 @@ import java.util.stream.Stream;
import javafx.css.Stylesheet;
/**
* Lazy man CSS to BSS compiler wrapper.
* A lazy man CSS to BSS compiler wrapper.
*/
public class ThemeCompiler {
/**
* The main class that accepts exactly one parameter, which is the path to
* the source directory to be scanned for CSS files.
*
* <p>Usage:
* <pre>{@code
* java ThemeCompiler <path>
* }</pre>
*
* @see #convertToBinary(Path)
*/
public static void main(String[] args) {
try {
if (args.length < 1) {
@ -38,7 +49,7 @@ public class ThemeCompiler {
/**
* Converts all CSS files in the specified directory to BSS.
*
* @param dir the source directory to scan for CSS files
* @param dir The source directory to scan for CSS files.
* @throws IOException to punish you for using Java
*/
public void convertToBinary(Path dir) throws IOException {
@ -62,8 +73,8 @@ public class ThemeCompiler {
* Converts the specified CSS file to BSS. If no output file is given,
* then the input file name is used with an extension of 'bss'.
*
* @param in input file path
* @param out output file path
* @param in The input file path.
* @param out The output file path.
* @throws IOException to punish you for using Java
*/
public void convertToBinary(Path in, Path out) throws IOException {

@ -3,9 +3,9 @@
package atlantafx.base.theme;
/**
* Contains extra style class names introduced to tweak some controls view if and where it makes sense.
* The reason of supporting tweaks is to allow users to write less CSS code. Search for #tweak/classname
* to find the controls supporting tweaks or check the control page in the Sampler app.
* Contains extra style class names introduced to tweak some controls view
* if and where it makes sense. The reason of supporting tweaks is to allow
* users to write less CSS code.
*/
public final class Tweaks {

@ -0,0 +1,5 @@
/**
* Contains provided themes and style constants.
*/
package atlantafx.base.theme;

@ -38,7 +38,7 @@ public final class Animations {
* Changes the node opacity to full transparency and then back to its
* original opacity in quick succession, creating a flashing effect.
*
* @param node the node to be animated
* @param node The node to be animated.
*/
public static Timeline flash(Node node) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -81,8 +81,8 @@ public final class Animations {
* Repeatedly increases and decreases the scale of the node,
* giving it a pulsating effect that draws attention to it.
*
* @param node the node to be animated
* @param scale the scale factor
* @param node The node to be animated.
* @param scale The scale factor.
*/
public static Timeline pulse(Node node, double scale) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -127,8 +127,8 @@ public final class Animations {
* Rapidly moves the node from side-to-side horizontally,
* creating a shaking or vibrating effect.
*
* @param node the node to be animated
* @param offset the shake offset
* @param node The node to be animated.
* @param offset The shake offset.
*/
public static Timeline shakeX(Node node, double offset) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -186,8 +186,8 @@ public final class Animations {
* Rapidly moves the node up and down vertically, creating
* a shaking or bouncing effect.
*
* @param node the node to be animated
* @param offset the shake offset
* @param node The node to be animated.
* @param offset The shake offset.
*/
public static Timeline shakeY(Node node, double offset) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -241,7 +241,7 @@ public final class Animations {
* Causes the node to rapidly wobble back and forth,
* creating a visually engaging effect.
*
* @param node the node to be animated
* @param node The node to be animated.
*/
public static Timeline wobble(Node node) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -295,8 +295,8 @@ public final class Animations {
* Gradually increases the opacity of the node from 0 to 1,
* making it appear on the scene with a fading-in effect.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeIn(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -324,8 +324,8 @@ public final class Animations {
* Gradually decreases the opacity of the node from 1 to 0,
* making it disappear from the scene with a fading-out effect.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeOut(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -353,8 +353,8 @@ public final class Animations {
* Combines the {@link #fadeIn(Node, Duration)} effect with the nodes downward
* movement, creating an animated entrance of the node from the top.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeInDown(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -386,8 +386,8 @@ public final class Animations {
* Combines the {@link #fadeOut(Node, Duration)} effect with the nodes downward
* movement, creating an animated exit of the node to the bottom.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeOutDown(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -418,8 +418,8 @@ public final class Animations {
* Combines the {@link #fadeIn(Node, Duration)} effect with the nodes leftward
* movement, creating an animated entrance of the node from the left.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeInLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -450,8 +450,8 @@ public final class Animations {
* Combines the {@link #fadeOut(Node, Duration)} effect with the nodes leftward
* movement, creating an animated exit of the node to the left.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeOutLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -482,8 +482,8 @@ public final class Animations {
* Combines the {@link #fadeIn(Node, Duration)} effect with the nodes rightward
* movement, creating an animated entrance of the node from the right.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeInRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -514,8 +514,8 @@ public final class Animations {
* Combines the {@link #fadeOut(Node, Duration)} effect with the nodes rightward
* movement, creating an animated exit of the node to the right.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeOutRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -546,8 +546,8 @@ public final class Animations {
* Combines the {@link #fadeIn(Node, Duration)} effect with the nodes upward
* movement, creating an animated entrance of the node from the bottom.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeInUp(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -578,8 +578,8 @@ public final class Animations {
* Combines the {@link #fadeOut(Node, Duration)} effect with the nodes upward
* movement, creating an animated exit of the node to the top.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline fadeOutUp(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -614,8 +614,8 @@ public final class Animations {
* Applies an animated effect to the node causing it to roll into
* the scene from the left side at an angle.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rollIn(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -649,8 +649,8 @@ public final class Animations {
* Applies an animated effect to the node causing it to roll out
* from the scene to the right side at an angle.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rollOut(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -688,8 +688,8 @@ public final class Animations {
* Rotates the node and gradually increases its opacity,
* giving it an animated entrance effect.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateIn(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -722,8 +722,8 @@ public final class Animations {
* Rotates the node and gradually decreases its opacity,
* giving it an animated exit effect.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateOut(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -757,8 +757,8 @@ public final class Animations {
* movement from the left, creating an animated entrance of the node from the top
* left corner.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateInDownLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -794,8 +794,8 @@ public final class Animations {
* movement to the left, creating an animated exit of the node towards the bottom
* left corner.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateOutDownLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -831,8 +831,8 @@ public final class Animations {
* movement from the right, creating an animated entrance of the node from the top
* right corner.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateInDownRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -871,8 +871,8 @@ public final class Animations {
* movement to the right, creating an animated exit of the node towards the bottom
* right corner.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateOutDownRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -911,8 +911,8 @@ public final class Animations {
* movement from the left, creating an animated entrance of the node from the
* bottom left corner.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateInUpLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -948,8 +948,8 @@ public final class Animations {
* movement to the left, creating an animated exit of the node towards the top
* left corner.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateOutUpLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -985,8 +985,8 @@ public final class Animations {
* movement from the right, creating an animated entrance of the node from the
* bottom right corner.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateInUpRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1025,8 +1025,8 @@ public final class Animations {
* movement to the right, creating an animated exit of the node towards the top
* right corner.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline rotateOutUpRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1068,8 +1068,8 @@ public final class Animations {
* Applies an animated effect to the node, causing it to slide into view
* from the top side.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline slideInDown(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1097,8 +1097,8 @@ public final class Animations {
* Applies an animated effect to the node, causing it to slide out of view
* through the bottom side.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline slideOutDown(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1126,8 +1126,8 @@ public final class Animations {
* Applies an animated effect to the node, causing it to slide into view
* from the left side.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline slideInLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1155,8 +1155,8 @@ public final class Animations {
* Applies an animated effect to the node, causing it to slide out of view
* through the left side.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline slideOutLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1184,8 +1184,8 @@ public final class Animations {
* Applies an animated effect to the node, causing it to slide into view
* from the right side.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline slideInRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1213,8 +1213,8 @@ public final class Animations {
* Applies an animated effect to the node, causing it to slide out of view
* through the right side.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline slideOutRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1242,8 +1242,8 @@ public final class Animations {
* Applies an animated effect to the node, causing it to slide into view
* from the bottom side.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline slideInUp(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1271,8 +1271,8 @@ public final class Animations {
* Applies an animated effect to the node, causing it to slide out of view
* through the top side.
*
* @param node the node to be animated
* @param duration the animation duration
* @param node The node to be animated.
* @param duration The animation duration.
*/
public static Timeline slideOutUp(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1311,9 +1311,9 @@ public final class Animations {
* Increases the scale of the node, starting from a smaller size and gradually
* zooming it to the regular size, emphasizing the nodes entrance.
*
* @param node the node to be animated
* @param duration the animation duration
* @param startValue the initial zoom value
* @param node The node to be animated.
* @param duration The animation duration.
* @param startValue The initial zoom value.
*/
public static Timeline zoomIn(Node node, Duration duration, double startValue) {
Objects.requireNonNull(node, "Node cannot be null!");
@ -1355,9 +1355,9 @@ public final class Animations {
* its original size and gradually zooms out to a smaller size, emphasizing
* the nodes exit.
*
* @param node the node to be animated
* @param duration the animation duration
* @param endValue the target zoom value
* @param node The node to be animated.
* @param duration The animation duration.
* @param endValue The target zoom value.
*/
public static Timeline zoomOut(Node node, Duration duration, double endValue) {
Objects.requireNonNull(node, "Node cannot be null!");

@ -36,7 +36,7 @@ import javafx.scene.text.TextFlow;
import org.jetbrains.annotations.Nullable;
/**
* Basic handler interface for the {@link BBCodeParser} that will
* The basic handler interface for the {@link BBCodeParser} that will
* receive notifications while processing user input text.
*/
public interface BBCodeHandler {
@ -57,10 +57,10 @@ public interface BBCodeHandler {
* Notifies about the start of the tag.
* In case of self-closing tag this also notifies about the end of the tag.
*
* @param name tag name
* @param params tag params
* @param start tag start position, i.e. the position of open square bracket (not the tag name start)
* @param length tag length, including closing bracket
* @param name The tag name.
* @param params The tag params.
* @param start The tag start position, i.e. the position of open square bracket (not the tag name start).
* @param length The tag length, including closing bracket.
*/
void startTag(String name, @Nullable Map<String, String> params, int start, int length);
@ -68,9 +68,9 @@ public interface BBCodeHandler {
* Notifies about the end of the tag.
* In case of self-closing tag only {@link #startTag(String, Map, int, int)} method is called.
*
* @param name tag name
* @param start tag start position, i.e. the position of open square bracket (not the tag name start)
* @param length tag length, including closing bracket
* @param name The tag name.
* @param start The tag start position, i.e. the position of open square bracket (not the tag name start).
* @param length The tag length, including closing bracket.
*/
void endTag(String name, int start, int length);
@ -78,8 +78,8 @@ public interface BBCodeHandler {
* Notifies about characters data that doesn't belong to any tag, i.e.
* leading, intermediate or trailing text.
*
* @param start text start position
* @param length text length
* @param start The text start position.
* @param length The text length.
*/
void characters(int start, int length);
@ -89,9 +89,9 @@ public interface BBCodeHandler {
* A basic {@link BBCodeHandler} implementation.<br/><br/>
*
* <p>While parsing all created nodes will be added to the given root container.
* The choice depends on the actual markup. Default constructor accepts any {@link Pane}
* or its descendant. Using {@link TextFlow} for text-only markup (no block nodes) and
* {@link VBox} otherwise, is recommended.<br/><br/>
* The container choice depends on the actual markup. Default constructor accepts any
* {@link Pane} or its descendant. Using the{@link TextFlow} for text-only markup
* (no block nodes) and {@link VBox} otherwise, is recommended.<br/><br/>
*
* <h3>Supported tags</h3><br/>
* <pre>
@ -149,16 +149,17 @@ public interface BBCodeHandler {
* </pre>
*
* <ul>
* <li>If tag param contains whitespaces or trailing slash is must be
* <li>If a tag param contains whitespaces or trailing slash is must be
* enclosed in double or single quotes.
* <li>If tag only has a single param, it can be shortened to {@code [name=value]{text}[/name]}.
* In that case tag param name considered to be equal to the tag name.
* <li>If a tag only has a single param, it can be shortened to the
* {@code [name=value]{text}[/name]}. In this case the tag param name
* considered to be equal to the tag name.
* <li>Unknown tag params will be ignored.
* </ul>
*
* <h3>Action Events</h3><br/>
* Some nodes, e.g. {@link Hyperlink} require action handlers. To avoid traversing
* the root node graph you can add an event filter.
* the root container's node graph you can add an event filter.
*
* <pre>{@code
* var input = "Visit the [url=https://example.com]website[/url].";
@ -672,10 +673,10 @@ public interface BBCodeHandler {
}
/**
* Generic block record.
* A generic block record.
*
* @param node the node that represents the block
* @param text text content
* @param node The node that represents the block.
* @param text The text content.
*/
record Block(Pane node, @Nullable TextFlow text) {
@ -712,10 +713,12 @@ public interface BBCodeHandler {
/**
* Generic tag record.
*
* @param name tag name
* @param params tag params
* @param styleClasses CSS classes, each element is either a single style or space delimited string
* @param styles CSS styles, each element is either a single style or semicolon delimited string
* @param name The tag name.
* @param params The tag params.
* @param styleClasses The CSS classes.
* Each element is either a single style or space delimited string.
* @param styles The CSS styles.
* Each element is either a single style or semicolon delimited string.
*/
record Tag(String name,
Type type,

@ -22,10 +22,11 @@ import org.jetbrains.annotations.Nullable;
*
* <p>The parser doesn't impose restrictions on tag names or tag params.
* It's a handler implementation responsibility to differentiate supported
* tags from unsupported and so to for tag params. This allows user to utilize
* tags from unsupported and so to for the tag params. This allows user to utilize
* arbitrary tags or params without changing the parser behaviour. The parser,
* however, verifies that each opening tag has the matching closing tag.
* If parsing is failed due to invalid input an {@link IllegalStateException}
*
* <p>If parsing is failed due to invalid input an {@link IllegalStateException}
* will be thrown.
*/
public class BBCodeParser {
@ -53,7 +54,9 @@ public class BBCodeParser {
private int lastClosingPos = 0;
/**
* See {@link #BBCodeParser(String, BBCodeHandler, Set)}.
* Creates a new parser.
*
* @see #BBCodeParser(String, BBCodeHandler, Set).
*/
public BBCodeParser(String input, BBCodeHandler handler) {
this(input, handler, RESERVED_TAGS);
@ -62,9 +65,9 @@ public class BBCodeParser {
/**
* Creates a new parser.
*
* @param input an input non-null string
* @param handler a {@link BBCodeHandler} implementation
* @param tags the list of processed tags, i.e. the tags that parser won't ignore
* @param input An input non-null string.
* @param handler A {@link BBCodeHandler} implementation.
* @param tags The list of processed tags, i.e. the tags that parser won't ignore.
*/
public BBCodeParser(String input, BBCodeHandler handler, @Nullable Set<String> tags) {
this.input = Objects.requireNonNull(input, "Input can't be null.");
@ -73,8 +76,8 @@ public class BBCodeParser {
}
/**
* Starts input parsing. There's no way to stop the process until
* parsing is finished.
* Starts input parsing.
* There's no way to stop the process until parsing is finished.
*/
public void parse() {
handler.startDocument(input.toCharArray());
@ -175,8 +178,9 @@ public class BBCodeParser {
* Parses the given string using BBCode markup and returns corresponding layout.
* This is a shorthand method for using the feature.
*
* @param input BBCode markup string
* @param container root container
* @param input The BBCode markup string.
* @param container The root container.
* @see BBCodeHandler
*/
public static <T extends Pane> T createLayout(String input, T container) {
var handler = new BBCodeHandler.Default<>(container);

@ -13,16 +13,16 @@ import javafx.scene.control.Tooltip;
import javafx.util.StringConverter;
/**
* Converts between user-edited strings and {@link Double} values.
* Accepts an optional {@link Runnable} that resets the editor on {@link NumberFormatException},
* or a {@link TextField} or {@link Spinner} that is preemptively monitored for invalid
* input during typing, and restricts valid input to a specified range when committed.
* <p>
* This implementation shows up to two decimal digits, but only if a fractional part exists.
* The default implementation always shows one decimal digit which hinders typing.</p>
* Converts between user-edited strings and Double values.
*
* <p>Accepts an optional Runnable that resets the editor on {@code NumberFormatException},
* or a TextField or Spinner that is preemptively monitored for invalid input
* during typing, and restricts valid input to a specified range when committed.
*
* <p>This implementation shows up to two decimal digits, but only if a fractional part
* exists. The default implementation always shows one decimal digit which hinders typing.
*
* @author Christoph Nahr
* @version 1.0.2
*/
public class DoubleStringConverter extends StringConverter<Double> {
@ -30,8 +30,9 @@ public class DoubleStringConverter extends StringConverter<Double> {
private Runnable reset;
/**
* Creates a {@link DoubleStringConverter}.
* Swallows {@link NumberFormatException} but does nothing
* Creates a DoubleStringConverter.
*
* <p>Swallows {@code NumberFormatException} but does nothing
* in response until {@link #setReset} is defined.
*/
public DoubleStringConverter() {
@ -39,26 +40,27 @@ public class DoubleStringConverter extends StringConverter<Double> {
}
/**
* Creates a {@link DoubleStringConverter} with an editor reset callback.
* Specifying {@code null} has the same effect as the default constructor.
* Creates a DoubleStringConverter with an editor reset callback.
* Specifying null has the same effect as the default constructor.
*
* @param reset the {@link Runnable} to call upon {@link NumberFormatException}
* @param reset the Runnable to call upon {@code NumberFormatException}
*/
public DoubleStringConverter(Runnable reset) {
this.reset = reset;
}
/**
* Creates a {@link DoubleStringConverter} with the specified input range.
* Preemptively monitors {@code input} to reject any invalid characters during
* typing, restricts {@code input} to [{@code min}, {@code max}] (inclusive) when
* valid text is committed, and resets {@code input} to the closest value to zero
* Creates a DoubleStringConverter with the specified input range.
*
* <p>Preemptively monitors input to reject any invalid characters during
* typing. Restricts input to [{@code min}, {@code max}] (inclusive) when
* valid text is committed, and resets input to the closest value to zero
* within [{@code min}, {@code max}] when invalid text is committed.
*
* @param input the {@link TextField} providing user-edited strings
* @param min the smallest valid {@link Double} value
* @param max the greatest valid {@link Double} value
* @throws NullPointerException if {@code input} is {@code null}
* @param input The TextField providing user-edited strings.
* @param min The smallest valid value.
* @param max The greatest valid value.
* @throws NullPointerException if input is {@code null}.
*/
public DoubleStringConverter(TextField input, double min, double max) {
if (input == null) {
@ -68,11 +70,6 @@ public class DoubleStringConverter extends StringConverter<Double> {
final double resetValue = Math.min(Math.max(0, min), max);
reset = () -> input.setText(decimalFormat.format(resetValue));
// bound JavaFX properties cannot be explicitly set
// if (!input.tooltipProperty().isBound()) {
// input.setTooltip(new Tooltip(String.format("Enter a value between %.2f and %.2f", min, max)));
// }
// restrict direct input to valid numerical characters
input.textProperty().addListener((ov, oldValue, newValue) -> {
if (newValue == null || newValue.isEmpty()) {
@ -115,14 +112,15 @@ public class DoubleStringConverter extends StringConverter<Double> {
}
/**
* Creates a {@link DoubleStringConverter} for the specified {@link Spinner}.
* Uses the {@link TextField} and minimum and maximum values of the specified
* {@link Spinner} for construction, and also sets the new {@link DoubleStringConverter}
* Creates a DoubleStringConverter for the specified Spinner.
*
* <p>Uses the TextField and minimum and maximum values of the specified
* Spinner for construction, and also sets the new DoubleStringConverter
* on its {@link SpinnerValueFactory.DoubleSpinnerValueFactory}.
*
* @param spinner the {@link Spinner} to create a {@link DoubleStringConverter} for
* @return the new {@link DoubleStringConverter}
* @throws NullPointerException if {@code spinner} is {@code null}
* @param spinner The Spinner to create a DoubleStringConverter for.
* @return the new DoubleStringConverter
* @throws NullPointerException if the Spinner is {@code null}
*/
public static DoubleStringConverter createFor(Spinner<Double> spinner) {
final SpinnerValueFactory.DoubleSpinnerValueFactory factory =
@ -141,13 +139,14 @@ public class DoubleStringConverter extends StringConverter<Double> {
/**
* Sets the editor reset callback.
* Specify {@code null} to clear a previously set {@link Runnable}. When creating
* a {@link DoubleStringConverter} for a {@link TextField} or {@link Spinner},
* this callback is automatically defined to reset committed invalid input to the
* closest value to zero within the legal range. Setting a different callback
* will overwrite this functionality.
*
* @param reset the {@link Runnable} to call upon {@link NumberFormatException}
* <p>Specify {@code null} to clear a previously set Runnable. When creating
* a DoubleStringConverter for a TextField or Spinner, this callback is
* automatically defined to reset committed invalid input to the closest value
* to zero within the legal range. Setting a different callback will overwrite this
* functionality.
*
* @param reset The Runnable to call upon NumberFormatException.
* @see #fromString
*/
public void setReset(Runnable reset) {
@ -155,12 +154,12 @@ public class DoubleStringConverter extends StringConverter<Double> {
}
/**
* Converts the specified {@link String} into its {@link Double} value.
* Converts the specified string into its double value.
* A {@code null}, empty, or otherwise invalid argument returns zero
* and also executes the editor reset callback, if any.
*
* @param s the {@link String} to convert
* @return the {@link Double} value of {@code s}
* @param s The string to convert.
* @return the double value of {@code s}
* @see #setReset
*/
@Override
@ -183,11 +182,11 @@ public class DoubleStringConverter extends StringConverter<Double> {
}
/**
* Converts the specified {@link Double} into its {@link String} form.
* Converts the specified double into its string form.
* A {@code null} argument is converted into the literal string "0".
*
* @param value the {@link Double} to convert
* @return the {@link String} form of {@code value}
* @param value The Double to convert.
* @return the string form of {@code value}
*/
@Override
public String toString(Double value) {

@ -12,47 +12,48 @@ import javafx.scene.control.Tooltip;
import javafx.util.StringConverter;
/**
* Converts between user-edited strings and {@link Integer} values.
* Accepts an optional {@link Runnable} that resets the editor on {@link NumberFormatException},
* or a {@link TextField} or {@link Spinner} that is preemptively monitored for invalid
* Converts between user-edited strings and integer values.
*
* <p>Accepts an optional Runnable that resets the editor on NumberFormatException,
* or a TextField or Spinner that is preemptively monitored for invalid
* input during typing, and restricts valid input to a specified range when committed.
*
* @author Christoph Nahr
* @version 1.0.2
*/
public class IntegerStringConverter extends StringConverter<Integer> {
private Runnable reset;
/**
* Creates an {@link IntegerStringConverter}.
* Swallows {@link NumberFormatException} but does nothing
* Creates an IntegerStringConverter.
* Swallows NumberFormatException but does nothing
* in response until {@link #setReset} is defined.
*/
public IntegerStringConverter() {
}
/**
* Creates an {@link IntegerStringConverter} with an editor reset callback.
* Creates an IntegerStringConverter with an editor reset callback.
* Specifying {@code null} has the same effect as the default constructor.
*
* @param reset the {@link Runnable} to call upon {@link NumberFormatException}
* @param reset The Runnable to call upon NumberFormatException.
*/
public IntegerStringConverter(Runnable reset) {
this.reset = reset;
}
/**
* Creates an {@link IntegerStringConverter} with the specified input range.
* Preemptively monitors {@code input} to reject any invalid characters during
* typing, restricts {@code input} to [{@code min}, {@code max}] (inclusive) when
* valid text is committed, and resets {@code input} to the closest value to zero
* Creates an IntegerStringConverter with the specified input range.
*
* <p>Preemptively monitors input to reject any invalid characters during
* typing, restricts input to [{@code min}, {@code max}] (inclusive) when
* valid text is committed, and resets input to the closest value to zero
* within [{@code min}, {@code max}] when invalid text is committed.
*
* @param input the {@link TextField} providing user-edited strings
* @param min the smallest valid {@link Integer} value
* @param max the greatest valid {@link Integer} value
* @throws NullPointerException if {@code input} is {@code null}
* @param input The TextField providing user-edited strings.
* @param min The smallest valid integer value.
* @param max The greatest valid integer value.
* @throws NullPointerException if input is {@code null}
*/
public IntegerStringConverter(TextField input, int min, int max) {
if (input == null) {
@ -62,11 +63,6 @@ public class IntegerStringConverter extends StringConverter<Integer> {
final int resetValue = Math.min(Math.max(0, min), max);
reset = () -> input.setText(Integer.toString(resetValue));
// bound JavaFX properties cannot be explicitly set
// if (!input.tooltipProperty().isBound()) {
// input.setTooltip(new Tooltip(String.format("Enter a value between %d and %d", min, max)));
// }
// restrict direct input to valid numerical characters
input.textProperty().addListener((ov, oldValue, newValue) -> {
if (newValue == null || newValue.isEmpty()) {
@ -109,13 +105,13 @@ public class IntegerStringConverter extends StringConverter<Integer> {
}
/**
* Creates an {@link IntegerStringConverter} for the specified {@link Spinner}.
* Uses the {@link TextField} and minimum and maximum values of the specified
* {@link Spinner} for construction, and also sets the new {@link IntegerStringConverter}
* Creates an IntegerStringConverter for the specified Spinner.
* Uses the TextField and minimum and maximum values of the specified
* Spinner for construction, and also sets the new IntegerStringConverter
* on its {@link SpinnerValueFactory.IntegerSpinnerValueFactory}.
*
* @param spinner the {@link Spinner} to create an {@link IntegerStringConverter} for
* @return the new {@link IntegerStringConverter}
* @param spinner The Spinner to create an IntegerStringConverter for.
* @return the new IntegerStringConverter
* @throws NullPointerException if {@code spinner} is {@code null}
*/
public static IntegerStringConverter createFor(Spinner<Integer> spinner) {
@ -135,13 +131,14 @@ public class IntegerStringConverter extends StringConverter<Integer> {
/**
* Sets the editor reset callback.
* Specify {@code null} to clear a previously set {@link Runnable}. When creating
* an {@link IntegerStringConverter} for a {@link TextField} or {@link Spinner},
* this callback is automatically defined to reset committed invalid input to the
* closest value to zero within the legal range. Setting a different callback
* will overwrite this functionality.
*
* @param reset the {@link Runnable} to call upon {@link NumberFormatException}
* <p>Specify {@code null} to clear a previously set Runnable. When creating
* an IntegerStringConverter for a TextField or Spinner, this callback is
* automatically defined to reset committed invalid input to the closest value
* to zero within the legal range. Setting a different callback will overwrite
* this functionality.
*
* @param reset The Runnable to call upon NumberFormatException.
* @see #fromString
*/
public void setReset(Runnable reset) {
@ -149,12 +146,12 @@ public class IntegerStringConverter extends StringConverter<Integer> {
}
/**
* Converts the specified {@link String} into its {@link Integer} value.
* Converts the specified string into its integer value.
* A {@code null}, empty, or otherwise invalid argument returns zero
* and also executes the editor reset callback, if any.
*
* @param s the {@link String} to convert
* @return the {@link Integer} value of {@code s}
* @param s The {@link String} to convert.
* @return the integer value of {@code s}
* @see #setReset
*/
@Override
@ -177,10 +174,10 @@ public class IntegerStringConverter extends StringConverter<Integer> {
}
/**
* Converts the specified {@link Integer} into its {@link String} form.
* Converts the specified integer into its string form.
* A {@code null} argument is converted into the literal string "0".
*
* @param value the {@link Integer} to convert
* @param value The integer to convert.
* @return the {@link String} form of {@code value}
*/
@Override

@ -26,7 +26,7 @@ public interface MaskChar {
char INPUT_MASK_DIGIT_ZERO = '0';
/**
* Returns true if the character is allowed, false otherwise.
* Returns "true" if the character is allowed, "false" otherwise.
*/
boolean isAllowed(char ch);

@ -36,14 +36,19 @@ import org.jetbrains.annotations.Nullable;
* </ul>
*
* <p><h3>Behavior</h3>
* Any {@code TextField} with {@code MaskTextFormatter} applied shows a placeholder mask by default.
* This is basically the input mask with all mask characters replaced with {@link MaskChar#getPlaceholder()}.
* The behavior changes if you set {@link TextField#promptTextProperty()}. In that case placeholder
* mask is only displayed when {@code TextField} gets focus and will be hidden after focus lost.
* So, the placeholder mask is always displayed when focus is set to the {@code TextField}.
* You can replace the placeholder mask with any sensible default simply by changing initial
* {@code TextField} text to any string that is valid against the input mask.
* <br/><br/>The caret will be positioned before the first not fixed character (see {@link MaskChar#isFixed()})
* Any {@code TextField} with {@code MaskTextFormatter} applied shows a placeholder
* mask by default. This is basically the input mask with all mask characters replaced
* with the {@link MaskChar#getPlaceholder()} character.
*
* <p>The behavior changes if you set the {@link TextField#promptTextProperty()}.
* In that case placeholder mask is only displayed when {@code TextField} gets focus and
* will be hidden after focus lost. So, the placeholder mask is always displayed when focus
* is set to the {@code TextField}.
*
* <p>You can replace the placeholder mask with any sensible default simply by changing initial
* {@code TextField} text to any string that is valid against the input mask.<br/><br/>
*
* <p>The caret will be positioned before the first not fixed character (see {@link MaskChar#isFixed()})
* starting from the beginning of the input mask.<br/><br/>
*
* <p><h3>Validation</h3>
@ -68,8 +73,6 @@ public class MaskTextFormatter extends TextFormatter<String> {
/**
* Creates a new text field with the provided string input mask.
* Use this if you create your controls from Java code and don't need to
* modify the default {@link MaskChar} implementation.
*/
public static TextField createTextField(String mask) {
return createTextField(fromString(mask));
@ -77,8 +80,6 @@ public class MaskTextFormatter extends TextFormatter<String> {
/**
* Creates a new text field with the provided input mask.
* Use this if you create your controls from Java code and want to
* modify the default {@link MaskChar} implementation.
*/
public static TextField createTextField(List<MaskChar> mask) {
final var field = new TextField();
@ -88,9 +89,7 @@ public class MaskTextFormatter extends TextFormatter<String> {
/**
* Creates a new mask text formatter with the provided string input mask and
* applies itself to the specified text field. Use this if you create your
* controls from FXML and don't need to modify the default {@link MaskChar}
* implementation.
* applies itself to the specified text field.
*/
public static MaskTextFormatter create(TextField field, String mask) {
return create(field, fromString(mask));
@ -98,8 +97,7 @@ public class MaskTextFormatter extends TextFormatter<String> {
/**
* Creates a new mask text formatter with the provided input mask and
* applies itself to the specified text field. Use this if you create your
* controls from FXML and want to modify the default {@link MaskChar} implementation.
* applies itself to the specified text field.
*/
public static MaskTextFormatter create(TextField field, List<MaskChar> mask) {
Objects.requireNonNull(field, "Text field can't be null");

@ -8,13 +8,14 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;
/**
* An alternative for the {@link javafx.scene.control.PasswordField}.
* The formatter (un)masks text field content based on boolean property.
* An alternative to the {@link PasswordField} class. This formatter masks
* or unmasks text field content based on a boolean property.
*/
public class PasswordTextFormatter extends TextFormatter<String> {

@ -3,8 +3,8 @@
package atlantafx.base.util;
/**
* Utility class that provides just some basic methods that's commonly necessary
* for control/skin development.
* A utility class that provides just some platform methods that's commonly
* necessary for control/skin development.
*/
public final class PlatformUtils {
@ -20,18 +20,30 @@ public final class PlatformUtils {
private static final boolean OPEN_BSD = OS.startsWith("openbsd");
private static final boolean NET_BSD = OS.startsWith("netbsd");
/**
* Returns "true" if this is Windows.
*/
public static boolean isWindows() {
return WINDOWS;
}
/**
* Returns "true" if this is Mac.
*/
public static boolean isMac() {
return MAC;
}
/**
* Returns "true" if this is Linux.
*/
public static boolean isLinux() {
return LINUX;
}
/**
* Returns "true" if this is a UNIX like system.
*/
public static boolean isUnix() {
return LINUX || FREE_BSD || OPEN_BSD || NET_BSD;
}

@ -17,21 +17,53 @@ public final class SimpleMaskChar implements MaskChar {
private final char placeholder;
private final boolean fixed;
/**
* Creates a SimpleMaskChar.
*
* @param matchExpr The matching predicate that determines which characters are masked.
* @see #SimpleMaskChar(Predicate, UnaryOperator, char, boolean)
*/
public SimpleMaskChar(Predicate<Character> matchExpr) {
this(matchExpr, UnaryOperator.identity(), UNDERSCORE, false);
}
/**
* Creates a SimpleMaskChar.
*
* @param matchExpr The matching predicate that determines which characters are masked.
* @param transform The transformation function that is applied to input characters.
* @see #SimpleMaskChar(Predicate, UnaryOperator, char, boolean)
*/
public SimpleMaskChar(Predicate<Character> matchExpr,
UnaryOperator<Character> transform) {
this(matchExpr, transform, UNDERSCORE, false);
}
/**
* Creates a SimpleMaskChar.
*
* @param matchExpr The matching predicate that determines which characters are masked.
* @param transform The transformation function that is applied to input characters.
* @param placeholder The placeholder character to use for masking.
* @see #SimpleMaskChar(Predicate, UnaryOperator, char, boolean)
*/
public SimpleMaskChar(Predicate<Character> matchExpr,
UnaryOperator<Character> transform,
char placeholder) {
this(matchExpr, transform, placeholder, false);
}
/**
* Creates a SimpleMaskChar.
*
* @param matchExpr The matching predicate that determines which characters are masked.
* @param transform The transformation function that is applied to input characters.
* No transformation is applied by default.
* @param placeholder The placeholder character to use for masking.
* The default replacement is underscore character.
* @param fixed Boolean value indicating if the character is fixed or not.
* Default is false.
*/
public SimpleMaskChar(Predicate<Character> matchExpr,
UnaryOperator<Character> transform,
char placeholder,
@ -42,26 +74,42 @@ public final class SimpleMaskChar implements MaskChar {
this.fixed = fixed;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isAllowed(final char ch) {
return matchExpr.test(ch);
}
/**
* {@inheritDoc}
*/
@Override
public char transform(final char ch) {
return transform.apply(ch);
}
/**
* {@inheritDoc}
*/
@Override
public char getPlaceholder() {
return placeholder;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isFixed() {
return fixed;
}
/**
* A utility method for creating a fixed character - that is, the character used to represent
* the fixed part (a prefix or a suffix) of the input mask.
*/
public static SimpleMaskChar fixed(char ch) {
return new SimpleMaskChar(c -> c == ch, UnaryOperator.identity(), ch, true);
}

@ -0,0 +1,5 @@
/**
* Provides various utility classes, formatters and converters.
*/
package atlantafx.base.util;

@ -1,4 +1,7 @@
/* SPDX-License-Identifier: MIT */
/**
* Provides additional controls, layout and Java API for
* custom themes support.
*/
module atlantafx.base {