From 4ff76d71745736877b0f4a03f65518cb114abc78 Mon Sep 17 00:00:00 2001 From: leewyatt Date: Fri, 6 Sep 2024 01:07:38 +0900 Subject: [PATCH 1/4] Refactor month selection in YearMonthViewSkin. Replaced month labels with MonthBox class to improve maintainability. Added PseudoClass handling for selected month styling and refactored CSS to support hidden and visible states of the indicator. --- .../dlsc/gemsfx/skins/YearMonthViewSkin.java | 113 ++++++++++-------- .../com/dlsc/gemsfx/year-month-view.css | 7 +- 2 files changed, 71 insertions(+), 49 deletions(-) diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java index 1a5500a0..c818d1ec 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java @@ -4,10 +4,10 @@ import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.css.PseudoClass; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.SkinBase; import javafx.scene.layout.ColumnConstraints; @@ -25,13 +25,18 @@ public class YearMonthViewSkin extends SkinBase { + private static final PseudoClass MONTH_SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected"); + private final ObjectProperty year = new SimpleObjectProperty<>(this, "year"); + private final ObjectProperty selectedMonth = new SimpleObjectProperty<>(this, "selectedMonth"); public YearMonthViewSkin(YearMonthView control) { super(control); - - year.set(control.getValue().getYear()); - control.valueProperty().addListener(it -> year.set(control.getValue().getYear())); + + control.valueProperty().subscribe(value -> { + year.set(value.getYear()); + selectedMonth.set(value.getMonth()); + }); Label yearLabel = new Label(); yearLabel.getStyleClass().add("year-label"); @@ -60,18 +65,18 @@ public YearMonthViewSkin(YearMonthView control) { GridPane gridPane = new GridPane(); gridPane.getStyleClass().add("grid-pane"); - gridPane.add(createMonth(Month.JANUARY), 0, 0); - gridPane.add(createMonth(Month.FEBRUARY), 2, 0); - gridPane.add(createMonth(Month.MARCH), 0, 1); - gridPane.add(createMonth(Month.APRIL), 2, 1); - gridPane.add(createMonth(Month.MAY), 0, 2); - gridPane.add(createMonth(Month.JUNE), 2, 2); - gridPane.add(createMonth(Month.JULY), 0, 3); - gridPane.add(createMonth(Month.AUGUST), 2, 3); - gridPane.add(createMonth(Month.SEPTEMBER), 0, 4); - gridPane.add(createMonth(Month.OCTOBER), 2, 4); - gridPane.add(createMonth(Month.NOVEMBER), 0, 5); - gridPane.add(createMonth(Month.DECEMBER), 2, 5); + gridPane.add(new MonthBox(Month.JANUARY, control), 0, 0); + gridPane.add(new MonthBox(Month.FEBRUARY, control), 2, 0); + gridPane.add(new MonthBox(Month.MARCH, control), 0, 1); + gridPane.add(new MonthBox(Month.APRIL, control), 2, 1); + gridPane.add(new MonthBox(Month.MAY, control), 0, 2); + gridPane.add(new MonthBox(Month.JUNE, control), 2, 2); + gridPane.add(new MonthBox(Month.JULY, control), 0, 3); + gridPane.add(new MonthBox(Month.AUGUST, control), 2, 3); + gridPane.add(new MonthBox(Month.SEPTEMBER, control), 0, 4); + gridPane.add(new MonthBox(Month.OCTOBER, control), 2, 4); + gridPane.add(new MonthBox(Month.NOVEMBER, control), 0, 5); + gridPane.add(new MonthBox(Month.DECEMBER, control), 2, 5); Region divider = new Region(); divider.getStyleClass().add("divider"); @@ -112,39 +117,51 @@ public YearMonthViewSkin(YearMonthView control) { container.setClip(clip); getChildren().add(container); + + // Updates the pseudo-class state based on whether the month of the box matches the newly selected month. + selectedMonth.subscribe(monthSelected -> + gridPane.getChildren().stream() + .filter(node -> node instanceof MonthBox) + .map(node -> (MonthBox) node) + .forEach(box -> box.pseudoClassStateChanged(MONTH_SELECTED_PSEUDO_CLASS, box.getMonth() == monthSelected)) + ); } - private Node createMonth(Month month) { - Label monthLabel = new Label(getSkinnable().getConverter().toString(month)); - monthLabel.getStyleClass().add("month-label"); - monthLabel.setMinWidth(Region.USE_PREF_SIZE); - monthLabel.setMaxWidth(Region.USE_PREF_SIZE); - - YearMonthView view = getSkinnable(); - - Region selectionIndicator = new Region(); - selectionIndicator.visibleProperty().bind(Bindings.createBooleanBinding(() -> view.getValue().equals(YearMonth.of(year.get(), month)), view.valueProperty(), year)); - selectionIndicator.getStyleClass().add("selection-indicator"); - - VBox box = new VBox(monthLabel, selectionIndicator); - box.setMaxWidth(Region.USE_PREF_SIZE); - box.setAlignment(Pos.CENTER); - box.getStyleClass().add("month-box"); - box.setOnMouseClicked(evt -> view.setValue(YearMonth.of(year.get(), month.getValue()))); - box.disableProperty().bind(Bindings.createObjectBinding(() -> { - YearMonth earliestMonth = view.getEarliestMonth(); - if (earliestMonth != null && YearMonth.of(view.getValue().getYear(), month.getValue()).isBefore(earliestMonth)) { - return true; - } - YearMonth latestMonth = view.getLatestMonth(); - if (latestMonth != null && YearMonth.of(view.getValue().getYear(), month.getValue()).isAfter(latestMonth)) { - return true; - } - return false; - }, view.earliestMonthProperty(), view.latestMonthProperty(), view.valueProperty())); - - GridPane.setMargin(box, new Insets(10, 30, 10, 30)); - - return box; + private class MonthBox extends VBox { + + private final Month month; + + public MonthBox(Month month, YearMonthView view) { + getStyleClass().add("month-box"); + + this.month = month; + + Label monthLabel = new Label(view.getConverter().toString(month)); + monthLabel.getStyleClass().add("month-label"); + monthLabel.setMinWidth(Region.USE_PREF_SIZE); + monthLabel.setMaxWidth(Region.USE_PREF_SIZE); + + Region indicator = new Region(); + indicator.getStyleClass().add("indicator"); + + getChildren().setAll(monthLabel, indicator); + GridPane.setMargin(this, new Insets(10, 30, 10, 30)); + + setMaxWidth(Region.USE_PREF_SIZE); + setAlignment(Pos.CENTER); + setOnMouseClicked(evt -> view.setValue(YearMonth.of(year.get(), month.getValue()))); + disableProperty().bind(Bindings.createObjectBinding(() -> { + YearMonth earliestMonth = view.getEarliestMonth(); + if (earliestMonth != null && YearMonth.of(view.getValue().getYear(), month.getValue()).isBefore(earliestMonth)) { + return true; + } + YearMonth latestMonth = view.getLatestMonth(); + return latestMonth != null && YearMonth.of(view.getValue().getYear(), month.getValue()).isAfter(latestMonth); + }, view.earliestMonthProperty(), view.latestMonthProperty(), view.valueProperty())); + } + + public final Month getMonth() { + return month; + } } } diff --git a/gemsfx/src/main/resources/com/dlsc/gemsfx/year-month-view.css b/gemsfx/src/main/resources/com/dlsc/gemsfx/year-month-view.css index 8648f5f2..c20fa6f0 100644 --- a/gemsfx/src/main/resources/com/dlsc/gemsfx/year-month-view.css +++ b/gemsfx/src/main/resources/com/dlsc/gemsfx/year-month-view.css @@ -17,12 +17,17 @@ -fx-cursor: hand; } -.year-month-view > .container > .grid-pane .month-box .selection-indicator { +.year-month-view > .container > .grid-pane .month-box > .indicator { + visibility: hidden; -fx-pref-height: 2px; -fx-background-color: -fx-text-background-color; -fx-background-insets: 0px -2px 0px -2px; } +.year-month-view > .container > .grid-pane .month-box:selected > .indicator { + visibility: visible; +} + .year-month-view > .container > .grid-pane .divider { -fx-pref-width: 1px; -fx-background-color: #e0e0e0; From 14001f1bf958397d3bcf43a65dfb0260db7865fa Mon Sep 17 00:00:00 2001 From: leewyatt Date: Fri, 6 Sep 2024 02:38:20 +0900 Subject: [PATCH 2/4] Update YearMonthViewSkin to track current and selected month Refactor the month selection logic and add a new pseudo-class to indicate the current month. Also, introduce a method to update the month boxes' pseudo-class state, improving code readability and functionality. --- .../dlsc/gemsfx/skins/YearMonthViewSkin.java | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java index c818d1ec..80e8ebe6 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java @@ -20,23 +20,22 @@ import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; +import java.time.LocalDate; import java.time.Month; import java.time.YearMonth; public class YearMonthViewSkin extends SkinBase { - private static final PseudoClass MONTH_SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected"); + private static final PseudoClass SELECTED_MONTH_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected"); + private static final PseudoClass CURRENT_MONTH_PSEUDO_CLASS = PseudoClass.getPseudoClass("current"); private final ObjectProperty year = new SimpleObjectProperty<>(this, "year"); - private final ObjectProperty selectedMonth = new SimpleObjectProperty<>(this, "selectedMonth"); + private boolean updatingMonthBox = false; public YearMonthViewSkin(YearMonthView control) { super(control); - - control.valueProperty().subscribe(value -> { - year.set(value.getYear()); - selectedMonth.set(value.getMonth()); - }); + + year.set(control.getValue().getYear()); Label yearLabel = new Label(); yearLabel.getStyleClass().add("year-label"); @@ -118,13 +117,35 @@ public YearMonthViewSkin(YearMonthView control) { getChildren().add(container); - // Updates the pseudo-class state based on whether the month of the box matches the newly selected month. - selectedMonth.subscribe(monthSelected -> - gridPane.getChildren().stream() - .filter(node -> node instanceof MonthBox) - .map(node -> (MonthBox) node) - .forEach(box -> box.pseudoClassStateChanged(MONTH_SELECTED_PSEUDO_CLASS, box.getMonth() == monthSelected)) - ); + control.valueProperty().subscribe(value -> { + updatingMonthBox = true; + year.set(value.getYear()); + updateMonthBoxes(value, gridPane); + updatingMonthBox = false; + }); + + year.addListener(it -> { + if (!updatingMonthBox) { + updateMonthBoxes(control.getValue(), gridPane); + } + }); + } + + /** + * Updates the pseudo-class state of the MonthBoxes. + */ + private void updateMonthBoxes(YearMonth value, GridPane gridPane) { + Month selectedMonth = value.getMonth(); + int currentYear = LocalDate.now().getYear(); + Month currentMonth = LocalDate.now().getMonth(); + + gridPane.getChildren().stream() + .filter(node -> node instanceof MonthBox) + .map(node -> (MonthBox) node) + .forEach(box -> { + box.pseudoClassStateChanged(SELECTED_MONTH_PSEUDO_CLASS, box.getMonth() == selectedMonth); + box.pseudoClassStateChanged(CURRENT_MONTH_PSEUDO_CLASS, box.getMonth() == currentMonth && year.get() == currentYear); + }); } private class MonthBox extends VBox { From 292faedf15515d51f6fa14097aa2b4dd005997cc Mon Sep 17 00:00:00 2001 From: leewyatt Date: Fri, 6 Sep 2024 02:47:49 +0900 Subject: [PATCH 3/4] Refactor month box addition to grid pane. Extracted repetitive code into the `addMonthBoxToGridPane` method. This improves the maintainability and readability of the `YearMonthViewSkin` class. --- .../dlsc/gemsfx/skins/YearMonthViewSkin.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java index 80e8ebe6..04a3adca 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java @@ -64,18 +64,7 @@ public YearMonthViewSkin(YearMonthView control) { GridPane gridPane = new GridPane(); gridPane.getStyleClass().add("grid-pane"); - gridPane.add(new MonthBox(Month.JANUARY, control), 0, 0); - gridPane.add(new MonthBox(Month.FEBRUARY, control), 2, 0); - gridPane.add(new MonthBox(Month.MARCH, control), 0, 1); - gridPane.add(new MonthBox(Month.APRIL, control), 2, 1); - gridPane.add(new MonthBox(Month.MAY, control), 0, 2); - gridPane.add(new MonthBox(Month.JUNE, control), 2, 2); - gridPane.add(new MonthBox(Month.JULY, control), 0, 3); - gridPane.add(new MonthBox(Month.AUGUST, control), 2, 3); - gridPane.add(new MonthBox(Month.SEPTEMBER, control), 0, 4); - gridPane.add(new MonthBox(Month.OCTOBER, control), 2, 4); - gridPane.add(new MonthBox(Month.NOVEMBER, control), 0, 5); - gridPane.add(new MonthBox(Month.DECEMBER, control), 2, 5); + addMonthBoxToGridPane(gridPane, control); Region divider = new Region(); divider.getStyleClass().add("divider"); @@ -131,6 +120,14 @@ public YearMonthViewSkin(YearMonthView control) { }); } + private void addMonthBoxToGridPane(GridPane gridPane, YearMonthView control) { + for (Month month : Month.values()) { + int columnIndex = (month.getValue() % 2 == 0) ? 2 : 0; + int rowIndex = (month.getValue() - 1) / 2; + gridPane.add(new MonthBox(month, control), columnIndex, rowIndex); + } + } + /** * Updates the pseudo-class state of the MonthBoxes. */ From a59822595b1b1bf7bcdb60669a660b07917be337 Mon Sep 17 00:00:00 2001 From: leewyatt Date: Fri, 6 Sep 2024 03:22:16 +0900 Subject: [PATCH 4/4] Remove unnecessary initialization for updatingMonthBox. The boolean variable updatingMonthBox no longer requires explicit initialization to false as it defaults to that value. This change simplifies the code by removing redundant initialization. --- .../src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java index 04a3adca..718eae28 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/YearMonthViewSkin.java @@ -30,7 +30,7 @@ public class YearMonthViewSkin extends SkinBase { private static final PseudoClass CURRENT_MONTH_PSEUDO_CLASS = PseudoClass.getPseudoClass("current"); private final ObjectProperty year = new SimpleObjectProperty<>(this, "year"); - private boolean updatingMonthBox = false; + private boolean updatingMonthBox; public YearMonthViewSkin(YearMonthView control) { super(control);