Improve date picker
- Support adding top/bottom node to embed custom user widgets - Move prev/next arrows to the right side - Add clock example - Add show/hide header example - Fix unreadable next month today fg color
This commit is contained in:
parent
54ae621bed
commit
a295fd799c
@ -35,7 +35,11 @@ import javafx.css.Styleable;
|
||||
import javafx.css.StyleableBooleanProperty;
|
||||
import javafx.css.StyleableProperty;
|
||||
import javafx.css.converter.BooleanConverter;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Cell;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.DateCell;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
@ -227,6 +231,34 @@ public class InlineDatePicker extends Control {
|
||||
return showWeekNumbersProperty().getValue();
|
||||
}
|
||||
|
||||
private final ObjectProperty<Node> topNode = new SimpleObjectProperty<>(this, "topNode", null);
|
||||
|
||||
public final void setTopNode(Node value) {
|
||||
topNode.setValue(value);
|
||||
}
|
||||
|
||||
public final Node getTopNode() {
|
||||
return topNode.getValue();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> topNodeProperty() {
|
||||
return topNode;
|
||||
}
|
||||
|
||||
private final ObjectProperty<Node> bottomNode = new SimpleObjectProperty<>(this, "bottomNode", null);
|
||||
|
||||
public final void setBottomNode(Node value) {
|
||||
bottomNode.setValue(value);
|
||||
}
|
||||
|
||||
public final Node getBottomNode() {
|
||||
return bottomNode.getValue();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> bottomNodeProperty() {
|
||||
return bottomNode;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Stylesheet Handling //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -114,6 +114,30 @@ public class InlineDatePickerSkin extends BehaviorSkinBase<InlineDatePicker, Inl
|
||||
updateGrid();
|
||||
updateWeekNumberCells();
|
||||
});
|
||||
|
||||
registerChangeListener(datePicker.topNodeProperty(), e -> {
|
||||
Node node = datePicker.getTopNode();
|
||||
if (node == null) {
|
||||
rootPane.getChildren().removeIf(c -> c.getStyleClass().contains("top-node"));
|
||||
} else {
|
||||
if (!node.getStyleClass().contains("top-node")) {
|
||||
node.getStyleClass().add("top-node");
|
||||
}
|
||||
rootPane.getChildren().add(0, node);
|
||||
}
|
||||
});
|
||||
|
||||
registerChangeListener(datePicker.bottomNodeProperty(), e -> {
|
||||
Node node = datePicker.getBottomNode();
|
||||
if (node == null) {
|
||||
rootPane.getChildren().removeIf(c -> c.getStyleClass().contains("bottom-node"));
|
||||
} else {
|
||||
if (!node.getStyleClass().contains("bottom-node")) {
|
||||
node.getStyleClass().add("bottom-node");
|
||||
}
|
||||
rootPane.getChildren().add(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -153,6 +177,11 @@ public class InlineDatePickerSkin extends BehaviorSkinBase<InlineDatePicker, Inl
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void createUI() {
|
||||
if (getControl().getTopNode() != null) {
|
||||
getControl().getTopNode().getStyleClass().add("top-node");
|
||||
rootPane.getChildren().add(getControl().getTopNode());
|
||||
}
|
||||
|
||||
// YearMonth //
|
||||
|
||||
LocalDate value = getControl().getValue();
|
||||
@ -191,6 +220,11 @@ public class InlineDatePickerSkin extends BehaviorSkinBase<InlineDatePicker, Inl
|
||||
rootPane.getStyleClass().addAll("date-picker-popup", "inline-date-picker");
|
||||
rootPane.getChildren().add(calendarGrid);
|
||||
|
||||
if (getControl().getBottomNode() != null) {
|
||||
getControl().getBottomNode().getStyleClass().add("bottom-node");
|
||||
rootPane.getChildren().add(getControl().getBottomNode());
|
||||
}
|
||||
|
||||
getChildren().add(rootPane);
|
||||
|
||||
getControl().setOnKeyPressed(e -> behavior.onKeyPressed(e));
|
||||
@ -211,8 +245,8 @@ public class InlineDatePickerSkin extends BehaviorSkinBase<InlineDatePicker, Inl
|
||||
leftArrow.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
|
||||
backButton.setGraphic(leftArrow);
|
||||
|
||||
Region leftSpacer = new Region();
|
||||
HBox.setHgrow(leftSpacer, Priority.ALWAYS);
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
monthLabel = new Label();
|
||||
monthLabel.getStyleClass().add("month-label");
|
||||
@ -220,9 +254,6 @@ public class InlineDatePickerSkin extends BehaviorSkinBase<InlineDatePicker, Inl
|
||||
yearLabel = new Label();
|
||||
yearLabel.getStyleClass().add("year-label");
|
||||
|
||||
Region rightSpacer = new Region();
|
||||
HBox.setHgrow(rightSpacer, Priority.ALWAYS);
|
||||
|
||||
forwardButton = new Button();
|
||||
forwardButton.getStyleClass().addAll("forward-button");
|
||||
forwardButton.setOnMouseClicked(e -> behavior.moveForward(e));
|
||||
@ -232,7 +263,7 @@ public class InlineDatePickerSkin extends BehaviorSkinBase<InlineDatePicker, Inl
|
||||
rightArrow.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
|
||||
forwardButton.setGraphic(rightArrow);
|
||||
|
||||
monthYearPane.getChildren().addAll(backButton, leftSpacer, monthLabel, yearLabel, rightSpacer, forwardButton);
|
||||
monthYearPane.getChildren().addAll(monthLabel, yearLabel, spacer, backButton, forwardButton);
|
||||
|
||||
return monthYearPane;
|
||||
}
|
||||
|
@ -7,26 +7,36 @@ import atlantafx.base.controls.ToggleSwitch;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.sampler.page.AbstractPage;
|
||||
import atlantafx.sampler.page.SampleBlock;
|
||||
import atlantafx.sampler.theme.CSSFragment;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.DateCell;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.material2.Material2AL;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.chrono.HijrahChronology;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
|
||||
import static atlantafx.base.theme.Styles.BUTTON_ICON;
|
||||
import static atlantafx.base.theme.Styles.FLAT;
|
||||
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
|
||||
import static javafx.scene.layout.GridPane.REMAINING;
|
||||
|
||||
public class DatePickerPage extends AbstractPage {
|
||||
@ -34,17 +44,32 @@ public class DatePickerPage extends AbstractPage {
|
||||
public static final String NAME = "DatePicker";
|
||||
private static final LocalDate TODAY = LocalDate.now();
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_DATE;
|
||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
private static final String DATE_FORMATTER_PROMPT = "yyyy-MM-dd";
|
||||
private static final int INLINE_DATE_PICKER_COL = 0;
|
||||
private static final int INLINE_DATE_PICKER_ROW = 4;
|
||||
private static final CSSFragment NO_YEAR_MONTH_STYLE = new CSSFragment("""
|
||||
.date-picker-popup >.month-year-pane {
|
||||
visibility: hidden;
|
||||
-fx-min-width: 0;
|
||||
-fx-pref-width: 0;
|
||||
-fx-max-width: 0;
|
||||
-fx-min-height: 0;
|
||||
-fx-pref-height: 0;
|
||||
-fx-max-height: 0;
|
||||
}
|
||||
""");
|
||||
|
||||
@Override
|
||||
public String getName() { return NAME; }
|
||||
|
||||
private final BooleanProperty weekNumProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty showClockProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty editableProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty offPastDatesProperty = new SimpleBooleanProperty();
|
||||
private final BooleanProperty disableProperty = new SimpleBooleanProperty();
|
||||
private final Clock clock = new Clock();
|
||||
private DatePickerColorSelector colorSelector;
|
||||
|
||||
public DatePickerPage() {
|
||||
super();
|
||||
@ -56,15 +81,12 @@ public class DatePickerPage extends AbstractPage {
|
||||
private GridPane createPlayground() {
|
||||
var grid = new GridPane();
|
||||
grid.setHgap(40);
|
||||
grid.setVgap(SampleBlock.BLOCK_VGAP);
|
||||
grid.setVgap(BLOCK_VGAP);
|
||||
|
||||
final var popupDatePicker = createPopupDatePicker();
|
||||
final var inlineDatePicker = createInlineDatePicker(null);
|
||||
|
||||
var inlineValue = new Label();
|
||||
inlineValue.setAlignment(Pos.CENTER);
|
||||
inlineValue.setMaxWidth(Double.MAX_VALUE);
|
||||
inlineValue.textProperty().bind(inlineDatePicker.valueProperty().asString());
|
||||
colorSelector = new DatePickerColorSelector(grid);
|
||||
final var inlineDatePicker = createInlineDatePicker(null);
|
||||
|
||||
// == CONTROLS ==
|
||||
|
||||
@ -72,6 +94,20 @@ public class DatePickerPage extends AbstractPage {
|
||||
weekNumProperty.bind(weekNumToggle.selectedProperty());
|
||||
weekNumToggle.setSelected(true);
|
||||
|
||||
var showClockToggle = new ToggleSwitch("Show clock");
|
||||
showClockProperty.bind(showClockToggle.selectedProperty());
|
||||
showClockToggle.setSelected(true);
|
||||
|
||||
var showYearMonthToggle = new ToggleSwitch("Show header");
|
||||
showYearMonthToggle.setSelected(true);
|
||||
showYearMonthToggle.selectedProperty().addListener((obs, old, val) -> {
|
||||
if (!val) {
|
||||
NO_YEAR_MONTH_STYLE.addTo(grid);
|
||||
} else {
|
||||
NO_YEAR_MONTH_STYLE.removeFrom(grid);
|
||||
}
|
||||
});
|
||||
|
||||
var chronologyToggle = new ToggleSwitch("Second chronology");
|
||||
chronologyToggle.selectedProperty().addListener(
|
||||
(obs, old, val) -> popupDatePicker.setChronology(val ? HijrahChronology.INSTANCE : null)
|
||||
@ -94,16 +130,16 @@ public class DatePickerPage extends AbstractPage {
|
||||
var datePicker = createInlineDatePicker(val ? dp -> new FutureDateCell() : null);
|
||||
grid.getChildren().removeIf(n -> n instanceof InlineDatePicker);
|
||||
grid.add(datePicker, INLINE_DATE_PICKER_COL, INLINE_DATE_PICKER_ROW);
|
||||
inlineValue.textProperty().unbind();
|
||||
inlineValue.textProperty().bind(datePicker.valueProperty().asString());
|
||||
});
|
||||
|
||||
var disablePickerToggle = new ToggleSwitch("Disable");
|
||||
disableProperty.bind(disablePickerToggle.selectedProperty());
|
||||
|
||||
var controls = new VBox(
|
||||
SampleBlock.BLOCK_VGAP,
|
||||
BLOCK_VGAP,
|
||||
weekNumToggle,
|
||||
showClockToggle,
|
||||
showYearMonthToggle,
|
||||
chronologyToggle,
|
||||
editableToggle,
|
||||
offPastDatesToggle,
|
||||
@ -111,8 +147,6 @@ public class DatePickerPage extends AbstractPage {
|
||||
);
|
||||
controls.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
var colorSelector = new DatePickerColorSelector(grid);
|
||||
|
||||
// == GRID ==
|
||||
|
||||
var defaultLabel = new Label("Default");
|
||||
@ -126,8 +160,6 @@ public class DatePickerPage extends AbstractPage {
|
||||
grid.add(new Spacer(20), 0, 2);
|
||||
grid.add(inlineLabel, 0, 3);
|
||||
grid.add(inlineDatePicker, INLINE_DATE_PICKER_COL, INLINE_DATE_PICKER_ROW);
|
||||
grid.add(inlineValue, 0, 5);
|
||||
grid.add(colorSelector, 0, 6);
|
||||
grid.add(controls, 1, 0, 1, REMAINING);
|
||||
|
||||
return grid;
|
||||
@ -152,6 +184,14 @@ public class DatePickerPage extends AbstractPage {
|
||||
datePicker.setValue(TODAY);
|
||||
datePicker.showWeekNumbersProperty().bind(weekNumProperty);
|
||||
datePicker.disableProperty().bind(disableProperty);
|
||||
|
||||
datePicker.setTopNode(clock);
|
||||
datePicker.setBottomNode(colorSelector);
|
||||
|
||||
datePicker.topNodeProperty().bind(Bindings.createObjectBinding(
|
||||
() -> showClockProperty.get() ? clock : null, showClockProperty)
|
||||
);
|
||||
|
||||
return datePicker;
|
||||
}
|
||||
|
||||
@ -184,21 +224,51 @@ public class DatePickerPage extends AbstractPage {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Clock extends VBox {
|
||||
|
||||
public Clock() {
|
||||
var clockLabel = new Label(TIME_FORMATTER.format(LocalTime.now()));
|
||||
clockLabel.getStyleClass().add(Styles.TITLE_2);
|
||||
|
||||
var dateLabel = new Label(DateTimeFormatter.ofPattern("EEEE, LLLL dd, yyyy").format(LocalDate.now()));
|
||||
|
||||
setStyle("""
|
||||
-fx-border-width: 0 0 0.5 0;
|
||||
-fx-border-color: -color-border-default;"""
|
||||
);
|
||||
setSpacing(BLOCK_VGAP);
|
||||
getChildren().setAll(clockLabel, dateLabel);
|
||||
|
||||
var t = new Timeline(new KeyFrame(
|
||||
Duration.seconds(1),
|
||||
e -> clockLabel.setText(TIME_FORMATTER.format(LocalTime.now()))
|
||||
));
|
||||
t.setCycleCount(Animation.INDEFINITE);
|
||||
t.playFromStart();
|
||||
}
|
||||
}
|
||||
|
||||
// This class shares stylesheet with the AccentColorSelector
|
||||
private static class DatePickerColorSelector extends HBox {
|
||||
|
||||
private final Pane parent;
|
||||
private final ObjectProperty<CSSFragment> style = new SimpleObjectProperty<>();
|
||||
|
||||
public DatePickerColorSelector(Pane parent) {
|
||||
super();
|
||||
this.parent = parent;
|
||||
this.parent = Objects.requireNonNull(parent);
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
var resetBtn = new Button("", new FontIcon(Material2AL.CLEAR));
|
||||
resetBtn.getStyleClass().addAll(BUTTON_ICON, FLAT);
|
||||
resetBtn.setOnAction(e -> resetColorVariables());
|
||||
resetBtn.setOnAction(e -> style.set(null));
|
||||
|
||||
style.addListener((obs, old, val) -> {
|
||||
if (old != null) { old.removeFrom(parent); }
|
||||
if (val != null) { val.addTo(parent); }
|
||||
});
|
||||
|
||||
setAlignment(Pos.CENTER);
|
||||
getChildren().setAll(
|
||||
@ -217,33 +287,27 @@ public class DatePickerPage extends AbstractPage {
|
||||
|
||||
var btn = new Button("", icon);
|
||||
btn.getStyleClass().addAll(BUTTON_ICON, FLAT, "color-button");
|
||||
btn.setOnAction(e -> setColorVariables(bgColorName, fgColorName));
|
||||
btn.setOnAction(e -> updateStyle(bgColorName, fgColorName));
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
private void setColorVariables(String bgColorName, String fgColorName) {
|
||||
for (Node node : parent.getChildren()) {
|
||||
var style = String.format("-color-date-border:%s;-color-date-month-year-bg:%s;-color-date-month-year-fg:%s;",
|
||||
bgColorName,
|
||||
bgColorName,
|
||||
fgColorName
|
||||
);
|
||||
|
||||
if (node instanceof InlineDatePicker dp) {
|
||||
var popup = dp.lookup(".date-picker-popup");
|
||||
if (popup != null) { popup.setStyle(style); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetColorVariables() {
|
||||
for (Node node : parent.getChildren()) {
|
||||
if (node instanceof InlineDatePicker dp) {
|
||||
var popup = dp.lookup(".date-picker-popup");
|
||||
if (popup != null) { popup.setStyle(null); }
|
||||
}
|
||||
}
|
||||
private void updateStyle(String bgColorName, String fgColorName) {
|
||||
style.set(new CSSFragment(String.format("""
|
||||
.date-picker-popup {
|
||||
-color-date-border: %s;
|
||||
-color-date-month-year-bg: %s;
|
||||
-color-date-month-year-fg: %s;
|
||||
}
|
||||
.date-picker-popup >.top-node {
|
||||
-fx-background-color: %s;
|
||||
-color-fg-default: %s;
|
||||
-color-border-default: %s;
|
||||
-fx-border-color: %s;
|
||||
}""",
|
||||
bgColorName, bgColorName, fgColorName,
|
||||
bgColorName, fgColorName, fgColorName, fgColorName
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,12 +24,12 @@ public final class CSSFragment {
|
||||
|
||||
public void removeFrom(Region region) {
|
||||
Objects.requireNonNull(region);
|
||||
region.getStyleClass().remove(toDataURI());
|
||||
region.getStylesheets().remove(toDataURI());
|
||||
}
|
||||
|
||||
public boolean existsIn(Region region) {
|
||||
Objects.requireNonNull(region);
|
||||
return region.getStyleClass().contains(toDataURI());
|
||||
return region.getStylesheets().contains(toDataURI());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -173,9 +173,15 @@ $chrono-cell-padding: 0.083333em $cell-padding-x 0.083333em 0.333333em !default;
|
||||
.inline-date-picker {
|
||||
-fx-effect: none;
|
||||
|
||||
>.top-node,
|
||||
>.bottom-node {
|
||||
-fx-padding: $content-padding-y calc($content-padding-x * 2) $content-padding-y calc($content-padding-x * 2);
|
||||
}
|
||||
|
||||
>.month-year-pane {
|
||||
-fx-alignment: CENTER;
|
||||
-fx-spacing: 10px;
|
||||
-fx-padding: $content-padding-y calc($content-padding-x * 2) $content-padding-y calc($content-padding-x * 2);
|
||||
-fx-alignment: CENTER_LEFT;
|
||||
-fx-spacing: 6px;
|
||||
|
||||
>.button {
|
||||
-fx-background-color: transparent;
|
||||
@ -185,6 +191,7 @@ $chrono-cell-padding: 0.083333em $cell-padding-x 0.083333em 0.333333em !default;
|
||||
}
|
||||
|
||||
>.back-button {
|
||||
-fx-padding: 0 1em 0 0;
|
||||
>.left-arrow {
|
||||
@include icons.get("chevron-left", false);
|
||||
-fx-background-color: -color-date-month-year-fg;
|
||||
@ -221,6 +228,8 @@ $chrono-cell-padding: 0.083333em $cell-padding-x 0.083333em 0.333333em !default;
|
||||
.date-picker-popup>.calendar-grid>.selected,
|
||||
.date-picker-popup>.calendar-grid>.selected>.secondary-text,
|
||||
.date-picker-popup>.calendar-grid>.previous-month.selected,
|
||||
.date-picker-popup>.calendar-grid>.previous-month.today.selected,
|
||||
.date-picker-popup>.calendar-grid>.next-month.today.selected,
|
||||
.date-picker-popup>.calendar-grid>.next-month.selected {
|
||||
-fx-background-color: -color-date-day-bg-selected;
|
||||
-fx-text-fill: -color-date-day-fg-selected;
|
||||
|
Loading…
Reference in New Issue
Block a user