Refactor and improve Card
This commit is contained in:
parent
34acefa8f8
commit
24a2e096ad
@ -20,6 +20,7 @@ public class Card extends Control {
|
||||
// Default constructor
|
||||
public Card() {
|
||||
super();
|
||||
getStyleClass().add("card");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3,14 +3,22 @@
|
||||
package atlantafx.base.controls;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class CardSkin implements Skin<Card> {
|
||||
|
||||
protected static final PseudoClass HAS_HEADER = PseudoClass.getPseudoClass("has-header");
|
||||
protected static final PseudoClass HAS_SUBHEADER = PseudoClass.getPseudoClass("has-subheader");
|
||||
protected static final PseudoClass HAS_BODY = PseudoClass.getPseudoClass("has-body");
|
||||
protected static final PseudoClass HAS_FOOTER = PseudoClass.getPseudoClass("has-footer");
|
||||
protected static final PseudoClass HAS_IMAGE = PseudoClass.getPseudoClass("has-image");
|
||||
|
||||
protected final Card control;
|
||||
protected final VBox root = new VBox();
|
||||
|
||||
@ -31,30 +39,42 @@ public class CardSkin implements Skin<Card> {
|
||||
|
||||
headerSlot = new StackPane();
|
||||
headerSlot.getStyleClass().add("header");
|
||||
headerSlotListener = new SlotListener(headerSlot);
|
||||
headerSlotListener = new SlotListener(
|
||||
headerSlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_HEADER, active)
|
||||
);
|
||||
control.headerProperty().addListener(headerSlotListener);
|
||||
headerSlotListener.changed(control.headerProperty(), null, control.getHeader());
|
||||
|
||||
subHeaderSlot = new StackPane();
|
||||
subHeaderSlot.getStyleClass().add("sub-header");
|
||||
subHeaderSlotListener = new SlotListener(subHeaderSlot);
|
||||
subHeaderSlotListener = new SlotListener(
|
||||
subHeaderSlot,
|
||||
(n, active) -> {
|
||||
getSkinnable().pseudoClassStateChanged(HAS_SUBHEADER, active);
|
||||
getSkinnable().pseudoClassStateChanged(HAS_IMAGE, n instanceof ImageView);
|
||||
}
|
||||
);
|
||||
control.subHeaderProperty().addListener(subHeaderSlotListener);
|
||||
subHeaderSlotListener.changed(control.subHeaderProperty(), null, control.getSubHeader());
|
||||
|
||||
bodySlot = new StackPane();
|
||||
bodySlot.getStyleClass().add("body");
|
||||
VBox.setVgrow(bodySlot, Priority.ALWAYS);
|
||||
bodySlotListener = new SlotListener(bodySlot);
|
||||
bodySlotListener = new SlotListener(
|
||||
bodySlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_BODY, active)
|
||||
);
|
||||
control.bodyProperty().addListener(bodySlotListener);
|
||||
bodySlotListener.changed(control.bodyProperty(), null, control.getBody());
|
||||
|
||||
footerSlot = new StackPane();
|
||||
footerSlot.getStyleClass().add("footer");
|
||||
footerSlotListener = new SlotListener(footerSlot);
|
||||
footerSlotListener = new SlotListener(
|
||||
footerSlot, (n, active) -> getSkinnable().pseudoClassStateChanged(HAS_FOOTER, active)
|
||||
);
|
||||
control.footerProperty().addListener(footerSlotListener);
|
||||
footerSlotListener.changed(control.footerProperty(), null, control.getFooter());
|
||||
|
||||
root.getStyleClass().add("card");
|
||||
root.getStyleClass().add("container");
|
||||
root.getChildren().setAll(headerSlot, subHeaderSlot, bodySlot, footerSlot);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import javafx.scene.layout.StackPane;
|
||||
|
||||
public class MessageSkin extends TileSkinBase<Message> {
|
||||
|
||||
private static final PseudoClass CLOSEABLE = PseudoClass.getPseudoClass("closeable");
|
||||
protected static final PseudoClass CLOSEABLE = PseudoClass.getPseudoClass("closeable");
|
||||
|
||||
protected final StackPane closeButton = new StackPane();
|
||||
protected final StackPane closeButtonIcon = new StackPane();
|
||||
|
@ -1,10 +1,12 @@
|
||||
package atlantafx.sampler.page.components;
|
||||
|
||||
import atlantafx.base.controls.Card;
|
||||
import atlantafx.base.controls.CustomTextField;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.controls.Tile;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.util.BBCodeParser;
|
||||
import atlantafx.sampler.Resources;
|
||||
import atlantafx.sampler.page.ExampleBox;
|
||||
import atlantafx.sampler.page.OutlinePage;
|
||||
import atlantafx.sampler.page.Snippet;
|
||||
@ -16,10 +18,16 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
import org.kordamp.ikonli.material2.Material2MZ;
|
||||
@ -45,13 +53,14 @@ public class CardPage extends OutlinePage {
|
||||
addFormattedText("""
|
||||
The [i]Card[/i] is a versatile container that can used in various contexts \
|
||||
such as headings, text, dialogs and more. It includes a header to provide a \
|
||||
brief overview or context of the information. The sub-header and body sections \
|
||||
brief overview or context of the information. The subheader and body sections \
|
||||
provide more detailed content, while the footer may include additional actions \
|
||||
or information."""
|
||||
);
|
||||
addNode(skeleton());
|
||||
addSection("Usage", usageExample());
|
||||
addSection("Elevation", elevationExample());
|
||||
addSection("Subheader", subHeaderExample());
|
||||
}
|
||||
|
||||
private Node skeleton() {
|
||||
@ -76,7 +85,7 @@ public class CardPage extends OutlinePage {
|
||||
box.setMaxWidth(500);
|
||||
box.getChildren().setAll(
|
||||
cellBuilder.apply("header"),
|
||||
cellBuilder.apply("sub-header"),
|
||||
cellBuilder.apply("subheader"),
|
||||
body,
|
||||
cellBuilder.apply("footer")
|
||||
);
|
||||
@ -133,7 +142,7 @@ public class CardPage extends OutlinePage {
|
||||
var box = new HBox(HGAP_20, tweetCard, dialogCard);
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The [i]Card[/i] pairs well with the [i]Tile[/i] component. \
|
||||
You can use the [i]Tile[/i] as either a header or body for the [i]Card[/i]. \
|
||||
You can use the [i]Tiles[/i] as either a header or body for the [i]Card[/i]. \
|
||||
It’s also suitable for building more complex dialogs as well."""
|
||||
);
|
||||
|
||||
@ -160,17 +169,95 @@ public class CardPage extends OutlinePage {
|
||||
"This is a title",
|
||||
"This is a description"
|
||||
));
|
||||
card2.setBody(new Label("This is content"));
|
||||
card2.setBody(new Label("This is a content"));
|
||||
//snippet_2:end
|
||||
|
||||
var box = new HBox(HGAP_20, card1, card2);
|
||||
box.setPadding(new Insets(0, 0, 10, 0));
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
With [code]Styles.ELEVATED_N[/code] or [code]Styles.INTERACTIVE[/code] styles classes \
|
||||
you can add raised shadow effect to the [i]Card[/i]."""
|
||||
To add the raised effect to the [i]Card[/i], use the [code]Styles.ELEVATED_N[/code] \
|
||||
or [code]Styles.INTERACTIVE[/code] style classes."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 2), description);
|
||||
}
|
||||
|
||||
private ExampleBox subHeaderExample() {
|
||||
//snippet_3:start
|
||||
var card1 = new Card();
|
||||
card1.getStyleClass().add(Styles.ELEVATED_1);
|
||||
card1.setMinWidth(300);
|
||||
card1.setMaxWidth(300);
|
||||
|
||||
var avatar1 = new Image(
|
||||
Resources.getResourceAsStream("images/avatars/avatar3.png")
|
||||
);
|
||||
var btn1 = new Button(null, new FontIcon(Feather.MORE_VERTICAL));
|
||||
btn1.getStyleClass().addAll(Styles.BUTTON_CIRCLE, Styles.FLAT);
|
||||
var header1 = new Tile(
|
||||
"Title",
|
||||
"This is a description",
|
||||
new ImageView(avatar1)
|
||||
);
|
||||
header1.setAction(btn1);
|
||||
card1.setHeader(header1);
|
||||
|
||||
var image1 = new WritableImage(
|
||||
new Image(
|
||||
Resources.getResourceAsStream("images/pattern.jpg")
|
||||
).getPixelReader(), 0, 0, 298, 150
|
||||
);
|
||||
card1.setSubHeader(new ImageView(image1));
|
||||
|
||||
var text1 = new TextFlow(new Text(FAKER.lorem().sentence(15)));
|
||||
text1.setMaxWidth(260);
|
||||
card1.setBody(text1);
|
||||
|
||||
// ~
|
||||
|
||||
var card2 = new Card();
|
||||
card2.getStyleClass().add(Styles.ELEVATED_1);
|
||||
card2.setMinWidth(300);
|
||||
card2.setMaxWidth(300);
|
||||
|
||||
var header2 = new Tile(
|
||||
"Reviewers",
|
||||
"Request up to 10 reviewers"
|
||||
);
|
||||
card2.setHeader(header2);
|
||||
|
||||
var tf2 = new CustomTextField();
|
||||
tf2.setPromptText("Search people");
|
||||
tf2.setLeft(new FontIcon(Material2MZ.SEARCH));
|
||||
card2.setSubHeader(tf2);
|
||||
|
||||
var body2 = new VBox(10);
|
||||
card2.setBody(body2);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
var cb = new CheckBox();
|
||||
var lbl = new Label(FAKER.name().fullName());
|
||||
var circle = new Circle(
|
||||
8, Color.web(FAKER.color().hex(true))
|
||||
);
|
||||
|
||||
var row = new HBox(10, circle, cb, lbl);
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
body2.getChildren().add(row);
|
||||
}
|
||||
|
||||
//snippet_3:end
|
||||
|
||||
var box = new HBox(HGAP_20, card1, card2);
|
||||
box.setPadding(new Insets(0, 0, 10, 0));
|
||||
|
||||
var description = BBCodeParser.createFormattedText("""
|
||||
The subheader slot is an optional space for interactive controls. Use \
|
||||
it to display a search field, filter menu, or local navigation component. \
|
||||
It also has special support for the [i]ImageView[/i]. If you place an image \
|
||||
inside the subheader, it will remove its horizontal padding."""
|
||||
);
|
||||
|
||||
return new ExampleBox(box, new Snippet(getClass(), 3), description);
|
||||
}
|
||||
}
|
||||
|
@ -339,11 +339,11 @@ public final class ModalPanePage extends OutlinePage {
|
||||
VBox.setVgrow(ta, Priority.ALWAYS);
|
||||
|
||||
var content = new VBox(
|
||||
20,
|
||||
16,
|
||||
new Tile("Example Dialog", FAKER.lorem().sentence(10)),
|
||||
ta
|
||||
);
|
||||
content.setPadding(new Insets(20));
|
||||
content.setPadding(new Insets(16));
|
||||
dialog.addContent(content);
|
||||
AnchorPane.setTopAnchor(content, 0d);
|
||||
AnchorPane.setRightAnchor(content, 0d);
|
||||
|
@ -7,12 +7,15 @@ $color-bg: -color-bg-default !default;
|
||||
$color-border: -color-border-default !default;
|
||||
$padding-x: 0.75em !default;
|
||||
$padding-y: 1em !default;
|
||||
$spacing: 10px !default;
|
||||
$spacing: 1em !default;
|
||||
|
||||
$title-font-size: cfg.$font-title-4;
|
||||
|
||||
.card {
|
||||
>.container {
|
||||
-fx-background-color: $color-bg;
|
||||
-fx-alignment: TOP_LEFT;
|
||||
-fx-padding: $padding-y $padding-x $padding-y $padding-x;
|
||||
-fx-padding: $padding-y 0 $padding-y 0;
|
||||
-fx-spacing: $spacing;
|
||||
|
||||
-fx-border-color: $color-border;
|
||||
@ -21,20 +24,23 @@ $spacing: 10px !default;
|
||||
|
||||
>.header {
|
||||
-fx-alignment: TOP_LEFT;
|
||||
-fx-padding: 0 $padding-x 0 $padding-x 0;
|
||||
}
|
||||
|
||||
>.sub-header {
|
||||
-fx-alignment: TOP_LEFT;
|
||||
-fx-padding: 0 $padding-x 0 $padding-x 0;
|
||||
}
|
||||
|
||||
>.body {
|
||||
// double spacing for body
|
||||
-fx-padding: $spacing 0 $spacing 0;
|
||||
// double vertical spacing for body
|
||||
-fx-padding: 0 $padding-x 0 $padding-x;
|
||||
-fx-alignment: TOP_LEFT;
|
||||
}
|
||||
|
||||
>.footer {
|
||||
-fx-alignment: TOP_LEFT;
|
||||
-fx-padding: 0 $padding-x 0 $padding-x 0;
|
||||
}
|
||||
|
||||
@each $level, $radius in cfg.$elevation {
|
||||
@ -46,16 +52,29 @@ $spacing: 10px !default;
|
||||
&.interactive:hover {
|
||||
@include effects.shadow(cfg.$elevation-color, cfg.$elevation-interactive);
|
||||
}
|
||||
}
|
||||
|
||||
.tile {
|
||||
// prevent double indentation
|
||||
&:has-image {
|
||||
>.container >.sub-header {
|
||||
-fx-padding: 0;
|
||||
-fx-background-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.edge-to-edge>.card {
|
||||
&.edge-to-edge>.container {
|
||||
-fx-border-width: 0;
|
||||
-fx-border-radius: 0;
|
||||
-fx-effect: none;
|
||||
}
|
||||
|
||||
.tile {
|
||||
>.container {
|
||||
// prevent double indentation
|
||||
-fx-padding: 0;
|
||||
-fx-background-radius: 0;
|
||||
|
||||
>.header >.title {
|
||||
-fx-font-size: $title-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,5 +46,11 @@ $close-button-icon-size: 0.3em !default;
|
||||
// prevent double indentation inside dialog
|
||||
-fx-padding: 0;
|
||||
-fx-background-radius: 0;
|
||||
|
||||
>.container {
|
||||
// prevent double indentation
|
||||
-fx-padding: 0;
|
||||
-fx-background-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user