Add Notification control
This commit is contained in:
parent
24a2e096ad
commit
c1f9a76e1e
@ -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
|
||||
|
168
base/src/main/java/atlantafx/base/controls/Notification.java
Normal file
168
base/src/main/java/atlantafx/base/controls/Notification.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
176
base/src/main/java/atlantafx/base/controls/NotificationSkin.java
Normal file
176
base/src/main/java/atlantafx/base/controls/NotificationSkin.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
BIN
sampler/src/main/resources/atlantafx/sampler/images/info_32.png
Normal file
BIN
sampler/src/main/resources/atlantafx/sampler/images/info_32.png
Normal file
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
159
styles/src/components/_notification.scss
Normal file
159
styles/src/components/_notification.scss
Normal file
@ -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
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user