Add Tile control
This commit is contained in:
parent
9c5936515c
commit
47e2d4b9a5
39
base/src/main/java/atlantafx/base/controls/SlotListener.java
Normal file
39
base/src/main/java/atlantafx/base/controls/SlotListener.java
Normal file
@ -0,0 +1,39 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.base.controls;
|
||||
|
||||
import java.util.Objects;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
final class SlotListener implements ChangeListener<Node> {
|
||||
|
||||
private static final PseudoClass FILLED = PseudoClass.getPseudoClass("filled");
|
||||
|
||||
private final Pane slot;
|
||||
|
||||
public SlotListener(Node slot) {
|
||||
Objects.requireNonNull(slot, "Slot cannot be null.");
|
||||
|
||||
if (slot instanceof Pane pane) {
|
||||
this.slot = pane;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid slot type. Pane is required.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Node> obs, Node old, Node val) {
|
||||
if (val != null) {
|
||||
slot.getChildren().setAll(val);
|
||||
} else {
|
||||
slot.getChildren().clear();
|
||||
}
|
||||
slot.setVisible(val != null);
|
||||
slot.setManaged(val != null);
|
||||
slot.pseudoClassStateChanged(FILLED, val != null);
|
||||
}
|
||||
}
|
138
base/src/main/java/atlantafx/base/controls/Tile.java
Normal file
138
base/src/main/java/atlantafx/base/controls/Tile.java
Normal file
@ -0,0 +1,138 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.base.controls;
|
||||
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
/**
|
||||
* A versatile container that can used in various contexts such as dialog headers,
|
||||
* list items, and cards. It can contain a graphic, a title, subtitle, and optional
|
||||
* actions.
|
||||
*/
|
||||
public class Tile extends Control {
|
||||
|
||||
public Tile() {
|
||||
this(null, null, null);
|
||||
}
|
||||
|
||||
public Tile(@NamedArg("title") String title,
|
||||
@NamedArg("subtitle") String subtitle) {
|
||||
this(title, subtitle, null);
|
||||
}
|
||||
|
||||
public Tile(@NamedArg("title") String title,
|
||||
@NamedArg("subtitle") String subtitle,
|
||||
@NamedArg("graphic") Node graphic) {
|
||||
super();
|
||||
setTitle(title);
|
||||
setSubTitle(subtitle);
|
||||
setGraphic(graphic);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new TileSkin(this);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Properties //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The property representing the tile’s graphic node. It is commonly used
|
||||
* to add images or icons that are associated with the tile.
|
||||
*/
|
||||
private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic");
|
||||
|
||||
public Node getGraphic() {
|
||||
return graphic.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> graphicProperty() {
|
||||
return graphic;
|
||||
}
|
||||
|
||||
public void setGraphic(Node graphic) {
|
||||
this.graphic.set(graphic);
|
||||
}
|
||||
|
||||
/**
|
||||
* The property representing the tile’s title. Although it is not mandatory,
|
||||
* you typically would not want to have a tile without a title.
|
||||
*/
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* The property representing the tile’s subtitle. This is usually an optional
|
||||
* text or a description.
|
||||
*/
|
||||
private final StringProperty subTitle = new SimpleStringProperty(this, "subTitle");
|
||||
|
||||
public String getSubTitle() {
|
||||
return subTitle.get();
|
||||
}
|
||||
|
||||
public StringProperty subTitleProperty() {
|
||||
return subTitle;
|
||||
}
|
||||
|
||||
public void setSubTitle(String subTitle) {
|
||||
this.subTitle.set(subTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* The property representing the tile’s action node. It is commonly used
|
||||
* to place an action controls that are associated with the tile.
|
||||
*/
|
||||
private final ObjectProperty<Node> action = new SimpleObjectProperty<>(this, "action");
|
||||
|
||||
public Node getAction() {
|
||||
return action.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> actionProperty() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(Node action) {
|
||||
this.action.set(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* The property representing the tile’s action handler. Setting an action handler
|
||||
* makes the tile interactive or clickable. When a user clicks on the interactive
|
||||
* tile, the specified action handler will be called.
|
||||
*/
|
||||
private final ObjectProperty<Runnable> actionHandler = new SimpleObjectProperty<>(this, "actionHandler");
|
||||
|
||||
public Runnable getActionHandler() {
|
||||
return actionHandler.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Runnable> actionHandlerProperty() {
|
||||
return actionHandler;
|
||||
}
|
||||
|
||||
public void setActionHandler(Runnable actionHandler) {
|
||||
this.actionHandler.set(actionHandler);
|
||||
}
|
||||
}
|
135
base/src/main/java/atlantafx/base/controls/TileSkin.java
Normal file
135
base/src/main/java/atlantafx/base/controls/TileSkin.java
Normal file
@ -0,0 +1,135 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
package atlantafx.base.controls;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class TileSkin extends SkinBase<Tile> {
|
||||
|
||||
private static final PseudoClass FILLED = PseudoClass.getPseudoClass("filled");
|
||||
|
||||
protected final HBox root = new HBox();
|
||||
protected final StackPane graphicSlot;
|
||||
protected final ChangeListener<Node> graphicSlotListener;
|
||||
protected final VBox headerBox;
|
||||
protected final Label titleLbl;
|
||||
protected final Label subTitleLbl;
|
||||
protected final StackPane actionSlot;
|
||||
protected final ChangeListener<Node> actionSlotListener;
|
||||
|
||||
public TileSkin(Tile control) {
|
||||
super(control);
|
||||
|
||||
graphicSlot = new StackPane();
|
||||
graphicSlot.getStyleClass().add("graphic");
|
||||
graphicSlotListener = new SlotListener(graphicSlot);
|
||||
control.graphicProperty().addListener(graphicSlotListener);
|
||||
graphicSlotListener.changed(control.graphicProperty(), null, control.getGraphic());
|
||||
|
||||
titleLbl = new Label(control.getTitle());
|
||||
titleLbl.getStyleClass().add("title");
|
||||
titleLbl.textProperty().bind(control.titleProperty());
|
||||
|
||||
subTitleLbl = new Label(control.getSubTitle());
|
||||
subTitleLbl.setWrapText(true);
|
||||
subTitleLbl.getStyleClass().add("subtitle");
|
||||
subTitleLbl.setVisible(control.getSubTitle() != null);
|
||||
subTitleLbl.setManaged(control.getSubTitle() != null);
|
||||
|
||||
headerBox = new VBox(titleLbl, subTitleLbl);
|
||||
headerBox.setFillWidth(true);
|
||||
headerBox.getStyleClass().add("header");
|
||||
HBox.setHgrow(headerBox, Priority.ALWAYS);
|
||||
headerBox.setMinHeight(Region.USE_COMPUTED_SIZE);
|
||||
headerBox.setPrefHeight(Region.USE_COMPUTED_SIZE);
|
||||
headerBox.setMaxHeight(Region.USE_COMPUTED_SIZE);
|
||||
|
||||
registerChangeListener(control.subTitleProperty(), o -> {
|
||||
var value = getSkinnable().getSubTitle();
|
||||
subTitleLbl.setText(value);
|
||||
subTitleLbl.setVisible(value != null);
|
||||
subTitleLbl.setManaged(value != null);
|
||||
|
||||
// header is considered to be “filled” when a subtitle is set
|
||||
// because a tile without a title is nonsense
|
||||
headerBox.pseudoClassStateChanged(FILLED, value != null);
|
||||
});
|
||||
|
||||
actionSlot = new StackPane();
|
||||
actionSlot.getStyleClass().add("action");
|
||||
actionSlotListener = new SlotListener(actionSlot);
|
||||
control.actionProperty().addListener(actionSlotListener);
|
||||
actionSlotListener.changed(control.actionProperty(), null, control.getAction());
|
||||
|
||||
// use pref size for slots, or they will be resized
|
||||
// to the bare minimum due to Priority.ALWAYS
|
||||
graphicSlot.setMinWidth(Region.USE_PREF_SIZE);
|
||||
actionSlot.setMinWidth(Region.USE_PREF_SIZE);
|
||||
|
||||
// label text wrapping inside VBox won't work without this
|
||||
subTitleLbl.setMaxWidth(Region.USE_PREF_SIZE);
|
||||
subTitleLbl.setMinHeight(Region.USE_PREF_SIZE);
|
||||
|
||||
// do not resize children or container won't restore
|
||||
// to its original size after expanding
|
||||
root.setFillHeight(false);
|
||||
|
||||
root.getStyleClass().add("tile");
|
||||
root.getChildren().setAll(graphicSlot, headerBox, actionSlot);
|
||||
|
||||
root.pseudoClassStateChanged(Styles.STATE_INTERACTIVE, control.getActionHandler() != null);
|
||||
registerChangeListener(
|
||||
control.actionHandlerProperty(),
|
||||
o -> root.pseudoClassStateChanged(Styles.STATE_INTERACTIVE, getSkinnable().getActionHandler() != null)
|
||||
);
|
||||
root.setOnMouseClicked(e -> {
|
||||
if (control.getActionHandler() != null) {
|
||||
control.getActionHandler().run();
|
||||
}
|
||||
});
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
|
||||
protected double calcHeight() {
|
||||
var headerHeight = headerBox.getSpacing()
|
||||
+ headerBox.getInsets().getTop()
|
||||
+ headerBox.getInsets().getBottom()
|
||||
+ titleLbl.getBoundsInLocal().getHeight()
|
||||
+ (subTitleLbl.isManaged() ? subTitleLbl.getBoundsInLocal().getHeight() : 0);
|
||||
|
||||
return Math.max(Math.max(graphicSlot.getHeight(), actionSlot.getHeight()), headerHeight)
|
||||
+ root.getPadding().getTop()
|
||||
+ root.getPadding().getBottom();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computeMinHeight(double width, double topInset, double rightInset,
|
||||
double bottomInset, double leftInset) {
|
||||
// change the control height when label changed its height due to text wrapping,
|
||||
// no other way to do that, all JavaFX containers completely ignore _the actual_
|
||||
// height of its children
|
||||
return calcHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
titleLbl.textProperty().unbind();
|
||||
unregisterChangeListeners(getSkinnable().subTitleProperty());
|
||||
getSkinnable().graphicProperty().removeListener(graphicSlotListener);
|
||||
getSkinnable().actionProperty().removeListener(actionSlotListener);
|
||||
unregisterChangeListeners(getSkinnable().actionHandlerProperty());
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -26,11 +26,6 @@ public final class Styles {
|
||||
public static final String WARNING = "warning";
|
||||
public static final String DANGER = "danger";
|
||||
|
||||
public static final PseudoClass STATE_ACCENT = PseudoClass.getPseudoClass(ACCENT);
|
||||
public static final PseudoClass STATE_SUCCESS = PseudoClass.getPseudoClass(SUCCESS);
|
||||
public static final PseudoClass STATE_WARNING = PseudoClass.getPseudoClass(WARNING);
|
||||
public static final PseudoClass STATE_DANGER = PseudoClass.getPseudoClass(DANGER);
|
||||
|
||||
// Controls
|
||||
|
||||
public static final String TEXT = "text";
|
||||
@ -90,6 +85,14 @@ public final class Styles {
|
||||
public static final String TEXT_MUTED = "text-muted";
|
||||
public static final String TEXT_SUBTLE = "text-subtle";
|
||||
|
||||
// Pseudo-classes
|
||||
|
||||
public static final PseudoClass STATE_ACCENT = PseudoClass.getPseudoClass(ACCENT);
|
||||
public static final PseudoClass STATE_SUCCESS = PseudoClass.getPseudoClass(SUCCESS);
|
||||
public static final PseudoClass STATE_WARNING = PseudoClass.getPseudoClass(WARNING);
|
||||
public static final PseudoClass STATE_DANGER = PseudoClass.getPseudoClass(DANGER);
|
||||
public static final PseudoClass STATE_INTERACTIVE = PseudoClass.getPseudoClass(INTERACTIVE);
|
||||
|
||||
private Styles() {
|
||||
// Default constructor
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ import atlantafx.sampler.page.components.TabPanePage;
|
||||
import atlantafx.sampler.page.components.TableViewPage;
|
||||
import atlantafx.sampler.page.components.TextAreaPage;
|
||||
import atlantafx.sampler.page.components.TextFieldPage;
|
||||
import atlantafx.sampler.page.components.TilePage;
|
||||
import atlantafx.sampler.page.components.TitledPanePage;
|
||||
import atlantafx.sampler.page.components.ToggleButtonPage;
|
||||
import atlantafx.sampler.page.components.ToggleSwitchPage;
|
||||
@ -164,6 +165,7 @@ public class MainModel {
|
||||
NAV_TREE.get(SeparatorPage.class),
|
||||
NAV_TREE.get(SplitPanePage.class),
|
||||
NAV_TREE.get(PopoverPage.class),
|
||||
NAV_TREE.get(TilePage.class),
|
||||
NAV_TREE.get(TitledPanePage.class),
|
||||
NAV_TREE.get(ToolBarPage.class)
|
||||
);
|
||||
@ -297,6 +299,7 @@ public class MainModel {
|
||||
map.put(SliderPage.class, NavTree.Item.page(SliderPage.NAME, SliderPage.class));
|
||||
map.put(SpinnerPage.class, NavTree.Item.page(SpinnerPage.NAME, SpinnerPage.class));
|
||||
map.put(SplitPanePage.class, NavTree.Item.page(SplitPanePage.NAME, SplitPanePage.class));
|
||||
map.put(TilePage.class, NavTree.Item.page(TilePage.NAME, TilePage.class));
|
||||
map.put(TableViewPage.class, NavTree.Item.page(TableViewPage.NAME, TableViewPage.class));
|
||||
map.put(TabPanePage.class, NavTree.Item.page(TabPanePage.NAME, TabPanePage.class));
|
||||
map.put(TextAreaPage.class, NavTree.Item.page(TextAreaPage.NAME, TextAreaPage.class));
|
||||
|
@ -0,0 +1,199 @@
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.controls.PasswordTextField;
|
||||
import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.base.controls.Tile;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
import java.net.URI;
|
||||
import java.util.function.BiFunction;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2OutlinedAL;
|
||||
|
||||
public class TilePage extends OutlinePage {
|
||||
|
||||
public static final String NAME = "Tile";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getJavadocUri() {
|
||||
return URI.create(String.format(AFX_JAVADOC_URI_TEMPLATE, "controls/" + getName()));
|
||||
}
|
||||
|
||||
public TilePage() {
|
||||
super();
|
||||
|
||||
addPageHeader();
|
||||
addFormattedText("""
|
||||
The Tile is a versatile container that can used in various contexts \
|
||||
such as dialog headers, list items, and cards. It can contain a graphic, \
|
||||
a title, subtitle, and optional actions."""
|
||||
);
|
||||
addNode(skeleton());
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Interactive", interactiveExample());
|
||||
addSection("Stacking", stackingExample());
|
||||
}
|
||||
|
||||
private Node skeleton() {
|
||||
BiFunction<String, Pos, Node> cellBuilder = (s, pos) -> {
|
||||
var cell = new VBox(new Label(s));
|
||||
cell.setPadding(new Insets(10));
|
||||
cell.setFillWidth(true);
|
||||
cell.setAlignment(pos);
|
||||
Styles.appendStyle(cell, "-fx-border-color", "-color-accent-muted");
|
||||
Styles.appendStyle(cell, "-fx-border-width", "5px");
|
||||
return cell;
|
||||
};
|
||||
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(10);
|
||||
grid.setVgap(10);
|
||||
grid.setMaxWidth(600);
|
||||
grid.add(cellBuilder.apply("Graphic", Pos.CENTER), 0, 0, 1, GridPane.REMAINING);
|
||||
grid.add(cellBuilder.apply("Title", Pos.CENTER_LEFT), 1, 0, 1, 1);
|
||||
grid.add(cellBuilder.apply("Subtitle", Pos.CENTER_LEFT), 1, 1, 1, 1);
|
||||
grid.add(cellBuilder.apply("Action", Pos.CENTER), 2, 0, 1, GridPane.REMAINING);
|
||||
grid.getColumnConstraints().setAll(
|
||||
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true),
|
||||
new ColumnConstraints(-1, -1, -1, Priority.ALWAYS, HPos.CENTER, true),
|
||||
new ColumnConstraints(-1, -1, -1, Priority.NEVER, HPos.CENTER, true)
|
||||
);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private Node usageExample() {
|
||||
//snippet_1:start
|
||||
var tile1 = new Tile(
|
||||
"Title",
|
||||
FAKER.lorem().sentence(15)
|
||||
);
|
||||
|
||||
var tile2 = new Tile(
|
||||
FAKER.name().fullName(),
|
||||
FAKER.elderScrolls().quote()
|
||||
);
|
||||
|
||||
var img = new ImageView(new Image(
|
||||
Resources.getResourceAsStream(
|
||||
"assets/fxml/blueprints/resources/avatar1.png"
|
||||
)
|
||||
));
|
||||
img.setFitWidth(64);
|
||||
img.setFitHeight(64);
|
||||
tile2.setGraphic(img);
|
||||
|
||||
var tile3 = new Tile("Photos", "Last updated: Jun 9, 2019");
|
||||
var btn = new Button("", new FontIcon(Material2OutlinedAL.DELETE));
|
||||
btn.getStyleClass().addAll(Styles.BUTTON_CIRCLE, Styles.FLAT);
|
||||
tile3.setAction(btn);
|
||||
//snippet_1:end
|
||||
|
||||
var box = new VBox(
|
||||
tile1, new Separator(),
|
||||
tile2, new Separator(),
|
||||
tile3
|
||||
);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
[i]Tile[/i] does not have any mandatory properties, but you will not want \
|
||||
to use it without a title. Additionally, note that only the subtitle supports \
|
||||
text wrapping."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 1), description);
|
||||
}
|
||||
|
||||
private Node interactiveExample() {
|
||||
//snippet_2:start
|
||||
var tile = new Tile(
|
||||
"Password",
|
||||
"Please enter your authentication password to unlock the content"
|
||||
);
|
||||
|
||||
var tf = new PasswordTextField(null);
|
||||
tf.setPromptText("Click on the tile");
|
||||
tf.setPrefWidth(150);
|
||||
|
||||
tile.setAction(tf);
|
||||
tile.setActionHandler(tf::requestFocus);
|
||||
//snippet_2:end
|
||||
|
||||
var box = new VBox(tile);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
A [i]Tile[/i] can be made interactive by setting an action handler that may \
|
||||
or may not be related to the action slot."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private Node stackingExample() {
|
||||
//snippet_3:start
|
||||
var tile1 = new Tile(
|
||||
"Content filtering",
|
||||
"Set the content filtering level to restrict downloaded apps"
|
||||
);
|
||||
var cmb = new ComboBox<>(FXCollections.observableArrayList(
|
||||
"Everyone", "Low", "Medium", "High"
|
||||
));
|
||||
cmb.getSelectionModel().selectLast();
|
||||
cmb.setPrefWidth(150);
|
||||
tile1.setAction(cmb);
|
||||
|
||||
var tile2 = new Tile(
|
||||
"Password",
|
||||
"Require password for purchase"
|
||||
);
|
||||
var tgl2 = new ToggleSwitch();
|
||||
tile2.setAction(tgl2);
|
||||
tile2.setActionHandler(() -> tgl2.setSelected(!tgl2.isSelected()));
|
||||
|
||||
var tile3 = new Tile("Cache Size (Mb)", null);
|
||||
var spinner = new Spinner<>(10, 100, 50);
|
||||
spinner.setPrefWidth(150);
|
||||
tile3.setAction(spinner);
|
||||
|
||||
var tile4 = new Tile(
|
||||
"Notifications",
|
||||
"Notify me about updates to apps that I downloaded"
|
||||
);
|
||||
var tgl3 = new ToggleSwitch();
|
||||
tile4.setAction(tgl3);
|
||||
tile4.setActionHandler(() -> tgl3.setSelected(!tgl3.isSelected()));
|
||||
|
||||
var box = new VBox(tile1, tile2, tile3, new Separator(), tile4);
|
||||
//snippet_3:end
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
You can stack several [i]Tiles[/i] vertically. Optionally, use the [i]Separator[/i] \
|
||||
to split them into groups."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@
|
||||
@use "split-pane";
|
||||
@use "tab-pane";
|
||||
@use "text-input";
|
||||
@use "tile";
|
||||
@use "titled-pane";
|
||||
@use "toggle-button";
|
||||
@use "toggle-switch";
|
||||
|
46
styles/src/components/_tile.scss
Normal file
46
styles/src/components/_tile.scss
Normal file
@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
$color-interactive: -color-bg-subtle !default;
|
||||
|
||||
@use "../settings/config" as cfg;
|
||||
|
||||
.tile {
|
||||
-fx-padding: 0.75em 1em 0.75em 1em;
|
||||
-fx-alignment: CENTER_LEFT;
|
||||
-fx-background-radius: cfg.$border-radius;
|
||||
|
||||
&:hover:interactive {
|
||||
-fx-background-color: $color-interactive;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
>.graphic {
|
||||
&:filled {
|
||||
-fx-padding: 0 1em 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
>.header {
|
||||
-fx-alignment: CENTER_LEFT;
|
||||
-fx-padding: 0;
|
||||
|
||||
>.title {
|
||||
-fx-font-size: cfg.$font-title-4;
|
||||
}
|
||||
|
||||
>.subtitle {
|
||||
-fx-text-fill: -color-fg-muted;
|
||||
}
|
||||
|
||||
&:filled {
|
||||
-fx-spacing: 5px;
|
||||
-fx-alignment: TOP_LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
>.action {
|
||||
&:filled {
|
||||
-fx-padding: 0 0 0 1em;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user