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:
mkpaz 2022-09-26 16:33:57 +04:00
parent 012dcdd950
commit 8489106717
14 changed files with 1140 additions and 4 deletions

@ -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);
}
}

@ -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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB