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); + } }