Add DialogPane layout

This commit is contained in:
mkpaz 2023-05-25 10:29:49 +04:00
parent 128836a550
commit f8a62c55ab
7 changed files with 282 additions and 6 deletions

@ -246,7 +246,7 @@ public class ModalPane extends Control {
switch (side) {
case TOP -> {
setInTransitionFactory(node -> Animations.slideInDown(node, durIn));
setOutTransitionFactory(node -> Animations.slideOutDown(node, durOut));
setOutTransitionFactory(node -> Animations.slideOutUp(node, durOut));
}
case RIGHT -> {
setInTransitionFactory(node -> Animations.slideInRight(node, durIn));
@ -254,7 +254,7 @@ public class ModalPane extends Control {
}
case BOTTOM -> {
setInTransitionFactory(node -> Animations.slideInUp(node, durIn));
setOutTransitionFactory(node -> Animations.slideOutUp(node, durOut));
setOutTransitionFactory(node -> Animations.slideOutDown(node, durOut));
}
case LEFT -> {
setInTransitionFactory(node -> Animations.slideInLeft(node, durIn));

@ -0,0 +1,173 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.layout;
import atlantafx.base.controls.ModalPane;
import java.util.Objects;
import javafx.beans.NamedArg;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import org.jetbrains.annotations.Nullable;
/**
* The DialogPane is a specialized control or layout designed to hold the
* {@link ModalPane} dialog content. It includes the close button out-of-the-box
* and allows for the addition of arbitrary children. The DialogPane 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 DialogPane extends AnchorPane {
protected final StackPane closeButton = new StackPane();
protected final StackPane closeButtonIcon = new StackPane();
protected final @Nullable String selector;
protected @Nullable ModalPane modalPane;
/**
* Creates an DialogPane layout.
*/
public DialogPane() {
this((String) null);
}
/**
* Creates an DialogPane layout with the given children.
*
* @param children the initial set of children for this pane
*/
public DialogPane(Node... children) {
this((String) null, children);
}
/**
* Creates a DialogPane layout with the given children and binds
* the close handler to a ModalPane via CSS selector. When user clicks
* on the close button, it performs a ModalPane lookup via the specified
* selector and calls the {@link ModalPane#hide()} method automatically.
*
* @param selector the ModalPane pane CSS selector
* @param children the initial set of children for this pane
*/
public DialogPane(@Nullable @NamedArg("selector") String selector, Node... children) {
super(children);
this.selector = selector;
this.modalPane = null;
createLayout();
}
/**
* Creates a DialogPane layout with the given children and binds
* the close handler to a ModalPane. When user clicks on the close button,
* it calls the {@link ModalPane#hide()} method automatically.
*
* @param modalPane the ModalPane pane CSS selector
* @param children the initial set of children for this pane
*/
public DialogPane(@Nullable ModalPane modalPane, Node... children) {
super(children);
this.selector = null;
this.modalPane = modalPane;
createLayout();
}
/**
* Adds (prepends) specified node to the DialogPane before the close button.
*
* <p>Otherwise, if the added node takes up the full size of the DialogPane
* and {@link Node#isMouseTransparent()} is false, then the close button
* will not receive mouse events and therefore will not be clickable.
*
* @param node the node to be added
*/
public void addContent(Node node) {
Objects.requireNonNull(node, "Node cannot be null.");
getChildren().add(getChildren().indexOf(closeButton), node);
}
protected void createLayout() {
closeButton.getStyleClass().add("close-button");
closeButton.getChildren().setAll(closeButtonIcon);
closeButton.setOnMouseClicked(this::handleClose);
closeButtonIcon.getStyleClass().add("icon");
getStyleClass().add("dialog-pane");
getChildren().add(closeButton);
setCloseButtonPosition();
}
protected void setCloseButtonPosition() {
setTopAnchor(closeButton, 10d);
setRightAnchor(closeButton, 10d);
}
protected void handleClose(MouseEvent event) {
if (modalPane != null) {
modalPane.hide(clearOnClose.get());
} else if (selector != null && getScene() != null) {
if (getScene().lookup(selector) instanceof ModalPane mp) {
// cache modal pane so that the lookup is executed only once
modalPane = mp;
modalPane.hide(clearOnClose.get());
}
}
// call user specified close handler
if (onClose.get() != null) {
onClose.get().handle(event);
}
}
///////////////////////////////////////////////////////////////////////////
// Properties //
///////////////////////////////////////////////////////////////////////////
/**
* 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.
*/
protected final ObjectProperty<EventHandler<? super MouseEvent>> onClose =
new SimpleObjectProperty<>(this, "onClose");
public EventHandler<? super MouseEvent> getOnClose() {
return onClose.get();
}
public ObjectProperty<EventHandler<? super MouseEvent>> onCloseProperty() {
return onClose;
}
public void setOnClose(EventHandler<? super MouseEvent> onClose) {
this.onClose.set(onClose);
}
/**
* See {@link ModalPane#hide(boolean)}.
*/
protected final BooleanProperty clearOnClose = new SimpleBooleanProperty(this, "clearOnClose");
public boolean isClearOnClose() {
return clearOnClose.get();
}
public BooleanProperty clearOnCloseProperty() {
return clearOnClose;
}
public void setClearOnClose(boolean clearOnClose) {
this.clearOnClose.set(clearOnClose);
}
}

@ -3,6 +3,8 @@
package atlantafx.sampler.page.components;
import atlantafx.base.controls.ModalPane;
import atlantafx.base.controls.Tile;
import atlantafx.base.layout.DialogPane;
import atlantafx.base.util.BBCodeParser;
import atlantafx.sampler.Resources;
import atlantafx.sampler.page.ExampleBox;
@ -15,9 +17,12 @@ import javafx.scene.Cursor;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
@ -41,6 +46,9 @@ public final class ModalPanePage extends OutlinePage {
// add modal pane to the root container, which is StackPane
getChildren().addAll(modalPane, modalPaneTop, modalPaneTopmost);
modalPane.setId("modalPane");
modalPaneTop.setId("modalPaneTop");
modalPaneTopmost.setId("modalPaneTopmost");
// reset side and transition to reuse a single modal pane between different examples
modalPane.displayProperty().addListener((obs, old, val) -> {
@ -62,6 +70,7 @@ public final class ModalPanePage extends OutlinePage {
addSection("Nesting", nestingExample());
addSection("Maximized", maximizedExample());
addSection("Overflowed", overflowedExample());
addSection("DialogPane", dialogPaneExample());
addSection("Lightbox", lightboxExample());
}
@ -296,6 +305,52 @@ public final class ModalPanePage extends OutlinePage {
return new ExampleBox(box, new Snippet(getClass(), 6), description);
}
private ExampleBox dialogPaneExample() {
//snippet_8:start
// you can use a selector
var dialog = new DialogPane("#modalPane");
// ... or your pass a ModalPane instance directly
//var dialog = new DialogPane(modalPane);
// ... or you can set your own close handler
//dialog.setOnClose(/* whatever */);
var ta = new TextArea();
ta.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
VBox.setVgrow(ta, Priority.ALWAYS);
var content = new VBox(
20,
new Tile("Example Dialog", FAKER.lorem().sentence(10)),
ta
);
content.setPadding(new Insets(20));
dialog.addContent(content);
AnchorPane.setTopAnchor(content, 0d);
AnchorPane.setRightAnchor(content, 0d);
AnchorPane.setBottomAnchor(content, 0d);
AnchorPane.setLeftAnchor(content, 0d);
var openBtn = new Button("Open Dialog");
openBtn.setOnAction(evt -> modalPane.show(dialog));
//snippet_8:end
dialog.setPrefSize(450, 450);
dialog.setMaxSize(450, 450);
var box = new HBox(openBtn);
box.setAlignment(Pos.CENTER);
var description = BBCodeParser.createFormattedText("""
The [i]DialogPane[/i] is a specialized control (or layout) designed to hold the \
[i]ModalPane[/i] dialog content. It includes the close button out-of-the-box \
and allows for the addition of arbitrary children."""
);
return new ExampleBox(box, new Snippet(getClass(), 8), description);
}
private ExampleBox lightboxExample() {
//snippet_7:start
var modalImage = new ImageView();

@ -17,6 +17,7 @@
@use "label";
@use "menu";
@use "menu-button";
@use "modal-pane";
@use "pagination";
@use "popover";
@use "progress";

@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
@use "../settings/icons";
$color-modal-bg: rgba(0, 0, 0, 0.25) !default;
$color-dialog-bg: -color-bg-default !default;
$color-dialog-close-fg: -color-fg-default !default;
$color-dialog-close-bg-hover: -color-bg-subtle !default;
$close-button-radius: 100px !default;
$close-button-padding: 0.6em !default;
$close-button-icon-size: 0.3em !default;
.modal-pane {
-color-modal-pane-overlay: $color-modal-bg;
>.scroll-pane>.viewport>*>.scrollable-content {
-fx-background-color: -color-modal-pane-overlay;
}
}
// DialogPane is directly related to the ModalPane
.dialog-pane {
-color-dialog-pane-bg: $color-dialog-bg;
-color-dialog-pane-close-fg: $color-dialog-close-fg;
-color-dialog-pane-close-bg-hover: $color-dialog-close-bg-hover;
-fx-background-color: -color-dialog-pane-bg;
>.close-button {
-fx-background-radius: $close-button-radius;
-fx-padding: $close-button-padding;
>.icon {
@include icons.get("close", true);
-fx-background-color: -color-dialog-pane-close-fg;
-fx-padding: $close-button-icon-size;
}
&:hover {
-fx-background-color: -color-dialog-pane-close-bg-hover;
}
}
.tile {
// prevent double indentation inside dialog
-fx-padding: 0;
-fx-background-radius: 0;
}
}

@ -14,10 +14,6 @@ $radius in cfg.$elevation {
@include effects.shadow(cfg.$elevation-color, cfg.$elevation-interactive);
}
.modal-pane>.scroll-pane>.viewport>*>.scrollable-content {
-fx-background-color: rgba(0, 0, 0, 0.25);
}
///////////////////////////////////////////////////////////////////////////////
// BBCode //
///////////////////////////////////////////////////////////////////////////////

@ -10,6 +10,7 @@ $material-icons: (
"check": "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z",
"chevron-left": "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z",
"chevron-right": "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z",
"close": "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
"expand-less": "M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z",
"expand-more": "M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z",
"minus": "M 17,13 H 7 v -2 h 10 z",