Refactor and improve Tile-based controls
This commit is contained in:
parent
998bd69334
commit
34acefa8f8
@ -2,34 +2,93 @@
|
|||||||
|
|
||||||
package atlantafx.base.controls;
|
package atlantafx.base.controls;
|
||||||
|
|
||||||
|
import javafx.beans.NamedArg;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.event.Event;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Skin;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Message is a component for displaying notifications or alerts
|
* The Message is a component for displaying notifications or alerts
|
||||||
* and is specifically designed to grab the user’s attention.
|
* and is specifically designed to grab the user’s attention.
|
||||||
* It is based on the Tile layout and shares its structure.
|
* It is based on the Tile layout and shares its structure.
|
||||||
*/
|
*/
|
||||||
public class Message extends Tile {
|
public class Message extends TileBase {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See {@link Tile#Tile()}.
|
* See {@link Tile#Tile()}.
|
||||||
*/
|
*/
|
||||||
public Message() {
|
public Message() {
|
||||||
super(null, null, null);
|
this(null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See {@link Tile#Tile(String, String)}.
|
* See {@link Tile#Tile(String, String)}.
|
||||||
*/
|
*/
|
||||||
public Message(String title, String subtitle) {
|
public Message(@Nullable @NamedArg("title") String title,
|
||||||
this(title, subtitle, null);
|
@Nullable @NamedArg("description") String description) {
|
||||||
|
this(title, description, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See {@link Tile#Tile(String, String, Node)}.
|
* See {@link Tile#Tile(String, String, Node)}.
|
||||||
*/
|
*/
|
||||||
public Message(String title, String subtitle, Node graphic) {
|
public Message(@Nullable String title,
|
||||||
super(title, subtitle, graphic);
|
@Nullable String description,
|
||||||
|
@Nullable Node graphic) {
|
||||||
|
super(title, description, graphic);
|
||||||
getStyleClass().add("message");
|
getStyleClass().add("message");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Skin<?> createDefaultSkin() {
|
||||||
|
return new MessageSkin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Properties //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property representing the message’s action handler. Setting an action handler
|
||||||
|
* makes the message interactive or clickable. When a user clicks on the interactive
|
||||||
|
* message, the specified action handler will be called.
|
||||||
|
*/
|
||||||
|
private final ObjectProperty<Runnable> actionHandler = new SimpleObjectProperty<>(this, "actionHandler");
|
||||||
|
|
||||||
|
public Runnable getActionHandler() {
|
||||||
|
return actionHandler.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<Runnable> actionHandlerProperty() {
|
||||||
|
return actionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionHandler(Runnable actionHandler) {
|
||||||
|
this.actionHandler.set(actionHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
78
base/src/main/java/atlantafx/base/controls/MessageSkin.java
Normal file
78
base/src/main/java/atlantafx/base/controls/MessageSkin.java
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
package atlantafx.base.controls;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import javafx.css.PseudoClass;
|
||||||
|
import javafx.event.Event;
|
||||||
|
import javafx.geometry.HPos;
|
||||||
|
import javafx.geometry.VPos;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
public class MessageSkin extends TileSkinBase<Message> {
|
||||||
|
|
||||||
|
private static final PseudoClass CLOSEABLE = PseudoClass.getPseudoClass("closeable");
|
||||||
|
|
||||||
|
protected final StackPane closeButton = new StackPane();
|
||||||
|
protected final StackPane closeButtonIcon = new StackPane();
|
||||||
|
|
||||||
|
public MessageSkin(Message control) {
|
||||||
|
super(control);
|
||||||
|
|
||||||
|
// ACTION
|
||||||
|
|
||||||
|
pseudoClassStateChanged(Styles.STATE_INTERACTIVE, control.getActionHandler() != null);
|
||||||
|
registerChangeListener(
|
||||||
|
control.actionHandlerProperty(),
|
||||||
|
o -> pseudoClassStateChanged(Styles.STATE_INTERACTIVE, getSkinnable().getActionHandler() != null)
|
||||||
|
);
|
||||||
|
|
||||||
|
root.setOnMouseClicked(e -> {
|
||||||
|
if (getSkinnable().getActionHandler() != null) {
|
||||||
|
getSkinnable().getActionHandler().run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// CLOSE BUTTON
|
||||||
|
|
||||||
|
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");
|
||||||
|
getChildren().add(closeButton);
|
||||||
|
|
||||||
|
pseudoClassStateChanged(CLOSEABLE, control.getOnClose() != null);
|
||||||
|
registerChangeListener(control.onCloseProperty(), o -> {
|
||||||
|
closeButton.setVisible(getSkinnable().getOnClose() != null);
|
||||||
|
closeButton.setManaged(getSkinnable().getOnClose() != null);
|
||||||
|
pseudoClassStateChanged(CLOSEABLE, getSkinnable().onCloseProperty() != null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleClose() {
|
||||||
|
if (getSkinnable().getOnClose() != null) {
|
||||||
|
getSkinnable().getOnClose().handle(new Event(Event.ANY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void layoutChildren(double x, double y, double w, double h) {
|
||||||
|
if (closeButton.isManaged()) {
|
||||||
|
var lb = closeButton.getLayoutBounds();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
unregisterChangeListeners(getSkinnable().actionHandlerProperty());
|
||||||
|
unregisterChangeListeners(getSkinnable().onCloseProperty());
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -3,21 +3,30 @@
|
|||||||
package atlantafx.base.controls;
|
package atlantafx.base.controls;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
final class SlotListener implements ChangeListener<Node> {
|
final class SlotListener implements ChangeListener<Node> {
|
||||||
|
|
||||||
private static final PseudoClass FILLED = PseudoClass.getPseudoClass("filled");
|
private static final PseudoClass FILLED = PseudoClass.getPseudoClass("filled");
|
||||||
|
|
||||||
private final Pane slot;
|
private final Pane slot;
|
||||||
|
private final @Nullable BiConsumer<Node, Boolean> onContentUpdate;
|
||||||
|
|
||||||
public SlotListener(Node slot) {
|
public SlotListener(Pane slot) {
|
||||||
|
this(slot, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlotListener(Node slot, @Nullable BiConsumer<Node, Boolean> onContentUpdate) {
|
||||||
Objects.requireNonNull(slot, "Slot cannot be null.");
|
Objects.requireNonNull(slot, "Slot cannot be null.");
|
||||||
|
|
||||||
|
this.onContentUpdate = onContentUpdate;
|
||||||
|
|
||||||
if (slot instanceof Pane pane) {
|
if (slot instanceof Pane pane) {
|
||||||
this.slot = pane;
|
this.slot = pane;
|
||||||
} else {
|
} else {
|
||||||
@ -35,5 +44,9 @@ final class SlotListener implements ChangeListener<Node> {
|
|||||||
slot.setVisible(val != null);
|
slot.setVisible(val != null);
|
||||||
slot.setManaged(val != null);
|
slot.setManaged(val != null);
|
||||||
slot.pseudoClassStateChanged(FILLED, val != null);
|
slot.pseudoClassStateChanged(FILLED, val != null);
|
||||||
|
|
||||||
|
if (onContentUpdate != null) {
|
||||||
|
onContentUpdate.accept(val, val != null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,35 +5,31 @@ package atlantafx.base.controls;
|
|||||||
import javafx.beans.NamedArg;
|
import javafx.beans.NamedArg;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Control;
|
|
||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A versatile container that can used in various contexts such as dialog headers,
|
* A versatile container that can used in various contexts such as dialog headers,
|
||||||
* list items, and cards. It can contain a graphic, a title, subtitle, and optional
|
* list items, and cards. It can contain a graphic, a title, description, and optional
|
||||||
* actions.
|
* actions.
|
||||||
*/
|
*/
|
||||||
public class Tile extends Control {
|
public class Tile extends TileBase {
|
||||||
|
|
||||||
public Tile() {
|
public Tile() {
|
||||||
this(null, null, null);
|
this(null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tile(@NamedArg("title") String title,
|
public Tile(@Nullable @NamedArg("title") String title,
|
||||||
@NamedArg("subTitle") String subTitle) {
|
@Nullable @NamedArg("description") String description) {
|
||||||
this(title, subTitle, null);
|
this(title, description, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tile(@NamedArg("title") String title,
|
public Tile(@Nullable String title,
|
||||||
@NamedArg("subTitle") String subTitle,
|
@Nullable String description,
|
||||||
@NamedArg("graphic") Node graphic) {
|
@Nullable Node graphic) {
|
||||||
super();
|
super(title, description, graphic);
|
||||||
setTitle(title);
|
getStyleClass().add("tile");
|
||||||
setSubTitle(subTitle);
|
|
||||||
setGraphic(graphic);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -45,60 +41,6 @@ public class Tile extends Control {
|
|||||||
// Properties //
|
// Properties //
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/**
|
|
||||||
* The property representing the tile’s graphic node. It is commonly used
|
|
||||||
* to add images or icons that are associated with the tile.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The property representing the tile’s title. Although it is not mandatory,
|
|
||||||
* you typically would not want to have a tile without a title.
|
|
||||||
*/
|
|
||||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty titleProperty() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title.set(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The property representing the tile’s subtitle. This is usually an optional
|
|
||||||
* text or a description.
|
|
||||||
*/
|
|
||||||
private final StringProperty subTitle = new SimpleStringProperty(this, "subTitle");
|
|
||||||
|
|
||||||
public String getSubTitle() {
|
|
||||||
return subTitle.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty subTitleProperty() {
|
|
||||||
return subTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubTitle(String subTitle) {
|
|
||||||
this.subTitle.set(subTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The property representing the tile’s action node. It is commonly used
|
* The property representing the tile’s action node. It is commonly used
|
||||||
* to place an action controls that are associated with the tile.
|
* to place an action controls that are associated with the tile.
|
||||||
|
92
base/src/main/java/atlantafx/base/controls/TileBase.java
Normal file
92
base/src/main/java/atlantafx/base/controls/TileBase.java
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
package atlantafx.base.controls;
|
||||||
|
|
||||||
|
import javafx.beans.NamedArg;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Control;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public abstract class TileBase extends Control {
|
||||||
|
|
||||||
|
public TileBase() {
|
||||||
|
this(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TileBase(@Nullable @NamedArg("title") String title,
|
||||||
|
@Nullable @NamedArg("description") String description) {
|
||||||
|
this(title, description, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TileBase(@Nullable String title,
|
||||||
|
@Nullable String description,
|
||||||
|
@Nullable Node graphic) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
setTitle(title);
|
||||||
|
setDescription(description);
|
||||||
|
setGraphic(graphic);
|
||||||
|
getStyleClass().add("tile-base");
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Properties //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property representing the tile’s graphic node. It is commonly used
|
||||||
|
* to add images or icons that are associated with the tile.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property representing the tile’s title. Although it is not mandatory,
|
||||||
|
* you typically would not want to have a tile without a title.
|
||||||
|
*/
|
||||||
|
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty titleProperty() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title.set(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property representing the tile’s description.
|
||||||
|
*/
|
||||||
|
private final StringProperty description = new SimpleStringProperty(this, "description");
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty descriptionProperty() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description.set(description);
|
||||||
|
}
|
||||||
|
}
|
@ -3,140 +3,30 @@
|
|||||||
package atlantafx.base.controls;
|
package atlantafx.base.controls;
|
||||||
|
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.css.PseudoClass;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
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;
|
|
||||||
|
|
||||||
public class TileSkin extends SkinBase<Tile> {
|
public class TileSkin extends TileSkinBase<Tile> {
|
||||||
|
|
||||||
private static final PseudoClass WITH_TITLE = PseudoClass.getPseudoClass("with-title");
|
|
||||||
private static final PseudoClass WITH_SUBTITLE = PseudoClass.getPseudoClass("with-subtitle");
|
|
||||||
|
|
||||||
protected final HBox root = new HBox();
|
|
||||||
protected final StackPane graphicSlot;
|
|
||||||
protected final ChangeListener<Node> graphicSlotListener;
|
|
||||||
protected final VBox headerBox;
|
|
||||||
protected final Label titleLbl;
|
|
||||||
protected final Label subTitleLbl;
|
|
||||||
protected final StackPane actionSlot;
|
|
||||||
protected final ChangeListener<Node> actionSlotListener;
|
|
||||||
|
|
||||||
public TileSkin(Tile control) {
|
public TileSkin(Tile control) {
|
||||||
super(control);
|
super(control);
|
||||||
|
|
||||||
graphicSlot = new StackPane();
|
|
||||||
graphicSlot.getStyleClass().add("graphic");
|
|
||||||
graphicSlotListener = new SlotListener(graphicSlot);
|
|
||||||
control.graphicProperty().addListener(graphicSlotListener);
|
|
||||||
graphicSlotListener.changed(control.graphicProperty(), null, control.getGraphic());
|
|
||||||
|
|
||||||
titleLbl = new Label(control.getTitle());
|
|
||||||
titleLbl.getStyleClass().add("title");
|
|
||||||
titleLbl.setVisible(control.getTitle() != null);
|
|
||||||
titleLbl.setManaged(control.getTitle() != null);
|
|
||||||
|
|
||||||
subTitleLbl = new Label(control.getSubTitle());
|
|
||||||
subTitleLbl.setWrapText(true);
|
|
||||||
subTitleLbl.getStyleClass().add("subtitle");
|
|
||||||
subTitleLbl.setVisible(control.getSubTitle() != null);
|
|
||||||
subTitleLbl.setManaged(control.getSubTitle() != null);
|
|
||||||
|
|
||||||
headerBox = new VBox(titleLbl, subTitleLbl);
|
|
||||||
headerBox.setFillWidth(true);
|
|
||||||
headerBox.getStyleClass().add("header");
|
|
||||||
HBox.setHgrow(headerBox, Priority.ALWAYS);
|
|
||||||
headerBox.setMinHeight(Region.USE_COMPUTED_SIZE);
|
|
||||||
headerBox.setPrefHeight(Region.USE_COMPUTED_SIZE);
|
|
||||||
headerBox.setMaxHeight(Region.USE_COMPUTED_SIZE);
|
|
||||||
|
|
||||||
|
|
||||||
root.pseudoClassStateChanged(WITH_TITLE, control.getTitle() != null);
|
|
||||||
registerChangeListener(control.titleProperty(), o -> {
|
|
||||||
var value = getSkinnable().getSubTitle();
|
|
||||||
titleLbl.setText(value);
|
|
||||||
titleLbl.setVisible(value != null);
|
|
||||||
titleLbl.setManaged(value != null);
|
|
||||||
root.pseudoClassStateChanged(WITH_TITLE, value != null);
|
|
||||||
});
|
|
||||||
|
|
||||||
root.pseudoClassStateChanged(WITH_SUBTITLE, control.getSubTitle() != null);
|
|
||||||
registerChangeListener(control.subTitleProperty(), o -> {
|
|
||||||
var value = getSkinnable().getSubTitle();
|
|
||||||
subTitleLbl.setText(value);
|
|
||||||
subTitleLbl.setVisible(value != null);
|
|
||||||
subTitleLbl.setManaged(value != null);
|
|
||||||
root.pseudoClassStateChanged(WITH_SUBTITLE, value != null);
|
|
||||||
});
|
|
||||||
|
|
||||||
actionSlot = new StackPane();
|
|
||||||
actionSlot.getStyleClass().add("action");
|
|
||||||
actionSlotListener = new SlotListener(actionSlot);
|
|
||||||
control.actionProperty().addListener(actionSlotListener);
|
control.actionProperty().addListener(actionSlotListener);
|
||||||
actionSlotListener.changed(control.actionProperty(), null, control.getAction());
|
actionSlotListener.changed(control.actionProperty(), null, control.getAction());
|
||||||
|
|
||||||
// use pref size for slots, or they will be resized
|
pseudoClassStateChanged(Styles.STATE_INTERACTIVE, control.getActionHandler() != null);
|
||||||
// to the bare minimum due to Priority.ALWAYS
|
|
||||||
graphicSlot.setMinWidth(Region.USE_PREF_SIZE);
|
|
||||||
actionSlot.setMinWidth(Region.USE_PREF_SIZE);
|
|
||||||
|
|
||||||
// label text wrapping inside VBox won't work without this
|
|
||||||
subTitleLbl.setMaxWidth(Region.USE_PREF_SIZE);
|
|
||||||
subTitleLbl.setMinHeight(Region.USE_PREF_SIZE);
|
|
||||||
|
|
||||||
// do not resize children or container won't restore
|
|
||||||
// to its original size after expanding
|
|
||||||
root.setFillHeight(false);
|
|
||||||
|
|
||||||
root.getStyleClass().add("tile");
|
|
||||||
root.getChildren().setAll(graphicSlot, headerBox, actionSlot);
|
|
||||||
|
|
||||||
root.pseudoClassStateChanged(Styles.STATE_INTERACTIVE, control.getActionHandler() != null);
|
|
||||||
registerChangeListener(
|
registerChangeListener(
|
||||||
control.actionHandlerProperty(),
|
control.actionHandlerProperty(),
|
||||||
o -> root.pseudoClassStateChanged(Styles.STATE_INTERACTIVE, getSkinnable().getActionHandler() != null)
|
o -> pseudoClassStateChanged(Styles.STATE_INTERACTIVE, getSkinnable().getActionHandler() != null)
|
||||||
);
|
);
|
||||||
|
|
||||||
root.setOnMouseClicked(e -> {
|
root.setOnMouseClicked(e -> {
|
||||||
if (control.getActionHandler() != null) {
|
if (getSkinnable().getActionHandler() != null) {
|
||||||
control.getActionHandler().run();
|
getSkinnable().getActionHandler().run();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
getChildren().setAll(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected double calcHeight() {
|
|
||||||
var headerHeight = headerBox.getSpacing()
|
|
||||||
+ headerBox.getInsets().getTop()
|
|
||||||
+ headerBox.getInsets().getBottom()
|
|
||||||
+ titleLbl.getBoundsInLocal().getHeight()
|
|
||||||
+ (subTitleLbl.isManaged() ? subTitleLbl.getBoundsInLocal().getHeight() : 0);
|
|
||||||
|
|
||||||
return Math.max(Math.max(graphicSlot.getHeight(), actionSlot.getHeight()), headerHeight)
|
|
||||||
+ root.getPadding().getTop()
|
|
||||||
+ root.getPadding().getBottom();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected double computeMinHeight(double width, double topInset, double rightInset,
|
|
||||||
double bottomInset, double leftInset) {
|
|
||||||
// change the control height when label changed its height due to text wrapping,
|
|
||||||
// no other way to do that, all JavaFX containers completely ignore _the actual_
|
|
||||||
// height of its children
|
|
||||||
return calcHeight();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
unregisterChangeListeners(getSkinnable().titleProperty());
|
|
||||||
unregisterChangeListeners(getSkinnable().subTitleProperty());
|
|
||||||
getSkinnable().graphicProperty().removeListener(graphicSlotListener);
|
|
||||||
getSkinnable().actionProperty().removeListener(actionSlotListener);
|
getSkinnable().actionProperty().removeListener(actionSlotListener);
|
||||||
unregisterChangeListeners(getSkinnable().actionHandlerProperty());
|
unregisterChangeListeners(getSkinnable().actionHandlerProperty());
|
||||||
|
|
||||||
|
144
base/src/main/java/atlantafx/base/controls/TileSkinBase.java
Normal file
144
base/src/main/java/atlantafx/base/controls/TileSkinBase.java
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
package atlantafx.base.controls;
|
||||||
|
|
||||||
|
import atlantafx.base.util.BBCodeParser;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.css.PseudoClass;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
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.TextFlow;
|
||||||
|
|
||||||
|
public abstract class TileSkinBase<T extends TileBase> extends SkinBase<T> {
|
||||||
|
|
||||||
|
protected static final PseudoClass HAS_GRAPHIC = PseudoClass.getPseudoClass("has-graphic");
|
||||||
|
protected static final PseudoClass HAS_TITLE = PseudoClass.getPseudoClass("has-title");
|
||||||
|
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 StackPane graphicSlot;
|
||||||
|
protected final ChangeListener<Node> graphicSlotListener;
|
||||||
|
protected final VBox headerBox;
|
||||||
|
protected final Label titleLbl;
|
||||||
|
protected final TextFlow descriptionText;
|
||||||
|
protected final StackPane actionSlot;
|
||||||
|
protected final ChangeListener<Node> actionSlotListener;
|
||||||
|
|
||||||
|
public TileSkinBase(T control) {
|
||||||
|
super(control);
|
||||||
|
|
||||||
|
graphicSlot = new StackPane();
|
||||||
|
graphicSlot.getStyleClass().add("graphic");
|
||||||
|
graphicSlotListener = new SlotListener(
|
||||||
|
graphicSlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_GRAPHIC, active)
|
||||||
|
);
|
||||||
|
control.graphicProperty().addListener(graphicSlotListener);
|
||||||
|
graphicSlotListener.changed(control.graphicProperty(), null, control.getGraphic());
|
||||||
|
|
||||||
|
titleLbl = new Label(control.getTitle());
|
||||||
|
titleLbl.getStyleClass().add("title");
|
||||||
|
titleLbl.setVisible(control.getTitle() != null);
|
||||||
|
titleLbl.setManaged(control.getTitle() != null);
|
||||||
|
|
||||||
|
descriptionText = new TextFlow();
|
||||||
|
descriptionText.getStyleClass().add("description");
|
||||||
|
descriptionText.setVisible(control.getDescription() != null);
|
||||||
|
descriptionText.setManaged(control.getDescription() != null);
|
||||||
|
setDescriptionText();
|
||||||
|
|
||||||
|
headerBox = new VBox(titleLbl, descriptionText);
|
||||||
|
headerBox.setFillWidth(true);
|
||||||
|
headerBox.getStyleClass().add("header");
|
||||||
|
HBox.setHgrow(headerBox, Priority.ALWAYS);
|
||||||
|
headerBox.setMinHeight(Region.USE_COMPUTED_SIZE);
|
||||||
|
headerBox.setPrefHeight(Region.USE_COMPUTED_SIZE);
|
||||||
|
headerBox.setMaxHeight(Region.USE_COMPUTED_SIZE);
|
||||||
|
|
||||||
|
control.pseudoClassStateChanged(HAS_TITLE, control.getTitle() != null);
|
||||||
|
registerChangeListener(control.titleProperty(), o -> {
|
||||||
|
var value = getSkinnable().getDescription();
|
||||||
|
titleLbl.setText(value);
|
||||||
|
titleLbl.setVisible(value != null);
|
||||||
|
titleLbl.setManaged(value != null);
|
||||||
|
getSkinnable().pseudoClassStateChanged(HAS_TITLE, value != null);
|
||||||
|
});
|
||||||
|
|
||||||
|
control.pseudoClassStateChanged(HAS_DESCRIPTION, control.getDescription() != null);
|
||||||
|
registerChangeListener(control.descriptionProperty(), o -> {
|
||||||
|
var value = getSkinnable().getDescription();
|
||||||
|
setDescriptionText();
|
||||||
|
descriptionText.setVisible(value != null);
|
||||||
|
descriptionText.setManaged(value != null);
|
||||||
|
getSkinnable().pseudoClassStateChanged(HAS_DESCRIPTION, value != null);
|
||||||
|
});
|
||||||
|
|
||||||
|
actionSlot = new StackPane();
|
||||||
|
actionSlot.getStyleClass().add("action");
|
||||||
|
actionSlotListener = new SlotListener(
|
||||||
|
actionSlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_ACTION, active)
|
||||||
|
);
|
||||||
|
|
||||||
|
// use pref size for slots, or they will be resized
|
||||||
|
// to the bare minimum due to Priority.ALWAYS
|
||||||
|
graphicSlot.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
actionSlot.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
// label 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);
|
||||||
|
|
||||||
|
root.getChildren().setAll(graphicSlot, headerBox, actionSlot);
|
||||||
|
root.getStyleClass().add("container");
|
||||||
|
getChildren().setAll(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setDescriptionText() {
|
||||||
|
if (!descriptionText.getChildren().isEmpty()) {
|
||||||
|
descriptionText.getChildren().clear();
|
||||||
|
}
|
||||||
|
if (getSkinnable().getDescription() != null && !getSkinnable().getDescription().isBlank()) {
|
||||||
|
BBCodeParser.createLayout(getSkinnable().getDescription(), descriptionText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double calcHeight() {
|
||||||
|
var headerHeight = headerBox.getSpacing()
|
||||||
|
+ headerBox.getInsets().getTop()
|
||||||
|
+ headerBox.getInsets().getBottom()
|
||||||
|
+ titleLbl.getBoundsInLocal().getHeight()
|
||||||
|
+ (descriptionText.isManaged() ? descriptionText.getBoundsInLocal().getHeight() : 0);
|
||||||
|
|
||||||
|
return Math.max(Math.max(graphicSlot.getHeight(), actionSlot.getHeight()), headerHeight)
|
||||||
|
+ root.getPadding().getTop()
|
||||||
|
+ root.getPadding().getBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double computeMinHeight(double width, double topInset, double rightInset,
|
||||||
|
double bottomInset, double leftInset) {
|
||||||
|
// change the control height when label changed its height due to text wrapping,
|
||||||
|
// no other way to do that, all JavaFX containers completely ignore _the actual_
|
||||||
|
// height of its children
|
||||||
|
return calcHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
unregisterChangeListeners(getSkinnable().titleProperty());
|
||||||
|
unregisterChangeListeners(getSkinnable().descriptionProperty());
|
||||||
|
getSkinnable().graphicProperty().removeListener(graphicSlotListener);
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -22,4 +22,8 @@ public final class BrowseEvent extends Event {
|
|||||||
+ "uri=" + uri
|
+ "uri=" + uri
|
||||||
+ "} " + super.toString();
|
+ "} " + super.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void fire(String url) {
|
||||||
|
Event.publish(new BrowseEvent(URI.create(url)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,4 +37,8 @@ public abstract class Event {
|
|||||||
+ "id=" + id
|
+ "id=" + id
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <E extends Event> void publish(E event) {
|
||||||
|
DefaultEventBus.getInstance().publish(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ public class CardPage extends OutlinePage {
|
|||||||
card1.setMaxWidth(250);
|
card1.setMaxWidth(250);
|
||||||
card1.setHeader(new Tile(
|
card1.setHeader(new Tile(
|
||||||
"This is a title",
|
"This is a title",
|
||||||
"This is a subtitle"
|
"This is a description"
|
||||||
));
|
));
|
||||||
card1.setBody(new Label("This is content"));
|
card1.setBody(new Label("This is content"));
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ public class CardPage extends OutlinePage {
|
|||||||
card2.setMaxWidth(250);
|
card2.setMaxWidth(250);
|
||||||
card2.setHeader(new Tile(
|
card2.setHeader(new Tile(
|
||||||
"This is a title",
|
"This is a title",
|
||||||
"This is a subtitle"
|
"This is a description"
|
||||||
));
|
));
|
||||||
card2.setBody(new Label("This is content"));
|
card2.setBody(new Label("This is content"));
|
||||||
//snippet_2:end
|
//snippet_2:end
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package atlantafx.sampler.page.components;
|
package atlantafx.sampler.page.components;
|
||||||
|
|
||||||
import atlantafx.base.controls.Message;
|
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
import atlantafx.base.theme.Tweaks;
|
|
||||||
import atlantafx.base.util.Animations;
|
import atlantafx.base.util.Animations;
|
||||||
import atlantafx.base.util.BBCodeParser;
|
import atlantafx.base.util.BBCodeParser;
|
||||||
|
import atlantafx.base.controls.Message;
|
||||||
|
import atlantafx.sampler.event.BrowseEvent;
|
||||||
import atlantafx.sampler.page.ExampleBox;
|
import atlantafx.sampler.page.ExampleBox;
|
||||||
import atlantafx.sampler.page.OutlinePage;
|
import atlantafx.sampler.page.OutlinePage;
|
||||||
import atlantafx.sampler.page.Snippet;
|
import atlantafx.sampler.page.Snippet;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.control.Hyperlink;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.util.Duration;
|
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
||||||
import org.kordamp.ikonli.material2.Material2OutlinedMZ;
|
import org.kordamp.ikonli.material2.Material2OutlinedMZ;
|
||||||
@ -38,27 +38,60 @@ public class MessagePage extends OutlinePage {
|
|||||||
|
|
||||||
addPageHeader();
|
addPageHeader();
|
||||||
addFormattedText("""
|
addFormattedText("""
|
||||||
The [i]Message[/i] is a component for displaying notifications or alerts \
|
The [i]Message[/i] is a component for displaying an important text or \
|
||||||
and is specifically designed to grab the user’s attention. It is \
|
alerts and is specifically designed to grab the user’s attention. It is \
|
||||||
based on the [i]Tile[/i] layout and shares its structure."""
|
based on the [i]Tile[/i] layout and shares its structure, except it doesn't \
|
||||||
|
provide the action slot."""
|
||||||
);
|
);
|
||||||
addSection("Usage", usageExample());
|
addSection("Usage", usageExample());
|
||||||
addSection("No Title", noTitleExample());
|
addSection("Intent", intentExample());
|
||||||
|
addSection("Incomplete Header", incompleteHeaderExample());
|
||||||
addSection("Interactive", interactiveExample());
|
addSection("Interactive", interactiveExample());
|
||||||
addSection("Banner", bannerExample());
|
addSection("Closeable", closeableExample());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node usageExample() {
|
private Node usageExample() {
|
||||||
|
// won't work inside the snippet, because code
|
||||||
|
// snippet use BBCode parse as well
|
||||||
|
var url = "https://wikipedia.org/wiki/The_Elder_Scrolls_III:_Morrowind";
|
||||||
|
var quote = FAKER.elderScrolls().quote()
|
||||||
|
+ " \n[url=" + url + "]Learn more[/url]";
|
||||||
|
|
||||||
//snippet_1:start
|
//snippet_1:start
|
||||||
|
var msg = new Message(
|
||||||
|
"Quote",
|
||||||
|
quote,
|
||||||
|
new FontIcon(Material2OutlinedAL.CHAT_BUBBLE_OUTLINE)
|
||||||
|
);
|
||||||
|
msg.addEventFilter(ActionEvent.ACTION, e -> {
|
||||||
|
if (e.getTarget() instanceof Hyperlink link) {
|
||||||
|
BrowseEvent.fire((String) link.getUserData());
|
||||||
|
}
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
//snippet_1:end
|
||||||
|
|
||||||
|
var box = new VBox(msg);
|
||||||
|
var description = BBCodeParser.createFormattedText("""
|
||||||
|
[i]Message[/i] does not have any mandatory properties. It supports text \
|
||||||
|
wrapping and [i]BBCode[/i] formatted text, but only in the description field."""
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node intentExample() {
|
||||||
|
//snippet_5:start
|
||||||
var info = new Message(
|
var info = new Message(
|
||||||
"Info",
|
"Info",
|
||||||
FAKER.lorem().sentence(5),
|
FAKER.lorem().sentence(10),
|
||||||
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
|
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
|
||||||
);
|
);
|
||||||
|
info.getStyleClass().add(Styles.ACCENT);
|
||||||
|
|
||||||
var success = new Message(
|
var success = new Message(
|
||||||
"Success",
|
"Success",
|
||||||
FAKER.lorem().sentence(10),
|
FAKER.lorem().sentence(15),
|
||||||
new FontIcon(Material2OutlinedAL.CHECK_CIRCLE_OUTLINE)
|
new FontIcon(Material2OutlinedAL.CHECK_CIRCLE_OUTLINE)
|
||||||
);
|
);
|
||||||
success.getStyleClass().add(Styles.SUCCESS);
|
success.getStyleClass().add(Styles.SUCCESS);
|
||||||
@ -76,45 +109,39 @@ public class MessagePage extends OutlinePage {
|
|||||||
new FontIcon(Material2OutlinedAL.ERROR_OUTLINE)
|
new FontIcon(Material2OutlinedAL.ERROR_OUTLINE)
|
||||||
);
|
);
|
||||||
danger.getStyleClass().add(Styles.DANGER);
|
danger.getStyleClass().add(Styles.DANGER);
|
||||||
//snippet_1:end
|
//snippet_5:end
|
||||||
|
|
||||||
var box = new VBox(VGAP_10, info, success, warning, danger);
|
var box = new VBox(VGAP_10, info, success, warning, danger);
|
||||||
var description = BBCodeParser.createFormattedText("""
|
var description = BBCodeParser.createFormattedText("""
|
||||||
The default [i]Message[/i] type is "info", which corresponds to the \
|
The [i]Message[/i] offers four severity levels that set a distinctive color. \
|
||||||
accent color. Success, warning, and danger colors are also supported."""
|
To change the [i]Message[/i] intent, use the corresponding style class modifier."""
|
||||||
);
|
);
|
||||||
|
|
||||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
return new ExampleBox(box, new Snippet(getClass(), 5), description);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node noTitleExample() {
|
private Node incompleteHeaderExample() {
|
||||||
//snippet_2:start
|
//snippet_2:start
|
||||||
var info1 = new Message(
|
var info1 = new Message(
|
||||||
null,
|
|
||||||
FAKER.lorem().sentence(5),
|
FAKER.lorem().sentence(5),
|
||||||
|
null,
|
||||||
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
|
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
|
||||||
);
|
);
|
||||||
|
info1.getStyleClass().add(Styles.ACCENT);
|
||||||
|
|
||||||
var info2 = new Message(
|
var info2 = new Message(
|
||||||
null,
|
|
||||||
FAKER.lorem().sentence(15)
|
|
||||||
);
|
|
||||||
|
|
||||||
var info3 = new Message(
|
|
||||||
null,
|
null,
|
||||||
FAKER.lorem().sentence(50)
|
FAKER.lorem().sentence(50)
|
||||||
);
|
);
|
||||||
var btn = new Button("Done");
|
info2.getStyleClass().add(Styles.ACCENT);
|
||||||
btn.getStyleClass().add(Styles.ACCENT);
|
|
||||||
info3.setAction(btn);
|
|
||||||
//snippet_2:end
|
//snippet_2:end
|
||||||
|
|
||||||
var box = new VBox(VGAP_10, info1, info2, info3);
|
var box = new VBox(VGAP_10, info1, info2);
|
||||||
var description = BBCodeParser.createFormattedText("""
|
var description = BBCodeParser.createFormattedText("""
|
||||||
Unlike the [i]Tile[/i], the message title is optional. This example \
|
Both the title and description are completely optional, but one them has to be \
|
||||||
demonstrates various messages without a title."""
|
specified in any case. Note that the styling changes depending on whether the [i]Message[/i] \
|
||||||
|
has only a title, only a description, or both."""
|
||||||
);
|
);
|
||||||
|
|
||||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,57 +155,72 @@ public class MessagePage extends OutlinePage {
|
|||||||
|
|
||||||
var btn = new Button("Undo");
|
var btn = new Button("Undo");
|
||||||
btn.getStyleClass().addAll(Styles.SUCCESS);
|
btn.getStyleClass().addAll(Styles.SUCCESS);
|
||||||
|
|
||||||
msg.setAction(btn);
|
|
||||||
msg.setActionHandler(() -> Animations.flash(msg).playFromStart());
|
msg.setActionHandler(() -> Animations.flash(msg).playFromStart());
|
||||||
//snippet_3:end
|
//snippet_3:end
|
||||||
|
|
||||||
var box = new VBox(msg);
|
var box = new VBox(msg);
|
||||||
box.setPadding(new Insets(0, 0, 5, 0));
|
box.setPadding(new Insets(0, 0, 5, 0));
|
||||||
var description = BBCodeParser.createFormattedText("""
|
var description = BBCodeParser.createFormattedText("""
|
||||||
A [i]Message[/i] can be made interactive by setting an action handler that may \
|
A [i]Message[/i] can be made interactive by setting an action handler. \
|
||||||
or may not be related to the action slot."""
|
This allows to call any arbitrary action when the user clicks inside \
|
||||||
|
the message container. For example, you could show an extended dialog or \
|
||||||
|
trigger a notification panel to appear.."""
|
||||||
);
|
);
|
||||||
|
|
||||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node bannerExample() {
|
private Node closeableExample() {
|
||||||
//snippet_4:start
|
//snippet_4:start
|
||||||
final var msg = new Message(
|
var regular = new Message(
|
||||||
null,
|
"Regular",
|
||||||
FAKER.lorem().sentence(10)
|
FAKER.lorem().sentence(5),
|
||||||
|
new FontIcon(Material2OutlinedAL.CHAT_BUBBLE_OUTLINE)
|
||||||
);
|
);
|
||||||
msg.getStyleClass().addAll(
|
regular.setOnClose(e -> Animations.flash(regular).playFromStart());
|
||||||
Styles.DANGER, Tweaks.EDGE_TO_EDGE
|
|
||||||
|
var info = new Message(
|
||||||
|
"Info",
|
||||||
|
FAKER.lorem().sentence(10),
|
||||||
|
new FontIcon(Material2OutlinedAL.HELP_OUTLINE)
|
||||||
);
|
);
|
||||||
|
info.getStyleClass().add(Styles.ACCENT);
|
||||||
|
info.setOnClose(e -> Animations.flash(info).playFromStart());
|
||||||
|
|
||||||
var closeBtn = new Button("Close");
|
var success = new Message(
|
||||||
closeBtn.getStyleClass().addAll(Styles.DANGER);
|
"Success",
|
||||||
msg.setAction(closeBtn);
|
FAKER.lorem().sentence(15),
|
||||||
|
new FontIcon(Material2OutlinedAL.CHECK_CIRCLE_OUTLINE)
|
||||||
|
);
|
||||||
|
success.getStyleClass().add(Styles.SUCCESS);
|
||||||
|
success.setOnClose(e -> Animations.flash(success).playFromStart());
|
||||||
|
|
||||||
var showBannerBtn = new Button("Show banner");
|
var warning = new Message(
|
||||||
showBannerBtn.setOnAction(e1 -> {
|
"Warning",
|
||||||
var parent = (BorderPane) getScene().lookup("#main");
|
FAKER.lorem().sentence(20),
|
||||||
|
new FontIcon(Material2OutlinedMZ.OUTLINED_FLAG)
|
||||||
|
);
|
||||||
|
warning.getStyleClass().add(Styles.WARNING);
|
||||||
|
warning.setOnClose(e -> Animations.flash(warning).playFromStart());
|
||||||
|
|
||||||
parent.setTop(new VBox(msg));
|
var danger = new Message(
|
||||||
closeBtn.setOnAction(e2 -> parent.setTop(null));
|
"Danger",
|
||||||
|
FAKER.lorem().sentence(25),
|
||||||
msg.setOpacity(0);
|
new FontIcon(Material2OutlinedAL.ERROR_OUTLINE)
|
||||||
Animations.fadeInDown(msg, Duration.millis(350))
|
);
|
||||||
.playFromStart();
|
danger.getStyleClass().add(Styles.DANGER);
|
||||||
});
|
danger.setOnClose(e -> Animations.flash(danger).playFromStart());
|
||||||
//snippet_4:end
|
//snippet_4:end
|
||||||
|
|
||||||
var box = new VBox(showBannerBtn);
|
var box = new VBox(VGAP_10, regular, info, success, warning, danger);
|
||||||
var description = BBCodeParser.createFormattedText("""
|
var description = BBCodeParser.createFormattedText("""
|
||||||
The [i]Message[/i] supports the [code]Tweaks.EDGE_TO_EDGE[/code] style class modifier, \
|
You can make the [i]Message[/i] closeable by setting an appropriate message handler. \
|
||||||
which can be used to create a fancy banner, for example."""
|
If the handler is set, the close button will appear in the top right corner of the \
|
||||||
|
[i]Message[/i]. This handler should provide some logic for removing the [i]Message[/i] \
|
||||||
|
from its parent container as no default implementation is provided."""
|
||||||
);
|
);
|
||||||
|
box.setPadding(new Insets(0, 0, 5, 0));
|
||||||
|
|
||||||
var example = new ExampleBox(box, new Snippet(getClass(), 4), description);
|
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||||
example.setAllowDisable(false);
|
|
||||||
|
|
||||||
return example;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
package atlantafx.sampler.page.components;
|
package atlantafx.sampler.page.components;
|
||||||
|
|
||||||
import atlantafx.base.controls.PasswordTextField;
|
import atlantafx.base.controls.PasswordTextField;
|
||||||
import atlantafx.base.controls.Tile;
|
|
||||||
import atlantafx.base.controls.ToggleSwitch;
|
import atlantafx.base.controls.ToggleSwitch;
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
|
import atlantafx.base.util.Animations;
|
||||||
import atlantafx.base.util.BBCodeParser;
|
import atlantafx.base.util.BBCodeParser;
|
||||||
import atlantafx.sampler.Resources;
|
import atlantafx.sampler.Resources;
|
||||||
|
import atlantafx.base.controls.Tile;
|
||||||
|
import atlantafx.sampler.event.BrowseEvent;
|
||||||
import atlantafx.sampler.page.ExampleBox;
|
import atlantafx.sampler.page.ExampleBox;
|
||||||
import atlantafx.sampler.page.OutlinePage;
|
import atlantafx.sampler.page.OutlinePage;
|
||||||
import atlantafx.sampler.page.Snippet;
|
import atlantafx.sampler.page.Snippet;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.Hyperlink;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Separator;
|
import javafx.scene.control.Separator;
|
||||||
import javafx.scene.control.Spinner;
|
import javafx.scene.control.Spinner;
|
||||||
@ -28,6 +32,7 @@ import javafx.scene.layout.GridPane;
|
|||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
import org.kordamp.ikonli.material2.Material2AL;
|
||||||
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
||||||
|
|
||||||
public class TilePage extends OutlinePage {
|
public class TilePage extends OutlinePage {
|
||||||
@ -51,12 +56,13 @@ public class TilePage extends OutlinePage {
|
|||||||
addFormattedText("""
|
addFormattedText("""
|
||||||
The Tile is a versatile container that can used in various contexts \
|
The Tile is a versatile container that can used in various contexts \
|
||||||
such as dialog headers, list items, and cards. It can contain a graphic, \
|
such as dialog headers, list items, and cards. It can contain a graphic, \
|
||||||
a title, subtitle, and optional actions."""
|
a title, description, and optional actions."""
|
||||||
);
|
);
|
||||||
addNode(skeleton());
|
addNode(skeleton());
|
||||||
addSection("Usage", usageExample());
|
addSection("Usage", usageExample());
|
||||||
addSection("Interactive", interactiveExample());
|
addSection("Interactive", interactiveExample());
|
||||||
addSection("Stacking", stackingExample());
|
addSection("Stacking", stackingExample());
|
||||||
|
addSection("Incomplete Header", incompleteHeaderExample());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node skeleton() {
|
private Node skeleton() {
|
||||||
@ -79,7 +85,7 @@ public class TilePage extends OutlinePage {
|
|||||||
grid.setMaxWidth(600);
|
grid.setMaxWidth(600);
|
||||||
grid.add(cellBuilder.apply("Graphic", Pos.CENTER), 0, 0, 1, GridPane.REMAINING);
|
grid.add(cellBuilder.apply("Graphic", Pos.CENTER), 0, 0, 1, GridPane.REMAINING);
|
||||||
grid.add(cellBuilder.apply("Title", Pos.CENTER_LEFT), 1, 0, 1, 1);
|
grid.add(cellBuilder.apply("Title", Pos.CENTER_LEFT), 1, 0, 1, 1);
|
||||||
grid.add(cellBuilder.apply("Subtitle", Pos.CENTER_LEFT), 1, 1, 1, 1);
|
grid.add(cellBuilder.apply("Description", Pos.CENTER_LEFT), 1, 1, 1, 1);
|
||||||
grid.add(cellBuilder.apply("Action", Pos.CENTER), 2, 0, 1, GridPane.REMAINING);
|
grid.add(cellBuilder.apply("Action", Pos.CENTER), 2, 0, 1, GridPane.REMAINING);
|
||||||
grid.getColumnConstraints().setAll(
|
grid.getColumnConstraints().setAll(
|
||||||
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true),
|
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true),
|
||||||
@ -91,16 +97,25 @@ public class TilePage extends OutlinePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Node usageExample() {
|
private Node usageExample() {
|
||||||
|
// won't work inside the snippet, because code
|
||||||
|
// snippet use BBCode parse as well
|
||||||
|
var url = "https://wikipedia.org/wiki/The_Elder_Scrolls_III:_Morrowind";
|
||||||
|
var quote = FAKER.elderScrolls().quote()
|
||||||
|
+ " \n[url=" + url + "]Learn more[/url]";
|
||||||
|
|
||||||
//snippet_1:start
|
//snippet_1:start
|
||||||
var tile1 = new Tile(
|
var tile1 = new Tile(
|
||||||
"Title",
|
"Multiline Description",
|
||||||
FAKER.lorem().sentence(15)
|
FAKER.lorem().sentence(50)
|
||||||
);
|
);
|
||||||
|
|
||||||
var tile2 = new Tile(
|
var tile2 = new Tile(FAKER.name().fullName(), quote);
|
||||||
FAKER.name().fullName(),
|
tile2.addEventFilter(ActionEvent.ACTION, e -> {
|
||||||
FAKER.elderScrolls().quote()
|
if (e.getTarget() instanceof Hyperlink link) {
|
||||||
);
|
BrowseEvent.fire((String) link.getUserData());
|
||||||
|
}
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
|
||||||
var img = new ImageView(new Image(
|
var img = new ImageView(new Image(
|
||||||
Resources.getResourceAsStream("images/avatars/avatar1.png")
|
Resources.getResourceAsStream("images/avatars/avatar1.png")
|
||||||
@ -121,9 +136,8 @@ public class TilePage extends OutlinePage {
|
|||||||
tile3
|
tile3
|
||||||
);
|
);
|
||||||
var description = BBCodeParser.createFormattedText("""
|
var description = BBCodeParser.createFormattedText("""
|
||||||
[i]Tile[/i] does not have any mandatory properties, but you will not want \
|
[i]Tile[/i] does not have any mandatory properties. It supports text \
|
||||||
to use it without a title. Additionally, note that only the subtitle supports \
|
wrapping and [i]BBCode[/i] formatted text, but only in the description field."""
|
||||||
text wrapping."""
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||||
@ -133,7 +147,7 @@ public class TilePage extends OutlinePage {
|
|||||||
//snippet_2:start
|
//snippet_2:start
|
||||||
var tile = new Tile(
|
var tile = new Tile(
|
||||||
"Password",
|
"Password",
|
||||||
"Please enter your authentication password to unlock the content"
|
"Please enter your authentication password"
|
||||||
);
|
);
|
||||||
|
|
||||||
var tf = new PasswordTextField(null);
|
var tf = new PasswordTextField(null);
|
||||||
@ -197,4 +211,28 @@ public class TilePage extends OutlinePage {
|
|||||||
|
|
||||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Node incompleteHeaderExample() {
|
||||||
|
//snippet_4:start
|
||||||
|
var tile1 = new Tile("Go to the next screen", null);
|
||||||
|
tile1.setAction(new FontIcon(Material2AL.ARROW_RIGHT));
|
||||||
|
tile1.setActionHandler(() ->
|
||||||
|
Animations.wobble(tile1).playFromStart()
|
||||||
|
);
|
||||||
|
|
||||||
|
var tile2 = new Tile(
|
||||||
|
null, FAKER.friends().quote(),
|
||||||
|
new FontIcon(Material2OutlinedAL.FORMAT_QUOTE)
|
||||||
|
);
|
||||||
|
//snippet_4:end
|
||||||
|
|
||||||
|
var box = new VBox(tile1, new Separator(), tile2);
|
||||||
|
var description = BBCodeParser.createFormattedText("""
|
||||||
|
Both the title and description are completely optional, but one them has to be \
|
||||||
|
specified in any case. Note that the styling changes depending on whether the [i]Tile[/i] \
|
||||||
|
has only a title, only a description, or both.\""""
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ExampleBox(box, new Snippet(getClass(), 4), description);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
<PasswordTextField layoutX="14.0" layoutY="546.0" prefHeight="36.0" prefWidth="264.0" text="password" />
|
<PasswordTextField layoutX="14.0" layoutY="546.0" prefHeight="36.0" prefWidth="264.0" text="password" />
|
||||||
<Label layoutX="14.0" layoutY="517.0" style="-fx-text-fill: green;" text="PasswordTextField" />
|
<Label layoutX="14.0" layoutY="517.0" style="-fx-text-fill: green;" text="PasswordTextField" />
|
||||||
<RingProgressIndicator layoutX="326.0" layoutY="25.0" progress="0.35" />
|
<RingProgressIndicator layoutX="326.0" layoutY="25.0" progress="0.35" />
|
||||||
<Tile layoutX="320.0" layoutY="209.0" prefHeight="73.0" prefWidth="483.0" subTitle="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla fermentum, quam eu pretium euismod, ipsum mauris interdum massa, at scelerisque nulla augue a nunc. Vivamus vehicula rhoncus est, ut placerat nulla pellentesque vel. Duis ac mattis sapien. " title="Title" />
|
<Tile layoutX="320.0" layoutY="209.0" prefHeight="73.0" prefWidth="483.0" description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla fermentum, quam eu pretium euismod, ipsum mauris interdum massa, at scelerisque nulla augue a nunc. Vivamus vehicula rhoncus est, ut placerat nulla pellentesque vel. Duis ac mattis sapien. " title="Title" />
|
||||||
<InputGroup layoutX="526.0" layoutY="57.0">
|
<InputGroup layoutX="526.0" layoutY="57.0">
|
||||||
<children>
|
<children>
|
||||||
<ToggleButton mnemonicParsing="false" text="Toggle1" />
|
<ToggleButton mnemonicParsing="false" text="Toggle1" />
|
||||||
|
@ -583,7 +583,7 @@
|
|||||||
<content>
|
<content>
|
||||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
|
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
|
||||||
<children>
|
<children>
|
||||||
<Tile layoutX="-56.0" layoutY="44.0" subTitle="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sagittis vehicula elit, ac dictum metus bibendum id. Integer elit purus, varius ac eros eu, convallis ultricies tellus." title="Title" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
<Tile layoutX="-56.0" layoutY="44.0" description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sagittis vehicula elit, ac dictum metus bibendum id. Integer elit purus, varius ac eros eu, convallis ultricies tellus." title="Title" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||||
</children>
|
</children>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
</content>
|
</content>
|
||||||
|
@ -1,111 +1,168 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
@use "../settings/config" as cfg;
|
@use "../settings/config" as cfg;
|
||||||
|
@use "../settings/icons";
|
||||||
|
|
||||||
$color-bg-accent: -color-accent-subtle !default;
|
$color-bg: -color-bg-subtle !default;
|
||||||
$color-fg-accent-primary: -color-accent-fg !default;
|
$color-fg-primary: -color-fg-default !default;
|
||||||
$color-fg-accent-secondary: -color-fg-default !default;
|
$color-fg-secondary: -color-fg-default !default;
|
||||||
$color-border-accent: -color-accent-muted !default;
|
$color-border: -color-border-muted !default;
|
||||||
$color-border-accent-hover: -color-accent-emphasis !default;
|
$color-border-hover: -color-border-default !default;
|
||||||
|
$color-button-bg-hover: -color-bg-default !default;
|
||||||
|
|
||||||
$color-bg-success: -color-success-subtle !default;
|
$color-bg-accent: -color-accent-subtle !default;
|
||||||
$color-fg-success-primary: -color-success-fg !default;
|
$color-fg-accent-primary: -color-accent-fg !default;
|
||||||
$color-fg-success-secondary: -color-fg-default !default;
|
$color-fg-accent-secondary: -color-fg-default !default;
|
||||||
$color-border-success: -color-success-muted !default;
|
$color-border-accent: -color-accent-muted !default;
|
||||||
$color-border-success-hover: -color-success-emphasis !default;
|
$color-border-accent-hover: -color-accent-emphasis !default;
|
||||||
|
$color-button-accent-bg-hover: -color-accent-muted !default;
|
||||||
|
|
||||||
$color-bg-warning: -color-warning-subtle !default;
|
$color-bg-success: -color-success-subtle !default;
|
||||||
$color-fg-warning-primary: -color-warning-fg !default;
|
$color-fg-success-primary: -color-success-fg !default;
|
||||||
$color-fg-warning-secondary: -color-fg-default !default;
|
$color-fg-success-secondary: -color-fg-default !default;
|
||||||
$color-border-warning: -color-warning-muted !default;
|
$color-border-success: -color-success-muted !default;
|
||||||
$color-border-warning-hover: -color-warning-emphasis !default;
|
$color-border-success-hover: -color-success-emphasis !default;
|
||||||
|
$color-button-success-bg-hover: -color-success-muted !default;
|
||||||
|
|
||||||
$color-bg-danger: -color-danger-subtle !default;
|
$color-bg-warning: -color-warning-subtle !default;
|
||||||
$color-fg-danger-primary: -color-danger-fg !default;
|
$color-fg-warning-primary: -color-warning-fg !default;
|
||||||
$color-fg-danger-secondary: -color-fg-default !default;
|
$color-fg-warning-secondary: -color-fg-default !default;
|
||||||
$color-border-danger: -color-danger-muted !default;
|
$color-border-warning: -color-warning-muted !default;
|
||||||
$color-border-danger-hover: -color-danger-emphasis !default;
|
$color-border-warning-hover: -color-warning-emphasis !default;
|
||||||
|
$color-button-warning-bg-hover: -color-warning-muted !default;
|
||||||
|
|
||||||
|
$color-bg-danger: -color-danger-subtle !default;
|
||||||
|
$color-fg-danger-primary: -color-danger-fg !default;
|
||||||
|
$color-fg-danger-secondary: -color-fg-default !default;
|
||||||
|
$color-border-danger: -color-danger-muted !default;
|
||||||
|
$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;
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
|
|
||||||
-color-message-bg: $color-bg-accent;
|
-color-message-bg: $color-bg;
|
||||||
-color-message-fg-primary: $color-fg-accent-primary;
|
-color-message-fg-primary: $color-fg-primary;
|
||||||
-color-message-fg-secondary: $color-fg-accent-secondary;
|
-color-message-fg-secondary: $color-fg-secondary;
|
||||||
-color-message-border: $color-border-accent;
|
-color-message-border: $color-border;
|
||||||
-color-message-border-interactive: $color-border-accent-hover;
|
-color-message-button-hover: $color-button-bg-hover;
|
||||||
|
-color-message-border-interactive: $color-border-hover;
|
||||||
|
|
||||||
|
&.accent {
|
||||||
|
-color-message-bg: $color-bg-accent;
|
||||||
|
-color-message-fg-primary: $color-fg-accent-primary;
|
||||||
|
-color-message-fg-secondary: $color-fg-accent-secondary;
|
||||||
|
-color-message-border: $color-border-accent;
|
||||||
|
-color-message-button-hover: $color-button-accent-bg-hover;
|
||||||
|
-color-message-border-interactive: $color-border-accent-hover;
|
||||||
|
}
|
||||||
|
|
||||||
&.success {
|
&.success {
|
||||||
-color-message-bg: $color-bg-success;
|
-color-message-bg: $color-bg-success;
|
||||||
-color-message-fg-primary: $color-fg-success-primary;
|
-color-message-fg-primary: $color-fg-success-primary;
|
||||||
-color-message-fg-secondary: $color-fg-success-secondary;
|
-color-message-fg-secondary: $color-fg-success-secondary;
|
||||||
-color-message-border: $color-border-success;
|
-color-message-border: $color-border-success;
|
||||||
|
-color-message-button-hover: $color-button-success-bg-hover;
|
||||||
-color-message-border-interactive: $color-border-success-hover;
|
-color-message-border-interactive: $color-border-success-hover;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.warning {
|
&.warning {
|
||||||
-color-message-bg: $color-bg-warning;
|
-color-message-bg: $color-bg-warning;
|
||||||
-color-message-fg-primary: $color-fg-warning-primary;
|
-color-message-fg-primary: $color-fg-warning-primary;
|
||||||
-color-message-fg-secondary: $color-fg-warning-secondary;
|
-color-message-fg-secondary: $color-fg-warning-secondary;
|
||||||
-color-message-border: $color-border-warning;
|
-color-message-border: $color-border-warning;
|
||||||
|
-color-message-button-hover: $color-button-warning-bg-hover;
|
||||||
-color-message-border-interactive: $color-border-warning-hover;
|
-color-message-border-interactive: $color-border-warning-hover;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.danger {
|
&.danger {
|
||||||
-color-message-bg: $color-bg-danger;
|
-color-message-bg: $color-bg-danger;
|
||||||
-color-message-fg-primary: $color-fg-danger-primary;
|
-color-message-fg-primary: $color-fg-danger-primary;
|
||||||
-color-message-fg-secondary: $color-fg-danger-secondary;
|
-color-message-fg-secondary: $color-fg-danger-secondary;
|
||||||
-color-message-border: $color-border-danger;
|
-color-message-border: $color-border-danger;
|
||||||
|
-color-message-button-hover: $color-button-danger-bg-hover;
|
||||||
-color-message-border-interactive: $color-border-danger-hover;
|
-color-message-border-interactive: $color-border-danger-hover;
|
||||||
}
|
}
|
||||||
|
|
||||||
>.tile {
|
&.tile-base {
|
||||||
-fx-background-color: -color-message-bg;
|
>.container {
|
||||||
-fx-alignment: TOP_LEFT;
|
|
||||||
-fx-border-color: -color-message-border;
|
-fx-background-color: -color-message-bg;
|
||||||
-fx-border-width: cfg.$border-width;
|
-fx-alignment: CENTER_LEFT;
|
||||||
-fx-border-radius: cfg.$border-radius;
|
-fx-border-color: -color-message-border;
|
||||||
|
-fx-border-width: cfg.$border-width;
|
||||||
|
-fx-border-radius: cfg.$border-radius;
|
||||||
|
|
||||||
|
>.header {
|
||||||
|
>.title {
|
||||||
|
-fx-text-fill: -color-message-fg-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
>.description {
|
||||||
|
Text {
|
||||||
|
-fx-fill: -color-message-fg-secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#{cfg.$font-icon-selector} {
|
||||||
|
-fx-icon-color: -color-message-fg-primary;
|
||||||
|
-fx-fill: -color-message-fg-primary;
|
||||||
|
-fx-icon-size: cfg.$icon-size-larger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover:interactive {
|
&:hover:interactive {
|
||||||
-fx-background-color: -color-message-bg;
|
>.container {
|
||||||
-fx-border-color: -color-message-border-interactive;
|
-fx-background-color: -color-message-bg;
|
||||||
}
|
-fx-border-color: -color-message-border-interactive;
|
||||||
|
-fx-cursor: hand;
|
||||||
&:with-title:with-subtitle {
|
|
||||||
-fx-alignment: TOP_LEFT;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:with-title,
|
|
||||||
&:with-subtitle {
|
|
||||||
-fx-alignment: CENTER_LEFT;
|
|
||||||
}
|
|
||||||
|
|
||||||
>.graphic {
|
|
||||||
-fx-alignment: TOP_LEFT;
|
|
||||||
}
|
|
||||||
|
|
||||||
>.header {
|
|
||||||
>.title {
|
|
||||||
-fx-text-fill: -color-message-fg-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
>.subtitle {
|
|
||||||
-fx-text-fill: -color-message-fg-secondary;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
>.action {
|
&:has-title:has-description {
|
||||||
-fx-alignment: TOP_LEFT;
|
>.container>.header {
|
||||||
}
|
>.title {
|
||||||
|
-fx-text-fill: -color-message-fg-primary;
|
||||||
|
}
|
||||||
|
|
||||||
#{cfg.$font-icon-selector} {
|
>.description {
|
||||||
-fx-icon-color: -color-message-fg-primary;
|
Text {
|
||||||
-fx-fill: -color-message-fg-primary;
|
-fx-fill: -color-message-fg-secondary;
|
||||||
-fx-icon-size: cfg.$icon-size-larger;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.edge-to-edge > .tile {
|
// CLOSE BUTTON
|
||||||
-fx-border-width: 0;
|
|
||||||
-fx-border-radius: 0;
|
>.close-button {
|
||||||
|
-fx-background-radius: $close-button-radius;
|
||||||
|
-fx-padding: $close-button-padding;
|
||||||
|
|
||||||
|
>.icon {
|
||||||
|
@include icons.get("close", true);
|
||||||
|
-fx-background-color: -color-message-fg-primary;
|
||||||
|
-fx-padding: $close-button-icon-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
-fx-background-color: -color-message-button-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:closeable {
|
||||||
|
>.container {
|
||||||
|
>.header {
|
||||||
|
>.title,
|
||||||
|
>.description {
|
||||||
|
-fx-padding: 0 ($close-button-padding * 3) 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,46 +3,50 @@
|
|||||||
@use "../settings/config" as cfg;
|
@use "../settings/config" as cfg;
|
||||||
|
|
||||||
$color-interactive: -color-bg-subtle !default;
|
$color-interactive: -color-bg-subtle !default;
|
||||||
|
$title-font-size: 1.05em !default; // semibold would be much better, but it's JavaFX after all
|
||||||
|
|
||||||
.tile {
|
.tile-base {
|
||||||
-fx-padding: 0.75em 1em 0.75em 1em;
|
|
||||||
-fx-alignment: CENTER_LEFT;
|
|
||||||
-fx-background-radius: cfg.$border-radius;
|
|
||||||
|
|
||||||
&:hover:interactive {
|
>.container {
|
||||||
-fx-background-color: $color-interactive;
|
-fx-padding: 1em;
|
||||||
-fx-cursor: hand;
|
|
||||||
}
|
|
||||||
|
|
||||||
>.graphic {
|
|
||||||
&:filled {
|
|
||||||
-fx-padding: 0 1em 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
>.header {
|
|
||||||
-fx-alignment: CENTER_LEFT;
|
-fx-alignment: CENTER_LEFT;
|
||||||
-fx-padding: 0;
|
-fx-background-radius: cfg.$border-radius;
|
||||||
|
-fx-spacing: 1em;
|
||||||
|
|
||||||
>.title {
|
// >.graphic {}
|
||||||
-fx-font-size: cfg.$font-title-4;
|
|
||||||
|
>.header {
|
||||||
|
-fx-alignment: CENTER_LEFT;
|
||||||
|
-fx-padding: 0;
|
||||||
|
|
||||||
|
>.title {
|
||||||
|
-fx-font-size: $title-font-size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
>.subtitle {
|
// >.action {}
|
||||||
-fx-text-fill: -color-fg-muted;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:with-title:with-subtitle {
|
&:has-title:has-description {
|
||||||
>.header {
|
>.container >.header {
|
||||||
-fx-spacing: 0.5em;
|
-fx-spacing: 0.5em;
|
||||||
-fx-alignment: TOP_LEFT;
|
-fx-alignment: TOP_LEFT;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
>.action {
|
>.description {
|
||||||
&:filled {
|
Text {
|
||||||
-fx-padding: 0 0 0 1em;
|
-fx-fill: -color-fg-muted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile {
|
||||||
|
|
||||||
|
&:hover:interactive {
|
||||||
|
>.container {
|
||||||
|
-fx-background-color: $color-interactive;
|
||||||
|
-fx-cursor: hand;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user