更新 JFXSlider (#5570)
This commit is contained in:
180
HMCL/src/main/java/com/jfoenix/controls/JFXSlider.java
Normal file
180
HMCL/src/main/java/com/jfoenix/controls/JFXSlider.java
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.controls;
|
||||
|
||||
import com.jfoenix.converters.IndicatorPositionConverter;
|
||||
import com.jfoenix.skins.JFXSliderSkin;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.css.*;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/// JFXSlider is the material design implementation of a slider.
|
||||
///
|
||||
/// @author Bashir Elias & Shadi Shaheen
|
||||
/// @version 1.0
|
||||
/// @since 2016-03-09
|
||||
public class JFXSlider extends Slider {
|
||||
|
||||
public JFXSlider() {
|
||||
super(0, 100, 50);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public JFXSlider(double min, double max, double value) {
|
||||
super(min, max, value);
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new JFXSliderSkin(this);
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
}
|
||||
|
||||
public enum IndicatorPosition {
|
||||
LEFT, RIGHT
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Properties *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
/// String binding factory for the slider value.
|
||||
/// Sets a custom string for the value text (by default, it shows the value rounded to the nearest whole number).
|
||||
///
|
||||
///
|
||||
/// For example, to have the value displayed as a percentage (assuming the slider has a range of (0, 100)):
|
||||
/// ```java
|
||||
/// JFXSlider mySlider = ...
|
||||
/// mySlider.setValueFactory(slider ->
|
||||
/// Bindings.createStringBinding(
|
||||
/// () -> ((int) slider.getValue()) + "%",
|
||||
/// slider.valueProperty()
|
||||
/// )
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// NOTE: might be replaced later with a call back to create the animated thumb node
|
||||
///
|
||||
/// @param callback a callback to create the string value binding
|
||||
private ObjectProperty<Callback<JFXSlider, StringBinding>> valueFactory;
|
||||
|
||||
public final ObjectProperty<Callback<JFXSlider, StringBinding>> valueFactoryProperty() {
|
||||
if (valueFactory == null) {
|
||||
valueFactory = new SimpleObjectProperty<>(this, "valueFactory");
|
||||
}
|
||||
return valueFactory;
|
||||
}
|
||||
|
||||
/// @return the current slider value factory
|
||||
public final Callback<JFXSlider, StringBinding> getValueFactory() {
|
||||
return valueFactory == null ? null : valueFactory.get();
|
||||
}
|
||||
|
||||
/// sets custom string binding for the slider text value
|
||||
///
|
||||
/// @param valueFactory a callback to create the string value binding
|
||||
public final void setValueFactory(final Callback<JFXSlider, StringBinding> valueFactory) {
|
||||
this.valueFactoryProperty().set(valueFactory);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Stylesheet Handling *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
/// Initialize the style class to 'jfx-slider'.
|
||||
///
|
||||
/// This is the selector class from which CSS can be used to style
|
||||
/// this control.
|
||||
private static final String DEFAULT_STYLE_CLASS = "jfx-slider";
|
||||
|
||||
/// indicates the position of the slider indicator, can be
|
||||
/// either LEFT or RIGHT
|
||||
private StyleableObjectProperty<IndicatorPosition> indicatorPosition;
|
||||
|
||||
public StyleableObjectProperty<IndicatorPosition> indicatorPositionProperty() {
|
||||
if (indicatorPosition == null) {
|
||||
indicatorPosition = new SimpleStyleableObjectProperty<>(
|
||||
StyleableProperties.INDICATOR_POSITION,
|
||||
JFXSlider.this,
|
||||
"indicatorPosition",
|
||||
IndicatorPosition.LEFT);
|
||||
}
|
||||
return this.indicatorPosition;
|
||||
}
|
||||
|
||||
public IndicatorPosition getIndicatorPosition() {
|
||||
return indicatorPosition == null ? IndicatorPosition.LEFT : indicatorPosition.get();
|
||||
}
|
||||
|
||||
public void setIndicatorPosition(IndicatorPosition pos) {
|
||||
indicatorPositionProperty().set(pos);
|
||||
}
|
||||
|
||||
private static final class StyleableProperties {
|
||||
private static final CssMetaData<JFXSlider, IndicatorPosition> INDICATOR_POSITION = new CssMetaData<>(
|
||||
"-jfx-indicator-position",
|
||||
IndicatorPositionConverter.getInstance(),
|
||||
IndicatorPosition.LEFT) {
|
||||
@Override
|
||||
public boolean isSettable(JFXSlider control) {
|
||||
return control.indicatorPosition == null || !control.indicatorPosition.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<IndicatorPosition> getStyleableProperty(JFXSlider control) {
|
||||
return control.indicatorPositionProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final List<CssMetaData<? extends Styleable, ?>> CHILD_STYLEABLES;
|
||||
|
||||
static {
|
||||
final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(
|
||||
Slider.getClassCssMetaData());
|
||||
Collections.addAll(styleables, INDICATOR_POSITION);
|
||||
CHILD_STYLEABLES = List.copyOf(styleables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
|
||||
return getClassCssMetaData();
|
||||
}
|
||||
|
||||
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||
return StyleableProperties.CHILD_STYLEABLES;
|
||||
}
|
||||
}
|
||||
291
HMCL/src/main/java/com/jfoenix/skins/JFXSliderSkin.java
Normal file
291
HMCL/src/main/java/com/jfoenix/skins/JFXSliderSkin.java
Normal file
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* 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 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.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.skin.SliderSkin;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
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 static final PseudoClass MIN_VALUE = PseudoClass.getPseudoClass("min");
|
||||
private static final PseudoClass MAX_VALUE = PseudoClass.getPseudoClass("max");
|
||||
|
||||
private final Pane mouseHandlerPane = new Pane();
|
||||
private final Text sliderValue;
|
||||
private final StackPane coloredTrack;
|
||||
private final StackPane thumb;
|
||||
private final StackPane track;
|
||||
private final StackPane animatedThumb;
|
||||
private NumberAxis tickLine;
|
||||
|
||||
private Timeline timeline;
|
||||
|
||||
private double indicatorRotation;
|
||||
private double horizontalRotation;
|
||||
private double shifting;
|
||||
|
||||
public JFXSliderSkin(JFXSlider slider) {
|
||||
super(slider);
|
||||
|
||||
track = (StackPane) getSkinnable().lookup(".track");
|
||||
thumb = (StackPane) getSkinnable().lookup(".thumb");
|
||||
tickLine = (NumberAxis) getSkinnable().lookup(".axis");
|
||||
if (tickLine != null) tickLine.setAnimated(false);
|
||||
|
||||
coloredTrack = new StackPane();
|
||||
coloredTrack.getStyleClass().add("colored-track");
|
||||
coloredTrack.setMouseTransparent(true);
|
||||
|
||||
sliderValue = new Text();
|
||||
sliderValue.getStyleClass().setAll("slider-value");
|
||||
|
||||
animatedThumb = new StackPane();
|
||||
animatedThumb.getStyleClass().add("animated-thumb");
|
||||
animatedThumb.getChildren().add(sliderValue);
|
||||
animatedThumb.setMouseTransparent(true);
|
||||
animatedThumb.setScaleX(0);
|
||||
animatedThumb.setScaleY(0);
|
||||
|
||||
thumb.layoutXProperty().addListener(x -> {
|
||||
if (slider.getOrientation() == Orientation.VERTICAL) initAnimation(Orientation.VERTICAL);
|
||||
});
|
||||
thumb.layoutYProperty().addListener(y -> {
|
||||
if (slider.getOrientation() == Orientation.HORIZONTAL) initAnimation(Orientation.HORIZONTAL);
|
||||
});
|
||||
|
||||
addJFXChildren();
|
||||
getChildren().addListener((ListChangeListener<Node>) c -> {
|
||||
while (c.next()) {
|
||||
if (c.wasAdded()) {
|
||||
c.getAddedSubList().forEach(added -> {
|
||||
if (added instanceof NumberAxis) {
|
||||
tickLine = (NumberAxis) added;
|
||||
tickLine.setAnimated(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
registerChangeListener(slider.showTickMarksProperty(), e -> addJFXChildren());
|
||||
registerChangeListener(slider.showTickLabelsProperty(), e -> addJFXChildren());
|
||||
registerChangeListener(slider.valueFactoryProperty(), obs -> refreshSliderValueBinding());
|
||||
|
||||
initListeners();
|
||||
}
|
||||
|
||||
private void addJFXChildren() {
|
||||
ObservableList<Node> children = getChildren();
|
||||
Slider slider = getSkinnable();
|
||||
if ((slider.isShowTickMarks() || slider.isShowTickLabels()) && tickLine != null && !children.contains(tickLine)) {
|
||||
children.add(0, tickLine);
|
||||
}
|
||||
if (children.contains(coloredTrack)) return;
|
||||
children.add(children.indexOf(thumb), coloredTrack);
|
||||
children.add(children.indexOf(thumb), animatedThumb);
|
||||
children.add(0, mouseHandlerPane);
|
||||
}
|
||||
|
||||
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 String.valueOf(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 (timeline == null) {
|
||||
initAnimation(getSkinnable().getOrientation());
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
private void initListeners() {
|
||||
// delegate slider mouse events to track node
|
||||
mouseHandlerPane.setOnMousePressed(this::delegateToTrack);
|
||||
mouseHandlerPane.setOnMouseReleased(this::delegateToTrack);
|
||||
mouseHandlerPane.setOnMouseDragged(this::delegateToTrack);
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
refreshSliderValueBinding();
|
||||
updateValueStyleClass();
|
||||
|
||||
getSkinnable().valueProperty().addListener(observable -> updateValueStyleClass());
|
||||
}
|
||||
|
||||
private void delegateToTrack(MouseEvent event) {
|
||||
if (!event.isConsumed()) {
|
||||
event.consume();
|
||||
track.fireEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateValueStyleClass() {
|
||||
getSkinnable().pseudoClassStateChanged(MIN_VALUE, getSkinnable().getMin() == getSkinnable().getValue());
|
||||
getSkinnable().pseudoClassStateChanged(MAX_VALUE, getSkinnable().getMax() == getSkinnable().getValue());
|
||||
}
|
||||
|
||||
private void initAnimation(Orientation orientation) {
|
||||
initializeVariables();
|
||||
|
||||
double thumbPos, thumbNewPos;
|
||||
DoubleProperty layoutProperty;
|
||||
|
||||
if (orientation == Orientation.HORIZONTAL) {
|
||||
if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) {
|
||||
thumbPos = thumb.getLayoutY() - thumb.getHeight();
|
||||
thumbNewPos = thumbPos - shifting;
|
||||
} else {
|
||||
double height = animatedThumb.prefHeight(animatedThumb.prefWidth(-1));
|
||||
thumbPos = thumb.getLayoutY() - height / 2;
|
||||
thumbNewPos = thumb.getLayoutY() - height - thumb.getHeight();
|
||||
}
|
||||
layoutProperty = animatedThumb.translateYProperty();
|
||||
} else {
|
||||
if (((JFXSlider) getSkinnable()).getIndicatorPosition() == IndicatorPosition.RIGHT) {
|
||||
thumbPos = thumb.getLayoutX() - thumb.getWidth();
|
||||
thumbNewPos = thumbPos - shifting;
|
||||
} else {
|
||||
double width = animatedThumb.prefWidth(-1);
|
||||
thumbPos = thumb.getLayoutX() - width / 2;
|
||||
thumbNewPos = thumb.getLayoutX() - width - thumb.getWidth();
|
||||
}
|
||||
layoutProperty = animatedThumb.translateXProperty();
|
||||
}
|
||||
|
||||
clearAnimation();
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
clearAnimation();
|
||||
}
|
||||
|
||||
private void clearAnimation() {
|
||||
if (timeline != null) {
|
||||
timeline.stop();
|
||||
timeline.getKeyFrames().clear();
|
||||
timeline = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -791,31 +791,39 @@
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.svg-slider .thumb {
|
||||
-fx-stroke: -monet-on-surface;
|
||||
-fx-fill: -monet-on-surface;
|
||||
.jfx-slider .track,
|
||||
.jfx-slider:vertical .track {
|
||||
-fx-background-color: -monet-secondary-container;
|
||||
-fx-background-radius: 5;
|
||||
-fx-background-insets: 0;
|
||||
-fx-pref-width: 2px;
|
||||
-fx-pref-height: 2px;
|
||||
}
|
||||
|
||||
.jfx-slider:disabled {
|
||||
-fx-opacity: 0.4;
|
||||
}
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* For the main demo sliders *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
.jfx-slider {
|
||||
-jfx-indicator-position: right;
|
||||
}
|
||||
|
||||
.jfx-slider .track {
|
||||
-fx-background-color: -monet-on-surface-variant-transparent-38;
|
||||
}
|
||||
|
||||
.jfx-slider .thumb {
|
||||
.jfx-slider .thumb,
|
||||
.jfx-slider:focused .thumb {
|
||||
-fx-background-color: -monet-primary;
|
||||
-fx-background-radius: 20;
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
.jfx-slider .colored-track {
|
||||
-fx-background-color: -monet-primary;
|
||||
-fx-background-radius: 5 0 0 5;
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
.jfx-slider .slider-value {
|
||||
-fx-stroke: -monet-inverse-on-surface;
|
||||
-fx-font-size: 10;
|
||||
}
|
||||
|
||||
.jfx-slider .animated-thumb {
|
||||
-fx-pref-width: 30px;
|
||||
-fx-pref-height: 30px;
|
||||
-fx-background-color: -monet-inverse-surface;
|
||||
-fx-background-radius: 50% 50% 50% 0%;
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
Reference in New Issue
Block a user