From a295fd799cfb154b2161838bc2f4d8bff8e01081 Mon Sep 17 00:00:00 2001 From: mkpaz Date: Thu, 29 Sep 2022 16:21:24 +0400 Subject: [PATCH] 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 --- .../base/controls/InlineDatePicker.java | 34 ++++- .../base/controls/InlineDatePickerSkin.java | 43 +++++- .../page/components/DatePickerPage.java | 142 +++++++++++++----- .../atlantafx/sampler/theme/CSSFragment.java | 4 +- styles/src/components/_date-picker.scss | 13 +- 5 files changed, 186 insertions(+), 50 deletions(-) diff --git a/base/src/main/java/atlantafx/base/controls/InlineDatePicker.java b/base/src/main/java/atlantafx/base/controls/InlineDatePicker.java index 1b7e7ef..781a124 100755 --- a/base/src/main/java/atlantafx/base/controls/InlineDatePicker.java +++ b/base/src/main/java/atlantafx/base/controls/InlineDatePicker.java @@ -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 topNode = new SimpleObjectProperty<>(this, "topNode", null); + + public final void setTopNode(Node value) { + topNode.setValue(value); + } + + public final Node getTopNode() { + return topNode.getValue(); + } + + public ObjectProperty topNodeProperty() { + return topNode; + } + + private final ObjectProperty bottomNode = new SimpleObjectProperty<>(this, "bottomNode", null); + + public final void setBottomNode(Node value) { + bottomNode.setValue(value); + } + + public final Node getBottomNode() { + return bottomNode.getValue(); + } + + public ObjectProperty bottomNodeProperty() { + return bottomNode; + } + /////////////////////////////////////////////////////////////////////////// // Stylesheet Handling // /////////////////////////////////////////////////////////////////////////// diff --git a/base/src/main/java/atlantafx/base/controls/InlineDatePickerSkin.java b/base/src/main/java/atlantafx/base/controls/InlineDatePickerSkin.java index f4ef86a..1fcee16 100755 --- a/base/src/main/java/atlantafx/base/controls/InlineDatePickerSkin.java +++ b/base/src/main/java/atlantafx/base/controls/InlineDatePickerSkin.java @@ -114,6 +114,30 @@ public class InlineDatePickerSkin extends BehaviorSkinBase { + 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 behavior.onKeyPressed(e)); @@ -211,8 +245,8 @@ public class InlineDatePickerSkin extends BehaviorSkinBase behavior.moveForward(e)); @@ -232,7 +263,7 @@ public class InlineDatePickerSkin extends BehaviorSkinBase.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 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 + ))); } } } diff --git a/sampler/src/main/java/atlantafx/sampler/theme/CSSFragment.java b/sampler/src/main/java/atlantafx/sampler/theme/CSSFragment.java index a5088e2..6cd7b7a 100644 --- a/sampler/src/main/java/atlantafx/sampler/theme/CSSFragment.java +++ b/sampler/src/main/java/atlantafx/sampler/theme/CSSFragment.java @@ -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 diff --git a/styles/src/components/_date-picker.scss b/styles/src/components/_date-picker.scss index 2bae1f5..1e14876 100755 --- a/styles/src/components/_date-picker.scss +++ b/styles/src/components/_date-picker.scss @@ -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;