276 lines
11 KiB
Java
276 lines
11 KiB
Java
package ctbrec.ui.controls.range;
|
|
|
|
import javafx.geometry.Orientation;
|
|
import javafx.geometry.Point2D;
|
|
import javafx.geometry.Side;
|
|
import javafx.scene.chart.Axis;
|
|
import javafx.scene.chart.NumberAxis;
|
|
import javafx.scene.chart.ValueAxis;
|
|
import javafx.scene.control.SkinBase;
|
|
import javafx.scene.layout.StackPane;
|
|
|
|
public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
|
|
|
|
private static final double TRACK_TO_TICK_GAP = 2;
|
|
private Axis<?> tickLine = null;
|
|
private StackPane track;
|
|
private ThumbRange thumbRange = new ThumbRange();
|
|
private RangeSliderBehavior<?> behavior;
|
|
private double thumbWidth;
|
|
private double thumbHeight;
|
|
private boolean showTickMarks;
|
|
private double trackStart;
|
|
private double trackLength;
|
|
private double lowThumbPos;
|
|
|
|
private double preDragPos; // used as a temp value for low and high thumbsRange
|
|
private Point2D preDragThumbPoint; // in skin coordinates
|
|
|
|
protected RangeSliderSkin(RangeSlider<?> control, RangeSliderBehavior<?> behavior) {
|
|
super(control);
|
|
this.behavior = behavior;
|
|
initTrack();
|
|
initThumbs(thumbRange);
|
|
registerChangeListener(control.getLow(), obsVal -> getSkinnable().requestLayout());
|
|
registerChangeListener(control.getHigh(), obsVal -> getSkinnable().requestLayout());
|
|
}
|
|
|
|
private void initThumbs(ThumbRange t) {
|
|
setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels());
|
|
|
|
getChildren().addAll(t.low, t.high, t.rangeBar);
|
|
|
|
t.low.setOnMousePressed(me -> {
|
|
preDragThumbPoint = t.low.localToParent(me.getX(), me.getY());
|
|
preDragPos = (getSkinnable().getLow().get().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero());
|
|
});
|
|
t.low.setOnMouseDragged(me -> {
|
|
Point2D cur = t.low.localToParent(me.getX(), me.getY());
|
|
double dragPos = (isHorizontal()) ? cur.getX() - preDragThumbPoint.getX() : -(cur.getY() - preDragThumbPoint.getY());
|
|
behavior.lowThumbDragged(preDragPos + dragPos / trackLength);
|
|
});
|
|
|
|
t.high.setOnMousePressed(me -> {
|
|
preDragThumbPoint = t.high.localToParent(me.getX(), me.getY());
|
|
preDragPos = (getSkinnable().getHigh().get().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero());
|
|
});
|
|
t.high.setOnMouseDragged(me -> {
|
|
boolean orientation = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
|
|
double trackLen = orientation ? track.getWidth() : track.getHeight();
|
|
Point2D cur = t.high.localToParent(me.getX(), me.getY());
|
|
double dragPos = getSkinnable().getOrientation() != Orientation.HORIZONTAL ? -(cur.getY() - preDragThumbPoint.getY()) : cur.getX() - preDragThumbPoint.getX();
|
|
behavior.highThumbDragged(preDragPos + dragPos / trackLen);
|
|
});
|
|
}
|
|
|
|
private boolean isHorizontal() {
|
|
return getSkinnable().getOrientation() == Orientation.HORIZONTAL;
|
|
}
|
|
|
|
private void initTrack() {
|
|
track = new StackPane();
|
|
track.getStyleClass().setAll("track");
|
|
|
|
getChildren().clear();
|
|
getChildren().add(track);
|
|
}
|
|
|
|
private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
|
|
showTickMarks = (ticksVisible || labelsVisible);
|
|
var rangeSlider = getSkinnable();
|
|
if (showTickMarks) {
|
|
if (tickLine == null) {
|
|
var range = rangeSlider.getRange();
|
|
tickLine = createAxis(range, ticksVisible, labelsVisible);
|
|
getChildren().addAll(tickLine);
|
|
} else {
|
|
tickLine.setTickLabelsVisible(labelsVisible);
|
|
tickLine.setTickMarkVisible(ticksVisible);
|
|
}
|
|
}
|
|
|
|
getSkinnable().requestLayout();
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private Axis<?> createAxis(Range<? extends Number> range, boolean ticksVisible, boolean labelsVisible) {
|
|
ValueAxis<Number> axis;
|
|
if (range instanceof DiscreteRange) {
|
|
axis = new LabeledNumberAxis((DiscreteRange<Number>) range);
|
|
} else {
|
|
axis = new NumberAxis();
|
|
}
|
|
|
|
axis.setUpperBound(range.getMaximum().doubleValue());
|
|
axis.setLowerBound(range.getMinimum().doubleValue());
|
|
axis.setMinorTickVisible(false);
|
|
axis.setMinorTickCount(0);
|
|
axis.setAutoRanging(false);
|
|
axis.setAnimated(false);
|
|
axis.setSide(isHorizontal() ? Side.BOTTOM : Side.RIGHT);
|
|
axis.setTickMarkVisible(ticksVisible);
|
|
axis.setTickLabelsVisible(labelsVisible);
|
|
return axis;
|
|
}
|
|
|
|
@Override
|
|
protected void layoutChildren(final double x, final double y, final double w, final double h) {
|
|
if (thumbRange != null) {
|
|
thumbWidth = thumbRange.low.prefWidth(-1);
|
|
thumbHeight = thumbRange.low.prefHeight(-1);
|
|
thumbRange.low.resize(thumbWidth, thumbHeight);
|
|
thumbRange.high.resize(thumbWidth, thumbHeight);
|
|
}
|
|
|
|
// we are assuming the is common radius's for all corners on the track
|
|
double trackRadius = track.getBackground() == null ? 0
|
|
: !track.getBackground().getFills().isEmpty() ? track.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius() : 0;
|
|
|
|
double tickLineHeight = (showTickMarks) ? tickLine.prefHeight(-1) : 0;
|
|
double trackHeight = 5;// track.prefHeight(-1);
|
|
double trackAreaHeight = Math.max(trackHeight, thumbHeight);
|
|
double totalHeightNeeded = trackAreaHeight + ((showTickMarks) ? TRACK_TO_TICK_GAP + tickLineHeight : 0);
|
|
double startY = y + ((h - totalHeightNeeded) / 2); // center slider in available height vertically
|
|
|
|
trackLength = w - thumbWidth;
|
|
trackStart = x + (thumbWidth / 2);
|
|
|
|
double trackTop = (int) (startY + ((trackAreaHeight - trackHeight) / 2));
|
|
lowThumbPos = (int) (startY + ((trackAreaHeight - thumbHeight) / 2));
|
|
|
|
// layout track
|
|
track.resizeRelocate(trackStart - trackRadius, trackTop, trackLength + trackRadius + trackRadius, trackHeight);
|
|
|
|
positionThumbs();
|
|
|
|
if (showTickMarks) {
|
|
tickLine.setLayoutX(trackStart);
|
|
tickLine.setLayoutY(trackTop + trackHeight + TRACK_TO_TICK_GAP);
|
|
tickLine.resize(trackLength, tickLineHeight);
|
|
tickLine.requestAxisLayout();
|
|
} else {
|
|
if (tickLine != null) {
|
|
tickLine.resize(0, 0);
|
|
tickLine.requestAxisLayout();
|
|
}
|
|
tickLine = null;
|
|
}
|
|
}
|
|
|
|
private void positionThumbs() {
|
|
RangeSlider<?> s = getSkinnable();
|
|
|
|
double lxl = trackStart + (trackLength * ((s.getLow().get().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D);
|
|
double lxh = trackStart + (trackLength * ((s.getHigh().get().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D);
|
|
double ly = lowThumbPos;
|
|
|
|
if (thumbRange != null) {
|
|
thumbRange.low.setLayoutX(lxl);
|
|
thumbRange.low.setLayoutY(ly);
|
|
|
|
thumbRange.high.setLayoutX(lxh);
|
|
thumbRange.high.setLayoutY(ly);
|
|
|
|
thumbRange.rangeBar.resizeRelocate(thumbRange.low.getLayoutX() + thumbRange.low.getWidth(), track.getLayoutY(),
|
|
thumbRange.high.getLayoutX() - thumbRange.low.getLayoutX() - thumbRange.low.getWidth(), track.getHeight());
|
|
}
|
|
}
|
|
|
|
private double minTrackLength() {
|
|
return 2 * ((thumbRange != null) ? thumbRange.low.prefWidth(-1) : 1);
|
|
}
|
|
|
|
@Override
|
|
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
if (isHorizontal()) {
|
|
return (leftInset + minTrackLength() + ((thumbRange != null) ? thumbRange.low.prefWidth(-1) : 1) + rightInset);
|
|
} else {
|
|
return (leftInset + ((thumbRange != null) ? thumbRange.low.prefWidth(-1) : 1) + rightInset);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
if (isHorizontal()) {
|
|
return (topInset + ((thumbRange != null) ? thumbRange.low.prefHeight(-1) : 1) + bottomInset);
|
|
} else {
|
|
return (topInset + minTrackLength() + ((thumbRange != null) ? thumbRange.low.prefHeight(-1) : 1) + bottomInset);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
if (isHorizontal()) {
|
|
if (showTickMarks) {
|
|
double w = Math.max(140, tickLine.prefWidth(-1));
|
|
return w;
|
|
} else {
|
|
return 140;
|
|
}
|
|
} else {
|
|
return leftInset + Math.max(((thumbRange != null) ? thumbRange.low.prefWidth(-1) : 1), track.prefWidth(-1))
|
|
+ ((showTickMarks) ? (TRACK_TO_TICK_GAP + tickLine.prefWidth(-1)) : 0) + rightInset;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
if (isHorizontal()) {
|
|
return getSkinnable().getInsets().getTop() + Math.max(((thumbRange != null) ? thumbRange.low.prefHeight(-1) : 1), track.prefHeight(-1))
|
|
+ ((showTickMarks) ? (TRACK_TO_TICK_GAP + tickLine.prefHeight(-1)) : 0) + bottomInset;
|
|
} else {
|
|
if (showTickMarks) {
|
|
return Math.max(140, tickLine.prefHeight(-1));
|
|
} else {
|
|
return 140;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
if (isHorizontal()) {
|
|
return Double.MAX_VALUE;
|
|
} else {
|
|
return getSkinnable().prefWidth(-1);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
|
if (isHorizontal()) {
|
|
return getSkinnable().prefHeight(width);
|
|
} else {
|
|
return Double.MAX_VALUE;
|
|
}
|
|
}
|
|
|
|
private double getMaxMinusMinNoZero() {
|
|
RangeSlider<?> s = getSkinnable();
|
|
return s.getMaximum().doubleValue() - s.getMinimum().doubleValue() == 0 ? 1 : s.getMaximum().doubleValue() - s.getMinimum().doubleValue();
|
|
}
|
|
|
|
private static class ThumbPane extends StackPane {
|
|
}
|
|
|
|
private static class ThumbRange {
|
|
ThumbPane low;
|
|
ThumbPane high;
|
|
StackPane rangeBar;
|
|
|
|
ThumbRange() {
|
|
low = new ThumbPane();
|
|
low.getStyleClass().setAll("low-thumb");
|
|
low.setFocusTraversable(false);
|
|
|
|
high = new ThumbPane();
|
|
high.getStyleClass().setAll("high-thumb");
|
|
high.setFocusTraversable(false);
|
|
|
|
rangeBar = new StackPane();
|
|
rangeBar.getStyleClass().setAll("range-bar");
|
|
rangeBar.setFocusTraversable(false);
|
|
}
|
|
}
|
|
}
|