modules/controls/src/main/java/javafx/scene/control/skin/SliderSkin.java
Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
*** 21,50 ****
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
! package com.sun.javafx.scene.control.skin;
import javafx.animation.Transition;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.geometry.Side;
import javafx.scene.AccessibleAttribute;
import javafx.scene.AccessibleRole;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Slider;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import javafx.util.StringConverter;
import com.sun.javafx.scene.control.behavior.SliderBehavior;
/**
! * Region/css based skin for Slider
! */
! public class SliderSkin extends BehaviorSkinBase<Slider, SliderBehavior> {
/** Track if slider is vertical/horizontal and cause re layout */
// private boolean horizontal;
private NumberAxis tickLine = null;
private double trackToTickGap = 2;
--- 21,65 ----
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
! package javafx.scene.control.skin;
+ import com.sun.javafx.scene.control.behavior.BehaviorBase;
import javafx.animation.Transition;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.geometry.Side;
import javafx.scene.AccessibleAttribute;
import javafx.scene.AccessibleRole;
+ import javafx.scene.Node;
import javafx.scene.chart.NumberAxis;
+ import javafx.scene.control.Accordion;
+ import javafx.scene.control.Button;
+ import javafx.scene.control.Control;
+ import javafx.scene.control.SkinBase;
import javafx.scene.control.Slider;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import javafx.util.StringConverter;
import com.sun.javafx.scene.control.behavior.SliderBehavior;
/**
! * Default skin implementation for the {@link Slider} control.
! *
! * @see Slider
! * @since 9
! */
! public class SliderSkin extends SkinBase<Slider> {
!
! /***************************************************************************
! * *
! * Private fields *
! * *
! **************************************************************************/
/** Track if slider is vertical/horizontal and cause re layout */
// private boolean horizontal;
private NumberAxis tickLine = null;
private double trackToTickGap = 2;
*** 63,147 ****
private StackPane thumb;
private StackPane track;
private boolean trackClicked = false;
// private double visibleAmount = 16;
! public SliderSkin(Slider slider) {
! super(slider, new SliderBehavior(slider));
!
! initialize();
! slider.requestLayout();
! registerChangeListener(slider.minProperty(), "MIN");
! registerChangeListener(slider.maxProperty(), "MAX");
! registerChangeListener(slider.valueProperty(), "VALUE");
! registerChangeListener(slider.orientationProperty(), "ORIENTATION");
! registerChangeListener(slider.showTickMarksProperty(), "SHOW_TICK_MARKS");
! registerChangeListener(slider.showTickLabelsProperty(), "SHOW_TICK_LABELS");
! registerChangeListener(slider.majorTickUnitProperty(), "MAJOR_TICK_UNIT");
! registerChangeListener(slider.minorTickCountProperty(), "MINOR_TICK_COUNT");
! registerChangeListener(slider.labelFormatterProperty(), "TICK_LABEL_FORMATTER");
! }
!
! private void initialize() {
! thumb = new StackPane() {
! @Override
! public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
! switch (attribute) {
! case VALUE: return getSkinnable().getValue();
! default: return super.queryAccessibleAttribute(attribute, parameters);
! }
! }
! };
! thumb.getStyleClass().setAll("thumb");
! thumb.setAccessibleRole(AccessibleRole.THUMB);
! track = new StackPane();
! track.getStyleClass().setAll("track");
! // horizontal = getSkinnable().isVertical();
!
! getChildren().clear();
! getChildren().addAll(track, thumb);
! setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels());
! track.setOnMousePressed(me -> {
! if (!thumb.isPressed()) {
! trackClicked = true;
! if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
! getBehavior().trackPress(me, (me.getX() / trackLength));
! } else {
! getBehavior().trackPress(me, (me.getY() / trackLength));
! }
! trackClicked = false;
! }
! });
!
! track.setOnMouseDragged(me -> {
! if (!thumb.isPressed()) {
! if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
! getBehavior().trackPress(me, (me.getX() / trackLength));
! } else {
! getBehavior().trackPress(me, (me.getY() / trackLength));
! }
! }
! });
!
! thumb.setOnMousePressed(me -> {
! getBehavior().thumbPressed(me, 0.0f);
! dragStart = thumb.localToParent(me.getX(), me.getY());
! preDragThumbPos = (getSkinnable().getValue() - getSkinnable().getMin()) /
! (getSkinnable().getMax() - getSkinnable().getMin());
! });
!
! thumb.setOnMouseReleased(me -> {
! getBehavior().thumbReleased(me);
! });
!
! thumb.setOnMouseDragged(me -> {
! Point2D cur = thumb.localToParent(me.getX(), me.getY());
! double dragPos = (getSkinnable().getOrientation() == Orientation.HORIZONTAL)?
! cur.getX() - dragStart.getX() : -(cur.getY() - dragStart.getY());
! getBehavior().thumbDragged(me, preDragThumbPos + dragPos / trackLength);
! });
! }
StringConverter<Number> stringConverterWrapper = new StringConverter<Number>() {
Slider slider = getSkinnable();
@Override public String toString(Number object) {
return(object != null) ? slider.getLabelFormatter().toString(object.doubleValue()) : "";
--- 78,88 ----
private StackPane thumb;
private StackPane track;
private boolean trackClicked = false;
// private double visibleAmount = 16;
! private final SliderBehavior behavior;
StringConverter<Number> stringConverterWrapper = new StringConverter<Number>() {
Slider slider = getSkinnable();
@Override public String toString(Number object) {
return(object != null) ? slider.getLabelFormatter().toString(object.doubleValue()) : "";
*** 149,278 ****
@Override public Number fromString(String string) {
return slider.getLabelFormatter().fromString(string);
}
};
- private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
- showTickMarks = (ticksVisible || labelsVisible);
- Slider slider = getSkinnable();
- if (showTickMarks) {
- if (tickLine == null) {
- tickLine = new NumberAxis();
- tickLine.setAutoRanging(false);
- tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
- tickLine.setUpperBound(slider.getMax());
- tickLine.setLowerBound(slider.getMin());
- tickLine.setTickUnit(slider.getMajorTickUnit());
- tickLine.setTickMarkVisible(ticksVisible);
- tickLine.setTickLabelsVisible(labelsVisible);
- tickLine.setMinorTickVisible(ticksVisible);
- // add 1 to the slider minor tick count since the axis draws one
- // less minor ticks than the number given.
- tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
- if (slider.getLabelFormatter() != null) {
- tickLine.setTickLabelFormatter(stringConverterWrapper);
- }
- getChildren().clear();
- getChildren().addAll(tickLine, track, thumb);
- } else {
- tickLine.setTickLabelsVisible(labelsVisible);
- tickLine.setTickMarkVisible(ticksVisible);
- tickLine.setMinorTickVisible(ticksVisible);
- }
- }
- else {
- getChildren().clear();
- getChildren().addAll(track, thumb);
- // tickLine = null;
- }
- getSkinnable().requestLayout();
- }
! @Override protected void handleControlPropertyChanged(String p) {
! super.handleControlPropertyChanged(p);
! Slider slider = getSkinnable();
! if ("ORIENTATION".equals(p)) {
if (showTickMarks && tickLine != null) {
! tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
}
getSkinnable().requestLayout();
! } else if ("VALUE".equals(p)) {
! // only animate thumb if the track was clicked - not if the thumb is dragged
! positionThumb(trackClicked);
! } else if ("MIN".equals(p) ) {
if (showTickMarks && tickLine != null) {
! tickLine.setLowerBound(slider.getMin());
}
getSkinnable().requestLayout();
! } else if ("MAX".equals(p)) {
if (showTickMarks && tickLine != null) {
! tickLine.setUpperBound(slider.getMax());
}
getSkinnable().requestLayout();
! } else if ("SHOW_TICK_MARKS".equals(p) || "SHOW_TICK_LABELS".equals(p)) {
! setShowTickMarks(slider.isShowTickMarks(), slider.isShowTickLabels());
! } else if ("MAJOR_TICK_UNIT".equals(p)) {
if (tickLine != null) {
! tickLine.setTickUnit(slider.getMajorTickUnit());
getSkinnable().requestLayout();
}
! } else if ("MINOR_TICK_COUNT".equals(p)) {
if (tickLine != null) {
! tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
getSkinnable().requestLayout();
}
! } else if ("TICK_LABEL_FORMATTER".equals(p)) {
if (tickLine != null) {
! if (slider.getLabelFormatter() == null) {
tickLine.setTickLabelFormatter(null);
} else {
tickLine.setTickLabelFormatter(stringConverterWrapper);
tickLine.requestAxisLayout();
}
}
! }
}
- /**
- * Called when ever either min, max or value changes, so thumb's layoutX, Y is recomputed.
- */
- void positionThumb(final boolean animate) {
- Slider s = getSkinnable();
- if (s.getValue() > s.getMax()) return;// this can happen if we are bound to something
- boolean horizontal = s.getOrientation() == Orientation.HORIZONTAL;
- final double endX = (horizontal) ? trackStart + (((trackLength * ((s.getValue() - s.getMin()) /
- (s.getMax() - s.getMin()))) - thumbWidth/2)) : thumbLeft;
- final double endY = (horizontal) ? thumbTop :
- snappedTopInset() + trackLength - (trackLength * ((s.getValue() - s.getMin()) /
- (s.getMax() - s.getMin()))); // - thumbHeight/2
- if (animate) {
- // lets animate the thumb transition
- final double startX = thumb.getLayoutX();
- final double startY = thumb.getLayoutY();
- Transition transition = new Transition() {
- {
- setCycleDuration(Duration.millis(200));
- }
! @Override protected void interpolate(double frac) {
! if (!Double.isNaN(startX)) {
! thumb.setLayoutX(startX + frac * (endX - startX));
! }
! if (!Double.isNaN(startY)) {
! thumb.setLayoutY(startY + frac * (endY - startY));
! }
! }
! };
! transition.play();
! } else {
! thumb.setLayoutX(endX);
! thumb.setLayoutY(endY);
}
}
@Override protected void layoutChildren(final double x, final double y,
final double w, final double h) {
// calculate the available space
// resize thumb to preferred size
thumbWidth = snapSize(thumb.prefWidth(-1));
--- 90,188 ----
@Override public Number fromString(String string) {
return slider.getLabelFormatter().fromString(string);
}
};
! /***************************************************************************
! * *
! * Constructors *
! * *
! **************************************************************************/
!
! /**
! * Creates a new SliderSkin instance, installing the necessary child
! * nodes into the Control {@link Control#getChildren() children} list, as
! * well as the necessary input mappings for handling key, mouse, etc events.
! *
! * @param control The control that this skin should be installed onto.
! */
! public SliderSkin(Slider control) {
! super(control);
!
! behavior = new SliderBehavior(control);
! // control.setInputMap(behavior.getInputMap());
!
! initialize();
! control.requestLayout();
! registerChangeListener(control.minProperty(), e -> {
if (showTickMarks && tickLine != null) {
! tickLine.setLowerBound(control.getMin());
}
getSkinnable().requestLayout();
! });
! registerChangeListener(control.maxProperty(), e -> {
if (showTickMarks && tickLine != null) {
! tickLine.setUpperBound(control.getMax());
}
getSkinnable().requestLayout();
! });
! registerChangeListener(control.valueProperty(), e -> {
! // only animate thumb if the track was clicked - not if the thumb is dragged
! positionThumb(trackClicked);
! });
! registerChangeListener(control.orientationProperty(), e -> {
if (showTickMarks && tickLine != null) {
! tickLine.setSide(control.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (control.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
}
getSkinnable().requestLayout();
! });
! registerChangeListener(control.showTickMarksProperty(), e -> setShowTickMarks(control.isShowTickMarks(), control.isShowTickLabels()));
! registerChangeListener(control.showTickLabelsProperty(), e -> setShowTickMarks(control.isShowTickMarks(), control.isShowTickLabels()));
! registerChangeListener(control.majorTickUnitProperty(), e -> {
if (tickLine != null) {
! tickLine.setTickUnit(control.getMajorTickUnit());
getSkinnable().requestLayout();
}
! });
! registerChangeListener(control.minorTickCountProperty(), e -> {
if (tickLine != null) {
! tickLine.setMinorTickCount(Math.max(control.getMinorTickCount(), 0) + 1);
getSkinnable().requestLayout();
}
! });
! registerChangeListener(control.labelFormatterProperty(), e -> {
if (tickLine != null) {
! if (control.getLabelFormatter() == null) {
tickLine.setTickLabelFormatter(null);
} else {
tickLine.setTickLabelFormatter(stringConverterWrapper);
tickLine.requestAxisLayout();
}
}
! });
}
! /***************************************************************************
! * *
! * Public API *
! * *
! **************************************************************************/
!
! /** {@inheritDoc} */
! @Override public void dispose() {
! super.dispose();
!
! if (behavior != null) {
! behavior.dispose();
}
}
+ /** {@inheritDoc} */
@Override protected void layoutChildren(final double x, final double y,
final double w, final double h) {
// calculate the available space
// resize thumb to preferred size
thumbWidth = snapSize(thumb.prefWidth(-1));
*** 343,375 ****
tickLine = null;
}
}
}
! double minTrackLength() {
! return 2*thumb.prefWidth(-1);
! }
!
@Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
final Slider s = getSkinnable();
if (s.getOrientation() == Orientation.HORIZONTAL) {
return (leftInset + minTrackLength() + thumb.minWidth(-1) + rightInset);
} else {
return(leftInset + thumb.prefWidth(-1) + rightInset);
}
}
@Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
final Slider s = getSkinnable();
if (s.getOrientation() == Orientation.HORIZONTAL) {
double axisHeight = showTickMarks ? (tickLine.prefHeight(-1) + trackToTickGap) : 0;
return topInset + thumb.prefHeight(-1) + axisHeight + bottomInset;
} else {
return topInset + minTrackLength() + thumb.prefHeight(-1) + bottomInset;
}
}
@Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
final Slider s = getSkinnable();
if (s.getOrientation() == Orientation.HORIZONTAL) {
if(showTickMarks) {
return Math.max(140, tickLine.prefWidth(-1));
--- 253,284 ----
tickLine = null;
}
}
}
! /** {@inheritDoc} */
@Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
final Slider s = getSkinnable();
if (s.getOrientation() == Orientation.HORIZONTAL) {
return (leftInset + minTrackLength() + thumb.minWidth(-1) + rightInset);
} else {
return(leftInset + thumb.prefWidth(-1) + rightInset);
}
}
+ /** {@inheritDoc} */
@Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
final Slider s = getSkinnable();
if (s.getOrientation() == Orientation.HORIZONTAL) {
double axisHeight = showTickMarks ? (tickLine.prefHeight(-1) + trackToTickGap) : 0;
return topInset + thumb.prefHeight(-1) + axisHeight + bottomInset;
} else {
return topInset + minTrackLength() + thumb.prefHeight(-1) + bottomInset;
}
}
+ /** {@inheritDoc} */
@Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
final Slider s = getSkinnable();
if (s.getOrientation() == Orientation.HORIZONTAL) {
if(showTickMarks) {
return Math.max(140, tickLine.prefWidth(-1));
*** 380,389 ****
--- 289,299 ----
double axisWidth = showTickMarks ? (tickLine.prefWidth(-1) + trackToTickGap) : 0;
return leftInset + Math.max(thumb.prefWidth(-1), track.prefWidth(-1)) + axisWidth + rightInset;
}
}
+ /** {@inheritDoc} */
@Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
final Slider s = getSkinnable();
if (s.getOrientation() == Orientation.HORIZONTAL) {
return topInset + Math.max(thumb.prefHeight(-1), track.prefHeight(-1)) +
((showTickMarks) ? (trackToTickGap+tickLine.prefHeight(-1)) : 0) + bottomInset;
*** 394,415 ****
--- 304,474 ----
return 140;
}
}
}
+ /** {@inheritDoc} */
@Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
return Double.MAX_VALUE;
} else {
return getSkinnable().prefWidth(-1);
}
}
+ /** {@inheritDoc} */
@Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
return getSkinnable().prefHeight(width);
} else {
return Double.MAX_VALUE;
}
}
+
+
+
+ /***************************************************************************
+ * *
+ * Private implementation *
+ * *
+ **************************************************************************/
+
+ private void initialize() {
+ thumb = new StackPane() {
+ @Override
+ public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
+ switch (attribute) {
+ case VALUE: return getSkinnable().getValue();
+ default: return super.queryAccessibleAttribute(attribute, parameters);
+ }
+ }
+ };
+ thumb.getStyleClass().setAll("thumb");
+ thumb.setAccessibleRole(AccessibleRole.THUMB);
+ track = new StackPane();
+ track.getStyleClass().setAll("track");
+ // horizontal = getSkinnable().isVertical();
+
+ getChildren().clear();
+ getChildren().addAll(track, thumb);
+ setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels());
+ track.setOnMousePressed(me -> {
+ if (!thumb.isPressed()) {
+ trackClicked = true;
+ if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
+ behavior.trackPress(me, (me.getX() / trackLength));
+ } else {
+ behavior.trackPress(me, (me.getY() / trackLength));
+ }
+ trackClicked = false;
+ }
+ });
+
+ track.setOnMouseDragged(me -> {
+ if (!thumb.isPressed()) {
+ if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
+ behavior.trackPress(me, (me.getX() / trackLength));
+ } else {
+ behavior.trackPress(me, (me.getY() / trackLength));
+ }
+ }
+ });
+
+ thumb.setOnMousePressed(me -> {
+ behavior.thumbPressed(me, 0.0f);
+ dragStart = thumb.localToParent(me.getX(), me.getY());
+ preDragThumbPos = (getSkinnable().getValue() - getSkinnable().getMin()) /
+ (getSkinnable().getMax() - getSkinnable().getMin());
+ });
+
+ thumb.setOnMouseReleased(me -> {
+ behavior.thumbReleased(me);
+ });
+
+ thumb.setOnMouseDragged(me -> {
+ Point2D cur = thumb.localToParent(me.getX(), me.getY());
+ double dragPos = (getSkinnable().getOrientation() == Orientation.HORIZONTAL) ?
+ cur.getX() - dragStart.getX() : -(cur.getY() - dragStart.getY());
+ behavior.thumbDragged(me, preDragThumbPos + dragPos / trackLength);
+ });
+ }
+
+ private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
+ showTickMarks = (ticksVisible || labelsVisible);
+ Slider slider = getSkinnable();
+ if (showTickMarks) {
+ if (tickLine == null) {
+ tickLine = new NumberAxis();
+ tickLine.setAutoRanging(false);
+ tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
+ tickLine.setUpperBound(slider.getMax());
+ tickLine.setLowerBound(slider.getMin());
+ tickLine.setTickUnit(slider.getMajorTickUnit());
+ tickLine.setTickMarkVisible(ticksVisible);
+ tickLine.setTickLabelsVisible(labelsVisible);
+ tickLine.setMinorTickVisible(ticksVisible);
+ // add 1 to the slider minor tick count since the axis draws one
+ // less minor ticks than the number given.
+ tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
+ if (slider.getLabelFormatter() != null) {
+ tickLine.setTickLabelFormatter(stringConverterWrapper);
+ }
+ getChildren().clear();
+ getChildren().addAll(tickLine, track, thumb);
+ } else {
+ tickLine.setTickLabelsVisible(labelsVisible);
+ tickLine.setTickMarkVisible(ticksVisible);
+ tickLine.setMinorTickVisible(ticksVisible);
+ }
+ }
+ else {
+ getChildren().clear();
+ getChildren().addAll(track, thumb);
+ // tickLine = null;
+ }
+
+ getSkinnable().requestLayout();
+ }
+
+ /**
+ * Called when ever either min, max or value changes, so thumb's layoutX, Y is recomputed.
+ */
+ void positionThumb(final boolean animate) {
+ Slider s = getSkinnable();
+ if (s.getValue() > s.getMax()) return;// this can happen if we are bound to something
+ boolean horizontal = s.getOrientation() == Orientation.HORIZONTAL;
+ final double endX = (horizontal) ? trackStart + (((trackLength * ((s.getValue() - s.getMin()) /
+ (s.getMax() - s.getMin()))) - thumbWidth/2)) : thumbLeft;
+ final double endY = (horizontal) ? thumbTop :
+ snappedTopInset() + trackLength - (trackLength * ((s.getValue() - s.getMin()) /
+ (s.getMax() - s.getMin()))); // - thumbHeight/2
+
+ if (animate) {
+ // lets animate the thumb transition
+ final double startX = thumb.getLayoutX();
+ final double startY = thumb.getLayoutY();
+ Transition transition = new Transition() {
+ {
+ setCycleDuration(Duration.millis(200));
+ }
+
+ @Override protected void interpolate(double frac) {
+ if (!Double.isNaN(startX)) {
+ thumb.setLayoutX(startX + frac * (endX - startX));
+ }
+ if (!Double.isNaN(startY)) {
+ thumb.setLayoutY(startY + frac * (endY - startY));
+ }
+ }
+ };
+ transition.play();
+ } else {
+ thumb.setLayoutX(endX);
+ thumb.setLayoutY(endY);
+ }
+ }
+
+ double minTrackLength() {
+ return 2*thumb.prefWidth(-1);
+ }
}