From c40ec4e3b33c5209bce6e4f96ff8187b03529116 Mon Sep 17 00:00:00 2001 From: mkpaz Date: Thu, 23 Feb 2023 16:24:40 +0400 Subject: [PATCH] Add label position support to toggle switch --- CHANGELOG.md | 6 +- .../controls/RingProgressIndicatorSkin.java | 5 +- .../atlantafx/base/controls/ToggleSwitch.java | 122 +++++++++++++-- .../base/controls/ToggleSwitchSkin.java | 139 +++++++++++------- .../page/components/ToggleSwitchPage.java | 20 ++- styles/src/components/_toggle-switch.scss | 8 + 6 files changed, 227 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e76c0..a97eafc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,17 @@ ### Improvements +- (Base) Improved Javadoc. See full API reference in [docs](https://mkpaz.github.io/atlantafx/apidocs/atlantafx.base/module-summary.html). +- (Base) `ToggleSwitch` label position support (left or right). - (CSS) `Button` shadow support (`-color-button-shadow`). Only for themes compiled with the `button.$use-shadow` flag enabled. - (CSS) Looked-up color variables support: `Separator`. +- (CSS) Added border radius/shadow to popup menu for `ComboBox` (and all `ComboBox`-based) controls. ### Bugfixes -- (CSS) Added border radius/shadow to popup menu for `ComboBox` (and `ComboBox`-based) controls. +- (Base) Fixed incorrect `Slider` progress track length calculation. - (CSS) Fixed `Popover` arrow background color. +- (CSS) Fixed `ListView` with `.bordered` class displays borders on empty cells. ## [1.2.0] - 2023-02-11 diff --git a/base/src/main/java/atlantafx/base/controls/RingProgressIndicatorSkin.java b/base/src/main/java/atlantafx/base/controls/RingProgressIndicatorSkin.java index 34f3843..255d914 100755 --- a/base/src/main/java/atlantafx/base/controls/RingProgressIndicatorSkin.java +++ b/base/src/main/java/atlantafx/base/controls/RingProgressIndicatorSkin.java @@ -249,8 +249,9 @@ public class RingProgressIndicatorSkin extends SkinBase { private static final List> STYLEABLES; static { - final List> styleables = - new ArrayList<>(SkinBase.getClassCssMetaData()); + final List> styleables = new ArrayList<>( + SkinBase.getClassCssMetaData() + ); styleables.add(INDETERMINATE_ANIMATION_TIME); STYLEABLES = Collections.unmodifiableList(styleables); } diff --git a/base/src/main/java/atlantafx/base/controls/ToggleSwitch.java b/base/src/main/java/atlantafx/base/controls/ToggleSwitch.java index 457a046..939e047 100755 --- a/base/src/main/java/atlantafx/base/controls/ToggleSwitch.java +++ b/base/src/main/java/atlantafx/base/controls/ToggleSwitch.java @@ -29,10 +29,21 @@ package atlantafx.base.controls; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanPropertyBase; +import javafx.beans.property.ObjectProperty; +import javafx.beans.value.WritableValue; +import javafx.css.CssMetaData; import javafx.css.PseudoClass; +import javafx.css.Styleable; +import javafx.css.StyleableObjectProperty; +import javafx.css.StyleableProperty; +import javafx.css.converter.EnumConverter; import javafx.event.ActionEvent; +import javafx.geometry.HorizontalDirection; import javafx.scene.control.Labeled; import javafx.scene.control.Skin; @@ -41,6 +52,7 @@ public class ToggleSwitch extends Labeled { protected static final String DEFAULT_STYLE_CLASS = "toggle-switch"; protected static final PseudoClass PSEUDO_CLASS_SELECTED = PseudoClass.getPseudoClass("selected"); + protected static final PseudoClass PSEUDO_CLASS_RIGHT = PseudoClass.getPseudoClass("right"); /** * Creates a toggle switch with empty string for its label. @@ -67,36 +79,22 @@ public class ToggleSwitch extends Labeled { // Properties // /////////////////////////////////////////////////////////////////////////// - /* - * Indicates whether this switch is selected. - */ private BooleanProperty selected; - /** - * Sets the selected value. - */ public final void setSelected(boolean value) { selectedProperty().set(value); } - /** - * Returns whether this Toggle Switch is selected. - */ public final boolean isSelected() { return selected != null && selected.get(); } /** - * Returns the selected property. + * Returns whether this Toggle Switch is selected. */ public final BooleanProperty selectedProperty() { if (selected == null) { selected = new BooleanPropertyBase() { - @Override - protected void invalidated() { - final boolean v = get(); - pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, v); - } @Override public Object getBean() { @@ -107,12 +105,67 @@ public class ToggleSwitch extends Labeled { public String getName() { return "selected"; } + + @Override + protected void invalidated() { + final boolean v = get(); + pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, v); + } }; } return selected; } + // ~ + + private ObjectProperty labelPosition; + + public final void setLabelPosition(HorizontalDirection pos) { + labelPositionProperty().setValue(pos); + } + + /** + * Returns whether this Toggle Switch is selected. + */ + public final HorizontalDirection getLabelPosition() { + return labelPosition == null ? HorizontalDirection.LEFT : labelPosition.getValue(); + } + + /** + * Specifies the side where {@link #textProperty()} values should be placed. + * Default is {@link HorizontalDirection#LEFT}. + */ + public final ObjectProperty labelPositionProperty() { + if (labelPosition == null) { + labelPosition = new StyleableObjectProperty<>(HorizontalDirection.LEFT) { + + @Override + public Object getBean() { + return ToggleSwitch.this; + } + + @Override + public String getName() { + return "labelPosition"; + } + + @Override + protected void invalidated() { + final HorizontalDirection v = get(); + pseudoClassStateChanged(ToggleSwitch.PSEUDO_CLASS_RIGHT, v == HorizontalDirection.RIGHT); + } + + @Override + public CssMetaData getCssMetaData() { + return StyleableProperties.LABEL_POSITION; + } + }; + } + + return labelPosition; + } + /////////////////////////////////////////////////////////////////////////// // Methods // /////////////////////////////////////////////////////////////////////////// @@ -135,4 +188,43 @@ public class ToggleSwitch extends Labeled { protected Skin createDefaultSkin() { return new ToggleSwitchSkin(this); } + + /** + * {@inheritDoc} + */ + @Override + public List> getControlCssMetaData() { + return StyleableProperties.STYLEABLES; + } + + /////////////////////////////////////////////////////////////////////////// + // Styleable Properties // + /////////////////////////////////////////////////////////////////////////// + + private static class StyleableProperties { + + private static final CssMetaData LABEL_POSITION = new CssMetaData<>( + "-fx-label-position", new EnumConverter<>(HorizontalDirection.class), HorizontalDirection.LEFT + ) { + + @Override + public boolean isSettable(ToggleSwitch c) { + return c.labelPositionProperty() == null || !c.labelPositionProperty().isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(ToggleSwitch c) { + var val = (WritableValue) c.labelPositionProperty(); + return (StyleableProperty) val; + } + }; + + private static final List> STYLEABLES; + + static { + final List> styleables = new ArrayList<>(Labeled.getClassCssMetaData()); + styleables.add(LABEL_POSITION); + STYLEABLES = Collections.unmodifiableList(styleables); + } + } } diff --git a/base/src/main/java/atlantafx/base/controls/ToggleSwitchSkin.java b/base/src/main/java/atlantafx/base/controls/ToggleSwitchSkin.java index fc07539..4572881 100755 --- a/base/src/main/java/atlantafx/base/controls/ToggleSwitchSkin.java +++ b/base/src/main/java/atlantafx/base/controls/ToggleSwitchSkin.java @@ -41,6 +41,7 @@ import javafx.css.Styleable; import javafx.css.StyleableDoubleProperty; import javafx.css.StyleableProperty; import javafx.css.converter.SizeConverter; +import javafx.geometry.HorizontalDirection; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.SkinBase; @@ -49,6 +50,8 @@ import javafx.util.Duration; public class ToggleSwitchSkin extends SkinBase { + protected static final Duration DEFAULT_ANIMATION_TIME = Duration.millis(200); + protected final StackPane thumb; protected final StackPane thumbArea; protected final Label label; @@ -59,20 +62,24 @@ public class ToggleSwitchSkin extends SkinBase { super(control); thumb = new StackPane(); + thumb.getStyleClass().setAll("thumb"); + thumbArea = new StackPane(); + thumbArea.getStyleClass().setAll("thumb-area"); + label = new Label(); + labelContainer = new StackPane(); labelContainer.getStyleClass().add("label-container"); - transition = new TranslateTransition(Duration.millis(getThumbMoveAnimationTime()), thumb); + + transition = new TranslateTransition(DEFAULT_ANIMATION_TIME, thumb); transition.setFromX(0.0); label.textProperty().bind(control.textProperty()); - getChildren().addAll(labelContainer, thumbArea, thumb); - labelContainer.getChildren().addAll(label); StackPane.setAlignment(label, Pos.CENTER_LEFT); - thumb.getStyleClass().setAll("thumb"); - thumbArea.getStyleClass().setAll("thumb-area"); + labelContainer.getChildren().addAll(label); + getChildren().addAll(labelContainer, thumbArea, thumb); thumbArea.setOnMouseReleased(event -> mousePressedOnToggleSwitch(control)); thumb.setOnMouseReleased(event -> mousePressedOnToggleSwitch(control)); @@ -109,7 +116,7 @@ public class ToggleSwitchSkin extends SkinBase { private DoubleProperty thumbMoveAnimationTimeProperty() { if (thumbMoveAnimationTime == null) { - thumbMoveAnimationTime = new StyleableDoubleProperty(200) { + thumbMoveAnimationTime = new StyleableDoubleProperty(DEFAULT_ANIMATION_TIME.toMillis()) { @Override public Object getBean() { @@ -121,22 +128,28 @@ public class ToggleSwitchSkin extends SkinBase { return "thumbMoveAnimationTime"; } + @Override + protected void invalidated() { + // update duration value + transition.setDuration(Duration.millis(getValue())); + } + @Override public CssMetaData getCssMetaData() { - return THUMB_MOVE_ANIMATION_TIME; + return StyleableProperties.THUMB_MOVE_ANIMATION_TIME; } }; } return thumbMoveAnimationTime; } - protected double getThumbMoveAnimationTime() { - return thumbMoveAnimationTime == null ? 200 : thumbMoveAnimationTime.get(); - } - + /** + * {@inheritDoc} + */ @Override protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) { - ToggleSwitch toggleSwitch = getSkinnable(); + ToggleSwitch c = getSkinnable(); + double thumbWidth = snapSizeX(thumb.prefWidth(-1)); double thumbHeight = snapSizeX(thumb.prefHeight(-1)); thumb.resize(thumbWidth, thumbHeight); @@ -144,14 +157,18 @@ public class ToggleSwitchSkin extends SkinBase { double thumbAreaWidth = snapSizeX(thumbArea.prefWidth(-1)); double thumbAreaHeight = snapSizeX(thumbArea.prefHeight(-1)); double thumbAreaY = snapPositionX(contentY + (contentHeight / 2) - (thumbAreaHeight / 2)); - double labelContainerWidth = label.getText() != null && !label.getText().isEmpty() - ? contentWidth - thumbAreaWidth : 0; + + double labelWidth = label.getText() != null && !label.getText().isEmpty() ? contentWidth - thumbAreaWidth : 0; + double labelX = c.getLabelPosition() == HorizontalDirection.RIGHT ? thumbAreaWidth : 0; + + double thumbAreaX = c.getLabelPosition() == HorizontalDirection.RIGHT ? 0 : labelWidth; thumbArea.resize(thumbAreaWidth, thumbAreaHeight); - thumbArea.setLayoutX(labelContainerWidth); + thumbArea.setLayoutX(thumbAreaX); thumbArea.setLayoutY(thumbAreaY); - labelContainer.resize(labelContainerWidth, thumbAreaHeight); + labelContainer.resize(labelWidth, thumbAreaHeight); + labelContainer.setLayoutX(labelX); labelContainer.setLayoutY(thumbAreaY); // layout the thumb on the "unselected" position @@ -169,84 +186,104 @@ public class ToggleSwitchSkin extends SkinBase { transition.playFrom(currentTime); } else { // if the transition is not running, simply apply the translateX value - thumb.setTranslateX(toggleSwitch.isSelected() ? thumbTarget : 0.0); + thumb.setTranslateX(c.isSelected() ? thumbTarget : 0.0); } } + /** + * {@inheritDoc} + */ @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { return leftInset + label.prefWidth(-1) + thumbArea.prefWidth(-1) + rightInset; } + /** + * {@inheritDoc} + */ @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { return topInset + Math.max(thumb.prefHeight(-1), label.prefHeight(-1)) + bottomInset; } + /** + * {@inheritDoc} + */ @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { return leftInset + label.prefWidth(-1) + 1 + thumbArea.prefWidth(-1) + rightInset; } + /** + * {@inheritDoc} + */ @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { return computeMinHeight(width, topInset, rightInset, bottomInset, leftInset); } + /** + * {@inheritDoc} + */ @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { return getSkinnable().prefWidth(height); } + /** + * {@inheritDoc} + */ @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { return getSkinnable().prefHeight(width); } - private static final CssMetaData THUMB_MOVE_ANIMATION_TIME = - new CssMetaData<>("-fx-thumb-move-animation-time", SizeConverter.getInstance(), 200) { - - @Override - public boolean isSettable(ToggleSwitch toggleSwitch) { - final ToggleSwitchSkin skin = (ToggleSwitchSkin) toggleSwitch.getSkin(); - return skin.thumbMoveAnimationTime == null || skin.thumbMoveAnimationTime.isBound(); - } - - @Override - @SuppressWarnings("RedundantCast") - public StyleableProperty getStyleableProperty(ToggleSwitch toggleSwitch) { - final ToggleSwitchSkin skin = (ToggleSwitchSkin) toggleSwitch.getSkin(); - return (StyleableProperty) (WritableValue) skin.thumbMoveAnimationTimeProperty(); - } - }; - - private static final List> STYLEABLES; - - static { - final List> styleables = new ArrayList<>(SkinBase.getClassCssMetaData()); - styleables.add(THUMB_MOVE_ANIMATION_TIME); - STYLEABLES = Collections.unmodifiableList(styleables); - } - - /** - * Returns the CssMetaData associated with this class, which may include the - * CssMetaData of its super classes. - */ - public static List> getClassCssMetaData() { - return STYLEABLES; - } - /** * {@inheritDoc} */ @Override public List> getCssMetaData() { - return getClassCssMetaData(); + return ToggleSwitchSkin.StyleableProperties.STYLEABLES; + } + + /////////////////////////////////////////////////////////////////////////// + // Styleable Properties // + /////////////////////////////////////////////////////////////////////////// + + static class StyleableProperties { + + private static final CssMetaData THUMB_MOVE_ANIMATION_TIME = new CssMetaData<>( + "-fx-thumb-move-animation-time", SizeConverter.getInstance(), DEFAULT_ANIMATION_TIME.toMillis() + ) { + + @Override + public boolean isSettable(ToggleSwitch toggleSwitch) { + final var skin = (ToggleSwitchSkin) toggleSwitch.getSkin(); + return skin.thumbMoveAnimationTime == null || !skin.thumbMoveAnimationTime.isBound(); + } + + @Override + @SuppressWarnings("RedundantCast") + public StyleableProperty getStyleableProperty(ToggleSwitch toggleSwitch) { + final var skin = (ToggleSwitchSkin) toggleSwitch.getSkin(); + return (StyleableProperty) (WritableValue) skin.thumbMoveAnimationTimeProperty(); + } + }; + + private static final List> STYLEABLES; + + static { + final List> styleables = new ArrayList<>( + SkinBase.getClassCssMetaData() + ); + styleables.add(THUMB_MOVE_ANIMATION_TIME); + STYLEABLES = Collections.unmodifiableList(styleables); + } } } diff --git a/sampler/src/main/java/atlantafx/sampler/page/components/ToggleSwitchPage.java b/sampler/src/main/java/atlantafx/sampler/page/components/ToggleSwitchPage.java index 8ba6c03..561f553 100644 --- a/sampler/src/main/java/atlantafx/sampler/page/components/ToggleSwitchPage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/components/ToggleSwitchPage.java @@ -6,7 +6,9 @@ import atlantafx.base.controls.ToggleSwitch; import atlantafx.sampler.page.AbstractPage; import atlantafx.sampler.page.Page; import atlantafx.sampler.page.SampleBlock; +import javafx.geometry.HorizontalDirection; import javafx.scene.layout.FlowPane; +import javafx.scene.layout.VBox; public class ToggleSwitchPage extends AbstractPage { @@ -26,9 +28,19 @@ public class ToggleSwitchPage extends AbstractPage { } private SampleBlock basicSample() { - var toggle = new ToggleSwitch(); - toggle.selectedProperty().addListener((obs, old, val) -> toggle.setText(val ? "Disable" : "Enable")); - toggle.setSelected(true); - return new SampleBlock("Basic", toggle); + var leftToggle = new ToggleSwitch("Enable"); + leftToggle.selectedProperty().addListener( + (obs, old, val) -> leftToggle.setText(val ? "Enabled" : "Disabled") + ); + leftToggle.setSelected(true); + + var rightToggle = new ToggleSwitch("Disable"); + rightToggle.selectedProperty().addListener( + (obs, old, val) -> rightToggle.setText(val ? "Enabled" : "Disabled") + ); + rightToggle.setLabelPosition(HorizontalDirection.RIGHT); + rightToggle.setSelected(false); + + return new SampleBlock("Basic", new VBox(SampleBlock.BLOCK_VGAP, leftToggle, rightToggle)); } } diff --git a/styles/src/components/_toggle-switch.scss b/styles/src/components/_toggle-switch.scss index 5aa2089..25ee268 100644 --- a/styles/src/components/_toggle-switch.scss +++ b/styles/src/components/_toggle-switch.scss @@ -62,6 +62,14 @@ $thumb-area-padding: 0.85em 1.4em 0.85em 1.4em !default; } } + &:right { + >.label-container { + >.label { + -fx-padding: cfg.$checkbox-label-padding 0 cfg.$checkbox-label-padding cfg.$graphic-gap; + } + } + } + &:disabled { -fx-opacity: cfg.$opacity-disabled; }