Add Notification control

This commit is contained in:
mkpaz 2023-05-29 20:58:51 +04:00
parent 24a2e096ad
commit c1f9a76e1e
17 changed files with 782 additions and 24 deletions

@ -72,10 +72,7 @@ public class Message extends TileBase {
}
/**
* The property representing the user specified close handler. Note that
* if you have also specified the ModalPane instance or CSS selector, this
* handler will be executed after the default close handler. Therefore, you
* can use it to perform arbitrary actions on dialog close.
* The property representing the user specified close handler.
*/
protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose");

@ -27,7 +27,7 @@ public class MessageSkin extends TileSkinBase<Message> {
o -> pseudoClassStateChanged(Styles.STATE_INTERACTIVE, getSkinnable().getActionHandler() != null)
);
root.setOnMouseClicked(e -> {
container.setOnMouseClicked(e -> {
if (getSkinnable().getActionHandler() != null) {
getSkinnable().getActionHandler().run();
}
@ -65,7 +65,7 @@ public class MessageSkin extends TileSkinBase<Message> {
layoutInArea(closeButton, w - lb.getWidth() - 5, 5, lb.getWidth(), lb.getHeight(), -1, HPos.RIGHT,
VPos.TOP);
}
layoutInArea(root, x, y, w, h, -1, HPos.CENTER, VPos.CENTER);
layoutInArea(container, x, y, w, h, -1, HPos.CENTER, VPos.CENTER);
}
@Override

@ -0,0 +1,168 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Skin;
import javafx.scene.layout.Region;
import org.jetbrains.annotations.Nullable;
/**
* The Notification control is intended for displaying alerts and messages
* to users as pop-ups. It's customizable with different colors and icons,
* can contain a graphic or image, along with the message and additional actions
* for users to take. The purpose of this control is to immediately notify users
* of significant events and provide them with quick access to related actions without
* interrupting their workflow.
*/
public class Notification extends Control {
public Notification() {
this(null, null);
}
public Notification(@Nullable @NamedArg("message") String message) {
this(message, null);
}
public Notification(@Nullable @NamedArg("message") String message,
@Nullable @NamedArg("graphic") Node graphic) {
super();
setMessage(message);
setGraphic(graphic);
// set reasonable default width
setPrefWidth(400);
setMaxWidth(Region.USE_PREF_SIZE);
getStyleClass().add("notification");
}
@Override
protected Skin<?> createDefaultSkin() {
return new NotificationSkin(this);
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* Represents an optional graphical component that can be displayed alongside
* the notification message.
*/
private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic");
public Node getGraphic() {
return graphic.get();
}
public ObjectProperty<Node> graphicProperty() {
return graphic;
}
public void setGraphic(Node graphic) {
this.graphic.set(graphic);
}
/**
* Stores a short text message that will be displayed to users when the
* notification appears. This property doesn't support the formatted text.
*/
private final StringProperty message = new SimpleStringProperty(this, "message");
public String getMessage() {
return message.get();
}
public StringProperty messageProperty() {
return message;
}
public void setMessage(String message) {
this.message.set(message);
}
/**
* Specifies the primary actions associated with this notification.
*
* <p>This property is used to store one or more action buttons that will
* be displayed at the bottom of the notification when it appears.
*/
private final ReadOnlyObjectWrapper<ObservableList<Button>> primaryActions =
new ReadOnlyObjectWrapper<>(this, "primaryActions", FXCollections.observableArrayList());
public ObservableList<Button> getPrimaryActions() {
return primaryActions.get();
}
public ReadOnlyObjectProperty<ObservableList<Button>> primaryActionsProperty() {
return primaryActions.getReadOnlyProperty();
}
public void setPrimaryActions(ObservableList<Button> buttons) {
this.primaryActions.set(buttons);
}
public void setPrimaryActions(Button... buttons) {
getPrimaryActions().setAll(buttons);
}
/**
* Specifies the secondary actions associated with this notification.
*
* <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.
*/
private final ReadOnlyObjectWrapper<ObservableList<MenuItem>> secondaryActions =
new ReadOnlyObjectWrapper<>(this, "secondaryActions", FXCollections.observableArrayList());
public ObservableList<MenuItem> getSecondaryActions() {
return secondaryActions.get();
}
public ReadOnlyObjectProperty<ObservableList<MenuItem>> secondaryActionsProperty() {
return secondaryActions.getReadOnlyProperty();
}
public void setSecondaryActions(ObservableList<MenuItem> items) {
this.secondaryActions.set(items);
}
public void setSecondaryActions(MenuItem... items) {
getSecondaryActions().setAll(items);
}
/**
* Specifies the close handler used to dismiss this notification.
*/
protected final ObjectProperty<EventHandler<? super Event>> onClose =
new SimpleObjectProperty<>(this, "onClose");
public EventHandler<? super Event> getOnClose() {
return onClose.get();
}
public ObjectProperty<EventHandler<? super Event>> onCloseProperty() {
return onClose;
}
public void setOnClose(EventHandler<? super Event> onClose) {
this.onClose.set(onClose);
}
}

@ -0,0 +1,176 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.controls;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class NotificationSkin extends SkinBase<Notification> {
protected final VBox container = new VBox();
protected final HBox header = new HBox();
protected final StackPane graphicSlot = new StackPane();
protected final ChangeListener<Node> graphicSlotListener = new SlotListener(graphicSlot);
protected final TextFlow messageText = new TextFlow();
protected final StackPane closeButton = new StackPane();
protected final StackPane closeButtonIcon = new StackPane();
protected final StackPane menuButton = new StackPane();
protected final StackPane menuButtonIcon = new StackPane();
protected final ContextMenu actionsMenu = new ContextMenu();
protected final HBox actionsBox = new HBox();
protected final ButtonBar buttonBar = new ButtonBar();
protected NotificationSkin(Notification control) {
super(control);
// == GRAPHIC ==
graphicSlot.getStyleClass().add("graphic");
control.graphicProperty().addListener(graphicSlotListener);
graphicSlotListener.changed(control.graphicProperty(), null, control.getGraphic());
// == MESSAGE ==
messageText.getStyleClass().add("message");
HBox.setHgrow(messageText, Priority.ALWAYS);
setMessageText();
registerChangeListener(control.messageProperty(), o -> setMessageText());
// text wrapping won't work without this
messageText.setMaxWidth(Region.USE_PREF_SIZE);
messageText.setMinHeight(Region.USE_PREF_SIZE);
// == TOP BUTTONS ==
menuButton.getStyleClass().add("secondary-menu-button");
menuButton.getChildren().setAll(menuButtonIcon);
menuButton.setOnMouseClicked(e -> actionsMenu.show(
menuButton,
menuButton.localToScreen(menuButton.getLayoutBounds()).getMinX(),
menuButton.localToScreen(menuButton.getLayoutBounds()).getMaxY()
));
menuButton.setVisible(!getSkinnable().getSecondaryActions().isEmpty());
menuButton.setManaged(!getSkinnable().getSecondaryActions().isEmpty());
menuButtonIcon.getStyleClass().add("icon");
Bindings.bindContent(actionsMenu.getItems(), getSkinnable().getSecondaryActions());
registerListChangeListener(actionsMenu.getItems(), o -> {
menuButton.setVisible(!getSkinnable().getSecondaryActions().isEmpty());
menuButton.setManaged(!getSkinnable().getSecondaryActions().isEmpty());
});
closeButton.getStyleClass().add("close-button");
closeButton.getChildren().setAll(closeButtonIcon);
closeButton.setOnMouseClicked(e -> handleClose());
closeButton.setVisible(control.getOnClose() != null);
closeButton.setManaged(control.getOnClose() != null);
closeButtonIcon.getStyleClass().add("icon");
registerChangeListener(control.onCloseProperty(), o -> {
closeButton.setVisible(getSkinnable().getOnClose() != null);
closeButton.setManaged(getSkinnable().getOnClose() != null);
});
actionsBox.getStyleClass().add("actions");
actionsBox.getChildren().setAll(menuButton, closeButton);
actionsBox.setFillHeight(false);
HBox.setMargin(actionsBox, new Insets(-8, -8, 0, 0));
// == HEADER ==
// use pref size for slots, or they will be resized
// to the bare minimum due to Priority.ALWAYS
graphicSlot.setMinWidth(Region.USE_PREF_SIZE);
actionsBox.setMinWidth(Region.USE_PREF_SIZE);
// do not resize children or container won't restore
// to its original size after expanding
header.setFillHeight(false);
header.getStyleClass().add("header");
header.getChildren().setAll(graphicSlot, messageText, actionsBox);
header.setAlignment(Pos.TOP_LEFT);
// == BUTTON BAR ==
buttonBar.getStyleClass().add("button-bar");
buttonBar.setVisible(!getSkinnable().getPrimaryActions().isEmpty());
buttonBar.setManaged(!getSkinnable().getPrimaryActions().isEmpty());
Bindings.bindContent(buttonBar.getButtons(), getSkinnable().getPrimaryActions());
registerListChangeListener(buttonBar.getButtons(), o -> {
buttonBar.setVisible(!getSkinnable().getPrimaryActions().isEmpty());
buttonBar.setManaged(!getSkinnable().getPrimaryActions().isEmpty());
});
// == CONTAINER ==
container.getChildren().setAll(header, buttonBar);
container.getStyleClass().add("container");
getChildren().setAll(container);
}
protected void setMessageText() {
if (!messageText.getChildren().isEmpty()) {
messageText.getChildren().clear();
}
if (getSkinnable().getMessage() != null && !getSkinnable().getMessage().isBlank()) {
messageText.getChildren().setAll(new Text(getSkinnable().getMessage()));
}
}
protected void handleClose() {
if (getSkinnable().getOnClose() != null) {
getSkinnable().getOnClose().handle(new Event(Event.ANY));
}
}
protected double calcHeight() {
var messageHeight = messageText.getBoundsInLocal().getHeight();
return Math.max(Math.max(graphicSlot.getHeight(), actionsBox.getHeight()), messageHeight)
+ (buttonBar.isManaged() ? buttonBar.getHeight() + container.getSpacing() : 0)
+ header.getPadding().getTop()
+ header.getPadding().getBottom()
+ container.getPadding().getTop()
+ container.getPadding().getBottom();
}
@Override
protected double computeMinHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
return calcHeight();
}
@Override
public void dispose() {
Bindings.unbindContent(actionsMenu.getItems(), getSkinnable().getSecondaryActions());
unregisterListChangeListeners(actionsMenu.getItems());
Bindings.unbindContent(buttonBar.getButtons(), getSkinnable().getPrimaryActions());
unregisterListChangeListeners(buttonBar.getButtons());
getSkinnable().graphicProperty().removeListener(graphicSlotListener);
unregisterChangeListeners(getSkinnable().messageProperty());
unregisterChangeListeners(getSkinnable().onCloseProperty());
super.dispose();
}
}

@ -18,7 +18,7 @@ public class TileSkin extends TileSkinBase<Tile> {
o -> pseudoClassStateChanged(Styles.STATE_INTERACTIVE, getSkinnable().getActionHandler() != null)
);
root.setOnMouseClicked(e -> {
container.setOnMouseClicked(e -> {
if (getSkinnable().getActionHandler() != null) {
getSkinnable().getActionHandler().run();
}

@ -22,7 +22,7 @@ public abstract class TileSkinBase<T extends TileBase> extends SkinBase<T> {
protected static final PseudoClass HAS_DESCRIPTION = PseudoClass.getPseudoClass("has-description");
protected static final PseudoClass HAS_ACTION = PseudoClass.getPseudoClass("has-action");
protected final HBox root = new HBox();
protected final HBox container = new HBox();
protected final StackPane graphicSlot;
protected final ChangeListener<Node> graphicSlotListener;
protected final VBox headerBox;
@ -90,17 +90,17 @@ public abstract class TileSkinBase<T extends TileBase> extends SkinBase<T> {
graphicSlot.setMinWidth(Region.USE_PREF_SIZE);
actionSlot.setMinWidth(Region.USE_PREF_SIZE);
// label text wrapping inside VBox won't work without this
// text wrapping inside VBox won't work without this
descriptionText.setMaxWidth(Region.USE_PREF_SIZE);
descriptionText.setMinHeight(Region.USE_PREF_SIZE);
// do not resize children or container won't restore
// to its original size after expanding
root.setFillHeight(false);
container.setFillHeight(false);
root.getChildren().setAll(graphicSlot, headerBox, actionSlot);
root.getStyleClass().add("container");
getChildren().setAll(root);
container.getChildren().setAll(graphicSlot, headerBox, actionSlot);
container.getStyleClass().add("container");
getChildren().setAll(container);
}
protected void setDescriptionText() {
@ -120,8 +120,8 @@ public abstract class TileSkinBase<T extends TileBase> extends SkinBase<T> {
+ (descriptionText.isManaged() ? descriptionText.getBoundsInLocal().getHeight() : 0);
return Math.max(Math.max(graphicSlot.getHeight(), actionSlot.getHeight()), headerHeight)
+ root.getPadding().getTop()
+ root.getPadding().getBottom();
+ container.getPadding().getTop()
+ container.getPadding().getBottom();
}
@Override

@ -9,7 +9,6 @@ import atlantafx.sampler.event.DefaultEventBus;
import atlantafx.sampler.event.NavEvent;
import atlantafx.sampler.page.Page;
import atlantafx.sampler.page.components.AccordionPage;
import atlantafx.sampler.page.general.AnimationsPage;
import atlantafx.sampler.page.components.BreadcrumbsPage;
import atlantafx.sampler.page.components.ButtonPage;
import atlantafx.sampler.page.components.CalendarPage;
@ -31,6 +30,7 @@ import atlantafx.sampler.page.components.MenuBarPage;
import atlantafx.sampler.page.components.MenuButtonPage;
import atlantafx.sampler.page.components.MessagePage;
import atlantafx.sampler.page.components.ModalPanePage;
import atlantafx.sampler.page.components.NotificationPage;
import atlantafx.sampler.page.components.PaginationPage;
import atlantafx.sampler.page.components.PopoverPage;
import atlantafx.sampler.page.components.ProgressIndicatorPage;
@ -52,6 +52,7 @@ import atlantafx.sampler.page.components.ToolBarPage;
import atlantafx.sampler.page.components.TooltipPage;
import atlantafx.sampler.page.components.TreeTableViewPage;
import atlantafx.sampler.page.components.TreeViewPage;
import atlantafx.sampler.page.general.AnimationsPage;
import atlantafx.sampler.page.general.BBCodePage;
import atlantafx.sampler.page.general.IconsPage;
import atlantafx.sampler.page.general.ThemePage;
@ -161,6 +162,7 @@ public class MainModel {
feedback.getChildren().setAll(
NAV_TREE.get(DialogPage.class),
NAV_TREE.get(MessagePage.class),
NAV_TREE.get(NotificationPage.class),
NAV_TREE.get(ProgressIndicatorPage.class),
NAV_TREE.get(TooltipPage.class)
);
@ -268,6 +270,7 @@ public class MainModel {
);
map.put(MessagePage.class, NavTree.Item.page(MessagePage.NAME, MessagePage.class));
map.put(ModalPanePage.class, NavTree.Item.page(ModalPanePage.NAME, ModalPanePage.class));
map.put(NotificationPage.class, NavTree.Item.page(NotificationPage.NAME, NotificationPage.class));
map.put(PaginationPage.class, NavTree.Item.page(PaginationPage.NAME, PaginationPage.class));
map.put(PopoverPage.class, NavTree.Item.page(PopoverPage.NAME, PopoverPage.class));
map.put(ProgressIndicatorPage.class, NavTree.Item.page(

@ -11,6 +11,7 @@ import atlantafx.sampler.page.components.DeckPanePage;
import atlantafx.sampler.page.components.InputGroupPage;
import atlantafx.sampler.page.components.MessagePage;
import atlantafx.sampler.page.components.ModalPanePage;
import atlantafx.sampler.page.components.NotificationPage;
import atlantafx.sampler.page.components.PopoverPage;
import atlantafx.sampler.page.components.TilePage;
import atlantafx.sampler.page.components.ToggleSwitchPage;
@ -39,6 +40,7 @@ record Nav(String title,
InputGroupPage.class,
MessagePage.class,
ModalPanePage.class,
NotificationPage.class,
PopoverPage.class,
TilePage.class,
ToggleSwitchPage.class

@ -0,0 +1,227 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.components;
import atlantafx.base.controls.Notification;
import atlantafx.base.theme.Styles;
import atlantafx.base.util.Animations;
import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.Resources;
import atlantafx.sampler.page.ExampleBox;
import atlantafx.sampler.page.OutlinePage;
import atlantafx.sampler.page.Snippet;
import java.net.URI;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.material2.Material2OutlinedAL;
public class NotificationPage extends OutlinePage {
public static final String NAME = "Notification";
@Override
public String getName() {
return NAME;
}
@Override
public URI getJavadocUri() {
return URI.create(String.format(AFX_JAVADOC_URI_TEMPLATE, "controls/" + getName()));
}
public NotificationPage() {
super();
addPageHeader();
addFormattedText("""
The [i]Notification[/i] control is intended for displaying alerts and messages \
to users as pop-ups. It's customizable with different colors and icons, can contain \
a graphic or image, along with the message and additional actions for users to take."""
);
addSection("Usage", usageExample());
addSection("Actions", actionsExample());
addSection("Intent", intentExample());
addSection("Popup", popupExample());
addSection("Elevation", elevationExample());
}
private Node usageExample() {
//snippet_1:start
var ntf1 = new Notification(
FAKER.lorem().sentence(15)
);
ntf1.getStyleClass().add(Styles.ELEVATED_1);
var icon1 = new ImageView(new Image(
Resources.getResourceAsStream("images/warning_32.png")
));
ntf1.setGraphic(icon1);
var ntf2 = new Notification(FAKER.lorem().sentence(15));
ntf2.getStyleClass().add(Styles.ELEVATED_1);
ntf2.setOnClose(e -> Animations.flash(ntf2).playFromStart());
//snippet_1:end
var box = new VBox(VGAP_20, ntf1, ntf2);
var description = BBCodeParser.createFormattedText("""
The [i]Notification[/i] has no mandatory properties, but it wouldn't make \
sense without a message. Optionally, you can use a graphic and also make the \
[i]Notification[/i] closeable by providing an appropriate dismiss handler."""
);
return new ExampleBox(box, new Snippet(getClass(), 1), description);
}
private Node actionsExample() {
//snippet_2:start
var ntf = new Notification(
FAKER.lorem().sentence(15),
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
);
ntf.getStyleClass().add(Styles.ELEVATED_1);
ntf.setOnClose(e -> Animations.flash(ntf).playFromStart());
var yesBtn = new Button("Yes");
yesBtn.setDefaultButton(true);
var noBtn = new Button("No");
ntf.setPrimaryActions(yesBtn, noBtn);
ntf.setSecondaryActions(
new MenuItem("Item 1"),
new MenuItem("Item 2")
);
//snippet_2:end
var box = new VBox(ntf);
var description = BBCodeParser.createFormattedText("""
The notification has two slots for setting custom actions. The primary action \
slot appears like a [i]ButtonBar[/i] (check the Javadoc for its features, by the way) \
at the bottom of the notification. The secondary actions slot is the dropdown menu \
at the top right corner. Both slots are completely optional."""
);
return new ExampleBox(box, new Snippet(getClass(), 2), description);
}
private Node intentExample() {
//snippet_3:start
var info = new Notification(
FAKER.lorem().sentence(15),
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
);
info.getStyleClass().add(Styles.ELEVATED_1);
info.getStyleClass().add(Styles.ACCENT);
info.setOnClose(e -> Animations.flash(info).playFromStart());
var success = new Notification(
FAKER.lorem().sentence(15),
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
);
success.getStyleClass().add(Styles.ELEVATED_1);
success.getStyleClass().add(Styles.SUCCESS);
success.setOnClose(e -> Animations.flash(success).playFromStart());
var warning = new Notification(
FAKER.lorem().sentence(15),
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
);
warning.getStyleClass().add(Styles.ELEVATED_1);
warning.getStyleClass().add(Styles.WARNING);
warning.setOnClose(e -> Animations.flash(warning).playFromStart());
var danger = new Notification(
FAKER.lorem().sentence(15),
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
);
danger.getStyleClass().add(Styles.ELEVATED_1);
danger.getStyleClass().add(Styles.DANGER);
danger.setOnClose(e -> Animations.flash(danger).playFromStart());
//snippet_3:end
var box = new VBox(VGAP_20, info, success, warning, danger);
var description = BBCodeParser.createFormattedText("""
The [i]Notification[/i] supports colors (or intents). To set them, \
use the corresponding style class modifier."""
);
return new ExampleBox(box, new Snippet(getClass(), 3), description);
}
private ExampleBox popupExample() {
//snippet_4:start
final var msg = new Notification(
FAKER.lorem().sentence(15),
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
);
msg.getStyleClass().addAll(
Styles.ACCENT, Styles.ELEVATED_1
);
msg.setPrefHeight(Region.USE_PREF_SIZE);
msg.setMaxHeight(Region.USE_PREF_SIZE);
StackPane.setAlignment(msg, Pos.TOP_RIGHT);
StackPane.setMargin(msg, new Insets(10, 10, 0, 0));
var btn = new Button("Show");
msg.setOnClose(e -> {
var out = Animations.slideOutUp(msg, Duration.millis(250));
out.setOnFinished(f -> getChildren().remove(msg));
out.playFromStart();
});
btn.setOnAction(e -> {
var in = Animations.slideInDown(msg, Duration.millis(250));
if (!getChildren().contains(msg)) {
getChildren().add(msg);
}
in.playFromStart();
});
//snippet_4:end
var box = new VBox(VGAP_20, btn);
box.setPadding(new Insets(0, 0, 10, 0));
var description = BBCodeParser.createFormattedText("""
There isn't any special support for popup notifications, but \
you can easily implement it by using the [i]StackPane[/i] layout."""
);
return new ExampleBox(box, new Snippet(getClass(), 4), description);
}
private Node elevationExample() {
//snippet_5:start
var ntf1 = new Notification(
FAKER.lorem().sentence(15),
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
);
ntf1.getStyleClass().add(Styles.ELEVATED_2);
ntf1.setOnClose(e -> Animations.flash(ntf1).playFromStart());
var ntf2 = new Notification(
FAKER.lorem().sentence(15),
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
);
ntf2.getStyleClass().add(Styles.INTERACTIVE);
ntf2.setOnClose(e -> Animations.flash(ntf2).playFromStart());
//snippet_5:end
var box = new VBox(VGAP_20, ntf1, ntf2);
box.setPadding(new Insets(0, 0, 10, 0));
var description = BBCodeParser.createFormattedText("""
To add the raised effect to the [i]Notification[/i], use the [code]Styles.ELEVATED_N[/code] \
or [code]Styles.INTERACTIVE[/code] style classes."""
);
return new ExampleBox(box, new Snippet(getClass(), 5), description);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -19,6 +19,7 @@
@use "menu-button";
@use "message";
@use "modal-pane";
@use "notification";
@use "pagination";
@use "popover";
@use "progress";

@ -39,8 +39,7 @@ $color-border-danger-hover: -color-danger-emphasis !default;
$color-button-danger-bg-hover: -color-danger-muted !default;
$close-button-radius: 100px !default;
$close-button-padding: 0.6em !default;
$close-button-icon-size: 0.3em !default;
$close-button-padding: 0.5em; // private variable
.message {
@ -147,11 +146,12 @@ $close-button-icon-size: 0.3em !default;
>.icon {
@include icons.get("close", true);
-fx-background-color: -color-message-fg-primary;
-fx-padding: $close-button-icon-size;
-fx-padding: 0.3em;
}
&:hover {
-fx-background-color: -color-message-button-hover;
-fx-background-color: -color-message-border, -color-message-button-hover;
-fx-background-insets: 0, 1;
}
}

@ -38,7 +38,8 @@ $close-button-icon-size: 0.3em !default;
}
&:hover {
-fx-background-color: -color-modal-box-close-bg-hover;
-fx-background-color: -color-border-muted, -color-modal-box-close-bg-hover;
-fx-background-insets: 0, 1;
}
}

@ -0,0 +1,159 @@
// SPDX-License-Identifier: MIT
@use "../settings/config" as cfg;
@use "../settings/effects";
@use "../settings/icons";
$color-bg: -color-bg-subtle !default;
$color-fg: -color-fg-default !default;
$color-bg-hover: -color-bg-default !default;
$color-fg-hover: -color-fg-default !default;
$color-border: -color-border-default !default;
$color-border-intent: -color-accent-emphasis !default;
$padding-x: 1em !default;
$padding-y: 1em !default;
$graphic-text-gap: 0.75em !default;
$intent-border-width: 5px !default;
$action-button-radius: 100px !default;
@mixin colored($color-intent) {
-color-notify-border-intent: $color-intent;
&>.container {
-fx-background-color: -color-notify-border,
-color-notify-bg,
-color-notify-border-intent -color-notify-bg -color-notify-bg -color-notify-bg,
-color-notify-bg;
-fx-background-insets: 0, 1, 1, 1 1 1 (1 + $intent-border-width);
-fx-background-radius: cfg.$border-radius;
>.header {
>.graphic {
#{cfg.$font-icon-selector} {
-fx-fill: $color-intent;
-fx-icon-color: $color-intent;
}
}
}
}
}
.notification {
-color-notify-bg: $color-bg;
-color-notify-fg: $color-fg;
-color-notify-bg-hover: $color-bg-hover;
-color-notify-fg-hover: $color-fg-hover;
-color-notify-border: $color-border;
-color-notify-border-intent: $color-border-intent;
>.container {
-fx-background-color: -color-notify-border, -color-notify-bg;
-fx-background-insets: 0, cfg.$border-width;
-fx-background-radius: cfg.$border-radius;
-fx-spacing: $padding-y;
-fx-padding: 0 0 $padding-y 0;
>.header {
-fx-padding: $padding-y $padding-x 0 $padding-x;
-fx-spacing: $graphic-text-gap;
>.graphic {
#{cfg.$font-icon-selector} {
-fx-icon-size: cfg.$icon-size-larger;
}
}
>.message {
Text {
-fx-fill: -color-notify-fg;
}
}
>.actions {
-fx-alignment: CENTER_RIGHT;
-fx-spacing: 5px;
>.secondary-menu-button {
-fx-background-radius: $action-button-radius;
-fx-padding: 0.5em 0.75em 0.5em 0.75em;
>.icon {
@include icons.get("more-vert", true);
-fx-background-color: -color-notify-fg;
-fx-background-insets: 0;
-fx-padding: 0.3em 0.1em 0.3em 0.1em;
}
&:hover {
-fx-background-color: -color-border-default, -color-notify-bg-hover;
-fx-background-insets: 0, 1;
>.icon {
-fx-background-color: -color-notify-fg-hover;
}
}
}
>.close-button {
-fx-background-radius: $action-button-radius;
-fx-padding: 0.5em;
>.icon {
@include icons.get("close", true);
-fx-background-color: -color-notify-fg;
-fx-padding: 0.3em;
}
&:hover {
-fx-background-color: -color-border-default, -color-notify-bg-hover;
-fx-background-insets: 0, 1;
>.icon {
-fx-background-color: -color-notify-fg-hover;
}
}
}
}
&:disabled {
-fx-opacity: cfg.$opacity-disabled;
}
}
>.button-bar {
-fx-padding: 0 $padding-x 0 $padding-x;
}
@each $level, $radius in cfg.$elevation {
&.elevated-#{$level} {
>.container {
@include effects.shadow(cfg.$elevation-color, $radius);
}
}
}
&.interactive:hover {
>.container {
@include effects.shadow(cfg.$elevation-color, cfg.$elevation-interactive);
}
}
}
&.accent {
@include colored(-color-accent-emphasis);
}
&.success {
@include colored(-color-success-emphasis);
}
&.warning {
@include colored(-color-warning-emphasis);
}
&.danger {
@include colored(-color-danger-emphasis);
}
}

@ -153,6 +153,18 @@ $nord16: #B48EAD; // hsl(311, 20.2, 63.1)
$color-menubar-bg-hover: -color-base-7
);
@forward "components/message" as message-* with (
$close-button-radius: 0
);
@forward "components/modal-pane" as modal-pane-* with (
$close-button-radius: 0
);
@forward "components/notification" as notification-* with (
$action-button-radius: 0
);
@forward "components/pagination" as pagination-* with (
$button-radius: 1
);

@ -139,6 +139,18 @@ $nord16: #B48EAD; // hsl(311, 20.2, 63.1)
@use "general";
@forward "components/message" as message-* with (
$close-button-radius: 0
);
@forward "components/modal-pane" as modal-pane-* with (
$close-button-radius: 0
);
@forward "components/notification" as notification-* with (
$action-button-radius: 0
);
@forward "components/pagination" as pagination-* with (
$button-radius: 1
);