6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.javafx.scene.control.skin;
27
28 import javafx.animation.Transition;
29 import javafx.geometry.Orientation;
30 import javafx.geometry.Point2D;
31 import javafx.geometry.Side;
32 import javafx.scene.AccessibleAttribute;
33 import javafx.scene.AccessibleRole;
34 import javafx.scene.chart.NumberAxis;
35 import javafx.scene.control.Slider;
36 import javafx.scene.layout.StackPane;
37 import javafx.util.Duration;
38 import javafx.util.StringConverter;
39
40 import com.sun.javafx.scene.control.behavior.SliderBehavior;
41
42 /**
43 * Region/css based skin for Slider
44 */
45 public class SliderSkin extends BehaviorSkinBase<Slider, SliderBehavior> {
46
47 /** Track if slider is vertical/horizontal and cause re layout */
48 // private boolean horizontal;
49 private NumberAxis tickLine = null;
50 private double trackToTickGap = 2;
51
52 private boolean showTickMarks;
53 private double thumbWidth;
54 private double thumbHeight;
55
56 private double trackStart;
57 private double trackLength;
58 private double thumbTop;
59 private double thumbLeft;
60 private double preDragThumbPos;
61 private Point2D dragStart; // in skin coordinates
62
63 private StackPane thumb;
64 private StackPane track;
65 private boolean trackClicked = false;
66 // private double visibleAmount = 16;
67
68 public SliderSkin(Slider slider) {
69 super(slider, new SliderBehavior(slider));
70
71 initialize();
72 slider.requestLayout();
73 registerChangeListener(slider.minProperty(), "MIN");
74 registerChangeListener(slider.maxProperty(), "MAX");
75 registerChangeListener(slider.valueProperty(), "VALUE");
76 registerChangeListener(slider.orientationProperty(), "ORIENTATION");
77 registerChangeListener(slider.showTickMarksProperty(), "SHOW_TICK_MARKS");
78 registerChangeListener(slider.showTickLabelsProperty(), "SHOW_TICK_LABELS");
79 registerChangeListener(slider.majorTickUnitProperty(), "MAJOR_TICK_UNIT");
80 registerChangeListener(slider.minorTickCountProperty(), "MINOR_TICK_COUNT");
81 registerChangeListener(slider.labelFormatterProperty(), "TICK_LABEL_FORMATTER");
82 }
83
84 private void initialize() {
85 thumb = new StackPane() {
86 @Override
87 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
88 switch (attribute) {
89 case VALUE: return getSkinnable().getValue();
90 default: return super.queryAccessibleAttribute(attribute, parameters);
91 }
92 }
93 };
94 thumb.getStyleClass().setAll("thumb");
95 thumb.setAccessibleRole(AccessibleRole.THUMB);
96 track = new StackPane();
97 track.getStyleClass().setAll("track");
98 // horizontal = getSkinnable().isVertical();
99
100 getChildren().clear();
101 getChildren().addAll(track, thumb);
102 setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels());
103 track.setOnMousePressed(me -> {
104 if (!thumb.isPressed()) {
105 trackClicked = true;
106 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
107 getBehavior().trackPress(me, (me.getX() / trackLength));
108 } else {
109 getBehavior().trackPress(me, (me.getY() / trackLength));
110 }
111 trackClicked = false;
112 }
113 });
114
115 track.setOnMouseDragged(me -> {
116 if (!thumb.isPressed()) {
117 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
118 getBehavior().trackPress(me, (me.getX() / trackLength));
119 } else {
120 getBehavior().trackPress(me, (me.getY() / trackLength));
121 }
122 }
123 });
124
125 thumb.setOnMousePressed(me -> {
126 getBehavior().thumbPressed(me, 0.0f);
127 dragStart = thumb.localToParent(me.getX(), me.getY());
128 preDragThumbPos = (getSkinnable().getValue() - getSkinnable().getMin()) /
129 (getSkinnable().getMax() - getSkinnable().getMin());
130 });
131
132 thumb.setOnMouseReleased(me -> {
133 getBehavior().thumbReleased(me);
134 });
135
136 thumb.setOnMouseDragged(me -> {
137 Point2D cur = thumb.localToParent(me.getX(), me.getY());
138 double dragPos = (getSkinnable().getOrientation() == Orientation.HORIZONTAL)?
139 cur.getX() - dragStart.getX() : -(cur.getY() - dragStart.getY());
140 getBehavior().thumbDragged(me, preDragThumbPos + dragPos / trackLength);
141 });
142 }
143
144 StringConverter<Number> stringConverterWrapper = new StringConverter<Number>() {
145 Slider slider = getSkinnable();
146 @Override public String toString(Number object) {
147 return(object != null) ? slider.getLabelFormatter().toString(object.doubleValue()) : "";
148 }
149 @Override public Number fromString(String string) {
150 return slider.getLabelFormatter().fromString(string);
151 }
152 };
153
154 private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
155 showTickMarks = (ticksVisible || labelsVisible);
156 Slider slider = getSkinnable();
157 if (showTickMarks) {
158 if (tickLine == null) {
159 tickLine = new NumberAxis();
160 tickLine.setAutoRanging(false);
161 tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
162 tickLine.setUpperBound(slider.getMax());
163 tickLine.setLowerBound(slider.getMin());
164 tickLine.setTickUnit(slider.getMajorTickUnit());
165 tickLine.setTickMarkVisible(ticksVisible);
166 tickLine.setTickLabelsVisible(labelsVisible);
167 tickLine.setMinorTickVisible(ticksVisible);
168 // add 1 to the slider minor tick count since the axis draws one
169 // less minor ticks than the number given.
170 tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
171 if (slider.getLabelFormatter() != null) {
172 tickLine.setTickLabelFormatter(stringConverterWrapper);
173 }
174 getChildren().clear();
175 getChildren().addAll(tickLine, track, thumb);
176 } else {
177 tickLine.setTickLabelsVisible(labelsVisible);
178 tickLine.setTickMarkVisible(ticksVisible);
179 tickLine.setMinorTickVisible(ticksVisible);
180 }
181 }
182 else {
183 getChildren().clear();
184 getChildren().addAll(track, thumb);
185 // tickLine = null;
186 }
187
188 getSkinnable().requestLayout();
189 }
190
191 @Override protected void handleControlPropertyChanged(String p) {
192 super.handleControlPropertyChanged(p);
193 Slider slider = getSkinnable();
194 if ("ORIENTATION".equals(p)) {
195 if (showTickMarks && tickLine != null) {
196 tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
197 }
198 getSkinnable().requestLayout();
199 } else if ("VALUE".equals(p)) {
200 // only animate thumb if the track was clicked - not if the thumb is dragged
201 positionThumb(trackClicked);
202 } else if ("MIN".equals(p) ) {
203 if (showTickMarks && tickLine != null) {
204 tickLine.setLowerBound(slider.getMin());
205 }
206 getSkinnable().requestLayout();
207 } else if ("MAX".equals(p)) {
208 if (showTickMarks && tickLine != null) {
209 tickLine.setUpperBound(slider.getMax());
210 }
211 getSkinnable().requestLayout();
212 } else if ("SHOW_TICK_MARKS".equals(p) || "SHOW_TICK_LABELS".equals(p)) {
213 setShowTickMarks(slider.isShowTickMarks(), slider.isShowTickLabels());
214 } else if ("MAJOR_TICK_UNIT".equals(p)) {
215 if (tickLine != null) {
216 tickLine.setTickUnit(slider.getMajorTickUnit());
217 getSkinnable().requestLayout();
218 }
219 } else if ("MINOR_TICK_COUNT".equals(p)) {
220 if (tickLine != null) {
221 tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
222 getSkinnable().requestLayout();
223 }
224 } else if ("TICK_LABEL_FORMATTER".equals(p)) {
225 if (tickLine != null) {
226 if (slider.getLabelFormatter() == null) {
227 tickLine.setTickLabelFormatter(null);
228 } else {
229 tickLine.setTickLabelFormatter(stringConverterWrapper);
230 tickLine.requestAxisLayout();
231 }
232 }
233 }
234 }
235
236 /**
237 * Called when ever either min, max or value changes, so thumb's layoutX, Y is recomputed.
238 */
239 void positionThumb(final boolean animate) {
240 Slider s = getSkinnable();
241 if (s.getValue() > s.getMax()) return;// this can happen if we are bound to something
242 boolean horizontal = s.getOrientation() == Orientation.HORIZONTAL;
243 final double endX = (horizontal) ? trackStart + (((trackLength * ((s.getValue() - s.getMin()) /
244 (s.getMax() - s.getMin()))) - thumbWidth/2)) : thumbLeft;
245 final double endY = (horizontal) ? thumbTop :
246 snappedTopInset() + trackLength - (trackLength * ((s.getValue() - s.getMin()) /
247 (s.getMax() - s.getMin()))); // - thumbHeight/2
248
249 if (animate) {
250 // lets animate the thumb transition
251 final double startX = thumb.getLayoutX();
252 final double startY = thumb.getLayoutY();
253 Transition transition = new Transition() {
254 {
255 setCycleDuration(Duration.millis(200));
256 }
257
258 @Override protected void interpolate(double frac) {
259 if (!Double.isNaN(startX)) {
260 thumb.setLayoutX(startX + frac * (endX - startX));
261 }
262 if (!Double.isNaN(startY)) {
263 thumb.setLayoutY(startY + frac * (endY - startY));
264 }
265 }
266 };
267 transition.play();
268 } else {
269 thumb.setLayoutX(endX);
270 thumb.setLayoutY(endY);
271 }
272 }
273
274 @Override protected void layoutChildren(final double x, final double y,
275 final double w, final double h) {
276 // calculate the available space
277 // resize thumb to preferred size
278 thumbWidth = snapSize(thumb.prefWidth(-1));
279 thumbHeight = snapSize(thumb.prefHeight(-1));
280 thumb.resize(thumbWidth, thumbHeight);
281 // we are assuming the is common radius's for all corners on the track
282 double trackRadius = track.getBackground() == null ? 0 : track.getBackground().getFills().size() > 0 ?
283 track.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius() : 0;
284
285 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
286 double tickLineHeight = (showTickMarks) ? tickLine.prefHeight(-1) : 0;
287 double trackHeight = snapSize(track.prefHeight(-1));
288 double trackAreaHeight = Math.max(trackHeight,thumbHeight);
289 double totalHeightNeeded = trackAreaHeight + ((showTickMarks) ? trackToTickGap+tickLineHeight : 0);
290 double startY = y + ((h - totalHeightNeeded)/2); // center slider in available height vertically
291 trackLength = snapSize(w - thumbWidth);
292 trackStart = snapPosition(x + (thumbWidth/2));
293 double trackTop = (int)(startY + ((trackAreaHeight-trackHeight)/2));
328 track.resizeRelocate(trackLeft,
329 (int)(trackStart - trackRadius),
330 trackWidth,
331 (int)(trackLength + trackRadius + trackRadius));
332 // layout tick line
333 if (showTickMarks) {
334 tickLine.setLayoutX(trackLeft+trackWidth+trackToTickGap);
335 tickLine.setLayoutY(trackStart);
336 tickLine.resize(tickLineWidth, trackLength);
337 tickLine.requestAxisLayout();
338 } else {
339 if (tickLine != null) {
340 tickLine.resize(0,0);
341 tickLine.requestAxisLayout();
342 }
343 tickLine = null;
344 }
345 }
346 }
347
348 double minTrackLength() {
349 return 2*thumb.prefWidth(-1);
350 }
351
352 @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
353 final Slider s = getSkinnable();
354 if (s.getOrientation() == Orientation.HORIZONTAL) {
355 return (leftInset + minTrackLength() + thumb.minWidth(-1) + rightInset);
356 } else {
357 return(leftInset + thumb.prefWidth(-1) + rightInset);
358 }
359 }
360
361 @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
362 final Slider s = getSkinnable();
363 if (s.getOrientation() == Orientation.HORIZONTAL) {
364 double axisHeight = showTickMarks ? (tickLine.prefHeight(-1) + trackToTickGap) : 0;
365 return topInset + thumb.prefHeight(-1) + axisHeight + bottomInset;
366 } else {
367 return topInset + minTrackLength() + thumb.prefHeight(-1) + bottomInset;
368 }
369 }
370
371 @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
372 final Slider s = getSkinnable();
373 if (s.getOrientation() == Orientation.HORIZONTAL) {
374 if(showTickMarks) {
375 return Math.max(140, tickLine.prefWidth(-1));
376 } else {
377 return 140;
378 }
379 } else {
380 double axisWidth = showTickMarks ? (tickLine.prefWidth(-1) + trackToTickGap) : 0;
381 return leftInset + Math.max(thumb.prefWidth(-1), track.prefWidth(-1)) + axisWidth + rightInset;
382 }
383 }
384
385 @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
386 final Slider s = getSkinnable();
387 if (s.getOrientation() == Orientation.HORIZONTAL) {
388 return topInset + Math.max(thumb.prefHeight(-1), track.prefHeight(-1)) +
389 ((showTickMarks) ? (trackToTickGap+tickLine.prefHeight(-1)) : 0) + bottomInset;
390 } else {
391 if(showTickMarks) {
392 return Math.max(140, tickLine.prefHeight(-1));
393 } else {
394 return 140;
395 }
396 }
397 }
398
399 @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
400 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
401 return Double.MAX_VALUE;
402 } else {
403 return getSkinnable().prefWidth(-1);
404 }
405 }
406
407 @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
408 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
409 return getSkinnable().prefHeight(width);
410 } else {
411 return Double.MAX_VALUE;
412 }
413 }
414 }
415
|
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package javafx.scene.control.skin;
27
28 import com.sun.javafx.scene.control.behavior.BehaviorBase;
29 import javafx.animation.Transition;
30 import javafx.geometry.Orientation;
31 import javafx.geometry.Point2D;
32 import javafx.geometry.Side;
33 import javafx.scene.AccessibleAttribute;
34 import javafx.scene.AccessibleRole;
35 import javafx.scene.Node;
36 import javafx.scene.chart.NumberAxis;
37 import javafx.scene.control.Accordion;
38 import javafx.scene.control.Button;
39 import javafx.scene.control.Control;
40 import javafx.scene.control.SkinBase;
41 import javafx.scene.control.Slider;
42 import javafx.scene.layout.StackPane;
43 import javafx.util.Duration;
44 import javafx.util.StringConverter;
45
46 import com.sun.javafx.scene.control.behavior.SliderBehavior;
47
48 /**
49 * Default skin implementation for the {@link Slider} control.
50 *
51 * @see Slider
52 * @since 9
53 */
54 public class SliderSkin extends SkinBase<Slider> {
55
56 /***************************************************************************
57 * *
58 * Private fields *
59 * *
60 **************************************************************************/
61
62 /** Track if slider is vertical/horizontal and cause re layout */
63 // private boolean horizontal;
64 private NumberAxis tickLine = null;
65 private double trackToTickGap = 2;
66
67 private boolean showTickMarks;
68 private double thumbWidth;
69 private double thumbHeight;
70
71 private double trackStart;
72 private double trackLength;
73 private double thumbTop;
74 private double thumbLeft;
75 private double preDragThumbPos;
76 private Point2D dragStart; // in skin coordinates
77
78 private StackPane thumb;
79 private StackPane track;
80 private boolean trackClicked = false;
81 // private double visibleAmount = 16;
82
83 private final SliderBehavior behavior;
84
85 StringConverter<Number> stringConverterWrapper = new StringConverter<Number>() {
86 Slider slider = getSkinnable();
87 @Override public String toString(Number object) {
88 return(object != null) ? slider.getLabelFormatter().toString(object.doubleValue()) : "";
89 }
90 @Override public Number fromString(String string) {
91 return slider.getLabelFormatter().fromString(string);
92 }
93 };
94
95
96
97 /***************************************************************************
98 * *
99 * Constructors *
100 * *
101 **************************************************************************/
102
103 /**
104 * Creates a new SliderSkin instance, installing the necessary child
105 * nodes into the Control {@link Control#getChildren() children} list, as
106 * well as the necessary input mappings for handling key, mouse, etc events.
107 *
108 * @param control The control that this skin should be installed onto.
109 */
110 public SliderSkin(Slider control) {
111 super(control);
112
113 behavior = new SliderBehavior(control);
114 // control.setInputMap(behavior.getInputMap());
115
116 initialize();
117 control.requestLayout();
118 registerChangeListener(control.minProperty(), e -> {
119 if (showTickMarks && tickLine != null) {
120 tickLine.setLowerBound(control.getMin());
121 }
122 getSkinnable().requestLayout();
123 });
124 registerChangeListener(control.maxProperty(), e -> {
125 if (showTickMarks && tickLine != null) {
126 tickLine.setUpperBound(control.getMax());
127 }
128 getSkinnable().requestLayout();
129 });
130 registerChangeListener(control.valueProperty(), e -> {
131 // only animate thumb if the track was clicked - not if the thumb is dragged
132 positionThumb(trackClicked);
133 });
134 registerChangeListener(control.orientationProperty(), e -> {
135 if (showTickMarks && tickLine != null) {
136 tickLine.setSide(control.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (control.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
137 }
138 getSkinnable().requestLayout();
139 });
140 registerChangeListener(control.showTickMarksProperty(), e -> setShowTickMarks(control.isShowTickMarks(), control.isShowTickLabels()));
141 registerChangeListener(control.showTickLabelsProperty(), e -> setShowTickMarks(control.isShowTickMarks(), control.isShowTickLabels()));
142 registerChangeListener(control.majorTickUnitProperty(), e -> {
143 if (tickLine != null) {
144 tickLine.setTickUnit(control.getMajorTickUnit());
145 getSkinnable().requestLayout();
146 }
147 });
148 registerChangeListener(control.minorTickCountProperty(), e -> {
149 if (tickLine != null) {
150 tickLine.setMinorTickCount(Math.max(control.getMinorTickCount(), 0) + 1);
151 getSkinnable().requestLayout();
152 }
153 });
154 registerChangeListener(control.labelFormatterProperty(), e -> {
155 if (tickLine != null) {
156 if (control.getLabelFormatter() == null) {
157 tickLine.setTickLabelFormatter(null);
158 } else {
159 tickLine.setTickLabelFormatter(stringConverterWrapper);
160 tickLine.requestAxisLayout();
161 }
162 }
163 });
164 }
165
166
167
168 /***************************************************************************
169 * *
170 * Public API *
171 * *
172 **************************************************************************/
173
174 /** {@inheritDoc} */
175 @Override public void dispose() {
176 super.dispose();
177
178 if (behavior != null) {
179 behavior.dispose();
180 }
181 }
182
183 /** {@inheritDoc} */
184 @Override protected void layoutChildren(final double x, final double y,
185 final double w, final double h) {
186 // calculate the available space
187 // resize thumb to preferred size
188 thumbWidth = snapSize(thumb.prefWidth(-1));
189 thumbHeight = snapSize(thumb.prefHeight(-1));
190 thumb.resize(thumbWidth, thumbHeight);
191 // we are assuming the is common radius's for all corners on the track
192 double trackRadius = track.getBackground() == null ? 0 : track.getBackground().getFills().size() > 0 ?
193 track.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius() : 0;
194
195 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
196 double tickLineHeight = (showTickMarks) ? tickLine.prefHeight(-1) : 0;
197 double trackHeight = snapSize(track.prefHeight(-1));
198 double trackAreaHeight = Math.max(trackHeight,thumbHeight);
199 double totalHeightNeeded = trackAreaHeight + ((showTickMarks) ? trackToTickGap+tickLineHeight : 0);
200 double startY = y + ((h - totalHeightNeeded)/2); // center slider in available height vertically
201 trackLength = snapSize(w - thumbWidth);
202 trackStart = snapPosition(x + (thumbWidth/2));
203 double trackTop = (int)(startY + ((trackAreaHeight-trackHeight)/2));
238 track.resizeRelocate(trackLeft,
239 (int)(trackStart - trackRadius),
240 trackWidth,
241 (int)(trackLength + trackRadius + trackRadius));
242 // layout tick line
243 if (showTickMarks) {
244 tickLine.setLayoutX(trackLeft+trackWidth+trackToTickGap);
245 tickLine.setLayoutY(trackStart);
246 tickLine.resize(tickLineWidth, trackLength);
247 tickLine.requestAxisLayout();
248 } else {
249 if (tickLine != null) {
250 tickLine.resize(0,0);
251 tickLine.requestAxisLayout();
252 }
253 tickLine = null;
254 }
255 }
256 }
257
258 /** {@inheritDoc} */
259 @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
260 final Slider s = getSkinnable();
261 if (s.getOrientation() == Orientation.HORIZONTAL) {
262 return (leftInset + minTrackLength() + thumb.minWidth(-1) + rightInset);
263 } else {
264 return(leftInset + thumb.prefWidth(-1) + rightInset);
265 }
266 }
267
268 /** {@inheritDoc} */
269 @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
270 final Slider s = getSkinnable();
271 if (s.getOrientation() == Orientation.HORIZONTAL) {
272 double axisHeight = showTickMarks ? (tickLine.prefHeight(-1) + trackToTickGap) : 0;
273 return topInset + thumb.prefHeight(-1) + axisHeight + bottomInset;
274 } else {
275 return topInset + minTrackLength() + thumb.prefHeight(-1) + bottomInset;
276 }
277 }
278
279 /** {@inheritDoc} */
280 @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
281 final Slider s = getSkinnable();
282 if (s.getOrientation() == Orientation.HORIZONTAL) {
283 if(showTickMarks) {
284 return Math.max(140, tickLine.prefWidth(-1));
285 } else {
286 return 140;
287 }
288 } else {
289 double axisWidth = showTickMarks ? (tickLine.prefWidth(-1) + trackToTickGap) : 0;
290 return leftInset + Math.max(thumb.prefWidth(-1), track.prefWidth(-1)) + axisWidth + rightInset;
291 }
292 }
293
294 /** {@inheritDoc} */
295 @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
296 final Slider s = getSkinnable();
297 if (s.getOrientation() == Orientation.HORIZONTAL) {
298 return topInset + Math.max(thumb.prefHeight(-1), track.prefHeight(-1)) +
299 ((showTickMarks) ? (trackToTickGap+tickLine.prefHeight(-1)) : 0) + bottomInset;
300 } else {
301 if(showTickMarks) {
302 return Math.max(140, tickLine.prefHeight(-1));
303 } else {
304 return 140;
305 }
306 }
307 }
308
309 /** {@inheritDoc} */
310 @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
311 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
312 return Double.MAX_VALUE;
313 } else {
314 return getSkinnable().prefWidth(-1);
315 }
316 }
317
318 /** {@inheritDoc} */
319 @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
320 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
321 return getSkinnable().prefHeight(width);
322 } else {
323 return Double.MAX_VALUE;
324 }
325 }
326
327
328
329 /***************************************************************************
330 * *
331 * Private implementation *
332 * *
333 **************************************************************************/
334
335 private void initialize() {
336 thumb = new StackPane() {
337 @Override
338 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
339 switch (attribute) {
340 case VALUE: return getSkinnable().getValue();
341 default: return super.queryAccessibleAttribute(attribute, parameters);
342 }
343 }
344 };
345 thumb.getStyleClass().setAll("thumb");
346 thumb.setAccessibleRole(AccessibleRole.THUMB);
347 track = new StackPane();
348 track.getStyleClass().setAll("track");
349 // horizontal = getSkinnable().isVertical();
350
351 getChildren().clear();
352 getChildren().addAll(track, thumb);
353 setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels());
354 track.setOnMousePressed(me -> {
355 if (!thumb.isPressed()) {
356 trackClicked = true;
357 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
358 behavior.trackPress(me, (me.getX() / trackLength));
359 } else {
360 behavior.trackPress(me, (me.getY() / trackLength));
361 }
362 trackClicked = false;
363 }
364 });
365
366 track.setOnMouseDragged(me -> {
367 if (!thumb.isPressed()) {
368 if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
369 behavior.trackPress(me, (me.getX() / trackLength));
370 } else {
371 behavior.trackPress(me, (me.getY() / trackLength));
372 }
373 }
374 });
375
376 thumb.setOnMousePressed(me -> {
377 behavior.thumbPressed(me, 0.0f);
378 dragStart = thumb.localToParent(me.getX(), me.getY());
379 preDragThumbPos = (getSkinnable().getValue() - getSkinnable().getMin()) /
380 (getSkinnable().getMax() - getSkinnable().getMin());
381 });
382
383 thumb.setOnMouseReleased(me -> {
384 behavior.thumbReleased(me);
385 });
386
387 thumb.setOnMouseDragged(me -> {
388 Point2D cur = thumb.localToParent(me.getX(), me.getY());
389 double dragPos = (getSkinnable().getOrientation() == Orientation.HORIZONTAL) ?
390 cur.getX() - dragStart.getX() : -(cur.getY() - dragStart.getY());
391 behavior.thumbDragged(me, preDragThumbPos + dragPos / trackLength);
392 });
393 }
394
395 private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
396 showTickMarks = (ticksVisible || labelsVisible);
397 Slider slider = getSkinnable();
398 if (showTickMarks) {
399 if (tickLine == null) {
400 tickLine = new NumberAxis();
401 tickLine.setAutoRanging(false);
402 tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
403 tickLine.setUpperBound(slider.getMax());
404 tickLine.setLowerBound(slider.getMin());
405 tickLine.setTickUnit(slider.getMajorTickUnit());
406 tickLine.setTickMarkVisible(ticksVisible);
407 tickLine.setTickLabelsVisible(labelsVisible);
408 tickLine.setMinorTickVisible(ticksVisible);
409 // add 1 to the slider minor tick count since the axis draws one
410 // less minor ticks than the number given.
411 tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
412 if (slider.getLabelFormatter() != null) {
413 tickLine.setTickLabelFormatter(stringConverterWrapper);
414 }
415 getChildren().clear();
416 getChildren().addAll(tickLine, track, thumb);
417 } else {
418 tickLine.setTickLabelsVisible(labelsVisible);
419 tickLine.setTickMarkVisible(ticksVisible);
420 tickLine.setMinorTickVisible(ticksVisible);
421 }
422 }
423 else {
424 getChildren().clear();
425 getChildren().addAll(track, thumb);
426 // tickLine = null;
427 }
428
429 getSkinnable().requestLayout();
430 }
431
432 /**
433 * Called when ever either min, max or value changes, so thumb's layoutX, Y is recomputed.
434 */
435 void positionThumb(final boolean animate) {
436 Slider s = getSkinnable();
437 if (s.getValue() > s.getMax()) return;// this can happen if we are bound to something
438 boolean horizontal = s.getOrientation() == Orientation.HORIZONTAL;
439 final double endX = (horizontal) ? trackStart + (((trackLength * ((s.getValue() - s.getMin()) /
440 (s.getMax() - s.getMin()))) - thumbWidth/2)) : thumbLeft;
441 final double endY = (horizontal) ? thumbTop :
442 snappedTopInset() + trackLength - (trackLength * ((s.getValue() - s.getMin()) /
443 (s.getMax() - s.getMin()))); // - thumbHeight/2
444
445 if (animate) {
446 // lets animate the thumb transition
447 final double startX = thumb.getLayoutX();
448 final double startY = thumb.getLayoutY();
449 Transition transition = new Transition() {
450 {
451 setCycleDuration(Duration.millis(200));
452 }
453
454 @Override protected void interpolate(double frac) {
455 if (!Double.isNaN(startX)) {
456 thumb.setLayoutX(startX + frac * (endX - startX));
457 }
458 if (!Double.isNaN(startY)) {
459 thumb.setLayoutY(startY + frac * (endY - startY));
460 }
461 }
462 };
463 transition.play();
464 } else {
465 thumb.setLayoutX(endX);
466 thumb.setLayoutY(endY);
467 }
468 }
469
470 double minTrackLength() {
471 return 2*thumb.prefWidth(-1);
472 }
473 }
474
|