Add DialogPane layout
This commit is contained in:
parent
128836a550
commit
f8a62c55ab
@ -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));
|
||||
|
173
base/src/main/java/atlantafx/base/layout/DialogPane.java
Normal file
173
base/src/main/java/atlantafx/base/layout/DialogPane.java
Normal file
@ -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";
|
||||
|
50
styles/src/components/_modal-pane.scss
Normal file
50
styles/src/components/_modal-pane.scss
Normal file
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user