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; package atlantafx.base.controls;
import javafx.scene.control.Control; import javafx.scene.control.Control;
import javafx.scene.control.SkinBase; 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 C control;
private S skin; 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) { protected BehaviorBase(C control, S skin) {
this.control = control; this.control = control;
this.skin = skin; this.skin = skin;
} }
/**
* Gets the control associated with this behavior.
*
* @return The control for this behavior.
*/
public C getControl() { public C getControl() {
return control; return control;
} }
/**
* Gets the skin associated with this behavior.
*
* @return The control for this behavior.
*/
public S getSkin() { public S getSkin() {
return skin; 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; package atlantafx.base.controls;
@ -7,21 +12,48 @@ import javafx.beans.value.ObservableValue;
import javafx.scene.control.Control; import javafx.scene.control.Control;
import javafx.scene.control.SkinBase; 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> { public abstract class BehaviorSkinBase<C extends Control, B extends BehaviorBase<C, ?>> extends SkinBase<C> {
protected B behavior; protected B behavior;
/**
* Constructor for all BehaviorSkinBase instances.
*
* @param control The control for which this Skin should attach to.
*/
protected BehaviorSkinBase(C control) { protected BehaviorSkinBase(C control) {
super(control); super(control);
behavior = createDefaultBehavior(); behavior = createDefaultBehavior();
} }
/**
* An abstract method for creating the behavior instance to be used by this skin.
*/
public abstract B createDefaultBehavior(); public abstract B createDefaultBehavior();
/**
* Gets the control associated with this skin.
*
* @return The control for this Skin.
*/
public C getControl() { public C getControl() {
return getSkinnable(); return getSkinnable();
} }
/**
* Gets the behavior associated with this skin.
*
* @return The behavior for this skin.
*/
public B getBehavior() { public B getBehavior() {
return behavior; return behavior;
} }

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

@ -41,17 +41,23 @@ import javafx.scene.control.ButtonBase;
import javafx.scene.control.SkinBase; import javafx.scene.control.SkinBase;
import javafx.scene.control.TreeItem.TreeModificationEvent; import javafx.scene.control.TreeItem.TreeModificationEvent;
/**
* The default skin for the {@link Breadcrumbs} control.
*/
public class BreadcrumbsSkin<T> extends SkinBase<Breadcrumbs<T>> { public class BreadcrumbsSkin<T> extends SkinBase<Breadcrumbs<T>> {
protected static final PseudoClass FIRST = PseudoClass.getPseudoClass("first"); protected static final PseudoClass FIRST = PseudoClass.getPseudoClass("first");
protected static final PseudoClass LAST = PseudoClass.getPseudoClass("last"); 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) { public BreadcrumbsSkin(final Breadcrumbs<T> control) {
super(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); updateSelectedPath(getSkinnable().selectedCrumbProperty().get(), null);
} }

@ -51,22 +51,23 @@ import javafx.scene.control.Control;
import javafx.scene.control.DateCell; import javafx.scene.control.DateCell;
import javafx.scene.control.Skin; import javafx.scene.control.Skin;
import javafx.util.Callback; 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 * 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 * <ul>
* {@link LocalDate}. The default value is null. * <li>The {@link #valueProperty() value} property represents the currently selected
* * {@link LocalDate}. The default value is null.</li>
* <p>The {@link #chronologyProperty() chronology} property specifies a calendar system to be used * <li>The {@link #chronologyProperty() chronology} property specifies a calendar system to be used
* for parsing, displaying, and choosing dates. * for parsing, displaying, and choosing dates.</li>
* * <li>The {@link #valueProperty() value} property is always defined in the ISO calendar system,
* <p>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 * 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 * 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 { public class Calendar extends Control {
@ -74,7 +75,8 @@ public class Calendar extends Control {
protected Chronology lastValidChronology = IsoChronology.INSTANCE; 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() { public Calendar() {
this(null); 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); setValue(localDate);
getStyleClass().add(DEFAULT_STYLE_CLASS); getStyleClass().add(DEFAULT_STYLE_CLASS);
} }
@ -126,6 +129,13 @@ public class Calendar extends Control {
// Properties // // 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"); private final ObjectProperty<LocalDate> value = new SimpleObjectProperty<>(this, "value");
public final LocalDate getValue() { public final LocalDate getValue() {
@ -136,10 +146,6 @@ public class Calendar extends Control {
valueProperty().set(value); valueProperty().set(value);
} }
public ObjectProperty<LocalDate> valueProperty() {
return value;
}
/** /**
* A custom cell factory can be provided to customize individual day cells * 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. * 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 * The calendar system used for parsing, displaying, and choosing dates in the
* control. * Calendar control.
* *
* <p>The default is usually {@link IsoChronology} unless provided explicitly * <p>The default is usually {@link IsoChronology} unless provided explicitly
* in the {@link Locale} by use of a Locale calendar extension. * in the {@link Locale} by use of a Locale calendar extension.
@ -176,7 +182,8 @@ public class Calendar extends Control {
return chronology; return chronology;
} }
private final ObjectProperty<Chronology> chronology = new SimpleObjectProperty<>(this, "chronology", null); private final ObjectProperty<Chronology> chronology
= new SimpleObjectProperty<>(this, "chronology", null);
@SuppressWarnings("CatchAndPrintStackTrace") @SuppressWarnings("CatchAndPrintStackTrace")
public final Chronology getChronology() { 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 * <p>The default value is specified in a resource bundle, and depends on the country of the
* current locale. * current locale.
@ -238,7 +245,15 @@ public class Calendar extends Control {
return showWeekNumbersProperty().getValue(); 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) { public final void setTopNode(Node value) {
topNode.setValue(value); topNode.setValue(value);
@ -248,11 +263,15 @@ public class Calendar extends Control {
return topNode.getValue(); 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) { public final void setBottomNode(Node value) {
bottomNode.setValue(value); bottomNode.setValue(value);
@ -262,10 +281,6 @@ public class Calendar extends Control {
return bottomNode.getValue(); return bottomNode.getValue();
} }
public ObjectProperty<Node> bottomNodeProperty() {
return bottomNode;
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Stylesheet Handling // // Stylesheet Handling //
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////

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

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

@ -2,20 +2,31 @@
package atlantafx.base.controls; package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.scene.control.CustomMenuItem; import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label; 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 { public class CaptionMenuItem extends CustomMenuItem {
protected final Label title = new Label(); protected final Label title = new Label();
/**
* Creates an empty menu item.
*/
public CaptionMenuItem() { public CaptionMenuItem() {
this(null); this(null);
} }
public CaptionMenuItem(String text) { /**
* Creates a CaptionMenuItem with the specified text as the title.
*/
public CaptionMenuItem(@Nullable @NamedArg("text") String text) {
super(); super();
setTitle(text); setTitle(text);
@ -24,6 +35,13 @@ public class CaptionMenuItem extends CustomMenuItem {
getStyleClass().addAll("caption-menu-item"); getStyleClass().addAll("caption-menu-item");
} }
/**
* Contains the title of the menu item.
*/
public StringProperty titleProperty() {
return title.textProperty();
}
public String getTitle() { public String getTitle() {
return title.getText(); return title.getText();
} }
@ -31,8 +49,4 @@ public class CaptionMenuItem extends CustomMenuItem {
public void setTitle(String text) { public void setTitle(String text) {
title.setText(text); title.setText(text);
} }
public StringProperty titleProperty() {
return title.textProperty();
}
} }

@ -9,7 +9,7 @@ import javafx.scene.control.Control;
import javafx.scene.control.Skin; 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 * 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 * or context of the information. The sub-header and body sections provide
* more detailed content, while the footer may include additional actions or * 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 { public class Card extends Control {
// Default constructor /**
* Creates an empty Card.
*/
public Card() { public Card() {
super(); super();
getStyleClass().add("card"); getStyleClass().add("card");
} }
/**
* {@inheritDoc}
*/
@Override @Override
protected Skin<?> createDefaultSkin() { protected Skin<?> createDefaultSkin() {
return new CardSkin(this); 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"); private final ObjectProperty<Node> header = new SimpleObjectProperty<>(this, "header");
public Node getHeader() { public Node getHeader() {
return header.get(); return header.get();
} }
public ObjectProperty<Node> headerProperty() {
return header;
}
public void setHeader(Node header) { public void setHeader(Node header) {
this.header.set(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"); private final ObjectProperty<Node> subHeader = new SimpleObjectProperty<>(this, "subHeader");
public Node getSubHeader() { public Node getSubHeader() {
return subHeader.get(); return subHeader.get();
} }
public final ObjectProperty<Node> subHeaderProperty() {
return subHeader;
}
public void setSubHeader(Node subHeader) { public void setSubHeader(Node subHeader) {
this.subHeader.set(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"); private final ObjectProperty<Node> body = new SimpleObjectProperty<>(this, "body");
public Node getBody() { public Node getBody() {
return body.get(); return body.get();
} }
public ObjectProperty<Node> bodyProperty() {
return body;
}
public void setBody(Node body) { public void setBody(Node body) {
this.body.set(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"); private final ObjectProperty<Node> footer = new SimpleObjectProperty<>(this, "footer");
public Node getFooter() { public Node getFooter() {
return footer.get(); return footer.get();
} }
public ObjectProperty<Node> footerProperty() {
return footer;
}
public void setFooter(Node footer) { public void setFooter(Node footer) {
this.footer.set(footer); this.footer.set(footer);
} }

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

@ -36,79 +36,29 @@ import javafx.scene.control.Skin;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
/** /**
* A base class for people wanting to customize a {@link TextField} to contain nodes * A base class for people wanting to customize a {@link TextField}
* inside the text field itself, without being on top of the users typed-in text. * 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 { public class CustomTextField extends TextField {
/** /**
* Instantiates a default CustomTextField. * Creates an empty CustomTextField.
*/ */
public CustomTextField() { public CustomTextField() {
getStyleClass().add("custom-text-field"); getStyleClass().add("custom-text-field");
} }
/**
* Creates a CustomTextField with initial text content.
*
* @param text A string for text content.
*/
public CustomTextField(String text) { public CustomTextField(String text) {
this(); this();
setText(text); 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} * {@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.layout.StackPane;
import javafx.scene.text.HitInfo; import javafx.scene.text.HitInfo;
/**
* The default skin for the {@link CustomTextField} control.
*/
public abstract class CustomTextFieldSkin extends TextFieldSkin { public abstract class CustomTextFieldSkin extends TextFieldSkin {
private static final PseudoClass HAS_NO_SIDE_NODE = PseudoClass.getPseudoClass("no-side-nodes"); 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.MaskChar;
import atlantafx.base.util.MaskTextFormatter; import atlantafx.base.util.MaskTextFormatter;
import atlantafx.base.util.SimpleMaskChar;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import javafx.beans.NamedArg; import javafx.beans.NamedArg;
@ -38,9 +39,9 @@ import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/** /**
* This is a convenience wrapper for instantiating a {@link CustomTextField} with a * A convenience wrapper for instantiating a {@link CustomTextField} with a
* {@code MaskTextFormatter}. For additional info refer to the {@link MaskTextFormatter} * {@code MaskTextFormatter}. For additional info refer to the
* docs. * {@link MaskTextFormatter} documentation.
*/ */
public class MaskTextField extends CustomTextField { public class MaskTextField extends CustomTextField {
@ -56,15 +57,35 @@ public class MaskTextField extends CustomTextField {
protected final ReadOnlyObjectWrapper<MaskTextFormatter> formatter = protected final ReadOnlyObjectWrapper<MaskTextFormatter> formatter =
new ReadOnlyObjectWrapper<>(this, "formatter"); new ReadOnlyObjectWrapper<>(this, "formatter");
/**
* Creates an empty MaskTextField.
*/
public MaskTextField() { public MaskTextField() {
super(""); super("");
init(); 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) { public MaskTextField(@NamedArg("mask") String mask) {
this("", 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, private MaskTextField(@NamedArg("text") String text,
@NamedArg("mask") String mask) { @NamedArg("mask") String mask) {
super(Objects.requireNonNullElse(text, "")); super(Objects.requireNonNullElse(text, ""));
@ -74,6 +95,15 @@ public class MaskTextField extends CustomTextField {
init(); 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) { public MaskTextField(String text, List<MaskChar> mask) {
super(Objects.requireNonNullElse(text, "")); 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() { public StringProperty maskProperty() {
return mask; return mask;
} }

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

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

@ -16,53 +16,93 @@ import javafx.geometry.Side;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Control; import javafx.scene.control.Control;
import javafx.scene.control.Skin; import javafx.scene.control.Skin;
import javafx.stage.Stage;
import javafx.util.Duration; import javafx.util.Duration;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/** /**
* A container for displaying application dialogs ot top of the current scene * 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 * without opening a modal {@link Stage}. It's a translucent (glass) pane
* that can hold arbitrary content as well as animate its appearance.<br/><br/> * that can hold arbitrary content as well as animate its appearance.
* *
* <p>When {@link #displayProperty()} value is changed the modal pane modifies own * <p>When {@link #displayProperty()} value is changed the modal pane modifies own
* {@link #viewOrderProperty()} value accordingly, thus moving itself on top of the * {@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 * 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()} * constructor param. This also means that one <b>must not</b> change the modal pane
* property manually. * {@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 { 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); 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); public static final Duration DEFAULT_DURATION_OUT = Duration.millis(100);
private final int topViewOrder; private final int topViewOrder;
/** /**
* See {@link #ModalPane(int)}. * Creates a new modal pane with the default {@code topViewOrder}
* property value.
*/ */
public ModalPane() { public ModalPane() {
this(Z_FRONT); 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 * @param topViewOrder The {@link #viewOrderProperty()} value to be set in order
* to display the modal pane on top of the parent container. * to display the ModalPane on top of the parent container.
*/ */
public ModalPane(@NamedArg("topViewOrder") int topViewOrder) { public ModalPane(@NamedArg("topViewOrder") int topViewOrder) {
super(); super();
this.topViewOrder = topViewOrder; this.topViewOrder = topViewOrder;
} }
/**
* {@inheritDoc}
*/
@Override @Override
protected Skin<?> createDefaultSkin() { protected Skin<?> createDefaultSkin() {
return new ModalPaneSkin(this); 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() { public int getTopViewOrder() {
return topViewOrder; return topViewOrder;
} }
@ -71,6 +111,13 @@ public class ModalPane extends Control {
// Properties // // 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); protected final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content", null);
public Node getContent() { 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() { public BooleanProperty displayProperty() {
return content; return display;
} }
// ~
protected final BooleanProperty display = new SimpleBooleanProperty(this, "display", false); protected final BooleanProperty display = new SimpleBooleanProperty(this, "display", false);
public boolean isDisplay() { public boolean isDisplay() {
@ -101,15 +148,12 @@ public class ModalPane extends Control {
} }
/** /**
* Whether the modal pane is set to top or not. * Specifies the alignment of the content node.
* When changed the pane {@link #viewOrderProperty()} value will be modified accordingly.
*/ */
public BooleanProperty displayProperty() { public ObjectProperty<Pos> alignmentProperty() {
return display; return alignment;
} }
// ~
protected final ObjectProperty<Pos> alignment = new SimpleObjectProperty<>(this, "alignment", Pos.CENTER); protected final ObjectProperty<Pos> alignment = new SimpleObjectProperty<>(this, "alignment", Pos.CENTER);
public Pos getAlignment() { 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() { public ObjectProperty<Function<Node, Animation>> inTransitionFactoryProperty() {
return alignment; return inTransitionFactory;
} }
// ~
protected final ObjectProperty<Function<Node, Animation>> inTransitionFactory = new SimpleObjectProperty<>( protected final ObjectProperty<Function<Node, Animation>> inTransitionFactory = new SimpleObjectProperty<>(
this, "inTransitionFactory", node -> Animations.zoomIn(node, DEFAULT_DURATION_IN) 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, * The factory that provides a transition to be played on content disappearance,
* i.e. when {@link #displayProperty()} is set to 'true'. * i.e. when {@link #displayProperty()} is set to 'false'.
*/ */
public ObjectProperty<Function<Node, Animation>> inTransitionFactoryProperty() { public ObjectProperty<Function<Node, Animation>> outTransitionFactoryProperty() {
return inTransitionFactory; return outTransitionFactory;
} }
// ~
protected final ObjectProperty<Function<Node, Animation>> outTransitionFactory = new SimpleObjectProperty<>( protected final ObjectProperty<Function<Node, Animation>> outTransitionFactory = new SimpleObjectProperty<>(
this, "outTransitionFactory", node -> Animations.zoomOut(node, DEFAULT_DURATION_OUT) 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, * Specifies whether the content should be treated as persistent or not.
* i.e. when {@link #displayProperty()} is set to 'false'. *
* <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() { public BooleanProperty persistentProperty() {
return outTransitionFactory; return persistent;
} }
// ~
protected final BooleanProperty persistent = new SimpleBooleanProperty(this, "persistent", false); protected final BooleanProperty persistent = new SimpleBooleanProperty(this, "persistent", false);
public boolean getPersistent() { public boolean getPersistent() {
@ -183,23 +225,13 @@ public class ModalPane extends Control {
this.persistent.set(persistent); 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 // // Public API //
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
/** /**
* A convenience method for setting the content of the modal pane content * 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) { public void show(Node node) {
// calling show method with no content specified doesn't make any sense // calling show method with no content specified doesn't make any sense
@ -208,6 +240,17 @@ public class ModalPane extends Control {
setDisplay(true); 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)}. * 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 * See {@link #usePredefinedTransitionFactories(Side, Duration, Duration)}.
* and triggering display state at the same time.
*/ */
public void hide(boolean clear) { public void usePredefinedTransitionFactories(@Nullable Side side) {
setDisplay(false); usePredefinedTransitionFactories(side, DEFAULT_DURATION_IN, DEFAULT_DURATION_OUT);
if (clear) {
setContent(null);
}
} }
/** /**
* Sets the predefined factory for both {@link #inTransitionFactoryProperty()} and * Sets the predefined factory for both {@link #inTransitionFactoryProperty()} and
* {@link #outTransitionFactoryProperty()} based on content position. * {@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, public void usePredefinedTransitionFactories(@Nullable Side side,
@Nullable Duration inDuration, @Nullable Duration inDuration,
@Nullable Duration outDuration) { @Nullable Duration outDuration) {

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

@ -15,6 +15,7 @@ import javafx.event.Event;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.Control; import javafx.scene.control.Control;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.Skin; import javafx.scene.control.Skin;
@ -22,23 +23,34 @@ import javafx.scene.layout.Region;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/** /**
* The Notification control is intended for displaying alerts and messages * A control that is intended for displaying notifications to users as pop-ups.
* to users as pop-ups. It's customizable with different colors and icons, * It is customizable with different colors and icons, can contain a graphic or image,
* can contain a graphic or image, along with the message and additional actions * along with the message and additional actions for users to take.
* 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.
*/ */
public class Notification extends Control { public class Notification extends Control {
/**
* Creates an empty Notification.
*/
public Notification() { public Notification() {
this(null, null); 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) { public Notification(@Nullable @NamedArg("message") String message) {
this(message, null); 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, public Notification(@Nullable @NamedArg("message") String message,
@Nullable @NamedArg("graphic") Node graphic) { @Nullable @NamedArg("graphic") Node graphic) {
super(); super();
@ -66,16 +78,16 @@ public class Notification extends Control {
* Represents an optional graphical component that can be displayed alongside * Represents an optional graphical component that can be displayed alongside
* the notification message. * the notification message.
*/ */
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic"); private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic");
public Node getGraphic() { public Node getGraphic() {
return graphic.get(); return graphic.get();
} }
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
public void setGraphic(Node graphic) { public void setGraphic(Node graphic) {
this.graphic.set(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 * Stores a short text message that will be displayed to users when the
* notification appears. This property doesn't support the formatted text. * notification appears. This property doesn't support the formatted text.
*/ */
public StringProperty messageProperty() {
return message;
}
private final StringProperty message = new SimpleStringProperty(this, "message"); private final StringProperty message = new SimpleStringProperty(this, "message");
public String getMessage() { public String getMessage() {
return message.get(); return message.get();
} }
public StringProperty messageProperty() {
return message;
}
public void setMessage(String message) { public void setMessage(String message) {
this.message.set(message); this.message.set(message);
} }
@ -102,8 +114,14 @@ public class Notification extends Control {
* Specifies the primary actions associated with this notification. * Specifies the primary actions associated with this notification.
* *
* <p>This property is used to store one or more action buttons that will * <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 = private final ReadOnlyObjectWrapper<ObservableList<Button>> primaryActions =
new ReadOnlyObjectWrapper<>(this, "primaryActions", FXCollections.observableArrayList()); new ReadOnlyObjectWrapper<>(this, "primaryActions", FXCollections.observableArrayList());
@ -111,10 +129,6 @@ public class Notification extends Control {
return primaryActions.get(); return primaryActions.get();
} }
public ReadOnlyObjectProperty<ObservableList<Button>> primaryActionsProperty() {
return primaryActions.getReadOnlyProperty();
}
public void setPrimaryActions(ObservableList<Button> buttons) { public void setPrimaryActions(ObservableList<Button> buttons) {
this.primaryActions.set(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 * <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. * 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 = private final ReadOnlyObjectWrapper<ObservableList<MenuItem>> secondaryActions =
new ReadOnlyObjectWrapper<>(this, "secondaryActions", FXCollections.observableArrayList()); new ReadOnlyObjectWrapper<>(this, "secondaryActions", FXCollections.observableArrayList());
@ -136,10 +156,6 @@ public class Notification extends Control {
return secondaryActions.get(); return secondaryActions.get();
} }
public ReadOnlyObjectProperty<ObservableList<MenuItem>> secondaryActionsProperty() {
return secondaryActions.getReadOnlyProperty();
}
public void setSecondaryActions(ObservableList<MenuItem> items) { public void setSecondaryActions(ObservableList<MenuItem> items) {
this.secondaryActions.set(items); this.secondaryActions.set(items);
} }
@ -150,7 +166,13 @@ public class Notification extends Control {
/** /**
* Specifies the close handler used to dismiss this notification. * 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 = protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose"); new SimpleObjectProperty<>(this, "onClose");
@ -158,10 +180,6 @@ public class Notification extends Control {
return onClose.get(); return onClose.get();
} }
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
public void setOnClose(EventHandler<? super Event> onClose) { public void setOnClose(EventHandler<? super Event> onClose) {
this.onClose.set(onClose); this.onClose.set(onClose);
} }

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

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

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

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

@ -8,7 +8,7 @@ import javafx.scene.control.skin.SliderSkin;
import javafx.scene.layout.StackPane; 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 { public class ProgressSliderSkin extends SliderSkin {

@ -2,6 +2,7 @@
package atlantafx.base.controls; package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyBooleanWrapper;
@ -11,21 +12,43 @@ import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.Skin; import javafx.scene.control.Skin;
import javafx.util.StringConverter; 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 { public class RingProgressIndicator extends ProgressIndicator {
/**
* Creates a new indeterminate ProgressIndicator.
*/
public RingProgressIndicator() { public RingProgressIndicator() {
super(); 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); 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); super(progress);
this.reverse.set(reverse); this.reverse.set(reverse);
} }
/**
* {@inheritDoc}
*/
@Override @Override
protected Skin<?> createDefaultSkin() { protected Skin<?> createDefaultSkin() {
return new RingProgressIndicatorSkin(this); return new RingProgressIndicatorSkin(this);
@ -35,6 +58,14 @@ public class RingProgressIndicator extends ProgressIndicator {
// Properties // // 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); protected final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic", null);
public Node getGraphic() { public Node getGraphic() {
@ -46,15 +77,15 @@ public class RingProgressIndicator extends ProgressIndicator {
} }
/** /**
* Any node to be displayed within the progress indicator. If null, * Represents an optional converter to transform the progress value to a string.
* it will fall back to the Label with integer progress value from 1 to 100. * It is only used if a custom graphic node is not set.
*
* @see #graphicProperty()
*/ */
public ObjectProperty<Node> graphicProperty() { public ObjectProperty<StringConverter<Double>> stringConverterProperty() {
return graphic; return stringConverter;
} }
// ~
protected final ObjectProperty<StringConverter<Double>> stringConverter = protected final ObjectProperty<StringConverter<Double>> stringConverter =
new SimpleObjectProperty<>(this, "converter", null); new SimpleObjectProperty<>(this, "converter", null);
@ -67,25 +98,16 @@ public class RingProgressIndicator extends ProgressIndicator {
} }
/** /**
* Optional converter to transform progress value to string. * Reverses the progress indicator scale. For the indeterminate variant,
*/
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
* this means it will be rotated counterclockwise. * this means it will be rotated counterclockwise.
*/ */
public ReadOnlyBooleanProperty reverseProperty() { public ReadOnlyBooleanProperty reverseProperty() {
return reverse.getReadOnlyProperty(); 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.scene.shape.Circle;
import javafx.util.Duration; import javafx.util.Duration;
/**
* The default skin for the {@link RingProgressIndicator} control.
*/
public class RingProgressIndicatorSkin extends SkinBase<RingProgressIndicator> { public class RingProgressIndicatorSkin extends SkinBase<RingProgressIndicator> {
protected static final double DEFAULT_ANIMATION_TIME = 3; protected static final double DEFAULT_ANIMATION_TIME = 3;

@ -11,6 +11,15 @@ import javafx.scene.Node;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import org.jetbrains.annotations.Nullable; 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> { final class SlotListener implements ChangeListener<Node> {
private static final PseudoClass FILLED = PseudoClass.getPseudoClass("filled"); private static final PseudoClass FILLED = PseudoClass.getPseudoClass("filled");
@ -18,10 +27,24 @@ final class SlotListener implements ChangeListener<Node> {
private final Pane slot; private final Pane slot;
private final @Nullable BiConsumer<Node, Boolean> onContentUpdate; 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) { public SlotListener(Pane slot) {
this(slot, null); 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) { public SlotListener(Node slot, @Nullable BiConsumer<Node, Boolean> onContentUpdate) {
Objects.requireNonNull(slot, "Slot cannot be null."); 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.Region;
import javafx.scene.layout.VBox; 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 { public class Spacer extends Region {
/**
* Creates a new horizontally oriented Spacer that expands
* to fill remaining space.
*/
public Spacer() { public Spacer() {
this(Orientation.HORIZONTAL); 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) { public Spacer(Orientation orientation) {
super(); 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) { public Spacer(double size) {
this(size, Orientation.HORIZONTAL); 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) { public Spacer(double size, Orientation orientation) {
super(); super();

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

@ -2,7 +2,7 @@
package atlantafx.base.controls; package atlantafx.base.controls;
import javafx.beans.NamedArg; import atlantafx.base.util.BBCodeParser;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
@ -11,14 +11,18 @@ import javafx.scene.Node;
import javafx.scene.control.Control; import javafx.scene.control.Control;
import org.jetbrains.annotations.Nullable; 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 abstract class TileBase extends Control {
public TileBase() { public TileBase() {
this(null, null, null); this(null, null, null);
} }
public TileBase(@Nullable @NamedArg("title") String title, public TileBase(@Nullable String title,
@Nullable @NamedArg("description") String description) { @Nullable String description) {
this(title, description, null); 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 * Represents the tiles graphic node. It is commonly used to add images or icons
* to add images or icons that are associated with the tile. * that are associated with the tile.
*/ */
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic"); private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic");
public Node getGraphic() { public Node getGraphic() {
return graphic.get(); return graphic.get();
} }
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
public void setGraphic(Node graphic) { public void setGraphic(Node graphic) {
this.graphic.set(graphic); this.graphic.set(graphic);
} }
/** /**
* The property representing the tiles title. Although it is not mandatory, * Represents the tiles title (or header).
* you typically would not want to have a tile without a title.
*/ */
public StringProperty titleProperty() {
return title;
}
private final StringProperty title = new SimpleStringProperty(this, "title"); private final StringProperty title = new SimpleStringProperty(this, "title");
public String getTitle() { public String getTitle() {
return title.get(); return title.get();
} }
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) { public void setTitle(String title) {
this.title.set(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"); private final StringProperty description = new SimpleStringProperty(this, "description");
public String getDescription() { public String getDescription() {
return description.get(); return description.get();
} }
public StringProperty descriptionProperty() {
return description;
}
public void setDescription(String description) { public void setDescription(String description) {
this.description.set(description); this.description.set(description);
} }

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

@ -15,6 +15,10 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.text.TextFlow; 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> { public abstract class TileSkinBase<T extends TileBase> extends SkinBase<T> {
protected static final PseudoClass HAS_GRAPHIC = PseudoClass.getPseudoClass("has-graphic"); 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.Toggle;
import javafx.scene.control.ToggleGroup; 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 { public class ToggleSwitch extends Labeled implements Toggle {
protected static final String DEFAULT_STYLE_CLASS = "toggle-switch"; 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. * 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) { public ToggleSwitch(String text) {
super(text); super(text);
initialize(); initialize();
} }
/**
* {@inheritDoc}
*/
@Override
protected Skin<?> createDefaultSkin() {
return new ToggleSwitchSkin(this);
}
private void initialize() { private void initialize() {
getStyleClass().setAll(DEFAULT_STYLE_CLASS); getStyleClass().setAll(DEFAULT_STYLE_CLASS);
} }
@ -83,18 +95,6 @@ public class ToggleSwitch extends Labeled implements Toggle {
// Properties // // 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. * Returns whether this Toggle Switch is selected.
*/ */
@ -151,24 +151,23 @@ public class ToggleSwitch extends Labeled implements Toggle {
return selected; 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. * The {@link ToggleGroup} to which this ToggleSwitch belongs. A toggle can only
* A {@code ToggleSwitch} can only be in one group at any one time. If the * be in one group at any one time. If the group is changed, then the toggle is
* group is changed, then the button is removed from the old group prior to * removed from the old group prior to being added to the new group.
* 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 @Override
public final ObjectProperty<ToggleGroup> toggleGroupProperty() { public final ObjectProperty<ToggleGroup> toggleGroupProperty() {
if (toggleGroup == null) { if (toggleGroup == null) {
@ -204,23 +203,20 @@ public class ToggleSwitch extends Labeled implements Toggle {
return toggleGroup; 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) { @Override
labelPositionProperty().setValue(pos); public final ToggleGroup getToggleGroup() {
return toggleGroup == null ? null : toggleGroup.get();
} }
/** /**
* Returns whether this Toggle Switch is selected. * Specifies the side where {@link #textProperty()} value should be placed.
*/
public final HorizontalDirection getLabelPosition() {
return labelPosition == null ? HorizontalDirection.LEFT : labelPosition.getValue();
}
/**
* Specifies the side where {@link #textProperty()} values should be placed.
* Default is {@link HorizontalDirection#LEFT}. * Default is {@link HorizontalDirection#LEFT}.
*/ */
public final ObjectProperty<HorizontalDirection> labelPositionProperty() { public final ObjectProperty<HorizontalDirection> labelPositionProperty() {
@ -253,13 +249,22 @@ public class ToggleSwitch extends Labeled implements Toggle {
return labelPosition; 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 // // Methods //
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
/** /**
* Toggles the state of the {@code Switch}. The {@code Switch} will cycle * Toggles the state of the switch, cycling through the selected and unselected states.
* through the selected and unselected states.
*/ */
public void fire() { public void fire() {
if (!isDisabled()) { if (!isDisabled()) {
@ -268,13 +273,9 @@ public class ToggleSwitch extends Labeled implements Toggle {
} }
} }
/** ///////////////////////////////////////////////////////////////////////////
* {@inheritDoc} // Styleable Properties //
*/ ///////////////////////////////////////////////////////////////////////////
@Override
protected Skin<?> createDefaultSkin() {
return new ToggleSwitchSkin(this);
}
/** /**
* {@inheritDoc} * {@inheritDoc}
@ -284,10 +285,6 @@ public class ToggleSwitch extends Labeled implements Toggle {
return StyleableProperties.STYLEABLES; return StyleableProperties.STYLEABLES;
} }
///////////////////////////////////////////////////////////////////////////
// Styleable Properties //
///////////////////////////////////////////////////////////////////////////
private static class StyleableProperties { private static class StyleableProperties {
private static final CssMetaData<ToggleSwitch, HorizontalDirection> LABEL_POSITION = new CssMetaData<>( 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.scene.layout.StackPane;
import javafx.util.Duration; import javafx.util.Duration;
/**
* The default skin for the {@link ToggleSwitch} control.
*/
public class ToggleSwitchSkin extends SkinBase<ToggleSwitch> { public class ToggleSwitchSkin extends SkinBase<ToggleSwitch> {
protected static final Duration DEFAULT_ANIMATION_TIME = Duration.millis(200); 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; import org.jetbrains.annotations.Nullable;
/** /**
* DeckPane represents a pane that displays all of its child nodes in a deck, * 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 * where only one node can be visible at a time. It <b>does not maintain any
* (model), but only cares about the top node, which can be changed by various * sequence</b> (model), but only cares about the top node, which can be changed
* transition effects.<p/> * by various transition effects.<p/>
* *
* <h3>View Order</h3> * <h3>View Order</h3>
* *
* <p>DeckPane manages {@link Node#viewOrderProperty()} of its children. Topmost * <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 * 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. * 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 // the rest of the nodes
protected static final int Z_DEFAULT = 0; protected static final int Z_DEFAULT = 0;
/**
* Creates a new empty DeckPane.
*/
public DeckPane() { public DeckPane() {
super(); 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) { public DeckPane(Node... children) {
this(); this();
getChildren().addAll(children); getChildren().addAll(children);
} }
/** /**
* Returns the node with the higher view order value or the last node * Returns the node with the highest view order value, or the
* if all child nodes have the same view order value. * last node if all child nodes have the same view order value.
*/ */
public @Nullable Node getTopNode() { public @Nullable Node getTopNode() {
var size = getChildren().size(); var size = getChildren().size();
@ -93,6 +101,8 @@ public class DeckPane extends AnchorPane {
/** /**
* Sets given node on top without playing any transition. * Sets given node on top without playing any transition.
* Does nothing if that node isn't added to the pane. * 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) { public void setTopNode(Node target) {
if (!getChildren().contains(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 * Adds the given nodes to the pane and binds them to the pane edges
* using the provided offsets. See {@link AnchorPane#setTopAnchor(Node, Double)} * using the provided offset. See {@link AnchorPane#setTopAnchor(Node, Double)}
* for the reference. * 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) { for (var node : nodes) {
AnchorPane.setTopAnchor(node, offsets.getTop()); AnchorPane.setTopAnchor(node, offset.getTop());
AnchorPane.setRightAnchor(node, offsets.getRight()); AnchorPane.setRightAnchor(node, offset.getRight());
AnchorPane.setBottomAnchor(node, offsets.getBottom()); AnchorPane.setBottomAnchor(node, offset.getBottom());
AnchorPane.setLeftAnchor(node, offsets.getLeft()); AnchorPane.setLeftAnchor(node, offset.getLeft());
} }
getChildren().addAll(nodes); getChildren().addAll(nodes);
} }
@ -138,6 +151,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the * Places target node on the top of the pane while playing the
* swipe transition from bottom to top. If the pane doesn't contain * swipe transition from bottom to top. If the pane doesn't contain
* that node, it will be added to the end before playing transition. * 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) { public void swipeUp(Node target) {
var topNode = getTopNode(); var topNode = getTopNode();
@ -164,6 +179,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the * Places target node on the top of the pane while playing the
* swipe transition from top to bottom. If the pane doesn't contain * swipe transition from top to bottom. If the pane doesn't contain
* that node, it will be added to the end before playing transition. * 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) { public void swipeDown(Node target) {
var topNode = getTopNode(); var topNode = getTopNode();
@ -190,6 +207,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the * Places target node on the top of the pane while playing the
* swipe transition from right to left. If the pane doesn't contain * swipe transition from right to left. If the pane doesn't contain
* that node, it will be added to the end before playing transition. * 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) { public void swipeLeft(Node target) {
var topNode = getTopNode(); var topNode = getTopNode();
@ -216,6 +235,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the * Places target node on the top of the pane while playing the
* swipe transition from left to right. If the pane doesn't contain * swipe transition from left to right. If the pane doesn't contain
* that node, it will be added to the end before playing transition. * 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) { public void swipeRight(Node target) {
var topNode = getTopNode(); var topNode = getTopNode();
@ -242,6 +263,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the * Places target node on the top of the pane while playing the
* slide transition from bottom to top. If the pane doesn't contain * slide transition from bottom to top. If the pane doesn't contain
* that node, it will be added to the end before playing transition. * 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) { public void slideUp(Node target) {
var topNode = getTopNode(); var topNode = getTopNode();
@ -265,6 +288,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the * Places target node on the top of the pane while playing the
* slide transition from top to bottom. If the pane doesn't contain * slide transition from top to bottom. If the pane doesn't contain
* that node, it will be added to the end before playing transition. * 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) { public void slideDown(Node target) {
var topNode = getTopNode(); var topNode = getTopNode();
@ -288,6 +313,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the * Places target node on the top of the pane while playing the
* slide transition from right to left. If the pane doesn't contain * slide transition from right to left. If the pane doesn't contain
* that node, it will be added to the end before playing transition. * 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) { public void slideLeft(Node target) {
var topNode = getTopNode(); var topNode = getTopNode();
@ -311,6 +338,8 @@ public class DeckPane extends AnchorPane {
* Places target node on the top of the pane while playing the * Places target node on the top of the pane while playing the
* slide transition from left to right. If the pane doesn't contain * slide transition from left to right. If the pane doesn't contain
* that node, it will be added to the end before playing transition. * 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) { public void slideRight(Node target) {
var topNode = getTopNode(); var topNode = getTopNode();
@ -334,6 +363,13 @@ public class DeckPane extends AnchorPane {
// Properties // // 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 = protected final ObjectProperty<Duration> animationDuration =
new SimpleObjectProperty<>(this, "animationDuration", Duration.seconds(1)); new SimpleObjectProperty<>(this, "animationDuration", Duration.seconds(1));
@ -341,18 +377,17 @@ public class DeckPane extends AnchorPane {
return animationDuration.get(); 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) { public void setAnimationDuration(@Nullable Duration animationDuration) {
this.animationDuration.set(Objects.requireNonNullElse(animationDuration, Duration.ZERO)); 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 = protected final ReadOnlyBooleanWrapper animationActive =
new ReadOnlyBooleanWrapper(this, "animationActive"); new ReadOnlyBooleanWrapper(this, "animationActive");
@ -361,19 +396,16 @@ public class DeckPane extends AnchorPane {
return animationActive.get(); 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) { protected void setAnimationActive(boolean animationActive) {
this.animationActive.set(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 = protected final ObjectProperty<Consumer<Node>> beforeShowCallback =
new SimpleObjectProperty<>(this, "beforeShowCallback"); new SimpleObjectProperty<>(this, "beforeShowCallback");
@ -382,13 +414,6 @@ public class DeckPane extends AnchorPane {
return beforeShowCallback.get(); 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) { public void setBeforeShowCallback(@Nullable Consumer<Node> callback) {
this.beforeShowCallback.set(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 = protected final ObjectProperty<Consumer<Node>> afterHideCallback =
new SimpleObjectProperty<>(this, "afterHideCallback"); new SimpleObjectProperty<>(this, "afterHideCallback");
@ -408,13 +438,6 @@ public class DeckPane extends AnchorPane {
return afterHideCallback.get(); 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) { public void setAfterHideCallback(@Nullable Consumer<Node> callback) {
this.afterHideCallback.set(callback); this.afterHideCallback.set(callback);
} }

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

@ -17,11 +17,12 @@ import javafx.scene.layout.StackPane;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/** /**
* The ModalBox is a specialized control or layout designed to hold the * A specialized control (or layout) designed to hold the {@link ModalPane}
* {@link ModalPane} dialog content. It includes the close button out-of-the-box * dialog content. It includes the close button out-of-the-box and allows for the
* and allows for the addition of arbitrary children. The ModalBox is derived * addition of arbitrary children.
* from the {@link AnchorPane}, so it inherits the same API. Just be sure that *
* you haven't removed the close button while using it. * <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 { public class ModalBox extends AnchorPane {
@ -40,7 +41,7 @@ public class ModalBox extends AnchorPane {
/** /**
* Creates a ModalBox layout with the given children. * 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) { public ModalBox(Node... children) {
this((String) null, 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 * on the close button, it performs a ModalPane lookup via the specified
* selector and calls the {@link ModalPane#hide()} method automatically. * selector and calls the {@link ModalPane#hide()} method automatically.
* *
* @param selector the ModalPane pane CSS selector * @param selector The ModalPane pane CSS selector.
* @param children the initial set of children for this pane * @param children The initial set of children for this pane.
*/ */
public ModalBox(@Nullable @NamedArg("selector") String selector, Node... children) { public ModalBox(@Nullable @NamedArg("selector") String selector, Node... children) {
super(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, * the close handler to a ModalPane. When user clicks on the close button,
* it calls the {@link ModalPane#hide()} method automatically. * it calls the {@link ModalPane#hide()} method automatically.
* *
* @param modalPane the ModalPane pane CSS selector * @param modalPane The ModalPane pane CSS selector.
* @param children the initial set of children for this pane * @param children The initial set of children for this pane.
*/ */
public ModalBox(@Nullable ModalPane modalPane, Node... children) { public ModalBox(@Nullable ModalPane modalPane, Node... children) {
super(children); super(children);
@ -88,7 +89,7 @@ public class ModalBox extends AnchorPane {
* and {@link Node#isMouseTransparent()} is false, then the close button * and {@link Node#isMouseTransparent()} is false, then the close button
* will not receive mouse events and therefore will not be clickable. * 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) { public void addContent(Node node) {
Objects.requireNonNull(node, "Node cannot be null."); 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 * The property representing the user specified close handler.
* if you have also specified the ModalPane instance or CSS selector, this *
* handler will be executed after the default close handler. Therefore, you * <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. * 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 = protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose"); new SimpleObjectProperty<>(this, "onClose");
@ -154,27 +160,25 @@ public class ModalBox extends AnchorPane {
return onClose.get(); return onClose.get();
} }
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
public void setOnClose(EventHandler<? super Event> onClose) { public void setOnClose(EventHandler<? super Event> onClose) {
this.onClose.set(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"); protected final BooleanProperty clearOnClose = new SimpleBooleanProperty(this, "clearOnClose");
public boolean isClearOnClose() { public boolean isClearOnClose() {
return clearOnClose.get(); return clearOnClose.get();
} }
public BooleanProperty clearOnCloseProperty() {
return clearOnClose;
}
public void setClearOnClose(boolean clearOnClose) { public void setClearOnClose(boolean clearOnClose) {
this.clearOnClose.set(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; import javafx.scene.control.TabPane;
/** /**
* A set of constants and utility methods that simplifies adding * A set of constants and utility methods that simplifies adding CSS
* CSS classes programmatically. * classes programmatically.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class Styles { 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 * Adds the given style class to the node if it's not present,
* removes it. * otherwise removes it.
* *
* @param node the target node * @param node The target node.
* @param styleClass the style class to be toggled * @param styleClass The style class to be toggled.
* @throws NullPointerException if node or style class is null * @throws NullPointerException if node or style class is null
*/ */
public static void toggleStyleClass(Node node, String styleClass) { 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 * This method is supposed to be used when only one from a set of classes
* have to be present at once. * have to be present at once.
* *
* @param node the target node * @param node The target node.
* @param styleClass the style class to be toggled * @param styleClass The style class to be toggled.
* @param excludes the style classes to be excluded * @param excludes The style classes to be excluded.
* @throws NullPointerException if node or styleClass is null * @throws NullPointerException if node or styleClass is null
*/ */
public static void addStyleClass(Node node, String styleClass, String... excludes) { 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 * This method is supposed to be used when only one from a set of pseudo-classes
* have to be present at once. * have to be present at once.
* *
* @param node the node to activate the pseudo-class on * @param node The node to activate the pseudo-class on.
* @param pseudoClass the pseudo-class to be activated * @param pseudoClass The pseudo-class to be activated.
* @param excludes the pseudo-classes to be deactivated * @param excludes The pseudo-classes to be deactivated.
* @throws NullPointerException if node or pseudo-class is null * @throws NullPointerException if node or pseudo-class is null
*/ */
public static void activatePseudoClass(Node node, PseudoClass pseudoClass, PseudoClass... excludes) { 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 * There's no check for duplicates, so the CSS declarations with the same property
* name can be appended multiple times. * name can be appended multiple times.
* *
* @param node the node to append the new style declaration * @param node The node to append the new style declaration.
* @param prop CSS property name * @param prop The CSS property name.
* @param value CSS property value * @param value The CSS property value.
* @throws NullPointerException if node is null * @throws NullPointerException if node is null
*/ */
public static void appendStyle(Node node, String prop, String value) { 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. * Removes the specified CSS style declaration from the specified node.
* *
* @param node the node to remove the style from * @param node The node to remove the style from.
* @param prop the name of the style property to remove * @param prop The name of the style property to remove.
* @throws NullPointerException if node is null * @throws NullPointerException if node is null
*/ */
@SuppressWarnings("StringSplitter") @SuppressWarnings("StringSplitter")
@ -284,8 +284,8 @@ public final class Styles {
* node.getStylesheets().remove(dataUri); * node.getStylesheets().remove(dataUri);
* </pre> * </pre>
* *
* @param css the CSS string to encode * @param css The CSS string to encode.
* @return the resulting data URI string * @return The resulting data URI string.
*/ */
public static String toDataURI(String css) { public static String toDataURI(String css) {
if (css == null) { if (css == null) {

@ -9,7 +9,7 @@ import javafx.application.Application;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/** /**
* Basic theme interface. * The basic theme interface.
*/ */
public interface Theme { public interface Theme {
@ -39,7 +39,7 @@ public interface Theme {
boolean isDarkMode(); 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) { static Theme of(final String name, final String userAgentStylesheet, final boolean darkMode) {
if (name == null) { if (name == null) {

@ -11,10 +11,21 @@ import java.util.stream.Stream;
import javafx.css.Stylesheet; import javafx.css.Stylesheet;
/** /**
* Lazy man CSS to BSS compiler wrapper. * A lazy man CSS to BSS compiler wrapper.
*/ */
public class ThemeCompiler { 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) { public static void main(String[] args) {
try { try {
if (args.length < 1) { if (args.length < 1) {
@ -38,7 +49,7 @@ public class ThemeCompiler {
/** /**
* Converts all CSS files in the specified directory to BSS. * 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 * @throws IOException to punish you for using Java
*/ */
public void convertToBinary(Path dir) throws IOException { 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, * 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'. * then the input file name is used with an extension of 'bss'.
* *
* @param in input file path * @param in The input file path.
* @param out output file path * @param out The output file path.
* @throws IOException to punish you for using Java * @throws IOException to punish you for using Java
*/ */
public void convertToBinary(Path in, Path out) throws IOException { public void convertToBinary(Path in, Path out) throws IOException {

@ -3,9 +3,9 @@
package atlantafx.base.theme; package atlantafx.base.theme;
/** /**
* Contains extra style class names introduced to tweak some controls view if and where it makes sense. * Contains extra style class names introduced to tweak some controls view
* The reason of supporting tweaks is to allow users to write less CSS code. Search for #tweak/classname * if and where it makes sense. The reason of supporting tweaks is to allow
* to find the controls supporting tweaks or check the control page in the Sampler app. * users to write less CSS code.
*/ */
public final class Tweaks { 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 * Changes the node opacity to full transparency and then back to its
* original opacity in quick succession, creating a flashing effect. * 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) { public static Timeline flash(Node node) {
Objects.requireNonNull(node, "Node cannot be null!"); Objects.requireNonNull(node, "Node cannot be null!");
@ -81,8 +81,8 @@ public final class Animations {
* Repeatedly increases and decreases the scale of the node, * Repeatedly increases and decreases the scale of the node,
* giving it a pulsating effect that draws attention to it. * giving it a pulsating effect that draws attention to it.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param scale the scale factor * @param scale The scale factor.
*/ */
public static Timeline pulse(Node node, double scale) { public static Timeline pulse(Node node, double scale) {
Objects.requireNonNull(node, "Node cannot be null!"); Objects.requireNonNull(node, "Node cannot be null!");
@ -127,8 +127,8 @@ public final class Animations {
* Rapidly moves the node from side-to-side horizontally, * Rapidly moves the node from side-to-side horizontally,
* creating a shaking or vibrating effect. * creating a shaking or vibrating effect.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param offset the shake offset * @param offset The shake offset.
*/ */
public static Timeline shakeX(Node node, double offset) { public static Timeline shakeX(Node node, double offset) {
Objects.requireNonNull(node, "Node cannot be null!"); Objects.requireNonNull(node, "Node cannot be null!");
@ -186,8 +186,8 @@ public final class Animations {
* Rapidly moves the node up and down vertically, creating * Rapidly moves the node up and down vertically, creating
* a shaking or bouncing effect. * a shaking or bouncing effect.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param offset the shake offset * @param offset The shake offset.
*/ */
public static Timeline shakeY(Node node, double offset) { public static Timeline shakeY(Node node, double offset) {
Objects.requireNonNull(node, "Node cannot be null!"); Objects.requireNonNull(node, "Node cannot be null!");
@ -241,7 +241,7 @@ public final class Animations {
* Causes the node to rapidly wobble back and forth, * Causes the node to rapidly wobble back and forth,
* creating a visually engaging effect. * 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) { public static Timeline wobble(Node node) {
Objects.requireNonNull(node, "Node cannot be null!"); 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, * Gradually increases the opacity of the node from 0 to 1,
* making it appear on the scene with a fading-in effect. * making it appear on the scene with a fading-in effect.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeIn(Node node, Duration duration) { public static Timeline fadeIn(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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, * Gradually decreases the opacity of the node from 1 to 0,
* making it disappear from the scene with a fading-out effect. * making it disappear from the scene with a fading-out effect.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeOut(Node node, Duration duration) { public static Timeline fadeOut(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Combines the {@link #fadeIn(Node, Duration)} effect with the nodes downward
* movement, creating an animated entrance of the node from the top. * movement, creating an animated entrance of the node from the top.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeInDown(Node node, Duration duration) { public static Timeline fadeInDown(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Combines the {@link #fadeOut(Node, Duration)} effect with the nodes downward
* movement, creating an animated exit of the node to the bottom. * movement, creating an animated exit of the node to the bottom.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeOutDown(Node node, Duration duration) { public static Timeline fadeOutDown(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Combines the {@link #fadeIn(Node, Duration)} effect with the nodes leftward
* movement, creating an animated entrance of the node from the left. * movement, creating an animated entrance of the node from the left.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeInLeft(Node node, Duration duration) { public static Timeline fadeInLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Combines the {@link #fadeOut(Node, Duration)} effect with the nodes leftward
* movement, creating an animated exit of the node to the left. * movement, creating an animated exit of the node to the left.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeOutLeft(Node node, Duration duration) { public static Timeline fadeOutLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Combines the {@link #fadeIn(Node, Duration)} effect with the nodes rightward
* movement, creating an animated entrance of the node from the right. * movement, creating an animated entrance of the node from the right.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeInRight(Node node, Duration duration) { public static Timeline fadeInRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Combines the {@link #fadeOut(Node, Duration)} effect with the nodes rightward
* movement, creating an animated exit of the node to the right. * movement, creating an animated exit of the node to the right.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeOutRight(Node node, Duration duration) { public static Timeline fadeOutRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Combines the {@link #fadeIn(Node, Duration)} effect with the nodes upward
* movement, creating an animated entrance of the node from the bottom. * movement, creating an animated entrance of the node from the bottom.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeInUp(Node node, Duration duration) { public static Timeline fadeInUp(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Combines the {@link #fadeOut(Node, Duration)} effect with the nodes upward
* movement, creating an animated exit of the node to the top. * movement, creating an animated exit of the node to the top.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline fadeOutUp(Node node, Duration duration) { public static Timeline fadeOutUp(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node causing it to roll into
* the scene from the left side at an angle. * the scene from the left side at an angle.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rollIn(Node node, Duration duration) { public static Timeline rollIn(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node causing it to roll out
* from the scene to the right side at an angle. * from the scene to the right side at an angle.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rollOut(Node node, Duration duration) { public static Timeline rollOut(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); Objects.requireNonNull(node, "Node cannot be null!");
@ -688,8 +688,8 @@ public final class Animations {
* Rotates the node and gradually increases its opacity, * Rotates the node and gradually increases its opacity,
* giving it an animated entrance effect. * giving it an animated entrance effect.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateIn(Node node, Duration duration) { public static Timeline rotateIn(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); Objects.requireNonNull(node, "Node cannot be null!");
@ -722,8 +722,8 @@ public final class Animations {
* Rotates the node and gradually decreases its opacity, * Rotates the node and gradually decreases its opacity,
* giving it an animated exit effect. * giving it an animated exit effect.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateOut(Node node, Duration duration) { public static Timeline rotateOut(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * movement from the left, creating an animated entrance of the node from the top
* left corner. * left corner.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateInDownLeft(Node node, Duration duration) { public static Timeline rotateInDownLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * movement to the left, creating an animated exit of the node towards the bottom
* left corner. * left corner.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateOutDownLeft(Node node, Duration duration) { public static Timeline rotateOutDownLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * movement from the right, creating an animated entrance of the node from the top
* right corner. * right corner.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateInDownRight(Node node, Duration duration) { public static Timeline rotateInDownRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * movement to the right, creating an animated exit of the node towards the bottom
* right corner. * right corner.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateOutDownRight(Node node, Duration duration) { public static Timeline rotateOutDownRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * movement from the left, creating an animated entrance of the node from the
* bottom left corner. * bottom left corner.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateInUpLeft(Node node, Duration duration) { public static Timeline rotateInUpLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * movement to the left, creating an animated exit of the node towards the top
* left corner. * left corner.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateOutUpLeft(Node node, Duration duration) { public static Timeline rotateOutUpLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * movement from the right, creating an animated entrance of the node from the
* bottom right corner. * bottom right corner.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateInUpRight(Node node, Duration duration) { public static Timeline rotateInUpRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * movement to the right, creating an animated exit of the node towards the top
* right corner. * right corner.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline rotateOutUpRight(Node node, Duration duration) { public static Timeline rotateOutUpRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node, causing it to slide into view
* from the top side. * from the top side.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline slideInDown(Node node, Duration duration) { public static Timeline slideInDown(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node, causing it to slide out of view
* through the bottom side. * through the bottom side.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline slideOutDown(Node node, Duration duration) { public static Timeline slideOutDown(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node, causing it to slide into view
* from the left side. * from the left side.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline slideInLeft(Node node, Duration duration) { public static Timeline slideInLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node, causing it to slide out of view
* through the left side. * through the left side.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline slideOutLeft(Node node, Duration duration) { public static Timeline slideOutLeft(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node, causing it to slide into view
* from the right side. * from the right side.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline slideInRight(Node node, Duration duration) { public static Timeline slideInRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node, causing it to slide out of view
* through the right side. * through the right side.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline slideOutRight(Node node, Duration duration) { public static Timeline slideOutRight(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node, causing it to slide into view
* from the bottom side. * from the bottom side.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline slideInUp(Node node, Duration duration) { public static Timeline slideInUp(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Applies an animated effect to the node, causing it to slide out of view
* through the top side. * through the top side.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
*/ */
public static Timeline slideOutUp(Node node, Duration duration) { public static Timeline slideOutUp(Node node, Duration duration) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * Increases the scale of the node, starting from a smaller size and gradually
* zooming it to the regular size, emphasizing the nodes entrance. * zooming it to the regular size, emphasizing the nodes entrance.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
* @param startValue the initial zoom value * @param startValue The initial zoom value.
*/ */
public static Timeline zoomIn(Node node, Duration duration, double startValue) { public static Timeline zoomIn(Node node, Duration duration, double startValue) {
Objects.requireNonNull(node, "Node cannot be null!"); 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 * its original size and gradually zooms out to a smaller size, emphasizing
* the nodes exit. * the nodes exit.
* *
* @param node the node to be animated * @param node The node to be animated.
* @param duration the animation duration * @param duration The animation duration.
* @param endValue the target zoom value * @param endValue The target zoom value.
*/ */
public static Timeline zoomOut(Node node, Duration duration, double endValue) { public static Timeline zoomOut(Node node, Duration duration, double endValue) {
Objects.requireNonNull(node, "Node cannot be null!"); Objects.requireNonNull(node, "Node cannot be null!");

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

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

@ -13,16 +13,16 @@ import javafx.scene.control.Tooltip;
import javafx.util.StringConverter; import javafx.util.StringConverter;
/** /**
* Converts between user-edited strings and {@link Double} values. * Converts between user-edited strings and 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 * <p>Accepts an optional Runnable that resets the editor on {@code NumberFormatException},
* input during typing, and restricts valid input to a specified range when committed. * or a TextField or Spinner that is preemptively monitored for invalid input
* <p> * during typing, and restricts valid input to a specified range when committed.
* 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> * <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 * @author Christoph Nahr
* @version 1.0.2
*/ */
public class DoubleStringConverter extends StringConverter<Double> { public class DoubleStringConverter extends StringConverter<Double> {
@ -30,8 +30,9 @@ public class DoubleStringConverter extends StringConverter<Double> {
private Runnable reset; private Runnable reset;
/** /**
* Creates a {@link DoubleStringConverter}. * Creates a DoubleStringConverter.
* Swallows {@link NumberFormatException} but does nothing *
* <p>Swallows {@code NumberFormatException} but does nothing
* in response until {@link #setReset} is defined. * in response until {@link #setReset} is defined.
*/ */
public DoubleStringConverter() { public DoubleStringConverter() {
@ -39,26 +40,27 @@ public class DoubleStringConverter extends StringConverter<Double> {
} }
/** /**
* Creates a {@link DoubleStringConverter} with an editor reset callback. * Creates a DoubleStringConverter with an editor reset callback.
* Specifying {@code null} has the same effect as the default constructor. * 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) { public DoubleStringConverter(Runnable reset) {
this.reset = reset; this.reset = reset;
} }
/** /**
* Creates a {@link DoubleStringConverter} with the specified input range. * Creates a 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 * <p>Preemptively monitors input to reject any invalid characters during
* valid text is committed, and resets {@code input} to the closest value to zero * 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. * within [{@code min}, {@code max}] when invalid text is committed.
* *
* @param input the {@link TextField} providing user-edited strings * @param input The TextField providing user-edited strings.
* @param min the smallest valid {@link Double} value * @param min The smallest valid value.
* @param max the greatest valid {@link Double} value * @param max The greatest valid value.
* @throws NullPointerException if {@code input} is {@code null} * @throws NullPointerException if input is {@code null}.
*/ */
public DoubleStringConverter(TextField input, double min, double max) { public DoubleStringConverter(TextField input, double min, double max) {
if (input == null) { if (input == null) {
@ -68,11 +70,6 @@ public class DoubleStringConverter extends StringConverter<Double> {
final double resetValue = Math.min(Math.max(0, min), max); final double resetValue = Math.min(Math.max(0, min), max);
reset = () -> input.setText(decimalFormat.format(resetValue)); 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 // restrict direct input to valid numerical characters
input.textProperty().addListener((ov, oldValue, newValue) -> { input.textProperty().addListener((ov, oldValue, newValue) -> {
if (newValue == null || newValue.isEmpty()) { if (newValue == null || newValue.isEmpty()) {
@ -115,14 +112,15 @@ public class DoubleStringConverter extends StringConverter<Double> {
} }
/** /**
* Creates a {@link DoubleStringConverter} for the specified {@link Spinner}. * Creates a DoubleStringConverter for the specified Spinner.
* Uses the {@link TextField} and minimum and maximum values of the specified *
* {@link Spinner} for construction, and also sets the new {@link DoubleStringConverter} * <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}. * on its {@link SpinnerValueFactory.DoubleSpinnerValueFactory}.
* *
* @param spinner the {@link Spinner} to create a {@link DoubleStringConverter} for * @param spinner The Spinner to create a DoubleStringConverter for.
* @return the new {@link DoubleStringConverter} * @return the new DoubleStringConverter
* @throws NullPointerException if {@code spinner} is {@code null} * @throws NullPointerException if the Spinner is {@code null}
*/ */
public static DoubleStringConverter createFor(Spinner<Double> spinner) { public static DoubleStringConverter createFor(Spinner<Double> spinner) {
final SpinnerValueFactory.DoubleSpinnerValueFactory factory = final SpinnerValueFactory.DoubleSpinnerValueFactory factory =
@ -141,13 +139,14 @@ public class DoubleStringConverter extends StringConverter<Double> {
/** /**
* Sets the editor reset callback. * 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 * @see #fromString
*/ */
public void setReset(Runnable reset) { 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 * A {@code null}, empty, or otherwise invalid argument returns zero
* and also executes the editor reset callback, if any. * and also executes the editor reset callback, if any.
* *
* @param s the {@link String} to convert * @param s The string to convert.
* @return the {@link Double} value of {@code s} * @return the double value of {@code s}
* @see #setReset * @see #setReset
*/ */
@Override @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". * A {@code null} argument is converted into the literal string "0".
* *
* @param value the {@link Double} to convert * @param value The Double to convert.
* @return the {@link String} form of {@code value} * @return the string form of {@code value}
*/ */
@Override @Override
public String toString(Double value) { public String toString(Double value) {

@ -12,47 +12,48 @@ import javafx.scene.control.Tooltip;
import javafx.util.StringConverter; import javafx.util.StringConverter;
/** /**
* Converts between user-edited strings and {@link Integer} values. * Converts between user-edited strings and 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 * <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. * input during typing, and restricts valid input to a specified range when committed.
* *
* @author Christoph Nahr * @author Christoph Nahr
* @version 1.0.2
*/ */
public class IntegerStringConverter extends StringConverter<Integer> { public class IntegerStringConverter extends StringConverter<Integer> {
private Runnable reset; private Runnable reset;
/** /**
* Creates an {@link IntegerStringConverter}. * Creates an IntegerStringConverter.
* Swallows {@link NumberFormatException} but does nothing * Swallows NumberFormatException but does nothing
* in response until {@link #setReset} is defined. * in response until {@link #setReset} is defined.
*/ */
public IntegerStringConverter() { 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. * 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) { public IntegerStringConverter(Runnable reset) {
this.reset = reset; this.reset = reset;
} }
/** /**
* Creates an {@link IntegerStringConverter} with the specified input range. * Creates an 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 * <p>Preemptively monitors input to reject any invalid characters during
* valid text is committed, and resets {@code input} to the closest value to zero * 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. * within [{@code min}, {@code max}] when invalid text is committed.
* *
* @param input the {@link TextField} providing user-edited strings * @param input The TextField providing user-edited strings.
* @param min the smallest valid {@link Integer} value * @param min The smallest valid integer value.
* @param max the greatest valid {@link Integer} value * @param max The greatest valid integer value.
* @throws NullPointerException if {@code input} is {@code null} * @throws NullPointerException if input is {@code null}
*/ */
public IntegerStringConverter(TextField input, int min, int max) { public IntegerStringConverter(TextField input, int min, int max) {
if (input == null) { if (input == null) {
@ -62,11 +63,6 @@ public class IntegerStringConverter extends StringConverter<Integer> {
final int resetValue = Math.min(Math.max(0, min), max); final int resetValue = Math.min(Math.max(0, min), max);
reset = () -> input.setText(Integer.toString(resetValue)); 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 // restrict direct input to valid numerical characters
input.textProperty().addListener((ov, oldValue, newValue) -> { input.textProperty().addListener((ov, oldValue, newValue) -> {
if (newValue == null || newValue.isEmpty()) { if (newValue == null || newValue.isEmpty()) {
@ -109,13 +105,13 @@ public class IntegerStringConverter extends StringConverter<Integer> {
} }
/** /**
* Creates an {@link IntegerStringConverter} for the specified {@link Spinner}. * Creates an IntegerStringConverter for the specified Spinner.
* Uses the {@link TextField} and minimum and maximum values of the specified * Uses the TextField and minimum and maximum values of the specified
* {@link Spinner} for construction, and also sets the new {@link IntegerStringConverter} * Spinner for construction, and also sets the new IntegerStringConverter
* on its {@link SpinnerValueFactory.IntegerSpinnerValueFactory}. * on its {@link SpinnerValueFactory.IntegerSpinnerValueFactory}.
* *
* @param spinner the {@link Spinner} to create an {@link IntegerStringConverter} for * @param spinner The Spinner to create an IntegerStringConverter for.
* @return the new {@link IntegerStringConverter} * @return the new IntegerStringConverter
* @throws NullPointerException if {@code spinner} is {@code null} * @throws NullPointerException if {@code spinner} is {@code null}
*/ */
public static IntegerStringConverter createFor(Spinner<Integer> spinner) { public static IntegerStringConverter createFor(Spinner<Integer> spinner) {
@ -135,13 +131,14 @@ public class IntegerStringConverter extends StringConverter<Integer> {
/** /**
* Sets the editor reset callback. * 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 * @see #fromString
*/ */
public void setReset(Runnable reset) { 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 * A {@code null}, empty, or otherwise invalid argument returns zero
* and also executes the editor reset callback, if any. * and also executes the editor reset callback, if any.
* *
* @param s the {@link String} to convert * @param s The {@link String} to convert.
* @return the {@link Integer} value of {@code s} * @return the integer value of {@code s}
* @see #setReset * @see #setReset
*/ */
@Override @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". * 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} * @return the {@link String} form of {@code value}
*/ */
@Override @Override

@ -26,7 +26,7 @@ public interface MaskChar {
char INPUT_MASK_DIGIT_ZERO = '0'; 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); boolean isAllowed(char ch);
@ -44,4 +44,4 @@ public interface MaskChar {
* Returns whether character is fixed (prefix, suffix or separator). * Returns whether character is fixed (prefix, suffix or separator).
*/ */
boolean isFixed(); boolean isFixed();
} }

@ -36,14 +36,19 @@ import org.jetbrains.annotations.Nullable;
* </ul> * </ul>
* *
* <p><h3>Behavior</h3> * <p><h3>Behavior</h3>
* Any {@code TextField} with {@code MaskTextFormatter} applied shows a placeholder mask by default. * Any {@code TextField} with {@code MaskTextFormatter} applied shows a placeholder
* This is basically the input mask with all mask characters replaced with {@link MaskChar#getPlaceholder()}. * mask by default. This is basically the input mask with all mask characters replaced
* The behavior changes if you set {@link TextField#promptTextProperty()}. In that case placeholder * with the {@link MaskChar#getPlaceholder()} character.
* 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>The behavior changes if you set the {@link TextField#promptTextProperty()}.
* You can replace the placeholder mask with any sensible default simply by changing initial * In that case placeholder mask is only displayed when {@code TextField} gets focus and
* {@code TextField} text to any string that is valid against the input mask. * will be hidden after focus lost. So, the placeholder mask is always displayed when focus
* <br/><br/>The caret will be positioned before the first not fixed character (see {@link MaskChar#isFixed()}) * 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/> * starting from the beginning of the input mask.<br/><br/>
* *
* <p><h3>Validation</h3> * <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. * 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) { public static TextField createTextField(String mask) {
return createTextField(fromString(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. * 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) { public static TextField createTextField(List<MaskChar> mask) {
final var field = new TextField(); 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 * 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 * applies itself to the specified text field.
* controls from FXML and don't need to modify the default {@link MaskChar}
* implementation.
*/ */
public static MaskTextFormatter create(TextField field, String mask) { public static MaskTextFormatter create(TextField field, String mask) {
return create(field, fromString(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 * 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 * applies itself to the specified text field.
* controls from FXML and want to modify the default {@link MaskChar} implementation.
*/ */
public static MaskTextFormatter create(TextField field, List<MaskChar> mask) { public static MaskTextFormatter create(TextField field, List<MaskChar> mask) {
Objects.requireNonNull(field, "Text field can't be null"); 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.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper; import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter; import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter; import javafx.util.StringConverter;
/** /**
* An alternative for the {@link javafx.scene.control.PasswordField}. * An alternative to the {@link PasswordField} class. This formatter masks
* The formatter (un)masks text field content based on boolean property. * or unmasks text field content based on a boolean property.
*/ */
public class PasswordTextFormatter extends TextFormatter<String> { public class PasswordTextFormatter extends TextFormatter<String> {

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

@ -17,21 +17,53 @@ public final class SimpleMaskChar implements MaskChar {
private final char placeholder; private final char placeholder;
private final boolean fixed; 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) { public SimpleMaskChar(Predicate<Character> matchExpr) {
this(matchExpr, UnaryOperator.identity(), UNDERSCORE, false); 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, public SimpleMaskChar(Predicate<Character> matchExpr,
UnaryOperator<Character> transform) { UnaryOperator<Character> transform) {
this(matchExpr, transform, UNDERSCORE, false); 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, public SimpleMaskChar(Predicate<Character> matchExpr,
UnaryOperator<Character> transform, UnaryOperator<Character> transform,
char placeholder) { char placeholder) {
this(matchExpr, transform, placeholder, false); 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, public SimpleMaskChar(Predicate<Character> matchExpr,
UnaryOperator<Character> transform, UnaryOperator<Character> transform,
char placeholder, char placeholder,
@ -42,26 +74,42 @@ public final class SimpleMaskChar implements MaskChar {
this.fixed = fixed; this.fixed = fixed;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public boolean isAllowed(final char ch) { public boolean isAllowed(final char ch) {
return matchExpr.test(ch); return matchExpr.test(ch);
} }
/**
* {@inheritDoc}
*/
@Override @Override
public char transform(final char ch) { public char transform(final char ch) {
return transform.apply(ch); return transform.apply(ch);
} }
/**
* {@inheritDoc}
*/
@Override @Override
public char getPlaceholder() { public char getPlaceholder() {
return placeholder; return placeholder;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public boolean isFixed() { public boolean isFixed() {
return fixed; 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) { public static SimpleMaskChar fixed(char ch) {
return new SimpleMaskChar(c -> c == ch, UnaryOperator.identity(), ch, true); 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 { module atlantafx.base {