Improve control and widget samples

This commit is contained in:
mkpaz 2022-10-04 16:34:44 +04:00
parent 1c4c6a5232
commit cc3cd4a391
15 changed files with 515 additions and 71 deletions

@ -137,6 +137,7 @@ public class RingProgressIndicatorSkin extends SkinBase<RingProgressIndicator> {
protected void toggleIndeterminate() {
var indeterminate = getSkinnable().isIndeterminate();
progressLabel.setManaged(!indeterminate);
progressLabel.setVisible(!indeterminate);
if (indeterminate) {
if (getSkinnable().isVisible()) {

@ -75,6 +75,7 @@ public final class Styles {
public static final String TEXT_OBLIQUE = "text-oblique";
public static final String TEXT_STRIKETHROUGH = "text-strikethrough";
public static final String TEXT_UNDERLINED = "text-underlined";
public static final String TEXT_MUTED = "text-muted";
// @formatter:on

@ -0,0 +1,158 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.base.util;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;
import java.util.Objects;
import java.util.function.UnaryOperator;
/**
* An alternative for the {@link javafx.scene.control.PasswordField}.
* The formatter (un)masks text field content based on boolean property.
*/
public class PasswordTextFormatter extends TextFormatter<String> {
public static final char BULLET = '\u2731'; // heavy asterisk
protected PasswordTextFormatter(StringConverter<String> valueConverter,
UnaryOperator<Change> filter,
TextField textField,
char bullet) {
super(valueConverter, null, filter);
Objects.requireNonNull(valueConverter);
Objects.requireNonNull(filter);
Objects.requireNonNull(textField);
PasswordFilter passwordFilter = (PasswordFilter) getFilter();
passwordFilter.setBullet(bullet);
passwordFilter.setInitialText(textField.getText());
revealPasswordProperty().addListener((obs, old, val) -> {
// Force text field update, because converter is only called on focus
// events by default. Don't use commitValue() here because caret position
// won't be correct due to #javafx-bug (https://bugs.openjdk.org/browse/JDK-8248914).
if (val == null) { return; }
textField.setText(passwordProperty().get());
});
// force text field update on scene show
Platform.runLater(textField::commitValue);
}
public ReadOnlyStringProperty passwordProperty() {
return ((PasswordFilter) getFilter()).password.getReadOnlyProperty();
}
public String getPassword() {
return passwordProperty().get();
}
public BooleanProperty revealPasswordProperty() {
return ((PasswordFilter) getFilter()).revealPassword;
}
public boolean isRevealPassword() {
return revealPasswordProperty().get();
}
public void setRevealPassword(boolean reveal) {
revealPasswordProperty().set(reveal);
}
// Life would be easier if TextFormatter had the default constructor.
public static PasswordTextFormatter create(TextField textField, char bullet) {
var filter = new PasswordFilter();
var converter = new PasswordStringConverter(filter);
return new PasswordTextFormatter(converter, filter, textField, bullet);
}
public static PasswordTextFormatter create(TextField textField) {
return create(textField, BULLET);
}
///////////////////////////////////////////////////////////////////////////
protected static class PasswordStringConverter extends StringConverter<String> {
protected final PasswordFilter filter;
public PasswordStringConverter(PasswordFilter filter) {
this.filter = filter;
}
@Override
public String toString(String s) {
if (s == null) { return ""; }
return filter.revealPassword.get() ? filter.password.get() : filter.maskText(s.length());
}
@Override
public String fromString(String string) {
return filter.password.get();
}
}
protected static class PasswordFilter implements UnaryOperator<Change> {
protected final ReadOnlyStringWrapper password = new ReadOnlyStringWrapper("");
protected final BooleanProperty revealPassword = new SimpleBooleanProperty(false);
protected final StringBuilder sb = new StringBuilder();
protected char bullet = PasswordTextFormatter.BULLET;
@Override
public TextFormatter.Change apply(TextFormatter.Change change) {
// Since we are using setText() to force text field to update (see above),
// we should protect internal password value from changing when `revealPassword`is toggled.
if (Objects.equals(change.getText(), sb.toString())) {
if (!revealPassword.get()) {
change.setText(maskText(change.getText().length()));
}
return change;
}
if (change.isReplaced()) {
sb.replace(change.getRangeStart(), change.getRangeEnd(), change.getText());
} else if (change.isDeleted()) {
sb.delete(change.getRangeStart(), change.getRangeEnd());
} else if (change.isAdded()) {
if (change.getRangeStart() == sb.length()) {
sb.append(change.getText());
} else {
sb.insert(change.getRangeStart(), change.getText());
}
}
// mask new text, so it won't appear on user input
if (change.getText() != null && !change.getText().isEmpty() && !revealPassword.get()) {
change.setText(maskText(change.getText().length()));
}
password.set(sb.toString());
return change;
}
protected void setBullet(char bullet) {
this.bullet = bullet;
}
protected String maskText(int length) {
return String.valueOf(bullet).repeat(length);
}
protected void setInitialText(String text) {
if (text != null && !text.isEmpty()) {
sb.append(text);
password.set(sb.toString());
}
}
}
}

@ -119,11 +119,11 @@ class MainLayer extends BorderPane {
// animate switching between pages
subLayerPane.getChildren().add(nextPage.getView());
subLayerPane.getChildren().remove(prevPage.getView());
var transition = new FadeTransition(Duration.millis(PAGE_TRANSITION_DURATION), nextPage.getView());
transition.setFromValue(0.0);
transition.setToValue(1.0);
transition.setOnFinished(t -> {
subLayerPane.getChildren().remove(prevPage.getView());
if (nextPage instanceof Pane nextPane) { nextPane.toFront(); }
});
transition.play();

@ -2,8 +2,10 @@
package atlantafx.sampler.page.components;
import atlantafx.base.controls.CustomTextField;
import atlantafx.base.util.PasswordTextFormatter;
import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.page.SampleBlock;
import javafx.scene.Cursor;
import javafx.scene.layout.FlowPane;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
@ -27,47 +29,71 @@ public class CustomTextFieldPage extends AbstractPage {
rightIconSample(),
bothIconsSample(),
successSample(),
dangerSample()
dangerSample(),
passwordSample()
));
}
private SampleBlock leftIconSample() {
var leftIconField = new CustomTextField();
leftIconField.setPromptText("Prompt text");
leftIconField.setRight(new FontIcon(Feather.X));
leftIconField.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Left", leftIconField);
var tf = new CustomTextField();
tf.setPromptText("Prompt text");
tf.setRight(new FontIcon(Feather.X));
tf.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Left", tf);
}
private SampleBlock rightIconSample() {
var rightIconField = new CustomTextField();
rightIconField.setPromptText("Prompt text");
rightIconField.setLeft(new FontIcon(Feather.MAP_PIN));
rightIconField.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Right", rightIconField);
var tf = new CustomTextField();
tf.setPromptText("Prompt text");
tf.setLeft(new FontIcon(Feather.MAP_PIN));
tf.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Right", tf);
}
private SampleBlock bothIconsSample() {
var bothIconField = new CustomTextField("Text");
bothIconField.setLeft(new FontIcon(Feather.MAP_PIN));
bothIconField.setRight(new FontIcon(Feather.X));
bothIconField.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Both Sides", bothIconField);
var tf = new CustomTextField("Text");
tf.setLeft(new FontIcon(Feather.MAP_PIN));
tf.setRight(new FontIcon(Feather.X));
tf.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Both Sides", tf);
}
private SampleBlock successSample() {
var successField = new CustomTextField("Text");
successField.pseudoClassStateChanged(STATE_SUCCESS, true);
successField.setRight(new FontIcon(Feather.X));
successField.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Success", successField);
var tf = new CustomTextField("Text");
tf.pseudoClassStateChanged(STATE_SUCCESS, true);
tf.setRight(new FontIcon(Feather.X));
tf.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Success", tf);
}
private SampleBlock dangerSample() {
var dangerField = new CustomTextField("Text");
dangerField.pseudoClassStateChanged(STATE_DANGER, true);
dangerField.setLeft(new FontIcon(Feather.MAP_PIN));
dangerField.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Danger", dangerField);
var tf = new CustomTextField();
tf.pseudoClassStateChanged(STATE_DANGER, true);
tf.setLeft(new FontIcon(Feather.MAP_PIN));
tf.setPrefWidth(PREF_WIDTH);
return new SampleBlock("Danger", tf);
}
private SampleBlock passwordSample() {
var tf = new CustomTextField("qwerty");
tf.setPrefWidth(PREF_WIDTH);
var passwordFormatter = PasswordTextFormatter.create(tf);
tf.setTextFormatter(passwordFormatter);
var icon = new FontIcon(Feather.EYE_OFF);
icon.setCursor(Cursor.HAND);
icon.setOnMouseClicked(e -> {
if (passwordFormatter.revealPasswordProperty().get()) {
passwordFormatter.revealPasswordProperty().set(false);
icon.setIconCode(Feather.EYE_OFF);
} else {
passwordFormatter.revealPasswordProperty().set(true);
icon.setIconCode(Feather.EYE);
}
});
tf.setRight(icon);
return new SampleBlock("Password", tf);
}
}

@ -1,11 +1,16 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.components;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.page.SampleBlock;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.scene.web.HTMLEditor;
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
public class HTMLEditorPage extends AbstractPage {
public static final String NAME = "HTMLEditor";
@ -21,9 +26,16 @@ public class HTMLEditorPage extends AbstractPage {
}
private SampleBlock editorSample() {
var description = new Text(
"HTMLEditor toolbar buttons use images from 'com/sun/javafx/scene/control/skin/modena'. " +
"In opposite, since AtlantaFX themes are also distributed as single CSS files, it contains no images. " +
"Unfortunately reusing Modena resources isn't possible, because the package isn't opened in OpenJFX 'module-info'."
);
description.getStyleClass().addAll(Styles.TEXT, Styles.WARNING);
var editor = new HTMLEditor();
editor.setPrefHeight(400);
editor.setHtmlText(String.join("<br/><br/>", FAKER.lorem().paragraphs(10)));
return new SampleBlock("Playground", editor);
return new SampleBlock("Playground", new VBox(BLOCK_VGAP, new TextFlow(description), editor));
}
}

@ -8,13 +8,18 @@ import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.page.Page;
import atlantafx.sampler.page.SampleBlock;
import atlantafx.sampler.theme.CSSFragment;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon;
import java.time.LocalDate;
@ -31,7 +36,7 @@ public class PopoverPage extends AbstractPage {
public PopoverPage() {
super();
setUserContent(new VBox(Page.PAGE_VGAP,
new HBox(PAGE_HGAP, textSample(), datePickerSample()),
new HBox(PAGE_HGAP, textSample(), datePickerSample(), dialogSample()),
positionSample()
));
}
@ -72,6 +77,42 @@ public class PopoverPage extends AbstractPage {
return new SampleBlock("Date Picker", link);
}
private SampleBlock dialogSample() {
var root = new VBox(BLOCK_VGAP);
var popover = new Popover(root);
popover.setHeaderAlwaysVisible(false);
popover.setDetachable(true);
var icon = new FontIcon(Feather.ALERT_TRIANGLE);
icon.setIconSize(32); // not always works
icon.setStyle("-fx-icon-size:32px;-fx-icon-color:-color-warning-fg;-fx-fill:-color-warning-fg;" + icon.getStyle());
var label = new Label(FAKER.chuckNorris().fact(), icon);
label.setStyle("-fx-graphic-text-gap:10;");
label.setWrapText(true);
label.setMaxWidth(300);
label.setMaxHeight(Double.MAX_VALUE);
root.getChildren().add(label);
var yesBtn = new Button("Yes");
yesBtn.setDefaultButton(true);
yesBtn.setOnAction(e -> popover.hide());
var noBtn = new Button("No");
noBtn.setOnAction(e -> popover.hide());
var box = new HBox(10, yesBtn, noBtn);
box.setPadding(new Insets(0, 0, 0, 42));
box.setAlignment(Pos.CENTER_LEFT);
root.getChildren().add(box);
var link = createHyperlink("Click me");
link.setOnAction(e -> popover.show(link));
return new SampleBlock("Dialog", link);
}
private SampleBlock positionSample() {
var grid = new GridPane();
grid.setHgap(BLOCK_HGAP);

@ -6,13 +6,13 @@ import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.page.Page;
import atlantafx.sampler.page.SampleBlock;
import atlantafx.sampler.theme.CSSFragment;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Task;
import javafx.css.PseudoClass;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.geometry.VPos;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.util.StringConverter;
@ -24,7 +24,7 @@ import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
// #javafx-bug Indeterminate (animated) progress bar and also progress indicator
// are VERY resource expensive. It consumes a single CPU core and a lot of memory.
// are very resource expensive. It consumes a single CPU core and a lot of memory.
public class ProgressPage extends AbstractPage {
public static final String NAME = "Progress";
@ -45,27 +45,46 @@ public class ProgressPage extends AbstractPage {
grid.add(ringIndicatorSample(), 0, 1);
grid.add(barSizeSample(), 1, 1);
grid.add(colorChangeSample(), 0, 2);
grid.add(indeterminateSample(), 0, 2);
grid.add(colorChangeSample(), 1, 2);
setUserContent(grid);
}
private SampleBlock basicBarSample() {
var flowPane = new FlowPane(BLOCK_HGAP, BLOCK_VGAP, createBar(0, false), createBar(0.5, false), createBar(1, false), createBar(0.5, true));
var flowPane = new FlowPane(
BLOCK_HGAP, BLOCK_VGAP,
createBar(0, false),
createBar(0.5, false),
createBar(1, false),
createBar(0.5, true)
);
flowPane.setAlignment(Pos.CENTER_LEFT);
return new SampleBlock("Progress Bar", flowPane);
}
private SampleBlock basicIndicatorSample() {
var flowPane = new FlowPane(BLOCK_HGAP, BLOCK_VGAP, createIndicator(0, false), createIndicator(0.5, false), createIndicator(1, false), createIndicator(0.5, true));
var flowPane = new FlowPane(
BLOCK_HGAP, BLOCK_VGAP,
createIndicator(0, false),
createIndicator(0.5, false),
createIndicator(1, false),
createIndicator(0.5, true)
);
flowPane.setAlignment(Pos.TOP_LEFT);
return new SampleBlock("Progress Indicator", flowPane);
}
private SampleBlock barSizeSample() {
var container = new VBox(BLOCK_VGAP, new HBox(20, createBar(0.5, false, SMALL), new Text("small")), new HBox(20, createBar(0.5, false, MEDIUM), new Text("medium")), new HBox(20, createBar(0.5, false, LARGE), new Text("large")));
var container = new VBox(
BLOCK_VGAP,
new HBox(20, createBar(0.5, false, SMALL), new Text("small")),
new HBox(20, createBar(0.5, false, MEDIUM), new Text("medium")),
new HBox(20, createBar(0.5, false, LARGE), new Text("large"))
);
container.setAlignment(Pos.TOP_LEFT);
container.getChildren().forEach(c -> ((HBox) c).setAlignment(Pos.CENTER_LEFT));
return new SampleBlock("Size", container);
@ -75,7 +94,7 @@ public class ProgressPage extends AbstractPage {
var basicIndicator = new RingProgressIndicator(0, false);
var customTextIndicator = new RingProgressIndicator(0.5, false);
customTextIndicator.setPrefSize(75, 75);
customTextIndicator.setMinSize(75, 75);
customTextIndicator.setStringConverter(new StringConverter<>() {
@Override
public String toString(Double progress) {
@ -89,7 +108,7 @@ public class ProgressPage extends AbstractPage {
});
var reverseIndicator = new RingProgressIndicator(0.25, true);
reverseIndicator.setPrefSize(150, 150);
reverseIndicator.setMinSize(150, 150);
var reverseIndicatorLabel = new Label("25%");
reverseIndicatorLabel.getStyleClass().add(TITLE_4);
@ -139,6 +158,60 @@ public class ProgressPage extends AbstractPage {
return new SampleBlock("Ring Indicator", box);
}
private SampleBlock indeterminateSample() {
var grid = new GridPane();
grid.setHgap(40);
grid.setVgap(BLOCK_VGAP);
grid.getColumnConstraints().setAll(
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true),
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true),
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true)
);
grid.getRowConstraints().setAll(
new RowConstraints(-1, -1, -1, Priority.ALWAYS, VPos.CENTER, true),
new RowConstraints(-1, -1, -1, Priority.NEVER, VPos.CENTER, true)
);
var barToggle = new ToggleButton("Start");
barToggle.textProperty().bind(Bindings.createStringBinding(
() -> barToggle.isSelected() ? "Stop" : "Start", barToggle.selectedProperty())
);
var bar = createBar(0, false);
bar.progressProperty().bind(Bindings.createDoubleBinding(
() -> barToggle.isSelected() ? -1d : 0d, barToggle.selectedProperty())
);
grid.add(bar, 0, 0);
grid.add(barToggle, 0, 1);
var indicatorToggle = new ToggleButton("Start");
indicatorToggle.textProperty().bind(Bindings.createStringBinding(
() -> indicatorToggle.isSelected() ? "Stop" : "Start", indicatorToggle.selectedProperty())
);
var indicator = createIndicator(0, false);
indicator.setPrefSize(75, 75);
indicator.progressProperty().bind(Bindings.createDoubleBinding(
() -> indicatorToggle.isSelected() ? -1d : 0d, indicatorToggle.selectedProperty())
);
grid.add(indicator, 1, 0);
grid.add(indicatorToggle, 1, 1);
var ringToggle = new ToggleButton("Start");
ringToggle.textProperty().bind(Bindings.createStringBinding(
() -> ringToggle.isSelected() ? "Stop" : "Start", ringToggle.selectedProperty())
);
var ring = new RingProgressIndicator(0, false);
ring.setMinSize(75, 75);
ring.progressProperty().bind(Bindings.createDoubleBinding(
() -> ringToggle.isSelected() ? -1d : 0d, ringToggle.selectedProperty())
);
grid.add(ring, 2, 0);
grid.add(ringToggle, 2, 1);
return new SampleBlock("Indeterminate", grid,
"Animated JavaFX progress indicators aren't cheap. They can consume quite a lot of CPU time."
);
}
private SampleBlock colorChangeSample() {
var stateSuccess = PseudoClass.getPseudoClass("state-success");
var stateDanger = PseudoClass.getPseudoClass("state-danger");
@ -164,6 +237,7 @@ public class ProgressPage extends AbstractPage {
var content = new VBox(BLOCK_VGAP);
content.getChildren().setAll(barStack, runBtn);
content.setAlignment(Pos.CENTER_LEFT);
content.setPrefHeight(200);
bar.progressProperty().addListener((obs, old, val) -> {
if (val == null) { return; }
@ -176,17 +250,17 @@ public class ProgressPage extends AbstractPage {
});
new CSSFragment("""
.example:state-success .progress-bar {
-color-progress-bar-fill: -color-success-emphasis;
}
.example:state-danger .progress-bar {
-color-progress-bar-fill: -color-danger-emphasis;
}
.example:state-success .label,
.example:state-danger .label {
-fx-text-fill: -color-fg-emphasis;
}
""").addTo(content);
.example:state-success .progress-bar {
-color-progress-bar-fill: -color-success-emphasis;
}
.example:state-danger .progress-bar {
-color-progress-bar-fill: -color-danger-emphasis;
}
.example:state-success .label,
.example:state-danger .label {
-fx-text-fill: -color-fg-emphasis;
}
""").addTo(content);
runBtn.setOnAction(e1 -> {
var task = new Task<Void>() {
@ -224,7 +298,7 @@ public class ProgressPage extends AbstractPage {
return new SampleBlock("Dynamic Color Change", content);
}
private ProgressIndicator createBar(double progress, boolean disabled, String... styleClasses) {
private ProgressBar createBar(double progress, boolean disabled, String... styleClasses) {
var bar = new ProgressBar(progress);
bar.getStyleClass().addAll(styleClasses);
bar.setDisable(disabled);

@ -7,15 +7,19 @@ import atlantafx.sampler.theme.CSSFragment;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.GridPane;
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 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;
@ -43,7 +47,8 @@ public class CardSample extends HBox {
new VBox(
PAGE_VGAP,
imageTextCard(),
titleImageCard()
titleImageCard(),
statisticCard()
)
);
}
@ -111,7 +116,7 @@ public class CardSample extends HBox {
card.setImage(new ImageView(cropImage));
card.setTitle("Title");
var text = new Text(FAKER.lorem().paragraph());
var text = new Text(FAKER.lorem().sentence());
card.setBody(new TextFlow(text));
return card;
@ -141,4 +146,34 @@ public class CardSample extends HBox {
return card;
}
private Card statisticCard() {
var card = new Card();
card.setMinWidth(CARD_WIDTH);
card.setMaxWidth(CARD_WIDTH);
card.setTitle("Statistic");
var grid = new GridPane();
grid.setHgap(40);
grid.setVgap(10);
card.setBody(grid);
var leftHead = new Text("Active");
leftHead.getStyleClass().add(Styles.TEXT_MUTED);
grid.add(leftHead, 0, 0);
var leftData = new Label("12.87", new FontIcon(Material2AL.ARROW_UPWARD));
leftData.getStyleClass().addAll(Styles.SUCCESS, Styles.TITLE_2);
grid.add(leftData, 0, 1);
var rightHead = new Text("Idle");
rightHead.getStyleClass().add(Styles.TEXT_MUTED);
grid.add(rightHead, 1, 0);
var rightData = new Label("3.74", new FontIcon(Material2AL.ARROW_DOWNWARD));
rightData.getStyleClass().addAll(Styles.DANGER, Styles.TITLE_2);
grid.add(rightData, 1, 1);
return card;
}
}

@ -56,7 +56,7 @@ public class Stepper extends HBox {
-fx-icon-color: -color-stepper-fg;
}
.stepper > .item:selected > .graphic {
-color-stepper-bg: -color-accent-subtle;
-color-stepper-bg: -color-bg-default;
-color-stepper-fg: -color-accent-fg;
-color-stepper-border: -color-accent-emphasis;
}

@ -7,6 +7,7 @@ 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.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.Node;
@ -61,20 +62,24 @@ public class StepperSample extends SampleBlock {
// == CONTROLS ==
var saveBtn = new Button("Save");
saveBtn.setDefaultButton(true);
saveBtn.setOnAction(e -> {
var nextBtn = new Button("Next");
nextBtn.setDefaultButton(true);
nextBtn.setOnAction(e -> {
// you can validate user input before moving forward here
stepper.getSelectedItem().setCompleted(true);
stepper.forward();
});
nextBtn.textProperty().bind(Bindings.createStringBinding(
() -> stepper.canGoForwardProperty().get() ? "Next" : "Done", stepper.canGoForwardProperty())
);
var cancelBtn = new Button("Cancel");
cancelBtn.getStyleClass().addAll(Styles.FLAT);
cancelBtn.setOnAction(e -> {
var prevBtn = new Button("Previous");
prevBtn.getStyleClass().addAll(Styles.FLAT);
prevBtn.setOnAction(e -> {
stepper.getSelectedItem().setCompleted(false);
stepper.backward();
});
prevBtn.disableProperty().bind(stepper.canGoBackProperty().not());
var iconToggle = new ToggleSwitch("Icons");
iconToggle.selectedProperty().addListener((obs, old, val) -> {
@ -101,8 +106,8 @@ public class StepperSample extends SampleBlock {
var controls = new HBox(
BLOCK_HGAP,
saveBtn,
cancelBtn,
nextBtn,
prevBtn,
new Spacer(),
iconToggle,
rotateBtn

@ -3,6 +3,9 @@ package atlantafx.sampler.page.showcase.widget;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import static javafx.scene.control.ContentDisplay.RIGHT;
public class Tag extends Button {
@ -10,6 +13,7 @@ public class Tag extends Button {
.tag {
-fx-padding: 4px 6px 4px 6px;
-fx-cursor: hand;
-color-button-border-hover: -color-button-border;
-color-button-border-focused: -color-button-border;
-color-button-border-pressed: -color-button-border;
}
@ -21,6 +25,27 @@ public class Tag extends Button {
public Tag(String text, Node graphic) {
super(text, graphic);
if (graphic != null) {
graphic.setOnMouseEntered(e -> {
if (getContentDisplay() == RIGHT) {
graphic.setScaleX(1.2);
graphic.setScaleY(1.2);
}
});
graphic.setOnMouseExited(e -> {
if (getContentDisplay() == RIGHT) {
graphic.setScaleX(1);
graphic.setScaleY(1);
}
});
graphic.setOnMouseClicked(e -> {
if (getContentDisplay() == RIGHT && getParent() != null && getParent() instanceof Pane pane) {
pane.getChildren().remove(this);
}
});
}
getStyleClass().add("tag");
}
}

@ -30,6 +30,7 @@ public class TagSample extends GridPane {
add(iconTagSample(), 1, 0);
add(outlinedTagSample(), 0, 1);
add(closeableTagSample(), 1, 1);
add(customColorTagSample(), 0, 2);
}
private SampleBlock filledTagSample() {
@ -114,4 +115,42 @@ public class TagSample extends GridPane {
return new SampleBlock("Removable", content);
}
private SampleBlock customColorTagSample() {
var content = new FlowPane(BLOCK_HGAP, BLOCK_VGAP);
content.setPrefWidth(PREF_WIDTH);
new CSSFragment("""
.brand {
-color-button-fg: -color-fg-emphasis;
-color-button-bg-hover: -color-button-bg;
-color-button-bg-pressed: -color-button-bg;
}
.twitter {
-color-button-bg: rgb(85, 172, 238);
-color-button-border: rgb(85, 172, 238);
}
.youtube {
-color-button-bg: rgb(205, 32, 31);
-color-button-border: rgb(205, 32, 31);
}
.facebook {
-color-button-bg: rgb(59, 89, 153);
-color-button-border: rgb(59, 89, 153);
}
""").addTo(content);
var twitterTag = new Tag("Twitter", new FontIcon(Feather.TWITTER));
twitterTag.getStyleClass().addAll("brand", "twitter");
content.getChildren().add(twitterTag);
var youtubeTag = new Tag("YouTube", new FontIcon(Feather.YOUTUBE));
youtubeTag.getStyleClass().addAll("brand", "youtube");
content.getChildren().add(youtubeTag);
var facebookTag = new Tag("Facebook", new FontIcon(Feather.FACEBOOK));
facebookTag.getStyleClass().addAll("brand", "facebook");
content.getChildren().add(facebookTag);
return new SampleBlock("Custom Color", content);
}
}

@ -53,14 +53,10 @@ $color-button-bg-selected: if(cfg.$darkMode, -color-base-6, -color-base-1) !defa
}
}
// Sadly JavaFX devs have a mania to declare everyting private and final.
// The below code isn't working, while there are no runtime errors and URLs are correct
// it's still not loaded for unknown reason.
// There are to many images here and HMTLEditor itself is obsolete type of control,
// so I don't want to use data-url. It's either be fixed at OpenJFX or not.
// Anyone who treats this as a problem can easily fix it by using addtitional CSS file.
// Just copy CSS rules from below and images from the OpenJFX repo and then use relative
// URLs. #javafx-bug
// JavaFX doesn't export (open) Modena resources package, so the below links won't work.
// Because of AtlantaFX themes are also distributed as single CSS files, it can't be fixed here.
// You can copy Modena resources and override those URLs in your app CSS to tackle the problem
// or better use TinyMCE or any other modern JS WYSIWYG editor instead of this legacy control.
$image-path: "/com/sun/javafx/scene/control/skin/modena" !default;
.color-picker.html-editor-foreground {

@ -48,24 +48,55 @@ Text {
}
.label.accent {
-fx-text-fill: -color-accent-fg;
#{cfg.$font-icon-selector} {
-fx-icon-color: -color-accent-fg;
-fx-fill: -color-accent-fg;
}
}
.text.success {
-fx-fill: -color-success-fg;
}
.label.success {
-fx-text-fill: -color-success-fg;
#{cfg.$font-icon-selector} {
-fx-icon-color: -color-success-fg;
-fx-fill: -color-success-fg;
}
}
.text.warning {
-fx-fill: -color-warning-fg;
}
.label.warning {
-fx-text-fill: -color-warning-fg;
#{cfg.$font-icon-selector} {
-fx-icon-color: -color-warning-fg;
-fx-fill: -color-warning-fg;
}
}
.text.danger {
-fx-fill: -color-danger-fg;
}
.label.danger {
-fx-text-fill: -color-danger-fg;
#{cfg.$font-icon-selector} {
-fx-icon-color: -color-danger-fg;
-fx-fill: -color-danger-fg;
}
}
.text-muted {
-fx-fill: -color-fg-muted;
}
.label.text-muted {
-fx-text-fill: -color-accent-fg;
#{cfg.$font-icon-selector} {
-fx-icon-color: -color-accent-fg;
-fx-fill: -color-accent-fg;
}
}
// font weight