Improve control and widget samples
This commit is contained in:
parent
1c4c6a5232
commit
cc3cd4a391
@ -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; }
|
||||
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user