diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java b/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java index 60187da4e94..7edfd0c2727 100644 --- a/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXToggleButton.java @@ -20,354 +20,373 @@ package com.jfoenix.controls; import com.jfoenix.skins.JFXToggleButtonSkin; -import javafx.css.*; +import javafx.css.CssMetaData; +import javafx.css.SimpleStyleableBooleanProperty; +import javafx.css.SimpleStyleableDoubleProperty; +import javafx.css.Styleable; +import javafx.css.StyleableBooleanProperty; +import javafx.css.StyleableDoubleProperty; +import javafx.css.StyleableProperty; import javafx.css.converter.BooleanConverter; -import javafx.css.converter.PaintConverter; +import javafx.css.converter.SizeConverter; +import javafx.geometry.Pos; +import javafx.scene.AccessibleAttribute; +import javafx.scene.AccessibleRole; import javafx.scene.control.Skin; import javafx.scene.control.ToggleButton; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; import org.jackhuang.hmcl.ui.animation.AnimationUtils; +import org.jetbrains.annotations.NotNullByDefault; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; -/** - * JFXToggleButton is the material design implementation of a toggle button. - * important CSS Selectors: - *

- * .jfx-toggle-button{ - * -fx-toggle-color: color-value; - * -fx-untoggle-color: color-value; - * -fx-toggle-line-color: color-value; - * -fx-untoggle-line-color: color-value; - * } - *

- * To change the rippler color when toggled: - *

- * .jfx-toggle-button .jfx-rippler{ - * -fx-rippler-fill: color-value; - * } - *

- * .jfx-toggle-button:selected .jfx-rippler{ - * -fx-rippler-fill: color-value; - * } - * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ +/// A Material Design 3 switch exposed through the historical JFoenix toggle button type. +/// +/// The class keeps the `JFXToggleButton` name and selected-state contract used by HMCL, while its default skin +/// renders an M3 switch track, thumb, touch target, state layer, and selection animation. +@NotNullByDefault public class JFXToggleButton extends ToggleButton { + /// The legacy style class retained for existing HMCL stylesheets. + public static final String DEFAULT_STYLE_CLASS = "jfx-toggle-button"; + + /// The Material switch style class used by the replacement skin. + public static final String M3_STYLE_CLASS = "m3-switch"; + + /// The default minimum touch target height. + private static final double DEFAULT_TOUCH_TARGET_SIZE = 40.0; + + /// The default rounded switch track radius. + private static final double DEFAULT_TRACK_SHAPE = 999.0; + + /// The legacy knob radius that maps to the default M3 switch scale. + private static final double DEFAULT_SIZE = 8.0; + + /// The styleable minimum touch target height. + private @Nullable StyleableDoubleProperty touchTargetSize; + + /// The styleable switch track radius. + private @Nullable StyleableDoubleProperty trackShape; + + /// The legacy styleable knob radius used as the switch scale. + private @Nullable StyleableDoubleProperty size; + + /// Whether focus state layers should be hidden. + private @Nullable StyleableBooleanProperty disableVisualFocus; - /** - * {@inheritDoc} - */ + /// Whether switch animations should be disabled. + private @Nullable StyleableBooleanProperty disableAnimation; + + /// Creates an empty switch. public JFXToggleButton() { initialize(); } - /** - * {@inheritDoc} - */ - @Override - protected Skin createDefaultSkin() { - return new JFXToggleButtonSkin(this); + /// Creates a switch with text. + public JFXToggleButton(String text) { + super(text); + initialize(); } - private void initialize() { - this.getStyleClass().add(DEFAULT_STYLE_CLASS); - // it's up for the user to add this behavior -// toggleColor.addListener((o, oldVal, newVal) -> { -// // update line color in case not set by the user -// if(newVal instanceof Color) -// toggleLineColor.set(((Color)newVal).desaturate().desaturate().brighter()); -// }); + /// Returns the preferred touch target size. + public final double getTouchTargetSize() { + return touchTargetSize == null ? DEFAULT_TOUCH_TARGET_SIZE : touchTargetSize.get(); } - /*************************************************************************** - * * - * styleable Properties * - * * - **************************************************************************/ - - /** - * Initialize the style class to 'jfx-toggle-button'. - *

- * This is the selector class from which CSS can be used to style - * this control. - */ - private static final String DEFAULT_STYLE_CLASS = "jfx-toggle-button"; - - /** - * default color used when the button is toggled - */ - private final StyleableObjectProperty toggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.TOGGLE_COLOR, - JFXToggleButton.this, - "toggleColor", - Color.valueOf( - "#009688")); - - public Paint getToggleColor() { - return toggleColor == null ? Color.valueOf("#009688") : toggleColor.get(); + /// Sets the preferred touch target size. + public final void setTouchTargetSize(double touchTargetSize) { + touchTargetSizeProperty().set(nonNegative(touchTargetSize, "touchTargetSize")); } - public StyleableObjectProperty toggleColorProperty() { - return this.toggleColor; + /// Returns the preferred touch target size property. + public final StyleableDoubleProperty touchTargetSizeProperty() { + if (touchTargetSize == null) { + touchTargetSize = new SimpleStyleableDoubleProperty( + StyleableProperties.TOUCH_TARGET_SIZE, + this, + "touchTargetSize", + DEFAULT_TOUCH_TARGET_SIZE + ) { + /// Applies updated layout metrics when the token changes. + @Override + protected void invalidated() { + set(nonNegative(get(), "touchTargetSize")); + updateMetrics(); + } + }; + } + return touchTargetSize; } - public void setToggleColor(Paint color) { - this.toggleColor.set(color); + /// Returns the switch track shape radius. + public final double getTrackShape() { + return trackShape == null ? DEFAULT_TRACK_SHAPE : trackShape.get(); } - /** - * default color used when the button is not toggled - */ - private StyleableObjectProperty untoggleColor = new SimpleStyleableObjectProperty<>(StyleableProperties.UNTOGGLE_COLOR, - JFXToggleButton.this, - "unToggleColor", - Color.valueOf( - "#FAFAFA")); - - public Paint getUnToggleColor() { - return untoggleColor == null ? Color.valueOf("#FAFAFA") : untoggleColor.get(); + /// Sets the switch track shape radius. + public final void setTrackShape(double trackShape) { + trackShapeProperty().set(nonNegative(trackShape, "trackShape")); } - public StyleableObjectProperty unToggleColorProperty() { - return this.untoggleColor; + /// Returns the switch track shape radius property. + public final StyleableDoubleProperty trackShapeProperty() { + if (trackShape == null) { + trackShape = new SimpleStyleableDoubleProperty( + StyleableProperties.TRACK_SHAPE, + this, + "trackShape", + DEFAULT_TRACK_SHAPE + ) { + /// Validates updated track shape values. + @Override + protected void invalidated() { + set(nonNegative(get(), "trackShape")); + } + }; + } + return trackShape; } - public void setUnToggleColor(Paint color) { - this.untoggleColor.set(color); + /// Returns the legacy knob radius used to scale the switch. + public double getSize() { + return size == null ? DEFAULT_SIZE : size.get(); } - /** - * default line color used when the button is toggled - */ - private final StyleableObjectProperty toggleLineColor = new SimpleStyleableObjectProperty<>( - StyleableProperties.TOGGLE_LINE_COLOR, - JFXToggleButton.this, - "toggleLineColor", - Color.valueOf("#77C2BB")); - - public Paint getToggleLineColor() { - return toggleLineColor == null ? Color.valueOf("#77C2BB") : toggleLineColor.get(); + /// Sets the legacy knob radius used to scale the switch. + public void setSize(double size) { + sizeProperty().set(nonNegative(size, "size")); } - public StyleableObjectProperty toggleLineColorProperty() { - return this.toggleLineColor; + /// Returns the legacy knob radius property used to scale the switch. + public StyleableDoubleProperty sizeProperty() { + if (size == null) { + size = new SimpleStyleableDoubleProperty( + StyleableProperties.SIZE, + this, + "size", + DEFAULT_SIZE + ) { + /// Applies updated layout metrics when the legacy size changes. + @Override + protected void invalidated() { + set(nonNegative(get(), "size")); + updateMetrics(); + } + }; + } + return size; } - public void setToggleLineColor(Paint color) { - this.toggleLineColor.set(color); + /// Returns the visual focus suppression property. + public final StyleableBooleanProperty disableVisualFocusProperty() { + if (disableVisualFocus == null) { + disableVisualFocus = new SimpleStyleableBooleanProperty( + StyleableProperties.DISABLE_VISUAL_FOCUS, + this, + "disableVisualFocus", + false + ); + } + return disableVisualFocus; } - /** - * default line color used when the button is not toggled - */ - private final StyleableObjectProperty untoggleLineColor = new SimpleStyleableObjectProperty<>( - StyleableProperties.UNTOGGLE_LINE_COLOR, - JFXToggleButton.this, - "unToggleLineColor", - Color.valueOf("#999999")); - - public Paint getUnToggleLineColor() { - return untoggleLineColor == null ? Color.valueOf("#999999") : untoggleLineColor.get(); + /// Returns whether focus state layers are hidden. + public final Boolean isDisableVisualFocus() { + return disableVisualFocus != null && disableVisualFocus.get(); } - public StyleableObjectProperty unToggleLineColorProperty() { - return this.untoggleLineColor; + /// Sets whether focus state layers should be hidden. + public final void setDisableVisualFocus(Boolean disabled) { + disableVisualFocusProperty().set(Objects.requireNonNull(disabled, "disabled")); } - public void setUnToggleLineColor(Paint color) { - this.untoggleLineColor.set(color); + /// Returns the animation suppression property. + public final StyleableBooleanProperty disableAnimationProperty() { + if (disableAnimation == null) { + disableAnimation = new SimpleStyleableBooleanProperty( + StyleableProperties.DISABLE_ANIMATION, + this, + "disableAnimation", + !AnimationUtils.isAnimationEnabled() + ); + } + return disableAnimation; } - /** - * Default size of the toggle button. - */ - private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty( - StyleableProperties.SIZE, - JFXToggleButton.this, - "size", - 10.0); - - public double getSize() { - return size.get(); + /// Returns whether switch animations are disabled. + public final Boolean isDisableAnimation() { + return disableAnimation != null ? disableAnimation.get() : !AnimationUtils.isAnimationEnabled(); } - public StyleableDoubleProperty sizeProperty() { - return this.size; + /// Sets whether switch animations should be disabled. + public final void setDisableAnimation(Boolean disabled) { + disableAnimationProperty().set(Objects.requireNonNull(disabled, "disabled")); } - public void setSize(double size) { - this.size.set(size); + /// Creates the default Material Design 3 switch skin. + @Override + protected Skin createDefaultSkin() { + return new JFXToggleButtonSkin(this); } - /** - * Disable the visual indicator for focus - */ - private final StyleableBooleanProperty disableVisualFocus = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_VISUAL_FOCUS, - JFXToggleButton.this, - "disableVisualFocus", - false); - - public final StyleableBooleanProperty disableVisualFocusProperty() { - return this.disableVisualFocus; + /// Returns the CSS metadata for this control class. + public static List> getClassCssMetaData() { + return StyleableProperties.STYLEABLES; } - public final Boolean isDisableVisualFocus() { - return disableVisualFocus != null && this.disableVisualFocusProperty().get(); + /// Returns the CSS metadata for this control. + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); } - public final void setDisableVisualFocus(final Boolean disabled) { - this.disableVisualFocusProperty().set(disabled); + /// Returns accessibility attributes for switch selection state. + @Override + public @Nullable Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { + Objects.requireNonNull(attribute, "attribute"); + return switch (attribute) { + case SELECTED -> isSelected(); + case TOGGLE_STATE -> isSelected() + ? AccessibleAttribute.ToggleState.CHECKED + : AccessibleAttribute.ToggleState.UNCHECKED; + default -> super.queryAccessibleAttribute(attribute, parameters); + }; } + /// Adds base style classes and switch defaults. + private void initialize() { + addStyleClass(DEFAULT_STYLE_CLASS); + addStyleClass(M3_STYLE_CLASS); + setAccessibleRole(AccessibleRole.CHECK_BOX); + setAlignment(Pos.CENTER_LEFT); + setFocusTraversable(true); + setMnemonicParsing(true); + updateMetrics(); + } - /** - * disable animation on button action - */ - private final StyleableBooleanProperty disableAnimation = new SimpleStyleableBooleanProperty(StyleableProperties.DISABLE_ANIMATION, - JFXToggleButton.this, - "disableAnimation", - !AnimationUtils.isAnimationEnabled()); - - public final StyleableBooleanProperty disableAnimationProperty() { - return this.disableAnimation; + /// Adds a style class when it is not already present. + private void addStyleClass(String styleClass) { + List styleClasses = getStyleClass(); + if (!styleClasses.contains(styleClass)) { + styleClasses.add(styleClass); + } } - public final Boolean isDisableAnimation() { - return disableAnimation != null && this.disableAnimationProperty().get(); + /// Applies size-related component tokens to JavaFX layout properties. + private void updateMetrics() { + double scaledTrackHeight = 32.0 * getSize() / DEFAULT_SIZE; + double size = Math.max(getTouchTargetSize(), scaledTrackHeight); + setMinHeight(size); + setPrefHeight(size); } - public final void setDisableAnimation(final Boolean disabled) { - this.disableAnimationProperty().set(disabled); + /// Validates that a size token is not negative. + private static double nonNegative(double value, String name) { + if (value < 0.0) { + throw new IllegalArgumentException(name + " must not be negative"); + } + return value; } + /// CSS metadata for switch component tokens. + @NotNullByDefault private static final class StyleableProperties { - private static final CssMetaData TOGGLE_COLOR = - new CssMetaData<>("-jfx-toggle-color", - PaintConverter.getInstance(), Color.valueOf("#009688")) { - @Override - public boolean isSettable(JFXToggleButton control) { - return control.toggleColor == null || !control.toggleColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXToggleButton control) { - return control.toggleColorProperty(); - } - }; - - private static final CssMetaData UNTOGGLE_COLOR = - new CssMetaData<>("-jfx-untoggle-color", - PaintConverter.getInstance(), Color.valueOf("#FAFAFA")) { + /// CSS metadata for the touch target size token. + private static final CssMetaData TOUCH_TARGET_SIZE = + new CssMetaData<>("-m3-touch-target-size", SizeConverter.getInstance(), DEFAULT_TOUCH_TARGET_SIZE) { + /// Returns whether this property can be set by CSS. @Override public boolean isSettable(JFXToggleButton control) { - return control.untoggleColor == null || !control.untoggleColor.isBound(); + return control.touchTargetSize == null || !control.touchTargetSize.isBound(); } + /// Returns the styleable property for a control. @Override - public StyleableProperty getStyleableProperty(JFXToggleButton control) { - return control.unToggleColorProperty(); - } - }; - - private static final CssMetaData TOGGLE_LINE_COLOR = - new CssMetaData<>("-jfx-toggle-line-color", - PaintConverter.getInstance(), Color.valueOf("#77C2BB")) { - @Override - public boolean isSettable(JFXToggleButton control) { - return control.toggleLineColor == null || !control.toggleLineColor.isBound(); - } - - @Override - public StyleableProperty getStyleableProperty(JFXToggleButton control) { - return control.toggleLineColorProperty(); + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.touchTargetSizeProperty(); } }; - private static final CssMetaData UNTOGGLE_LINE_COLOR = - new CssMetaData<>("-jfx-untoggle-line-color", - PaintConverter.getInstance(), Color.valueOf("#999999")) { + /// CSS metadata for the switch track shape token. + private static final CssMetaData TRACK_SHAPE = + new CssMetaData<>("-m3-track-shape", SizeConverter.getInstance(), DEFAULT_TRACK_SHAPE) { + /// Returns whether this property can be set by CSS. @Override public boolean isSettable(JFXToggleButton control) { - return control.untoggleLineColor == null || !control.untoggleLineColor.isBound(); + return control.trackShape == null || !control.trackShape.isBound(); } + /// Returns the styleable property for a control. @Override - public StyleableProperty getStyleableProperty(JFXToggleButton control) { - return control.unToggleLineColorProperty(); + public StyleableProperty getStyleableProperty(JFXToggleButton control) { + return control.trackShapeProperty(); } }; + /// CSS metadata for the legacy switch size token. private static final CssMetaData SIZE = - new CssMetaData<>("-jfx-size", - StyleConverter.getSizeConverter(), 10.0) { + new CssMetaData<>("-jfx-size", SizeConverter.getInstance(), DEFAULT_SIZE) { + /// Returns whether this property can be set by CSS. @Override public boolean isSettable(JFXToggleButton control) { - return !control.size.isBound(); + return control.size == null || !control.size.isBound(); } + /// Returns the styleable property for a control. @Override public StyleableProperty getStyleableProperty(JFXToggleButton control) { return control.sizeProperty(); } }; + + /// CSS metadata for the visual focus suppression flag. private static final CssMetaData DISABLE_VISUAL_FOCUS = - new CssMetaData<>("-jfx-disable-visual-focus", - BooleanConverter.getInstance(), false) { + new CssMetaData<>("-jfx-disable-visual-focus", BooleanConverter.getInstance(), false) { + /// Returns whether this property can be set by CSS. @Override public boolean isSettable(JFXToggleButton control) { return control.disableVisualFocus == null || !control.disableVisualFocus.isBound(); } + /// Returns the styleable property for a control. @Override - public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) { + public StyleableProperty getStyleableProperty(JFXToggleButton control) { return control.disableVisualFocusProperty(); } }; + /// CSS metadata for the animation suppression flag. private static final CssMetaData DISABLE_ANIMATION = - new CssMetaData<>("-jfx-disable-animation", - BooleanConverter.getInstance(), false) { + new CssMetaData<>("-jfx-disable-animation", BooleanConverter.getInstance(), false) { + /// Returns whether this property can be set by CSS. @Override public boolean isSettable(JFXToggleButton control) { return control.disableAnimation == null || !control.disableAnimation.isBound(); } + /// Returns the styleable property for a control. @Override - public StyleableBooleanProperty getStyleableProperty(JFXToggleButton control) { + public StyleableProperty getStyleableProperty(JFXToggleButton control) { return control.disableAnimationProperty(); } }; - private static final List> CHILD_STYLEABLES; + /// The complete immutable CSS metadata list. + private static final List> STYLEABLES; static { - final List> styleables = - new ArrayList<>(ToggleButton.getClassCssMetaData()); - Collections.addAll(styleables, + List> styleables = new ArrayList<>(ToggleButton.getClassCssMetaData()); + Collections.addAll( + styleables, + TOUCH_TARGET_SIZE, + TRACK_SHAPE, SIZE, - TOGGLE_COLOR, - UNTOGGLE_COLOR, - TOGGLE_LINE_COLOR, - UNTOGGLE_LINE_COLOR, DISABLE_VISUAL_FOCUS, DISABLE_ANIMATION ); - CHILD_STYLEABLES = Collections.unmodifiableList(styleables); + STYLEABLES = Collections.unmodifiableList(styleables); } } - - @Override - public List> getControlCssMetaData() { - return getClassCssMetaData(); - } - - public static List> getClassCssMetaData() { - return StyleableProperties.CHILD_STYLEABLES; - } - } diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java index 0548204d7cb..95beb51e97b 100644 --- a/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXToggleButtonSkin.java @@ -19,168 +19,587 @@ package com.jfoenix.skins; -import com.jfoenix.controls.JFXRippler; -import com.jfoenix.controls.JFXRippler.RipplerMask; -import com.jfoenix.controls.JFXRippler.RipplerPos; import com.jfoenix.controls.JFXToggleButton; -import com.jfoenix.effects.JFXDepthManager; -import com.jfoenix.transitions.JFXAnimationTimer; -import com.jfoenix.transitions.JFXKeyFrame; -import com.jfoenix.transitions.JFXKeyValue; import javafx.animation.Interpolator; -import javafx.geometry.Insets; -import javafx.scene.Cursor; -import javafx.scene.control.skin.ToggleButtonSkin; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.InvalidationListener; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.value.ChangeListener; +import javafx.event.EventHandler; +import javafx.geometry.Point2D; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.SkinBase; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Line; -import javafx.scene.shape.StrokeLineCap; import javafx.util.Duration; +import org.jetbrains.annotations.NotNullByDefault; -/** - *

Material Design ToggleButton Skin

- * - * @author Shadi Shaheen - * @version 1.0 - * @since 2016-03-09 - */ -public class JFXToggleButtonSkin extends ToggleButtonSkin { - - - private Runnable releaseManualRippler = null; - - private JFXAnimationTimer timer; - private final Circle circle; - private final Line line; - - public JFXToggleButtonSkin(JFXToggleButton toggleButton) { - super(toggleButton); - - double circleRadius = toggleButton.getSize(); - - line = new Line(); - line.setStroke(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor()); - line.setStartX(0); - line.setStartY(0); - line.setEndX(circleRadius * 2 + 2); - line.setEndY(0); - line.setStrokeWidth(circleRadius * 1.5); - line.setStrokeLineCap(StrokeLineCap.ROUND); - line.setSmooth(true); - - circle = new Circle(); - circle.setFill(getSkinnable().isSelected() ? toggleButton.getToggleColor() : toggleButton.getUnToggleColor()); - circle.setCenterX(-circleRadius); - circle.setCenterY(0); - circle.setRadius(circleRadius); - circle.setSmooth(true); - JFXDepthManager.setDepth(circle, 1); - - StackPane circlePane = new StackPane(); - circlePane.getChildren().add(circle); - circlePane.setPadding(new Insets(circleRadius * 1.5)); - - JFXRippler rippler = new JFXRippler(circlePane, RipplerMask.CIRCLE, RipplerPos.BACK); - rippler.setRipplerFill(getSkinnable().isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor()); - rippler.setTranslateX(computeTranslation(circleRadius, line)); - - final StackPane main = new StackPane(); - main.getChildren().setAll(line, rippler); - main.setCursor(Cursor.HAND); - - // show focus traversal effect - getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> { - if (newVal) { - releaseManualRippler = rippler.createManualRipple(); - } else if (releaseManualRippler != null) { - releaseManualRippler.run(); - } - }); - toggleButton.focusedProperty().addListener((o, oldVal, newVal) -> { - if (!toggleButton.isDisableVisualFocus()) { - if (newVal) { - if (!getSkinnable().isPressed()) { - rippler.setOverlayVisible(true); - } - } else { - rippler.setOverlayVisible(false); - } - } - }); - toggleButton.pressedProperty().addListener(observable -> rippler.setOverlayVisible(false)); - - // add change listener to selected property - getSkinnable().selectedProperty().addListener(observable -> { - rippler.setRipplerFill(toggleButton.isSelected() ? toggleButton.getToggleLineColor() : toggleButton.getUnToggleLineColor()); - if (!toggleButton.isDisableAnimation()) { - timer.reverseAndContinue(); - } else { - rippler.setTranslateX(computeTranslation(circleRadius, line)); - } - }); - - getSkinnable().setGraphic(main); - - timer = new JFXAnimationTimer( - new JFXKeyFrame(Duration.millis(100), - JFXKeyValue.builder() - .setTarget(rippler.translateXProperty()) - .setEndValueSupplier(() -> computeTranslation(circleRadius, line)) - .setInterpolator(Interpolator.EASE_BOTH) - .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation()) - .build(), - - JFXKeyValue.builder() - .setTarget(line.strokeProperty()) - .setEndValueSupplier(() -> getSkinnable().isSelected() ? - ((JFXToggleButton) getSkinnable()).getToggleLineColor() - : ((JFXToggleButton) getSkinnable()).getUnToggleLineColor()) - .setInterpolator(Interpolator.EASE_BOTH) - .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation()) - .build(), - - JFXKeyValue.builder() - .setTarget(circle.fillProperty()) - .setEndValueSupplier(() -> getSkinnable().isSelected() ? - ((JFXToggleButton) getSkinnable()).getToggleColor() - : ((JFXToggleButton) getSkinnable()).getUnToggleColor()) - .setInterpolator(Interpolator.EASE_BOTH) - .setAnimateCondition(() -> !((JFXToggleButton) getSkinnable()).isDisableAnimation()) - .build() - ) - ); - timer.setCacheNodes(circle, line); - - registerChangeListener(toggleButton.toggleColorProperty(), observableValue -> { - if (getSkinnable().isSelected()) { - circle.setFill(((JFXToggleButton) getSkinnable()).getToggleColor()); - } - }); - registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> { - if (!getSkinnable().isSelected()) { - circle.setFill(((JFXToggleButton) getSkinnable()).getUnToggleColor()); - } - }); - registerChangeListener(toggleButton.toggleLineColorProperty(), observableValue -> { - if (getSkinnable().isSelected()) { - line.setStroke(((JFXToggleButton) getSkinnable()).getToggleLineColor()); - } - }); - registerChangeListener(toggleButton.unToggleColorProperty(), observableValue -> { - if (!getSkinnable().isSelected()) { - line.setStroke(((JFXToggleButton) getSkinnable()).getUnToggleLineColor()); - } - }); - } +/// The Material Design 3 switch skin used by [JFXToggleButton]. +@NotNullByDefault +public class JFXToggleButtonSkin extends SkinBase { + /// The unscaled switch track width. + private static final double TRACK_WIDTH = 52.0; + + /// The unscaled switch track height. + private static final double TRACK_HEIGHT = 32.0; + + /// The unscaled minimum circular state layer size around the thumb. + private static final double STATE_LAYER_SIZE = 40.0; + + /// The unscaled off-state thumb center within the track. + private static final double OFF_THUMB_CENTER_X = 16.0; + + /// The unscaled on-state thumb center within the track. + private static final double ON_THUMB_CENTER_X = 36.0; + + /// The unscaled off-state thumb size. + private static final double OFF_THUMB_SIZE = 16.0; + + /// The unscaled on-state thumb size. + private static final double ON_THUMB_SIZE = 24.0; + + /// The legacy size value that represents the default M3 switch scale. + private static final double DEFAULT_SIZE = 8.0; + + /// The animation duration for thumb movement. + private static final Duration SELECTION_DURATION = Duration.millis(150.0); + + /// The animation duration for state layer opacity changes. + private static final Duration STATE_LAYER_DURATION = Duration.millis(100.0); + + /// The animation duration for ripple expansion. + private static final Duration RIPPLE_EXPANSION_DURATION = Duration.millis(250.0); + + /// The animation duration for ripple fade-out. + private static final Duration RIPPLE_FADE_DURATION = Duration.millis(150.0); + + /// The root layout container. + private final HBox container = new HBox(); + + /// The switch touch target slot. + private final StackPane indicatorSlot = new StackPane(); + + /// The visual switch track. + private final StackPane box = new StackPane(); + + /// The visual switch thumb. + private final StackPane thumb = new StackPane(); + + /// The persistent interaction state layer. + private final Region stateLayer = new Region(); + + /// The animated press ripple. + private final Region ripple = new Region(); + + /// The label that mirrors the skinnable control's labeled content. + private final Label label = new Label(); + + /// The animated thumb position from off to on. + private final DoubleProperty thumbPosition = new SimpleDoubleProperty(this, "thumbPosition"); + + /// The thumb position animation. + private final Timeline selectionAnimation = new Timeline(); + + /// The state layer opacity animation. + private final Timeline stateLayerAnimation = new Timeline(); + + /// The ripple animation. + private final Timeline rippleAnimation = new Timeline(); + + /// Requests layout after thumb position changes. + private final InvalidationListener thumbPositionListener = observable -> getSkinnable().requestLayout(); - private double computeTranslation(double circleRadius, Line line) { - return (getSkinnable().isSelected() ? 1 : -1) * ((line.getLayoutBounds().getWidth() / 2) - circleRadius + 2); + /// Applies size token changes to the switch layout. + private final InvalidationListener metricsInvalidation = observable -> updateMetrics(); + + /// Applies track shape token changes to the switch track. + private final InvalidationListener trackShapeInvalidation = observable -> updateTrackStyle(); + + /// Animates the thumb after selection changes. + private final ChangeListener selectedListener = + (observable, oldValue, newValue) -> animateThumbPosition(newValue); + + /// Updates state layer opacity when an interaction state changes. + private final ChangeListener interactionStateListener = + (observable, oldValue, newValue) -> updateStateLayerOpacity(); + + /// Handles primary mouse presses. + private final EventHandler mousePressedHandler = this::handleMousePressed; + + /// Handles primary mouse releases. + private final EventHandler mouseReleasedHandler = this::handleMouseReleased; + + /// Handles pointer entry while a mouse press is active. + private final EventHandler mouseEnteredHandler = this::handleMouseEntered; + + /// Handles pointer exit while a mouse press is active. + private final EventHandler mouseExitedHandler = this::handleMouseExited; + + /// Handles keyboard activation presses. + private final EventHandler keyPressedHandler = this::handleKeyPressed; + + /// Handles keyboard activation releases. + private final EventHandler keyReleasedHandler = this::handleKeyReleased; + + /// Whether the current interaction was started by a primary mouse press. + private boolean mousePressed; + + /// Whether the space key currently owns the armed state. + private boolean spaceKeyPressed; + + /// Creates a switch skin. + public JFXToggleButtonSkin(JFXToggleButton control) { + super(control); + configureNodes(control); + bindLabel(control); + installListeners(control); + installInteractionHandlers(control); + thumbPosition.set(control.isSelected() ? 1.0 : 0.0); + thumbPosition.addListener(thumbPositionListener); + updateMetrics(); + updateStateLayerOpacity(); } + /// Removes listeners and stops animations before the skin is disposed. @Override public void dispose() { + JFXToggleButton control = getSkinnable(); + resetInteractionState(); + selectionAnimation.stop(); + stateLayerAnimation.stop(); + rippleAnimation.stop(); + thumbPosition.removeListener(thumbPositionListener); + uninstallListeners(control); + uninstallInteractionHandlers(control); + unbindLabel(); super.dispose(); - timer.dispose(); - timer = null; + } + + /// Computes the minimum width from the internal container. + @Override + protected double computeMinWidth( + double height, + double topInset, + double rightInset, + double bottomInset, + double leftInset + ) { + return leftInset + container.minWidth(height) + rightInset; + } + + /// Computes the minimum height from the internal container. + @Override + protected double computeMinHeight( + double width, + double topInset, + double rightInset, + double bottomInset, + double leftInset + ) { + return topInset + container.minHeight(width) + bottomInset; + } + + /// Computes the preferred width from the internal container. + @Override + protected double computePrefWidth( + double height, + double topInset, + double rightInset, + double bottomInset, + double leftInset + ) { + return leftInset + container.prefWidth(height) + rightInset; + } + + /// Computes the preferred height from the internal container. + @Override + protected double computePrefHeight( + double width, + double topInset, + double rightInset, + double bottomInset, + double leftInset + ) { + return topInset + container.prefHeight(width) + bottomInset; + } + + /// Lays out the internal container and switch thumb. + @Override + protected void layoutChildren(double x, double y, double width, double height) { + container.resizeRelocate(x, y, width, height); + layoutThumb(); + } + + /// Configures static node classes and hierarchy. + private void configureNodes(JFXToggleButton control) { + container.getStyleClass().add("m3-selection-container"); + indicatorSlot.getStyleClass().add("m3-selection-indicator"); + box.getStyleClass().addAll("box", "m3-switch-track"); + thumb.getStyleClass().addAll("thumb", "m3-switch-thumb"); + stateLayer.getStyleClass().add("m3-state-layer"); + ripple.getStyleClass().add("m3-ripple"); + label.getStyleClass().add("m3-selection-label"); + + container.setAlignment(Pos.CENTER_LEFT); + indicatorSlot.setAlignment(Pos.CENTER); + stateLayer.setManaged(false); + ripple.setManaged(false); + thumb.setManaged(false); + stateLayer.setMouseTransparent(true); + ripple.setMouseTransparent(true); + stateLayer.setOpacity(0.0); + ripple.setOpacity(0.0); + + indicatorSlot.getChildren().addAll(box, stateLayer, ripple, thumb); + container.getChildren().addAll(indicatorSlot, label); + getChildren().add(container); + + } + + /// Installs property listeners for layout, state, and interaction updates. + private void installListeners(JFXToggleButton control) { + control.sizeProperty().addListener(metricsInvalidation); + control.touchTargetSizeProperty().addListener(metricsInvalidation); + control.trackShapeProperty().addListener(trackShapeInvalidation); + control.selectedProperty().addListener(selectedListener); + control.hoverProperty().addListener(interactionStateListener); + control.focusedProperty().addListener(interactionStateListener); + control.armedProperty().addListener(interactionStateListener); + control.pressedProperty().addListener(interactionStateListener); + control.disabledProperty().addListener(interactionStateListener); + } + + /// Uninstalls property listeners installed by this skin. + private void uninstallListeners(JFXToggleButton control) { + control.sizeProperty().removeListener(metricsInvalidation); + control.touchTargetSizeProperty().removeListener(metricsInvalidation); + control.trackShapeProperty().removeListener(trackShapeInvalidation); + control.selectedProperty().removeListener(selectedListener); + control.hoverProperty().removeListener(interactionStateListener); + control.focusedProperty().removeListener(interactionStateListener); + control.armedProperty().removeListener(interactionStateListener); + control.pressedProperty().removeListener(interactionStateListener); + control.disabledProperty().removeListener(interactionStateListener); + } + + /// Installs mouse and keyboard behavior handlers. + private void installInteractionHandlers(JFXToggleButton control) { + control.addEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedHandler); + control.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedHandler); + control.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredHandler); + control.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler); + control.addEventHandler(KeyEvent.KEY_PRESSED, keyPressedHandler); + control.addEventHandler(KeyEvent.KEY_RELEASED, keyReleasedHandler); + } + + /// Uninstalls mouse and keyboard behavior handlers. + private void uninstallInteractionHandlers(JFXToggleButton control) { + control.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedHandler); + control.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedHandler); + control.removeEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredHandler); + control.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler); + control.removeEventHandler(KeyEvent.KEY_PRESSED, keyPressedHandler); + control.removeEventHandler(KeyEvent.KEY_RELEASED, keyReleasedHandler); + } + + /// Binds label content and presentation properties to the skinnable control. + private void bindLabel(JFXToggleButton control) { + label.textProperty().bind(control.textProperty()); + label.graphicProperty().bind(control.graphicProperty()); + label.textFillProperty().bind(control.textFillProperty()); + label.fontProperty().bind(control.fontProperty()); + label.contentDisplayProperty().bind(control.contentDisplayProperty()); + label.graphicTextGapProperty().bind(control.graphicTextGapProperty()); + label.alignmentProperty().bind(control.alignmentProperty()); + label.textAlignmentProperty().bind(control.textAlignmentProperty()); + label.textOverrunProperty().bind(control.textOverrunProperty()); + label.ellipsisStringProperty().bind(control.ellipsisStringProperty()); + label.wrapTextProperty().bind(control.wrapTextProperty()); + label.underlineProperty().bind(control.underlineProperty()); + label.mnemonicParsingProperty().bind(control.mnemonicParsingProperty()); + } + + /// Unbinds mirrored label properties from the skinnable control. + private void unbindLabel() { + label.textProperty().unbind(); + label.graphicProperty().unbind(); + label.textFillProperty().unbind(); + label.fontProperty().unbind(); + label.contentDisplayProperty().unbind(); + label.graphicTextGapProperty().unbind(); + label.alignmentProperty().unbind(); + label.textAlignmentProperty().unbind(); + label.textOverrunProperty().unbind(); + label.ellipsisStringProperty().unbind(); + label.wrapTextProperty().unbind(); + label.underlineProperty().unbind(); + label.mnemonicParsingProperty().unbind(); + } + + /// Applies size-related control tokens to the skin nodes. + private void updateMetrics() { + double scale = scale(); + double trackWidth = TRACK_WIDTH * scale; + double trackHeight = TRACK_HEIGHT * scale; + double touchTargetHeight = Math.max(getSkinnable().getTouchTargetSize(), trackHeight); + setFixedSize(indicatorSlot, trackWidth, touchTargetHeight); + setFixedSize(box, trackWidth, trackHeight); + updateTrackStyle(); + getSkinnable().requestLayout(); + } + + /// Applies the switch track shape token to the visual track. + private void updateTrackStyle() { + String shape = formatPixels(getSkinnable().getTrackShape()); + box.setStyle("-fx-background-radius: " + shape + "; -fx-border-radius: " + shape + ";"); + } + + /// Animates the thumb to the selected or unselected position. + private void animateThumbPosition(boolean selected) { + double target = selected ? 1.0 : 0.0; + selectionAnimation.stop(); + if (getSkinnable().isDisableAnimation()) { + thumbPosition.set(target); + return; + } + + selectionAnimation.getKeyFrames().setAll(new KeyFrame( + SELECTION_DURATION, + new KeyValue(thumbPosition, target, Interpolator.EASE_BOTH) + )); + selectionAnimation.playFromStart(); + } + + /// Lays out the thumb and its interaction layers from the animated position value. + private void layoutThumb() { + double scale = scale(); + double position = thumbPosition.get(); + double thumbSize = scaled(OFF_THUMB_SIZE + (ON_THUMB_SIZE - OFF_THUMB_SIZE) * position, scale); + double thumbCenterX = scaled(OFF_THUMB_CENTER_X + (ON_THUMB_CENTER_X - OFF_THUMB_CENTER_X) * position, scale); + double thumbX = thumbCenterX - thumbSize / 2.0; + double touchTargetHeight = Math.max(getSkinnable().getTouchTargetSize(), scaled(TRACK_HEIGHT, scale)); + double thumbY = (touchTargetHeight - thumbSize) / 2.0; + double stateLayerSize = Math.max(scaled(STATE_LAYER_SIZE, scale), getSkinnable().getTouchTargetSize()); + double stateLayerX = thumbCenterX - stateLayerSize / 2.0; + double stateLayerY = (touchTargetHeight - stateLayerSize) / 2.0; + double radius = stateLayerSize / 2.0; + + layoutStateRegion(stateLayer, stateLayerX, stateLayerY, stateLayerSize, radius); + layoutStateRegion(ripple, stateLayerX, stateLayerY, stateLayerSize, radius); + thumb.resizeRelocate(thumbX, thumbY, thumbSize, thumbSize); + } + + /// Applies a circular size and radius to a state feedback region. + private void layoutStateRegion(Region region, double x, double y, double size, double radius) { + region.resizeRelocate(x, y, size, size); + region.setStyle("-fx-background-radius: " + formatPixels(radius) + ";"); + } + + /// Updates the persistent state layer opacity from current interaction state. + private void updateStateLayerOpacity() { + JFXToggleButton control = getSkinnable(); + double targetOpacity; + if (control.isDisabled()) { + targetOpacity = 0.0; + } else if (control.isPressed() || control.isArmed()) { + targetOpacity = 0.12; + } else if (control.isFocused() && !control.isDisableVisualFocus()) { + targetOpacity = 0.10; + } else if (control.isHover()) { + targetOpacity = 0.08; + } else { + targetOpacity = 0.0; + } + + animateOpacity(stateLayer, stateLayerAnimation, targetOpacity, STATE_LAYER_DURATION); + } + + /// Arms the control on primary mouse press. + private void handleMousePressed(MouseEvent event) { + JFXToggleButton control = getSkinnable(); + if (control.isDisabled() || event.getButton() != MouseButton.PRIMARY) { + return; + } + + mousePressed = true; + if (control.isFocusTraversable()) { + control.requestFocus(); + } + playRipple(event); + control.arm(); + event.consume(); + } + + /// Fires the control when a primary mouse press is released inside the control. + private void handleMouseReleased(MouseEvent event) { + JFXToggleButton control = getSkinnable(); + if (!mousePressed || event.getButton() != MouseButton.PRIMARY) { + return; + } + + boolean shouldFire = control.isArmed() && control.contains(event.getX(), event.getY()); + mousePressed = false; + releaseRipple(); + control.disarm(); + if (shouldFire) { + control.fire(); + } + event.consume(); + } + + /// Re-arms the control when a pressed pointer re-enters the control. + private void handleMouseEntered(MouseEvent event) { + JFXToggleButton control = getSkinnable(); + if (mousePressed && !control.isDisabled()) { + control.arm(); + event.consume(); + } + } + + /// Disarms the control when a pressed pointer exits the control. + private void handleMouseExited(MouseEvent event) { + JFXToggleButton control = getSkinnable(); + if (mousePressed && !control.isDisabled()) { + control.disarm(); + event.consume(); + } + } + + /// Handles keyboard activation for enter and space. + private void handleKeyPressed(KeyEvent event) { + JFXToggleButton control = getSkinnable(); + if (control.isDisabled()) { + return; + } + + if (event.getCode() == KeyCode.SPACE) { + if (!spaceKeyPressed) { + spaceKeyPressed = true; + playCenteredRipple(); + control.arm(); + } + event.consume(); + } else if (event.getCode() == KeyCode.ENTER) { + playCenteredRipple(); + releaseRipple(); + control.fire(); + event.consume(); + } + } + + /// Fires the control when a space key activation is released. + private void handleKeyReleased(KeyEvent event) { + JFXToggleButton control = getSkinnable(); + if (event.getCode() != KeyCode.SPACE || !spaceKeyPressed) { + return; + } + + boolean shouldFire = control.isArmed() && !control.isDisabled(); + spaceKeyPressed = false; + releaseRipple(); + control.disarm(); + if (shouldFire) { + control.fire(); + } + event.consume(); + } + + /// Clears armed state and transient feedback. + private void resetInteractionState() { + mousePressed = false; + spaceKeyPressed = false; + rippleAnimation.stop(); + ripple.setOpacity(0.0); + getSkinnable().disarm(); + } + + /// Plays the press ripple from a mouse event. + private void playRipple(MouseEvent event) { + Point2D point = indicatorSlot.sceneToLocal(event.getSceneX(), event.getSceneY()); + ripple.setTranslateX((point.getX() - (ripple.getLayoutX() + ripple.getWidth() / 2.0)) * 0.12); + ripple.setTranslateY((point.getY() - (ripple.getLayoutY() + ripple.getHeight() / 2.0)) * 0.12); + playRippleAnimation(); + } + + /// Plays the press ripple from the current thumb center. + private void playCenteredRipple() { + ripple.setTranslateX(0.0); + ripple.setTranslateY(0.0); + playRippleAnimation(); + } + + /// Starts the ripple expansion animation. + private void playRippleAnimation() { + rippleAnimation.stop(); + ripple.setScaleX(0.45); + ripple.setScaleY(0.45); + ripple.setOpacity(0.18); + if (getSkinnable().isDisableAnimation()) { + ripple.setScaleX(1.0); + ripple.setScaleY(1.0); + return; + } + + rippleAnimation.getKeyFrames().setAll(new KeyFrame( + RIPPLE_EXPANSION_DURATION, + new KeyValue(ripple.scaleXProperty(), 1.0, Interpolator.EASE_OUT), + new KeyValue(ripple.scaleYProperty(), 1.0, Interpolator.EASE_OUT) + )); + rippleAnimation.playFromStart(); + } + + /// Releases the active ripple and fades it out. + private void releaseRipple() { + rippleAnimation.stop(); + animateOpacity(ripple, rippleAnimation, 0.0, RIPPLE_FADE_DURATION); + } + + /// Animates a region opacity or applies the target immediately when animations are disabled. + private void animateOpacity(Region region, Timeline timeline, double targetOpacity, Duration duration) { + timeline.stop(); + if (getSkinnable().isDisableAnimation()) { + region.setOpacity(targetOpacity); + return; + } + + timeline.getKeyFrames().setAll(new KeyFrame( + duration, + new KeyValue(region.opacityProperty(), targetOpacity, Interpolator.EASE_BOTH) + )); + timeline.playFromStart(); + } + + /// Applies a fixed size to a region. + private static void setFixedSize(Region region, double width, double height) { + region.setMinSize(width, height); + region.setPrefSize(width, height); + region.setMaxSize(width, height); + } + + /// Returns the current legacy-size scale factor. + private double scale() { + return getSkinnable().getSize() / DEFAULT_SIZE; + } + + /// Scales a base pixel value. + private static double scaled(double value, double scale) { + return value * scale; + } + + /// Formats a CSS pixel value. + private static String formatPixels(double value) { + if (Math.rint(value) == value) { + return (long) value + "px"; + } + return value + "px"; } } diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 26a9bccadd0..c69bcb343af 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -1180,40 +1180,99 @@ .jfx-toggle-button:focused, .jfx-toggle-button:selected, .jfx-toggle-button:focused:selected { - -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT; + -m3-touch-target-size: 40px; + -m3-track-shape: 999px; + -jfx-size: 8px; + -fx-background-color: transparent; -fx-background-radius: 3px; - -fx-background-insets: 0px; + -fx-background-insets: 0; + -fx-text-fill: -monet-on-surface; + -fx-font-size: 14px; +} - -jfx-toggle-color: -monet-primary; - -jfx-toggle-line-color: -monet-secondary-container; - -jfx-untoggle-color: -monet-outline; - -jfx-untoggle-line-color: -monet-surface-container-highest; - -jfx-size: 10; +.jfx-toggle-button .m3-selection-container { + -fx-alignment: center-left; + -fx-spacing: 4px; } +.jfx-toggle-button .m3-selection-indicator { + -fx-alignment: center; +} -.jfx-toggle-button Line { - -fx-stroke: -jfx-untoggle-line-color; +.jfx-toggle-button .box, +.jfx-toggle-button:hover .box, +.jfx-toggle-button:focused .box, +.jfx-toggle-button:armed .box, +.jfx-toggle-button:pressed .box { + -fx-background-color: -monet-surface-container-highest; + -fx-background-radius: 999px; + -fx-border-color: -monet-outline; + -fx-border-insets: 0; + -fx-border-radius: 999px; + -fx-border-width: 2px; + -fx-padding: 4px; + -fx-alignment: center-left; } -.jfx-toggle-button:selected Line { - -fx-stroke: -jfx-toggle-line-color; +.jfx-toggle-button:selected .box, +.jfx-toggle-button:selected:hover .box, +.jfx-toggle-button:selected:focused .box, +.jfx-toggle-button:selected:armed .box, +.jfx-toggle-button:selected:pressed .box { + -fx-background-color: -monet-primary; + -fx-border-color: -monet-primary; } -.jfx-toggle-button Circle { - -fx-fill: -jfx-untoggle-color; +.jfx-toggle-button .thumb, +.jfx-toggle-button:hover .thumb, +.jfx-toggle-button:focused .thumb, +.jfx-toggle-button:armed .thumb, +.jfx-toggle-button:pressed .thumb { + -fx-background-color: -monet-outline; + -fx-background-insets: 0; + -fx-background-radius: 999px; } -.jfx-toggle-button:selected Circle { - -fx-fill: -jfx-toggle-color; +.jfx-toggle-button:selected .thumb, +.jfx-toggle-button:selected:hover .thumb, +.jfx-toggle-button:selected:focused .thumb, +.jfx-toggle-button:selected:armed .thumb, +.jfx-toggle-button:selected:pressed .thumb { + -fx-background-color: -monet-on-primary; } -.jfx-toggle-button .jfx-rippler { - -jfx-rippler-fill: -jfx-untoggle-line-color; +.jfx-toggle-button .m3-state-layer, +.jfx-toggle-button .m3-ripple { + -fx-background-color: -monet-primary; + -fx-background-insets: 0; +} + +.jfx-toggle-button:disabled .m3-selection-label { + -fx-opacity: 0.38; } -.jfx-toggle-button:selected .jfx-rippler { - -jfx-rippler-fill: -jfx-toggle-line-color; +.jfx-toggle-button:disabled .box, +.jfx-toggle-button:disabled:hover .box, +.jfx-toggle-button:disabled:focused .box, +.jfx-toggle-button:disabled:armed .box, +.jfx-toggle-button:disabled:pressed .box { + -fx-background-color: -monet-on-surface; + -fx-border-color: -monet-on-surface; + -fx-opacity: 0.12; +} + +.jfx-toggle-button:disabled .thumb, +.jfx-toggle-button:disabled:hover .thumb, +.jfx-toggle-button:disabled:focused .thumb, +.jfx-toggle-button:disabled:armed .thumb, +.jfx-toggle-button:disabled:pressed .thumb, +.jfx-toggle-button:selected:disabled .thumb, +.jfx-toggle-button:selected:disabled:hover .thumb, +.jfx-toggle-button:selected:disabled:focused .thumb, +.jfx-toggle-button:selected:disabled:armed .thumb, +.jfx-toggle-button:selected:disabled:pressed .thumb { + -fx-background-color: -monet-on-surface; + -fx-opacity: 0.38; }