com.jfoenix.skins.JFXSliderSkin Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.jfoenix.skins;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXSlider.IndicatorPosition;
import com.sun.javafx.scene.control.skin.SliderSkin;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.util.Duration;
/**
* Material Design Slider Skin
*
* rework of JFXSliderSkin by extending Java SliderSkin
* this solves padding and resizing issues
*
* @author Shadi Shaheen
* @version 1.0
* @since 2016-03-09
*/
public class JFXSliderSkin extends SliderSkin {
private final Pane mouseHandlerPane = new Pane();
private Paint thumbColor = Color.valueOf("#0F9D58"), trackColor = Color.valueOf("#CCCCCC");
private Text sliderValue;
private StackPane coloredTrack;
private StackPane thumb;
private StackPane track;
private StackPane animatedThumb;
private Timeline timeline;
private double indicatorRotation;
private double horizontalRotation;
private double shifting;
private boolean isValid = false;
public JFXSliderSkin(JFXSlider slider) {
super(slider);
track = (StackPane) getSkinnable().lookup(".track");
thumb = (StackPane) getSkinnable().lookup(".thumb");
track.setBackground(new Background(new BackgroundFill(trackColor, new CornerRadii(5), Insets.EMPTY)));
thumb.setBackground(new Background(new BackgroundFill(thumbColor, new CornerRadii(20), Insets.EMPTY)));
track.setPrefHeight(2);
track.setPrefWidth(2);
coloredTrack = new StackPane();
coloredTrack.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
BackgroundFill trackBackgroundFill = track.getBackground().getFills().get(0);
return new Background(new BackgroundFill(thumb.getBackground().getFills().get(0).getFill(),
trackBackgroundFill.getRadii(),
trackBackgroundFill.getInsets()));
}, track.backgroundProperty(), thumb.backgroundProperty()));
coloredTrack.setMouseTransparent(true);
sliderValue = new Text();
sliderValue.setStroke(Color.WHITE);
sliderValue.setFont(new Font(10));
sliderValue.getStyleClass().setAll("slider-value");
animatedThumb = new StackPane();
animatedThumb.getStyleClass().add("animated-thumb");
animatedThumb.getChildren().add(sliderValue);
animatedThumb.setMouseTransparent(true);
animatedThumb.setPrefSize(30, 30);
animatedThumb.setBackground(new Background(new BackgroundFill(thumbColor,
new CornerRadii(50, 50, 50, 0, true),
null)));
animatedThumb.setScaleX(0);
animatedThumb.setScaleY(0);
getChildren().add(getChildren().indexOf(thumb), coloredTrack);
getChildren().add(getChildren().indexOf(thumb), animatedThumb);
getChildren().add(0, mouseHandlerPane);
registerChangeListener(slider.valueFactoryProperty(), "VALUE_FACTORY");
initListeners();
}
@Override
protected void handleControlPropertyChanged(String p) {
super.handleControlPropertyChanged(p);
if ("VALUE_FACTORY".equals(p)) {
refreshSliderValueBinding();
}
}
private void refreshSliderValueBinding() {
sliderValue.textProperty().unbind();
if (((JFXSlider) getSkinnable()).getValueFactory() != null) {
sliderValue.textProperty()
.bind(((JFXSlider) getSkinnable()).getValueFactory().call((JFXSlider) getSkinnable()));
} else {
sliderValue.textProperty().bind(Bindings.createStringBinding(() -> {
if (getSkinnable().getLabelFormatter() != null) {
return getSkinnable().getLabelFormatter().toString(getSkinnable().getValue());
} else {
return Math.round(getSkinnable().getValue()) + "";
}
}, getSkinnable().valueProperty()));
}
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
super.layoutChildren(x, y, w, h);
if (!isValid) {
initializeVariables();
initAnimation(getSkinnable().getOrientation());
isValid = true;
}
double prefWidth = animatedThumb.prefWidth(-1);
animatedThumb.resize(prefWidth, animatedThumb.prefHeight(prefWidth));
boolean horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
double width, height, layoutX, layoutY;
if (horizontal) {
width = thumb.getLayoutX() - snappedLeftInset();
height = track.getHeight();
layoutX = track.getLayoutX();
layoutY = track.getLayoutY();
animatedThumb.setLayoutX(thumb.getLayoutX() + thumb.getWidth() / 2 - animatedThumb.getWidth() / 2);
} else {
height = track.getLayoutBounds().getMaxY() + track.getLayoutY() - thumb.getLayoutY() - snappedBottomInset();
width = track.getWidth();
layoutX = track.getLayoutX();
layoutY = thumb.getLayoutY();
animatedThumb.setLayoutY(thumb.getLayoutY() + thumb.getHeight() / 2 - animatedThumb.getHeight() / 2);
}
coloredTrack.resizeRelocate(layoutX, layoutY, width, height);
mouseHandlerPane.resizeRelocate(x, y, w, h);
}
private boolean internalChange = false;
private void initializeVariables() {
shifting = 30 + thumb.getWidth();
if (getSkinnable().getOrientation() != Orientation.HORIZONTAL) {
horizontalRotation = -90;
}
if (((JFXSlider) getSkinnable()).getIndicatorPosition() != IndicatorPosition.LEFT) {
indicatorRotation = 180;
shifting = -shifting;
}
final double rotationAngle = 45;
sliderValue.setRotate(rotationAngle + indicatorRotation + 3 * horizontalRotation);
animatedThumb.setRotate(-rotationAngle + indicatorRotation + horizontalRotation);
thumb.backgroundProperty().addListener((o, oldVal, newVal) -> {
if (animatedThumb.getBackground() != null) {
animatedThumb.setBackground(new Background(new BackgroundFill(newVal.getFills().get(0).getFill(),
animatedThumb.getBackground()
.getFills()
.get(0)
.getRadii(),
animatedThumb.getBackground()
.getFills()
.get(0)
.getInsets())));
} else {
animatedThumb.setBackground(new Background(new BackgroundFill(newVal.getFills().get(0).getFill(),
new CornerRadii(50, 50, 50, 0, true),
null)));
}
});
}
private void initListeners() {
// delegate slider mouse events to track node
mouseHandlerPane.setOnMousePressed(me -> {
if (!me.isConsumed()) {
me.consume();
track.fireEvent(me);
}
});
mouseHandlerPane.setOnMouseReleased(me -> {
if (!me.isConsumed()) {
me.consume();
track.fireEvent(me);
}
});
mouseHandlerPane.setOnMouseDragged(me -> {
if (!me.isConsumed()) {
me.consume();
track.fireEvent(me);
}
});
// animate value node
track.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
timeline.setRate(1);
timeline.play();
});
track.addEventHandler(MouseEvent.MOUSE_RELEASED, (event) -> {
timeline.setRate(-1);
timeline.play();
});
thumb.addEventHandler(MouseEvent.MOUSE_PRESSED, (event) -> {
timeline.setRate(1);
timeline.play();
});
thumb.addEventHandler(MouseEvent.MOUSE_RELEASED, (event) -> {
timeline.setRate(-1);
timeline.play();
});
track.backgroundProperty().addListener((o, oldVal, newVal) -> {
// prevent internal color change
if (!internalChange && newVal != null) {
trackColor = newVal.getFills().get(0).getFill();
}
});
thumb.backgroundProperty().addListener((o, oldVal, newVal) -> {
// prevent internal color change
if (!internalChange && newVal != null) {
thumbColor = newVal.getFills().get(0).getFill();
if (getSkinnable().getValue() == getSkinnable().getMin()) {
internalChange = true;
thumb.setBackground(new Background(new BackgroundFill(trackColor,
new CornerRadii(20),
Insets.EMPTY)));
internalChange = false;
}
}
});
refreshSliderValueBinding();
getSkinnable().valueProperty().addListener((o, oldVal, newVal) -> {
internalChange = true;
if (getSkinnable().getMin() == newVal.doubleValue()) {
thumb.setBackground(new Background(new BackgroundFill(trackColor, new CornerRadii(20), Insets.EMPTY)));
animatedThumb.pseudoClassStateChanged(PseudoClass.getPseudoClass("min"), true);
} else if (oldVal.doubleValue() == getSkinnable().getMin()) {
thumb.setBackground(new Background(new BackgroundFill(thumbColor, new CornerRadii(20), Insets.EMPTY)));
animatedThumb.pseudoClassStateChanged(PseudoClass.getPseudoClass("min"), false);
}
internalChange = false;
});
getSkinnable().orientationProperty().addListener((o, oldVal, newVal) -> initAnimation(newVal));
animatedThumb.layoutBoundsProperty()
.addListener((o, oldVal, newVal) -> initAnimation(getSkinnable().getOrientation()));
}
private void initAnimation(Orientation orientation) {
double thumbPos, thumbNewPos;
DoubleProperty layoutProperty;
if (orientation == Orientation.HORIZONTAL) {
if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) {
thumbPos = thumb.getLayoutY() - thumb.getHeight();
thumbNewPos = thumbPos - shifting;
} else {
thumbPos = thumb.getLayoutY() - animatedThumb.getHeight() / 2;
thumbNewPos = thumb.getLayoutY() - animatedThumb.getHeight() - thumb.getHeight();
}
layoutProperty = animatedThumb.translateYProperty();
} else {
if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) {
thumbPos = thumb.getLayoutX() - thumb.getWidth();
thumbNewPos = thumbPos - shifting;
} else {
thumbPos = thumb.getLayoutX() - animatedThumb.getWidth() / 2;
thumbNewPos = thumb.getLayoutX() - animatedThumb.getWidth() - thumb.getWidth();
}
layoutProperty = animatedThumb.translateXProperty();
}
timeline = new Timeline(
new KeyFrame(
Duration.ZERO,
new KeyValue(animatedThumb.scaleXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(animatedThumb.scaleYProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(layoutProperty, thumbPos, Interpolator.EASE_BOTH)),
new KeyFrame(
Duration.seconds(0.2),
new KeyValue(animatedThumb.scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(animatedThumb.scaleYProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(layoutProperty, thumbNewPos, Interpolator.EASE_BOTH)));
}
}