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,30 +21,45 @@
  * 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;
+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;
 
 /**
- * Region/css based skin for Slider
-*/
-public class SliderSkin extends BehaviorSkinBase<Slider, 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,85 +78,11 @@
     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);
-        });
-    }
+    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,130 +90,99 @@
         @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)) {
+    /***************************************************************************
+     *                                                                         *
+     * 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.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
+                tickLine.setLowerBound(control.getMin());
             }
             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) ) {
+        });
+        registerChangeListener(control.maxProperty(), e -> {
             if (showTickMarks && tickLine != null) {
-                tickLine.setLowerBound(slider.getMin());
+                tickLine.setUpperBound(control.getMax());
             }
             getSkinnable().requestLayout();
-        } else if ("MAX".equals(p)) {
+        });
+        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.setUpperBound(slider.getMax());
+                tickLine.setSide(control.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (control.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
             }
             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)) {
+        });
+        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(slider.getMajorTickUnit());
+                tickLine.setTickUnit(control.getMajorTickUnit());
                 getSkinnable().requestLayout();
             }
-        } else if ("MINOR_TICK_COUNT".equals(p)) {
+        });
+        registerChangeListener(control.minorTickCountProperty(), e -> {
             if (tickLine != null) {
-                tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
+                tickLine.setMinorTickCount(Math.max(control.getMinorTickCount(), 0) + 1);
                 getSkinnable().requestLayout();
             }
-        } else if ("TICK_LABEL_FORMATTER".equals(p)) {
+        });
+        registerChangeListener(control.labelFormatterProperty(), e -> {
             if (tickLine != null) {
-                if (slider.getLabelFormatter() == null) {
+                if (control.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);
+    /***************************************************************************
+     *                                                                         *
+     * 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,33 +253,32 @@
                 tickLine = null;
             }
         }
     }
 
-    double minTrackLength() {
-        return 2*thumb.prefWidth(-1);
-    }
-
+    /** {@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,10 +289,11 @@
             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,22 +304,171 @@
                 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);
+    }
 }