Add widgets showcase
Includes additional showcase page for widgets and examples how to implement some conventional controls: card, message, stepper and tag. More widgets can be added in the future.
This commit is contained in:
parent
012dcdd950
commit
8489106717
@ -108,7 +108,7 @@ class MainLayer extends BorderPane {
|
||||
|
||||
// startup, no prev page, no animation
|
||||
if (getScene() == null) {
|
||||
subLayerPane.getChildren().add(nextPage.getView());
|
||||
subLayerPane.getChildren().setAll(nextPage.getView());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@ class MainLayer extends BorderPane {
|
||||
prevPage.reset();
|
||||
|
||||
// animate switching between pages
|
||||
subLayerPane.getChildren().add(nextPage.getView());
|
||||
subLayerPane.getChildren().setAll(nextPage.getView());
|
||||
var transition = new FadeTransition(Duration.millis(PAGE_TRANSITION_DURATION), nextPage.getView());
|
||||
transition.setFromValue(0.0);
|
||||
transition.setToValue(1.0);
|
||||
|
@ -9,6 +9,7 @@ import atlantafx.sampler.page.general.ThemePage;
|
||||
import atlantafx.sampler.page.general.TypographyPage;
|
||||
import atlantafx.sampler.page.showcase.filemanager.FileManagerPage;
|
||||
import atlantafx.sampler.page.showcase.musicplayer.MusicPlayerPage;
|
||||
import atlantafx.sampler.page.showcase.widget.WidgetCollectionPage;
|
||||
import atlantafx.sampler.util.Containers;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.FXCollections;
|
||||
@ -209,7 +210,8 @@ class Sidebar extends StackPane {
|
||||
navLink(TreeTablePage.NAME, TreeTablePage.class),
|
||||
caption("SHOWCASE"),
|
||||
navLink(FileManagerPage.NAME, FileManagerPage.class),
|
||||
navLink(MusicPlayerPage.NAME, MusicPlayerPage.class)
|
||||
navLink(MusicPlayerPage.NAME, MusicPlayerPage.class),
|
||||
navLink(WidgetCollectionPage.NAME, WidgetCollectionPage.class)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7,14 +7,20 @@ import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import net.datafaker.Faker;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
|
||||
public class SampleBlock extends VBox {
|
||||
|
||||
public static final int BLOCK_HGAP = 20;
|
||||
public static final int BLOCK_VGAP = 10;
|
||||
|
||||
protected static final Faker FAKER = new Faker();
|
||||
protected static final Random RANDOM = new Random();
|
||||
|
||||
protected final Label titleLabel;
|
||||
protected final Node content; // can be either Pane or Control
|
||||
protected TextFlow descriptionText;
|
||||
@ -58,4 +64,8 @@ public class SampleBlock extends VBox {
|
||||
VBox.setVgrow(content, Priority.NEVER);
|
||||
}
|
||||
}
|
||||
|
||||
protected static Feather randomIcon() {
|
||||
return Feather.values()[RANDOM.nextInt(Feather.values().length)];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,160 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class Card extends VBox {
|
||||
|
||||
public static final String CSS = """
|
||||
.card {
|
||||
-fx-background-color: -color-bg-default;
|
||||
-fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8, 0.5, 0, 2);
|
||||
}
|
||||
.card > .subtitle {
|
||||
-fx-text-fill: -color-fg-muted;
|
||||
-fx-padding: 0px 15px 10px 15px;
|
||||
}
|
||||
.card > .title,
|
||||
.card > .body,
|
||||
.card > .footer {
|
||||
-fx-padding: 10px 15px 10px 15px;
|
||||
}
|
||||
""";
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<ImageView> image = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Parent> body = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Parent> footer = new SimpleObjectProperty<>();
|
||||
|
||||
public Card() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var footerSep = new Separator();
|
||||
footerSep.getStyleClass().add(Styles.SMALL);
|
||||
footerSep.managedProperty().bind(Bindings.createObjectBinding(
|
||||
() -> footer.get() != null && footer.get().isManaged(), footer
|
||||
));
|
||||
|
||||
getChildren().setAll(
|
||||
createPlaceholder(), // title
|
||||
createPlaceholder(), // subtitle
|
||||
createPlaceholder(), // image
|
||||
createPlaceholder(), // body
|
||||
footerSep,
|
||||
createPlaceholder() // footer
|
||||
);
|
||||
|
||||
image.addListener(
|
||||
(obs, old, val) -> setChild(0, val, "image")
|
||||
);
|
||||
title.addListener(
|
||||
(obs, old, val) -> setChild(1, val != null ? new Label(val) : null, "title", Styles.TITLE_4)
|
||||
);
|
||||
subtitle.addListener(
|
||||
(obs, old, val) -> setChild(2, val != null ? new Label(val) : null, "subtitle")
|
||||
);
|
||||
body.addListener(
|
||||
(obs, old, val) -> setChild(3, val, "body")
|
||||
);
|
||||
footer.addListener(
|
||||
(obs, old, val) -> setChild(5, val, "footer")
|
||||
);
|
||||
|
||||
getStyleClass().addAll("card", Styles.BORDERED);
|
||||
}
|
||||
|
||||
private void setChild(int index, Node node, String... styleClass) {
|
||||
if (node != null) {
|
||||
for (var s : styleClass) {
|
||||
if (!node.getStyleClass().contains(s)) {
|
||||
node.getStyleClass().add(s);
|
||||
}
|
||||
}
|
||||
getChildren().set(index, node);
|
||||
} else {
|
||||
getChildren().set(index, createPlaceholder());
|
||||
}
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public ImageView getImage() {
|
||||
return image.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<ImageView> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setImage(ImageView image) {
|
||||
this.image.set(image);
|
||||
}
|
||||
|
||||
public Parent getBody() {
|
||||
return body.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Parent> bodyProperty() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(Parent body) {
|
||||
this.body.set(body);
|
||||
}
|
||||
|
||||
public Parent getFooter() {
|
||||
return footer.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Parent> footerProperty() {
|
||||
return footer;
|
||||
}
|
||||
|
||||
public void setFooter(Parent footer) {
|
||||
this.footer.set(footer);
|
||||
}
|
||||
|
||||
private Parent createPlaceholder() {
|
||||
var g = new Group();
|
||||
g.setManaged(false);
|
||||
return g;
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelReader;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import net.datafaker.Faker;
|
||||
|
||||
import static atlantafx.sampler.page.Page.PAGE_HGAP;
|
||||
import static atlantafx.sampler.page.Page.PAGE_VGAP;
|
||||
|
||||
public class CardSample extends HBox {
|
||||
|
||||
private static final Faker FAKER = new Faker();
|
||||
private static final int CARD_WIDTH = 300;
|
||||
|
||||
public CardSample() {
|
||||
new CSSFragment(Card.CSS).addTo(this);
|
||||
|
||||
setSpacing(PAGE_HGAP);
|
||||
setAlignment(Pos.TOP_CENTER);
|
||||
setMinWidth(CARD_WIDTH * 2 + PAGE_HGAP);
|
||||
getChildren().setAll(
|
||||
// column 0
|
||||
new VBox(
|
||||
PAGE_VGAP,
|
||||
textFooterCard(),
|
||||
titleTextCard(),
|
||||
quoteCard()
|
||||
),
|
||||
// column 1
|
||||
new VBox(
|
||||
PAGE_VGAP,
|
||||
imageTextCard(),
|
||||
titleImageCard()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private Card textFooterCard() {
|
||||
var card = new Card();
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
var text = new Text(FAKER.chuckNorris().fact());
|
||||
card.setBody(new TextFlow(text));
|
||||
|
||||
var btn = new Button("More!");
|
||||
btn.getStyleClass().addAll(Styles.ACCENT, Styles.BUTTON_OUTLINED);
|
||||
btn.setOnAction(e -> text.setText(FAKER.chuckNorris().fact()));
|
||||
|
||||
card.setFooter(new HBox(btn));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private Card imageTextCard() {
|
||||
var card = new Card();
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
var image = new ImageView(new Image(Resources.getResourceAsStream("images/20_min_adventure.jpg")));
|
||||
image.setFitWidth(300);
|
||||
image.setPreserveRatio(true);
|
||||
card.setImage(image);
|
||||
|
||||
var text = new Text(FAKER.rickAndMorty().quote());
|
||||
card.setBody(new TextFlow(text));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private Card titleTextCard() {
|
||||
var card = new Card();
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
card.setTitle("Title");
|
||||
card.setSubtitle("Subtitle");
|
||||
|
||||
var text = new Text(FAKER.lorem().paragraph());
|
||||
card.setBody(new TextFlow(text));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private Card titleImageCard() {
|
||||
var card = new Card();
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
var image = new Image(Resources.getResourceAsStream("images/pattern.jpg"));
|
||||
PixelReader pixelReader = image.getPixelReader();
|
||||
var cropImage = new WritableImage(pixelReader, 0, 0, 300, 100);
|
||||
|
||||
card.setImage(new ImageView(cropImage));
|
||||
card.setTitle("Title");
|
||||
|
||||
var text = new Text(FAKER.lorem().paragraph());
|
||||
card.setBody(new TextFlow(text));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private Card quoteCard() {
|
||||
var card = new Card();
|
||||
card.setMinWidth(CARD_WIDTH);
|
||||
card.setMaxWidth(CARD_WIDTH);
|
||||
|
||||
var quoteText = new Text(FAKER.bojackHorseman().quotes());
|
||||
quoteText.getStyleClass().add(Styles.TITLE_3);
|
||||
|
||||
var authorText = new Text("Bojack Horseman");
|
||||
|
||||
card.setBody(new VBox(
|
||||
10,
|
||||
new TextFlow(quoteText),
|
||||
authorText
|
||||
));
|
||||
|
||||
card.setFooter(new TextFlow(
|
||||
new Text("Share on "),
|
||||
new Hyperlink("Twitter")
|
||||
));
|
||||
|
||||
return card;
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Message extends StackPane {
|
||||
|
||||
private static final int ANIMATION_DURATION = 500;
|
||||
|
||||
public enum Type {
|
||||
INFO, SUCCESS, WARNING, DANGER
|
||||
}
|
||||
|
||||
public static final String CSS = """
|
||||
.message {
|
||||
-color-message-bg: -color-bg-default;
|
||||
-color-message-fg: -color-fg-default;
|
||||
-fx-background-color: -color-message-bg;
|
||||
-fx-border-color: -color-message-fg;
|
||||
-fx-border-width: 0 0 0 5px;
|
||||
-fx-pref-width: 600px;
|
||||
-fx-alignment: TOP_LEFT;
|
||||
}
|
||||
.message > .header {
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
.message Text {
|
||||
-fx-fill: -color-message-fg;
|
||||
}
|
||||
.message > .button {
|
||||
-color-button-fg: -color-message-fg;
|
||||
}
|
||||
.message.info {
|
||||
-color-message-bg: -color-accent-subtle;
|
||||
-color-message-fg: -color-accent-fg;
|
||||
}
|
||||
.message.success {
|
||||
-color-message-bg: -color-success-subtle;
|
||||
-color-message-fg: -color-success-fg;
|
||||
}
|
||||
.message.warning {
|
||||
-color-message-bg: -color-warning-subtle;
|
||||
-color-message-fg: -color-warning-fg;
|
||||
}
|
||||
.message.danger {
|
||||
-color-message-bg: -color-danger-subtle;
|
||||
-color-message-fg: -color-danger-fg;
|
||||
}
|
||||
""";
|
||||
|
||||
private final Type type;
|
||||
private final String header;
|
||||
private final String text;
|
||||
private Consumer<Message> closeHandler;
|
||||
|
||||
public Message(Type type, String header, String text) {
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.header = header;
|
||||
this.text = Objects.requireNonNull(text);
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
if (header != null) {
|
||||
var headerText = new Text(header);
|
||||
headerText.getStyleClass().addAll("header");
|
||||
StackPane.setMargin(headerText, new Insets(10, 10, 0, 15));
|
||||
getChildren().add(headerText);
|
||||
}
|
||||
|
||||
var messageText = new TextFlow(new Text(text));
|
||||
if (header != null) {
|
||||
StackPane.setMargin(messageText, new Insets(40, 10, 10, 15));
|
||||
} else {
|
||||
StackPane.setMargin(messageText, new Insets(10, 10, 10, 15));
|
||||
}
|
||||
messageText.maxWidthProperty().bind(widthProperty().subtract(50));
|
||||
getChildren().add(messageText);
|
||||
|
||||
var closeBtn = new Button("", new FontIcon(Material2AL.CLOSE));
|
||||
closeBtn.getStyleClass().addAll(Styles.BUTTON_CIRCLE, Styles.FLAT);
|
||||
closeBtn.setOnAction(e -> handleClose());
|
||||
StackPane.setMargin(closeBtn, new Insets(2));
|
||||
StackPane.setAlignment(closeBtn, Pos.TOP_RIGHT);
|
||||
getChildren().add(closeBtn);
|
||||
|
||||
parentProperty().addListener((obs, old, val) -> {
|
||||
if (val != null) { handleOpen(); }
|
||||
});
|
||||
|
||||
getStyleClass().setAll("message", type.name().toLowerCase());
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setCloseHandler(Consumer<Message> closeHandler) {
|
||||
this.closeHandler = closeHandler;
|
||||
}
|
||||
|
||||
private void handleOpen() {
|
||||
var transition = new FadeTransition(new Duration(500), this);
|
||||
transition.setFromValue(0);
|
||||
transition.setToValue(1);
|
||||
transition.play();
|
||||
}
|
||||
|
||||
private void handleClose() {
|
||||
var transition = new FadeTransition(new Duration(ANIMATION_DURATION), this);
|
||||
transition.setFromValue(1);
|
||||
transition.setToValue(0);
|
||||
transition.setOnFinished(e -> {
|
||||
if (getParent() != null && getParent() instanceof Pane pane) {
|
||||
pane.getChildren().remove(this);
|
||||
}
|
||||
if (closeHandler != null) {
|
||||
closeHandler.accept(this);
|
||||
}
|
||||
});
|
||||
transition.play();
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MessageSample extends SampleBlock {
|
||||
|
||||
public MessageSample() {
|
||||
super("Message", createContent());
|
||||
}
|
||||
|
||||
private static Node createContent() {
|
||||
var content = new VBox(BLOCK_VGAP);
|
||||
content.setAlignment(Pos.TOP_CENTER);
|
||||
VBox.setVgrow(content, Priority.ALWAYS);
|
||||
new CSSFragment(Message.CSS).addTo(content);
|
||||
|
||||
var closeHandler = new Consumer<Message>() {
|
||||
@Override
|
||||
public void accept(Message msg) {
|
||||
var newMsg = new Message(msg.getType(), msg.getHeader(), FAKER.chuckNorris().fact());
|
||||
newMsg.setCloseHandler(this);
|
||||
content.getChildren().add(newMsg);
|
||||
}
|
||||
};
|
||||
|
||||
for (Message.Type type : Message.Type.values()) {
|
||||
var msg = new Message(type, type.name(), FAKER.chuckNorris().fact());
|
||||
msg.setCloseHandler(closeHandler);
|
||||
content.getChildren().add(msg);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.kordamp.ikonli.Ikon;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Stepper extends HBox {
|
||||
|
||||
public static final String CSS = """
|
||||
.stepper {
|
||||
-color-stepper-bg: -color-bg-subtle;
|
||||
-color-stepper-fg: -color-fg-default;
|
||||
-color-stepper-border: -color-border-default;
|
||||
-fx-pref-width: 600px;
|
||||
-fx-spacing: 10px;
|
||||
}
|
||||
.stepper > .item {
|
||||
-fx-graphic-text-gap: 10px;
|
||||
}
|
||||
.stepper > .item > .graphic {
|
||||
-fx-font-size: 1.1em;
|
||||
-fx-min-width: 2.2em;
|
||||
-fx-max-width: 2.2em;
|
||||
-fx-min-height: 2.2em;
|
||||
-fx-max-height: 2.2em;
|
||||
-fx-text-fill: -color-stepper-fg;
|
||||
-fx-background-color: -color-stepper-bg;
|
||||
-fx-background-radius: 10em;
|
||||
-fx-border-color: -color-stepper-border;
|
||||
-fx-border-radius: 10em;
|
||||
-fx-border-width: 1;
|
||||
-fx-alignment: CENTER;
|
||||
}
|
||||
.stepper > .item .ikonli-font-icon {
|
||||
-fx-fill: -color-stepper-fg;
|
||||
-fx-icon-color: -color-stepper-fg;
|
||||
}
|
||||
.stepper > .item:selected > .graphic {
|
||||
-color-stepper-bg: -color-accent-subtle;
|
||||
-color-stepper-fg: -color-accent-fg;
|
||||
-color-stepper-border: -color-accent-emphasis;
|
||||
}
|
||||
.stepper > .item:completed {
|
||||
-color-stepper-bg: -color-accent-emphasis;
|
||||
-color-stepper-fg: -color-fg-emphasis;
|
||||
-color-stepper-border: -color-accent-emphasis;
|
||||
}
|
||||
""";
|
||||
|
||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
private static final PseudoClass COMPLETED = PseudoClass.getPseudoClass("completed");
|
||||
|
||||
private final List<Item> items;
|
||||
private final ObjectProperty<Side> textPosition = new SimpleObjectProperty<>(Side.LEFT);
|
||||
private final ObjectProperty<Item> selectedItem = new SimpleObjectProperty<>();
|
||||
private final BooleanBinding canGoBack;
|
||||
private final BooleanBinding canGoForward;
|
||||
|
||||
public Stepper(Item... items) {
|
||||
this(Arrays.asList(items));
|
||||
}
|
||||
|
||||
public Stepper(List<Item> items) {
|
||||
if (items == null || items.isEmpty()) {
|
||||
throw new IllegalArgumentException("Item list can't be null or empty.");
|
||||
}
|
||||
|
||||
this.items = Collections.unmodifiableList(items);
|
||||
|
||||
canGoBack = Bindings.createBooleanBinding(() -> {
|
||||
if (selectedItem.get() == null) { return false; }
|
||||
var idx = items.indexOf(selectedItem.get());
|
||||
return idx > 0 && idx <= items.size() - 1;
|
||||
}, selectedItem);
|
||||
|
||||
canGoForward = Bindings.createBooleanBinding(() -> {
|
||||
if (selectedItem.get() == null) { return false; }
|
||||
var idx = items.indexOf(selectedItem.get());
|
||||
return idx >= 0 && idx < items.size() - 1;
|
||||
}, selectedItem);
|
||||
|
||||
selectedItem.addListener((obs, old, val) -> {
|
||||
if (old != null) { old.pseudoClassStateChanged(SELECTED, false); }
|
||||
if (val != null) { val.pseudoClassStateChanged(SELECTED, true); }
|
||||
});
|
||||
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
alignmentProperty().bind(Bindings.createObjectBinding(() ->
|
||||
switch (textPositionProperty().get()) {
|
||||
case TOP -> Pos.TOP_LEFT;
|
||||
case BOTTOM -> Pos.BOTTOM_LEFT;
|
||||
default -> Pos.CENTER_LEFT;
|
||||
}, textPositionProperty())
|
||||
);
|
||||
|
||||
updateItems();
|
||||
getStyleClass().add("stepper");
|
||||
}
|
||||
|
||||
private void updateItems() {
|
||||
var children = new ArrayList<Node>();
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
var item = items.get(i);
|
||||
item.contentDisplayProperty().bind(Bindings.createObjectBinding(() ->
|
||||
switch (textPositionProperty().get()) {
|
||||
case TOP -> ContentDisplay.TOP;
|
||||
case BOTTOM -> ContentDisplay.BOTTOM;
|
||||
case LEFT -> ContentDisplay.LEFT;
|
||||
case RIGHT -> ContentDisplay.RIGHT;
|
||||
}, textPositionProperty())
|
||||
);
|
||||
|
||||
children.add(item);
|
||||
|
||||
if (i < items.size() - 1) {
|
||||
var sep = new Separator();
|
||||
HBox.setHgrow(sep, Priority.ALWAYS);
|
||||
children.add(sep);
|
||||
}
|
||||
}
|
||||
getChildren().setAll(children);
|
||||
}
|
||||
|
||||
public List<Item> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public Side getTextPosition() {
|
||||
return textPosition.get();
|
||||
}
|
||||
|
||||
public void setTextPosition(Side textPosition) {
|
||||
this.textPosition.set(textPosition);
|
||||
}
|
||||
|
||||
public ObjectProperty<Side> textPositionProperty() {
|
||||
return textPosition;
|
||||
}
|
||||
|
||||
public Item getSelectedItem() {
|
||||
return selectedItem.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Item> selectedItemProperty() {
|
||||
return selectedItem;
|
||||
}
|
||||
|
||||
public void setSelectedItem(Item selectedItem) {
|
||||
this.selectedItem.set(selectedItem);
|
||||
}
|
||||
|
||||
public BooleanBinding canGoBackProperty() {
|
||||
return canGoBack;
|
||||
}
|
||||
|
||||
public void backward() {
|
||||
if (!canGoBack.get()) { return; }
|
||||
var idx = items.indexOf(selectedItem.get());
|
||||
selectedItem.set(items.get(idx - 1));
|
||||
}
|
||||
|
||||
public BooleanBinding canGoForwardProperty() {
|
||||
return canGoForward;
|
||||
}
|
||||
|
||||
public void forward() {
|
||||
if (!canGoForward.get()) { return; }
|
||||
var idx = items.indexOf(selectedItem.get());
|
||||
selectedItem.set(items.get(idx + 1));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static class Item extends Label {
|
||||
|
||||
private final BooleanProperty completed = new SimpleBooleanProperty();
|
||||
|
||||
public Item(String text) {
|
||||
super(text);
|
||||
|
||||
var graphicLabel = new Label();
|
||||
graphicLabel.getStyleClass().add("graphic");
|
||||
setGraphic(graphicLabel);
|
||||
|
||||
completed.addListener((obs, old, val) -> pseudoClassStateChanged(COMPLETED, val));
|
||||
getStyleClass().add("item");
|
||||
setContentDisplay(ContentDisplay.LEFT);
|
||||
}
|
||||
|
||||
public void setGraphic(Ikon icon) {
|
||||
var graphicLabel = ((Label) getGraphic());
|
||||
if (icon != null) {
|
||||
graphicLabel.setText(null);
|
||||
graphicLabel.setGraphic(new FontIcon(icon));
|
||||
}
|
||||
}
|
||||
|
||||
public void setGraphic(String text) {
|
||||
var graphicLabel = ((Label) getGraphic());
|
||||
if (text != null) {
|
||||
graphicLabel.setText(text);
|
||||
graphicLabel.setGraphic(null);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return completed.get();
|
||||
}
|
||||
|
||||
public void setCompleted(boolean state) {
|
||||
completed.set(state);
|
||||
}
|
||||
|
||||
public BooleanProperty completedProperty() {
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.page.showcase.widget.Stepper.Item;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2MZ;
|
||||
|
||||
public class StepperSample extends SampleBlock {
|
||||
|
||||
public StepperSample() {
|
||||
super("Stepper", createContent());
|
||||
}
|
||||
|
||||
private static Node createContent() {
|
||||
var content = new VBox(BLOCK_VGAP);
|
||||
new CSSFragment(Stepper.CSS).addTo(content);
|
||||
|
||||
// == STEPPER CONTENT ==
|
||||
|
||||
var stackContent = new Label();
|
||||
stackContent.getStyleClass().add(Styles.TITLE_1);
|
||||
stackContent.setStyle("-fx-background-color:-color-bg-default;");
|
||||
stackContent.setWrapText(true);
|
||||
stackContent.setMinHeight(200);
|
||||
stackContent.setMaxWidth(400);
|
||||
stackContent.setAlignment(Pos.CENTER);
|
||||
|
||||
var stack = new StackPane(stackContent);
|
||||
stack.setPrefHeight(200);
|
||||
|
||||
// == STEPPER ==
|
||||
|
||||
var firstItem = new Item("First");
|
||||
firstItem.setGraphic("A");
|
||||
|
||||
var secondItem = new Item("Second");
|
||||
secondItem.setGraphic("B");
|
||||
|
||||
var thirdItem = new Item("Third");
|
||||
thirdItem.setGraphic("C");
|
||||
|
||||
var stepper = new Stepper(firstItem, secondItem, thirdItem);
|
||||
stepper.selectedItemProperty().addListener(
|
||||
(obs, old, val) -> stackContent.setText(val != null ? val.getText() : null)
|
||||
);
|
||||
stepper.setSelectedItem(stepper.getItems().get(0));
|
||||
|
||||
// == CONTROLS ==
|
||||
|
||||
var saveBtn = new Button("Save");
|
||||
saveBtn.setDefaultButton(true);
|
||||
saveBtn.setOnAction(e -> {
|
||||
// you can validate user input before moving forward here
|
||||
stepper.getSelectedItem().setCompleted(true);
|
||||
stepper.forward();
|
||||
});
|
||||
|
||||
var cancelBtn = new Button("Cancel");
|
||||
cancelBtn.getStyleClass().addAll(Styles.FLAT);
|
||||
cancelBtn.setOnAction(e -> {
|
||||
stepper.getSelectedItem().setCompleted(false);
|
||||
stepper.backward();
|
||||
});
|
||||
|
||||
var iconToggle = new ToggleSwitch("Icons");
|
||||
iconToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
for (int i = 0; i < stepper.getItems().size(); i++) {
|
||||
var item = stepper.getItems().get(i);
|
||||
if (val) {
|
||||
item.setGraphic(randomIcon());
|
||||
} else {
|
||||
item.setGraphic(String.valueOf(i + 1));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var rotateBtn = new Button("Rotate", new FontIcon(Material2MZ.ROTATE_RIGHT));
|
||||
rotateBtn.setOnAction(e -> {
|
||||
Side nextSide = switch (stepper.getTextPosition()) {
|
||||
case LEFT -> Side.TOP;
|
||||
case TOP -> Side.RIGHT;
|
||||
case RIGHT -> Side.BOTTOM;
|
||||
case BOTTOM -> Side.LEFT;
|
||||
};
|
||||
stepper.setTextPosition(nextSide);
|
||||
});
|
||||
|
||||
var controls = new HBox(
|
||||
BLOCK_HGAP,
|
||||
saveBtn,
|
||||
cancelBtn,
|
||||
new Spacer(),
|
||||
iconToggle,
|
||||
rotateBtn
|
||||
);
|
||||
controls.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// ~
|
||||
|
||||
content.getChildren().setAll(stepper, stack, new Separator(), controls);
|
||||
return content;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
|
||||
public class Tag extends Button {
|
||||
|
||||
public static final String CSS = """
|
||||
.tag {
|
||||
-fx-padding: 4px 6px 4px 6px;
|
||||
-fx-cursor: hand;
|
||||
-color-button-border-focused: -color-button-border;
|
||||
-color-button-border-pressed: -color-button-border;
|
||||
}
|
||||
""";
|
||||
|
||||
public Tag(String text) {
|
||||
this(text, null);
|
||||
}
|
||||
|
||||
public Tag(String text, Node graphic) {
|
||||
super(text, graphic);
|
||||
getStyleClass().add("tag");
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
import static atlantafx.sampler.page.Page.PAGE_HGAP;
|
||||
import static atlantafx.sampler.page.Page.PAGE_VGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
|
||||
public class TagSample extends GridPane {
|
||||
|
||||
private static final int PREF_WIDTH = 300;
|
||||
|
||||
public TagSample() {
|
||||
new CSSFragment(Tag.CSS).addTo(this);
|
||||
|
||||
setHgap(PAGE_HGAP);
|
||||
setVgap(PAGE_VGAP);
|
||||
|
||||
add(filledTagSample(), 0, 0);
|
||||
add(iconTagSample(), 1, 0);
|
||||
add(outlinedTagSample(), 0, 1);
|
||||
add(closeableTagSample(), 1, 1);
|
||||
}
|
||||
|
||||
private SampleBlock filledTagSample() {
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var basicTag = new Tag("basic");
|
||||
content.getChildren().add(basicTag);
|
||||
|
||||
var accentTag = new Tag("accent");
|
||||
accentTag.getStyleClass().add(Styles.ACCENT);
|
||||
content.getChildren().add(accentTag);
|
||||
|
||||
var successTag = new Tag("success");
|
||||
successTag.getStyleClass().add(Styles.SUCCESS);
|
||||
content.getChildren().add(successTag);
|
||||
|
||||
var dangerTag = new Tag("danger");
|
||||
dangerTag.getStyleClass().add(Styles.DANGER);
|
||||
content.getChildren().add(dangerTag);
|
||||
|
||||
return new SampleBlock("Filled", content);
|
||||
}
|
||||
|
||||
private SampleBlock iconTagSample() {
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var basicTag = new Tag("image", new FontIcon(Feather.IMAGE));
|
||||
content.getChildren().add(basicTag);
|
||||
|
||||
var accentTag = new Tag("music", new FontIcon(Feather.MUSIC));
|
||||
content.getChildren().add(accentTag);
|
||||
|
||||
var successTag = new Tag("video", new FontIcon(Feather.VIDEO));
|
||||
content.getChildren().add(successTag);
|
||||
|
||||
return new SampleBlock("Icon", content);
|
||||
}
|
||||
|
||||
private SampleBlock outlinedTagSample() {
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var accentTag = new Tag("accent");
|
||||
accentTag.getStyleClass().addAll(Styles.ACCENT, Styles.BUTTON_OUTLINED);
|
||||
content.getChildren().add(accentTag);
|
||||
|
||||
var successTag = new Tag("success");
|
||||
successTag.getStyleClass().addAll(Styles.SUCCESS, Styles.BUTTON_OUTLINED);
|
||||
content.getChildren().add(successTag);
|
||||
|
||||
var dangerTag = new Tag("danger");
|
||||
dangerTag.getStyleClass().addAll(Styles.DANGER, Styles.BUTTON_OUTLINED);
|
||||
content.getChildren().add(dangerTag);
|
||||
|
||||
return new SampleBlock("Outlined", content);
|
||||
}
|
||||
|
||||
private SampleBlock closeableTagSample() {
|
||||
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
|
||||
content.setPrefWidth(PREF_WIDTH);
|
||||
|
||||
var basicTag = new Tag("basic", new FontIcon(Material2AL.CLOSE));
|
||||
basicTag.setContentDisplay(ContentDisplay.RIGHT);
|
||||
content.getChildren().add(basicTag);
|
||||
|
||||
var accentTag = new Tag("accent", new FontIcon(Material2AL.CANCEL));
|
||||
accentTag.setContentDisplay(ContentDisplay.RIGHT);
|
||||
accentTag.getStyleClass().add(Styles.ACCENT);
|
||||
content.getChildren().add(accentTag);
|
||||
|
||||
var successTag = new Tag("success", new FontIcon(Material2AL.CANCEL));
|
||||
successTag.setContentDisplay(ContentDisplay.RIGHT);
|
||||
successTag.getStyleClass().add(Styles.SUCCESS);
|
||||
content.getChildren().add(successTag);
|
||||
|
||||
var dangerTag = new Tag("danger", new FontIcon(Material2AL.CANCEL));
|
||||
dangerTag.setContentDisplay(ContentDisplay.RIGHT);
|
||||
dangerTag.getStyleClass().add(Styles.DANGER);
|
||||
content.getChildren().add(dangerTag);
|
||||
|
||||
return new SampleBlock("Removable", content);
|
||||
}
|
||||
}
|
125
sampler/src/main/java/atlantafx/sampler/page/showcase/widget/WidgetCollectionPage.java
Normal file
125
sampler/src/main/java/atlantafx/sampler/page/showcase/widget/WidgetCollectionPage.java
Normal file
@ -0,0 +1,125 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
package atlantafx.sampler.page.showcase.widget;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import atlantafx.sampler.page.Page;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
// JavaFX Skin API is very complex and almost undocumented. In many cases it's much simpler
|
||||
// to create a small widget that just do the job than wasting hours to debug control behaviour.
|
||||
// Consider this as a cookbook of those widgets.
|
||||
public class WidgetCollectionPage extends BorderPane implements Page {
|
||||
|
||||
public static final String NAME = "Widgets";
|
||||
|
||||
@Override
|
||||
public String getName() { return NAME; }
|
||||
|
||||
private final ListView<Example> toc = new ListView<>();
|
||||
private final VBox widgetWrapper = new VBox(PAGE_HGAP);
|
||||
private boolean isRendered = false;
|
||||
|
||||
public WidgetCollectionPage() {
|
||||
super();
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
widgetWrapper.getStyleClass().add("widget");
|
||||
widgetWrapper.setAlignment(Pos.TOP_CENTER);
|
||||
widgetWrapper.setFillWidth(false);
|
||||
|
||||
toc.setCellFactory(c -> new TocCell());
|
||||
toc.getStyleClass().addAll("toc", Styles.DENSE, Tweaks.EDGE_TO_EDGE);
|
||||
toc.getItems().setAll(Example.values());
|
||||
toc.getSelectionModel().selectedItemProperty().addListener((obs, old, val) -> {
|
||||
if (val == null) { return; }
|
||||
widgetWrapper.getChildren().setAll(val.getSupplier().get());
|
||||
});
|
||||
|
||||
// ~
|
||||
|
||||
setCenter(widgetWrapper);
|
||||
setRight(toc);
|
||||
BorderPane.setMargin(toc, new Insets(0, 0, 0, PAGE_HGAP));
|
||||
getStyleClass().setAll("page", "widget-collection");
|
||||
|
||||
toc.getSelectionModel().selectFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent getView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDisplaySourceCode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChangeThemeSettings() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() { }
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
if (isRendered) { return; }
|
||||
|
||||
isRendered = true;
|
||||
toc.getSelectionModel().selectFirst();
|
||||
toc.requestFocus();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public enum Example {
|
||||
CARD("Card", () -> new CardSample()),
|
||||
MESSAGE("Message", () -> new MessageSample()),
|
||||
STEPPER("Stepper", () -> new StepperSample()),
|
||||
TAG("Tag", () -> new TagSample());
|
||||
|
||||
private final String name;
|
||||
private final Supplier<Pane> supplier;
|
||||
|
||||
Example(String name, Supplier<Pane> supplier) {
|
||||
this.name = name;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Supplier<Pane> getSupplier() {
|
||||
return supplier;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TocCell extends ListCell<Example> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Example item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.widget-collection {
|
||||
-fx-padding: 40px;
|
||||
-fx-min-width: 800px;
|
||||
-fx-max-width: 1200px;
|
||||
|
||||
>.toc {
|
||||
-fx-pref-width: 150px;
|
||||
-color-cell-bg-selected: -color-cell-bg;
|
||||
-color-cell-fg-selected: -color-accent-fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sample-block {
|
||||
|
BIN
sampler/src/main/resources/images/pattern.jpg
Normal file
BIN
sampler/src/main/resources/images/pattern.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
Loading…
Reference in New Issue
Block a user