Add DialogPane layout
This commit is contained in:
parent
128836a550
commit
f8a62c55ab
@ -246,7 +246,7 @@ public class ModalPane extends Control {
|
|||||||
switch (side) {
|
switch (side) {
|
||||||
case TOP -> {
|
case TOP -> {
|
||||||
setInTransitionFactory(node -> Animations.slideInDown(node, durIn));
|
setInTransitionFactory(node -> Animations.slideInDown(node, durIn));
|
||||||
setOutTransitionFactory(node -> Animations.slideOutDown(node, durOut));
|
setOutTransitionFactory(node -> Animations.slideOutUp(node, durOut));
|
||||||
}
|
}
|
||||||
case RIGHT -> {
|
case RIGHT -> {
|
||||||
setInTransitionFactory(node -> Animations.slideInRight(node, durIn));
|
setInTransitionFactory(node -> Animations.slideInRight(node, durIn));
|
||||||
@ -254,7 +254,7 @@ public class ModalPane extends Control {
|
|||||||
}
|
}
|
||||||
case BOTTOM -> {
|
case BOTTOM -> {
|
||||||
setInTransitionFactory(node -> Animations.slideInUp(node, durIn));
|
setInTransitionFactory(node -> Animations.slideInUp(node, durIn));
|
||||||
setOutTransitionFactory(node -> Animations.slideOutUp(node, durOut));
|
setOutTransitionFactory(node -> Animations.slideOutDown(node, durOut));
|
||||||
}
|
}
|
||||||
case LEFT -> {
|
case LEFT -> {
|
||||||
setInTransitionFactory(node -> Animations.slideInLeft(node, durIn));
|
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;
|
package atlantafx.sampler.page.components;
|
||||||
|
|
||||||
import atlantafx.base.controls.ModalPane;
|
import atlantafx.base.controls.ModalPane;
|
||||||
|
import atlantafx.base.controls.Tile;
|
||||||
|
import atlantafx.base.layout.DialogPane;
|
||||||
import atlantafx.base.util.BBCodeParser;
|
import atlantafx.base.util.BBCodeParser;
|
||||||
import atlantafx.sampler.Resources;
|
import atlantafx.sampler.Resources;
|
||||||
import atlantafx.sampler.page.ExampleBox;
|
import atlantafx.sampler.page.ExampleBox;
|
||||||
@ -15,9 +17,12 @@ import javafx.scene.Cursor;
|
|||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.control.TextArea;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
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;
|
||||||
import javafx.scene.text.Text;
|
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
|
// add modal pane to the root container, which is StackPane
|
||||||
getChildren().addAll(modalPane, modalPaneTop, modalPaneTopmost);
|
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
|
// reset side and transition to reuse a single modal pane between different examples
|
||||||
modalPane.displayProperty().addListener((obs, old, val) -> {
|
modalPane.displayProperty().addListener((obs, old, val) -> {
|
||||||
@ -62,6 +70,7 @@ public final class ModalPanePage extends OutlinePage {
|
|||||||
addSection("Nesting", nestingExample());
|
addSection("Nesting", nestingExample());
|
||||||
addSection("Maximized", maximizedExample());
|
addSection("Maximized", maximizedExample());
|
||||||
addSection("Overflowed", overflowedExample());
|
addSection("Overflowed", overflowedExample());
|
||||||
|
addSection("DialogPane", dialogPaneExample());
|
||||||
addSection("Lightbox", lightboxExample());
|
addSection("Lightbox", lightboxExample());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,6 +305,52 @@ public final class ModalPanePage extends OutlinePage {
|
|||||||
return new ExampleBox(box, new Snippet(getClass(), 6), description);
|
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() {
|
private ExampleBox lightboxExample() {
|
||||||
//snippet_7:start
|
//snippet_7:start
|
||||||
var modalImage = new ImageView();
|
var modalImage = new ImageView();
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
@use "label";
|
@use "label";
|
||||||
@use "menu";
|
@use "menu";
|
||||||
@use "menu-button";
|
@use "menu-button";
|
||||||
|
@use "modal-pane";
|
||||||
@use "pagination";
|
@use "pagination";
|
||||||
@use "popover";
|
@use "popover";
|
||||||
@use "progress";
|
@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);
|
@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 //
|
// BBCode //
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -10,6 +10,7 @@ $material-icons: (
|
|||||||
"check": "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z",
|
"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-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",
|
"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-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",
|
"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",
|
"minus": "M 17,13 H 7 v -2 h 10 z",
|
||||||
|
Loading…
Reference in New Issue
Block a user