All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pixelduke.control.skin.FXToggleSwitchSkin Maven / Gradle / Ivy

/*
 * FXSkins,
 * Copyright (C) 2021 PixelDuke (Pedro Duque Vieira - www.pixelduke.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */

package com.pixelduke.control.skin;

import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.beans.InvalidationListener;
import javafx.beans.property.DoubleProperty;
import javafx.css.*;
import javafx.css.converter.EnumConverter;
import javafx.css.converter.SizeConverter;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import org.controlsfx.control.ToggleSwitch;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class FXToggleSwitchSkin extends SkinBase {
    private HBox mainContainer = new HBox();

    private final Label label = new Label();
    private final StackPane labelContainer = new StackPane();
    private final InnerToggleSwitch innerToggleSwitch;

    private InvalidationListener thumbDisplayChanged = observable -> this.updateThumbDisplay(getThumbDisplay());

    /**
     * How many milliseconds it should take for the thumb to go from
     * one edge to the other
     */
    private DoubleProperty thumbMoveAnimationTime = null;

    private StyleableObjectProperty thumbDisplay = new SimpleStyleableObjectProperty<>(THUMB_DISPLAY_META_DATA, ThumbDisplay.LEFT) {
        @Override
        protected void invalidated() {
            FXToggleSwitchSkin.this.getSkinnable().requestLayout();
        }
    };

    /**
     * Constructor for all ToggleSwitchSkin instances.
     *
     * @param control The ToggleSwitch for which this Skin should attach to.
     */
    public FXToggleSwitchSkin(ToggleSwitch control) {
        super(control);

        innerToggleSwitch = new InnerToggleSwitch(this);

        label.textProperty().bind(control.textProperty());

        mainContainer.getStyleClass().setAll("main-container");

        getChildren().setAll(mainContainer);

        labelContainer.getChildren().addAll(label);
        StackPane.setAlignment(label, Pos.CENTER_LEFT);
        labelContainer.getStyleClass().setAll("label-container");

        updateThumbDisplay(getThumbDisplay());
        thumbDisplay.addListener(thumbDisplayChanged);
    }

    private void updateThumbDisplay(ThumbDisplay thumbDisplay) {
        switch (thumbDisplay) {
            case LEFT:
                mainContainer.getChildren().setAll(innerToggleSwitch, labelContainer);
                break;
            case RIGHT:
                mainContainer.getChildren().setAll(labelContainer, innerToggleSwitch);
                break;
            case THUMB_ONLY:
                mainContainer.getChildren().setAll(innerToggleSwitch);
                break;
        }

    }

    @Override
    public void dispose() {
        thumbDisplay.removeListener(thumbDisplayChanged);

        super.dispose();
    }

    /***************************************************************************
     *                                                                         *
     *                            Properties                                   *
     *                                                                         *
     **************************************************************************/

    // --- thumb move animation time

    private DoubleProperty thumbMoveAnimationTimeProperty() {
        if (thumbMoveAnimationTime == null) {
            thumbMoveAnimationTime = new StyleableDoubleProperty(200) {

                @Override
                public Object getBean() {
                    return FXToggleSwitchSkin.this;
                }

                @Override
                public String getName() {
                    return "thumbMoveAnimationTime";
                }

                @Override
                public CssMetaData getCssMetaData() {
                    return THUMB_MOVE_ANIMATION_TIME_META_DATA;
                }
            };
        }
        return thumbMoveAnimationTime;
    }

    private double getThumbMoveAnimationTime() {
        return thumbMoveAnimationTime == null ? 200 : thumbMoveAnimationTime.get();
    }

    // --- thumb display

    private StyleableObjectProperty thumbDisplayProperty() { return thumbDisplay; }

    private ThumbDisplay getThumbDisplay() { return thumbDisplay.get(); }


    /***************************************************************************
     *                                                                         *
     *                            CSS Handling                                 *
     *                                                                         *
     **************************************************************************/

    private static final CssMetaData THUMB_MOVE_ANIMATION_TIME_META_DATA =
            new CssMetaData<>("-thumb-move-animation-time",
                    SizeConverter.getInstance(), 200) {

                @Override
                public boolean isSettable(ToggleSwitch toggleSwitch) {
                    final FXToggleSwitchSkin skin = (FXToggleSwitchSkin) toggleSwitch.getSkin();
                    return skin.thumbMoveAnimationTime == null ||
                            !skin.thumbMoveAnimationTime.isBound();
                }

                @Override
                public StyleableProperty getStyleableProperty(ToggleSwitch toggleSwitch) {
                    final FXToggleSwitchSkin skin = (FXToggleSwitchSkin) toggleSwitch.getSkin();
                    return (StyleableProperty) skin.thumbMoveAnimationTimeProperty();
                }
            };

    private static final CssMetaData THUMB_DISPLAY_META_DATA =
            new CssMetaData<>("-toggle-display", new EnumConverter<>(ThumbDisplay.class)) {

                @Override
                public boolean isSettable(ToggleSwitch toggleSwitch) {
                    final FXToggleSwitchSkin skin = (FXToggleSwitchSkin) toggleSwitch.getSkin();
                    return !skin.thumbDisplay.isBound();
                }

                @Override
                public StyleableProperty getStyleableProperty(ToggleSwitch toggleSwitch) {
                    final FXToggleSwitchSkin skin = (FXToggleSwitchSkin) toggleSwitch.getSkin();
                    return skin.thumbDisplayProperty();
                }
            };

    private static final List> STYLEABLES;

    static {
        final List> styleables =
                new ArrayList<>(SkinBase.getClassCssMetaData());
        styleables.add(THUMB_MOVE_ANIMATION_TIME_META_DATA);
        styleables.add(THUMB_DISPLAY_META_DATA);
        STYLEABLES = Collections.unmodifiableList(styleables);
    }

    /**
     * @return 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();
    }

    /****************************************************************************
     *                                                                          *
     * Supporting Classes / Enums                                               *
     *                                                                          *
     ***************************************************************************/

    private enum ThumbDisplay {
        LEFT, RIGHT, THUMB_ONLY
    }

    private static class InnerToggleSwitch extends Region {
        private boolean isFirstLayoutPass = true;
        private final FXToggleSwitchSkin toggleSwitchSkin;
        private final TranslateTransition transition;

        private final StackPane thumb = new StackPane();

        private InvalidationListener selectionChanged = observable -> this.selectedStateChanged();
        private EventHandler mouseReleased;

        public InnerToggleSwitch(FXToggleSwitchSkin toggleSwitchSkin) {
            transition = new TranslateTransition(Duration.millis(toggleSwitchSkin.getThumbMoveAnimationTime()), thumb);
            transition.setInterpolator(Interpolator.EASE_OUT);

            this.toggleSwitchSkin = toggleSwitchSkin;

            getStyleClass().setAll("thumb-area");
            thumb.getStyleClass().setAll("thumb");

            getChildren().add(thumb);

            thumb.setMouseTransparent(true);

            ToggleSwitch skinnable = toggleSwitchSkin.getSkinnable();

            mouseReleased = event -> onMouseReleased(skinnable);
            setOnMouseReleased(mouseReleased);

            initThumbTransformX();
            skinnable.selectedProperty().addListener(selectionChanged);
        }

        private void onMouseReleased(ToggleSwitch toggleSwitch) {
            toggleSwitch.setSelected(!toggleSwitch.isSelected());
        }

        private void initThumbTransformX() {
            if (!toggleSwitchSkin.getSkinnable().isSelected()) {
                thumb.setTranslateX(0);
            } else {
                thumb.setTranslateX(calculateSelectedToggleTranslateX());
            }
        }

        private void selectedStateChanged() {
            ToggleSwitch toggleSwitch = toggleSwitchSkin.getSkinnable();
            transition.stop();
            transition.setDuration(Duration.millis(toggleSwitchSkin.getThumbMoveAnimationTime()));

            double thumbTranslateSelectedX = calculateSelectedToggleTranslateX();

            if (!toggleSwitch.isSelected()) {  // selected to not selected
                transition.setFromX(thumbTranslateSelectedX);
                transition.setToX(0);
            } else {                           // not selected to selected
                transition.setFromX(0);
                transition.setToX(thumbTranslateSelectedX);
            }
            transition.setCycleCount(1);
            transition.play();
        }

        private double calculateSelectedToggleTranslateX() {
            double thumbAreaWidth = getWidth() - (snappedLeftInset() + snappedRightInset());
            double thumbWidth = snapSizeX(thumb.prefWidth(-1));
            return thumbAreaWidth - thumbWidth;
        }

        @Override
        protected void layoutChildren() {
            final double x = snappedLeftInset();
            final double y = snappedTopInset();
            final double height = getHeight() - (snappedTopInset() + snappedBottomInset());

            double thumbWidth = thumb.prefWidth(-1);
            double thumbHeight = thumb.prefHeight(-1);
            thumb.resize(snapSizeX(thumbWidth), snapSizeY(thumbHeight));

            thumb.setLayoutX(snapPositionX(x));
            thumb.setLayoutY(snapPositionY(y + height / 2d) - thumbHeight / 2);

            if (isFirstLayoutPass) {
                initThumbTransformX();
            }
        }
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy