1 /* 2 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 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.NodeHelper; 29 import java.lang.ref.WeakReference; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 34 import javafx.animation.Timeline; 35 import javafx.animation.Transition; 36 import javafx.beans.binding.When; 37 import javafx.beans.property.BooleanProperty; 38 import javafx.beans.property.DoubleProperty; 39 import javafx.beans.value.WritableValue; 40 import javafx.css.CssMetaData; 41 import javafx.css.StyleableBooleanProperty; 42 import javafx.css.StyleableDoubleProperty; 43 import javafx.css.StyleableProperty; 44 import javafx.scene.Node; 45 import javafx.scene.control.Control; 46 import javafx.scene.control.ProgressBar; 47 import javafx.scene.control.ProgressIndicator; 48 import javafx.scene.control.SkinBase; 49 import javafx.scene.layout.Background; 50 import javafx.scene.layout.BackgroundFill; 51 import javafx.scene.layout.Region; 52 import javafx.scene.layout.StackPane; 53 import javafx.scene.paint.Color; 54 import javafx.util.Duration; 55 56 import javafx.css.converter.BooleanConverter; 57 import javafx.css.converter.SizeConverter; 58 59 import javafx.css.Styleable; 60 61 /** 62 * Default skin implementation for the {@link ProgressBar} control. 63 * 64 * @see ProgressBar 65 * @since 9 66 */ 67 public class ProgressBarSkin extends ProgressIndicatorSkin { 68 69 /*************************************************************************** 70 * * 71 * Private fields * 72 * * 73 **************************************************************************/ 74 75 private StackPane bar; 76 private StackPane track; 77 private Region clipRegion; 78 79 // clean up progress so we never go out of bounds or update graphics more than twice per pixel 80 private double barWidth; 81 82 83 84 /*************************************************************************** 85 * * 86 * Constructors * 87 * * 88 **************************************************************************/ 89 90 /** 91 * Creates a new ProgressBarSkin instance, installing the necessary child 92 * nodes into the Control {@link Control#getChildren() children} list. 93 * 94 * @param control The control that this skin should be installed onto. 95 */ 96 public ProgressBarSkin(ProgressBar control) { 97 super(control); 98 99 barWidth = ((int) (control.getWidth() - snappedLeftInset() - snappedRightInset()) * 2 * Math.min(1, Math.max(0, control.getProgress()))) / 2.0F; 100 101 control.widthProperty().addListener(observable -> updateProgress()); 102 103 initialize(); 104 getSkinnable().requestLayout(); 105 } 106 107 108 109 /*************************************************************************** 110 * * 111 * Properties * 112 * * 113 **************************************************************************/ 114 115 /** 116 * The length of the bouncing progress bar in indeterminate state 117 */ 118 private DoubleProperty indeterminateBarLength = null; 119 private DoubleProperty indeterminateBarLengthProperty() { 120 if (indeterminateBarLength == null) { 121 indeterminateBarLength = new StyleableDoubleProperty(60.0) { 122 123 @Override 124 public Object getBean() { 125 return ProgressBarSkin.this; 126 } 127 128 @Override 129 public String getName() { 130 return "indeterminateBarLength"; 131 } 132 133 @Override 134 public CssMetaData<ProgressBar,Number> getCssMetaData() { 135 return StyleableProperties.INDETERMINATE_BAR_LENGTH; 136 } 137 138 }; 139 } 140 return indeterminateBarLength; 141 } 142 143 private Double getIndeterminateBarLength() { 144 return indeterminateBarLength == null ? 60.0 : indeterminateBarLength.get(); 145 } 146 147 /** 148 * If the progress bar should escape the ends of the progress bar region in indeterminate state 149 */ 150 private BooleanProperty indeterminateBarEscape = null; 151 private BooleanProperty indeterminateBarEscapeProperty() { 152 if (indeterminateBarEscape == null) { 153 indeterminateBarEscape = new StyleableBooleanProperty(true) { 154 155 @Override 156 public Object getBean() { 157 return ProgressBarSkin.this; 158 } 159 160 @Override 161 public String getName() { 162 return "indeterminateBarEscape"; 163 } 164 165 @Override 166 public CssMetaData<ProgressBar,Boolean> getCssMetaData() { 167 return StyleableProperties.INDETERMINATE_BAR_ESCAPE; 168 } 169 170 171 }; 172 } 173 return indeterminateBarEscape; 174 } 175 176 private Boolean getIndeterminateBarEscape() { 177 return indeterminateBarEscape == null ? true : indeterminateBarEscape.get(); 178 } 179 180 /** 181 * If the progress bar should flip when it gets to the ends in indeterminate state 182 */ 183 private BooleanProperty indeterminateBarFlip = null; 184 private BooleanProperty indeterminateBarFlipProperty() { 185 if (indeterminateBarFlip == null) { 186 indeterminateBarFlip = new StyleableBooleanProperty(true) { 187 188 @Override 189 public Object getBean() { 190 return ProgressBarSkin.this; 191 } 192 193 @Override 194 public String getName() { 195 return "indeterminateBarFlip"; 196 } 197 198 @Override 199 public CssMetaData<ProgressBar,Boolean> getCssMetaData() { 200 return StyleableProperties.INDETERMINATE_BAR_FLIP; 201 } 202 203 }; 204 } 205 return indeterminateBarFlip; 206 } 207 208 private Boolean getIndeterminateBarFlip() { 209 return indeterminateBarFlip == null ? true : indeterminateBarFlip.get(); 210 } 211 212 /** 213 * How many seconds it should take for the indeterminate bar to go from 214 * one edge to the other 215 */ 216 private DoubleProperty indeterminateBarAnimationTime = null; 217 218 private DoubleProperty indeterminateBarAnimationTimeProperty() { 219 if (indeterminateBarAnimationTime == null) { 220 indeterminateBarAnimationTime = new StyleableDoubleProperty(2.0) { 221 222 @Override 223 public Object getBean() { 224 return ProgressBarSkin.this; 225 } 226 227 @Override 228 public String getName() { 229 return "indeterminateBarAnimationTime"; 230 } 231 232 @Override 233 public CssMetaData<ProgressBar,Number> getCssMetaData() { 234 return StyleableProperties.INDETERMINATE_BAR_ANIMATION_TIME; 235 } 236 237 238 }; 239 } 240 return indeterminateBarAnimationTime; 241 } 242 243 private double getIndeterminateBarAnimationTime() { 244 return indeterminateBarAnimationTime == null ? 2.0 : indeterminateBarAnimationTime.get(); 245 } 246 247 248 249 /*************************************************************************** 250 * * 251 * Public API * 252 * * 253 **************************************************************************/ 254 255 /** {@inheritDoc} */ 256 @Override public double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) { 257 return Node.BASELINE_OFFSET_SAME_AS_HEIGHT; 258 } 259 260 /** {@inheritDoc} */ 261 @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 262 return Math.max(100, leftInset + bar.prefWidth(getSkinnable().getWidth()) + rightInset); 263 } 264 265 /** {@inheritDoc} */ 266 @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 267 return topInset + bar.prefHeight(width) + bottomInset; 268 } 269 270 /** {@inheritDoc} */ 271 @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 272 return getSkinnable().prefWidth(height); 273 } 274 275 /** {@inheritDoc} */ 276 @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 277 return getSkinnable().prefHeight(width); 278 } 279 280 /** {@inheritDoc} */ 281 @Override protected void layoutChildren(final double x, final double y, 282 final double w, final double h) { 283 284 final ProgressIndicator control = getSkinnable(); 285 boolean isIndeterminate = control.isIndeterminate(); 286 287 // resize clip 288 clipRegion.resizeRelocate(0, 0, w, h); 289 290 track.resizeRelocate(x, y, w, h); 291 bar.resizeRelocate(x, y, isIndeterminate ? getIndeterminateBarLength() : barWidth, h); 292 293 // things should be invisible only when well below minimum length 294 track.setVisible(true); 295 296 // width might have changed so recreate our animation if needed 297 if (isIndeterminate) { 298 createIndeterminateTimeline(); 299 if (NodeHelper.isTreeShowing(getSkinnable())) { 300 indeterminateTransition.play(); 301 } 302 303 // apply clip 304 bar.setClip(clipRegion); 305 } else if (indeterminateTransition != null) { 306 indeterminateTransition.stop(); 307 indeterminateTransition = null; 308 309 // remove clip 310 bar.setClip(null); 311 bar.setScaleX(1); 312 bar.setTranslateX(0); 313 clipRegion.translateXProperty().unbind(); 314 } 315 } 316 317 318 319 /*************************************************************************** 320 * * 321 * Private implementation * 322 * * 323 **************************************************************************/ 324 325 /** {@inheritDoc} */ 326 @Override void initialize() { 327 track = new StackPane(); 328 track.getStyleClass().setAll("track"); 329 330 bar = new StackPane(); 331 bar.getStyleClass().setAll("bar"); 332 333 getChildren().setAll(track, bar); 334 335 // create a region to use as the clip for skin for animated indeterminate state 336 clipRegion = new Region(); 337 338 // listen to the backgrounds on the bar and apply them to the clip but making them solid black for 100% 339 // solid anywhere the bar draws 340 bar.backgroundProperty().addListener((observable, oldValue, newValue) -> { 341 if (newValue != null && !newValue.getFills().isEmpty()) { 342 final BackgroundFill[] fills = new BackgroundFill[newValue.getFills().size()]; 343 for (int i = 0; i < newValue.getFills().size(); i++) { 344 BackgroundFill bf = newValue.getFills().get(i); 345 fills[i] = new BackgroundFill(Color.BLACK,bf.getRadii(),bf.getInsets()); 346 } 347 clipRegion.setBackground(new Background(fills)); 348 } 349 }); 350 } 351 352 /** {@inheritDoc} */ 353 @Override void createIndeterminateTimeline() { 354 if (indeterminateTransition != null) indeterminateTransition.stop(); 355 356 ProgressIndicator control = getSkinnable(); 357 final double w = control.getWidth() - (snappedLeftInset() + snappedRightInset()); 358 final double startX = getIndeterminateBarEscape() ? -getIndeterminateBarLength() : 0; 359 final double endX = getIndeterminateBarEscape() ? w : w - getIndeterminateBarLength(); 360 361 // Set up the timeline. We do not want to reverse if we are not flipping. 362 indeterminateTransition = new IndeterminateTransition(startX, endX, this); 363 indeterminateTransition.setCycleCount(Timeline.INDEFINITE); 364 365 clipRegion.translateXProperty().bind(new When(bar.scaleXProperty().isEqualTo(-1.0, 1e-100)). 366 then(bar.translateXProperty().subtract(w).add(indeterminateBarLengthProperty())). 367 otherwise(bar.translateXProperty().negate())); 368 } 369 370 boolean wasIndeterminate = false; 371 372 /** {@inheritDoc} */ 373 @Override void updateProgress() { 374 ProgressIndicator control = getSkinnable(); 375 // RT-33789: if the ProgressBar was indeterminate and still is indeterminate, don't update the bar width 376 final boolean isIndeterminate = control.isIndeterminate(); 377 if (!(isIndeterminate && wasIndeterminate)) { 378 barWidth = ((int) (control.getWidth() - snappedLeftInset() - snappedRightInset()) * 2 * Math.min(1, Math.max(0, control.getProgress()))) / 2.0F; 379 getSkinnable().requestLayout(); 380 } 381 wasIndeterminate = isIndeterminate; 382 } 383 384 385 386 /*************************************************************************** 387 * * 388 * Stylesheet Handling * 389 * * 390 **************************************************************************/ 391 392 /* 393 * Super-lazy instantiation pattern from Bill Pugh. 394 */ 395 private static class StyleableProperties { 396 private static final CssMetaData<ProgressBar, Number> INDETERMINATE_BAR_LENGTH = 397 new CssMetaData<ProgressBar, Number>("-fx-indeterminate-bar-length", 398 SizeConverter.getInstance(), 60.0) { 399 400 @Override 401 public boolean isSettable(ProgressBar n) { 402 final ProgressBarSkin skin = (ProgressBarSkin) n.getSkin(); 403 return skin.indeterminateBarLength == null || 404 !skin.indeterminateBarLength.isBound(); 405 } 406 407 @Override 408 public StyleableProperty<Number> getStyleableProperty(ProgressBar n) { 409 final ProgressBarSkin skin = (ProgressBarSkin) n.getSkin(); 410 return (StyleableProperty<Number>) (WritableValue<Number>) skin.indeterminateBarLengthProperty(); 411 } 412 }; 413 414 private static final CssMetaData<ProgressBar, Boolean> INDETERMINATE_BAR_ESCAPE = 415 new CssMetaData<ProgressBar, Boolean>("-fx-indeterminate-bar-escape", 416 BooleanConverter.getInstance(), Boolean.TRUE) { 417 418 @Override 419 public boolean isSettable(ProgressBar n) { 420 final ProgressBarSkin skin = (ProgressBarSkin) n.getSkin(); 421 return skin.indeterminateBarEscape == null || 422 !skin.indeterminateBarEscape.isBound(); 423 } 424 425 @Override 426 public StyleableProperty<Boolean> getStyleableProperty(ProgressBar n) { 427 final ProgressBarSkin skin = (ProgressBarSkin) n.getSkin(); 428 return (StyleableProperty<Boolean>) (WritableValue<Boolean>) skin.indeterminateBarEscapeProperty(); 429 } 430 }; 431 432 private static final CssMetaData<ProgressBar, Boolean> INDETERMINATE_BAR_FLIP = 433 new CssMetaData<ProgressBar, Boolean>("-fx-indeterminate-bar-flip", 434 BooleanConverter.getInstance(), Boolean.TRUE) { 435 436 @Override 437 public boolean isSettable(ProgressBar n) { 438 final ProgressBarSkin skin = (ProgressBarSkin) n.getSkin(); 439 return skin.indeterminateBarFlip == null || 440 !skin.indeterminateBarFlip.isBound(); 441 } 442 443 @Override 444 public StyleableProperty<Boolean> getStyleableProperty(ProgressBar n) { 445 final ProgressBarSkin skin = (ProgressBarSkin) n.getSkin(); 446 return (StyleableProperty<Boolean>) (WritableValue<Boolean>) skin.indeterminateBarFlipProperty(); 447 } 448 }; 449 450 private static final CssMetaData<ProgressBar, Number> INDETERMINATE_BAR_ANIMATION_TIME = 451 new CssMetaData<ProgressBar, Number>("-fx-indeterminate-bar-animation-time", 452 SizeConverter.getInstance(), 2.0) { 453 454 @Override 455 public boolean isSettable(ProgressBar n) { 456 final ProgressBarSkin skin = (ProgressBarSkin) n.getSkin(); 457 return skin.indeterminateBarAnimationTime == null || 458 !skin.indeterminateBarAnimationTime.isBound(); 459 } 460 461 @Override 462 public StyleableProperty<Number> getStyleableProperty(ProgressBar n) { 463 final ProgressBarSkin skin = (ProgressBarSkin) n.getSkin(); 464 return (StyleableProperty<Number>) (WritableValue<Number>) skin.indeterminateBarAnimationTimeProperty(); 465 } 466 }; 467 468 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 469 470 static { 471 final List<CssMetaData<? extends Styleable, ?>> styleables = 472 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData()); 473 styleables.add(INDETERMINATE_BAR_LENGTH); 474 styleables.add(INDETERMINATE_BAR_ESCAPE); 475 styleables.add(INDETERMINATE_BAR_FLIP); 476 styleables.add(INDETERMINATE_BAR_ANIMATION_TIME); 477 STYLEABLES = Collections.unmodifiableList(styleables); 478 } 479 } 480 481 /** 482 * Returns the CssMetaData associated with this class, which may include the 483 * CssMetaData of its superclasses. 484 */ 485 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 486 return StyleableProperties.STYLEABLES; 487 } 488 489 /** 490 * {@inheritDoc} 491 */ 492 @Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 493 return getClassCssMetaData(); 494 } 495 496 497 498 /*************************************************************************** 499 * * 500 * Support classes * 501 * * 502 **************************************************************************/ 503 504 private static class IndeterminateTransition extends Transition { 505 private final WeakReference<ProgressBarSkin> skin; 506 private final double startX; 507 private final double endX; 508 private final boolean flip; 509 510 public IndeterminateTransition(double startX, double endX, ProgressBarSkin progressBarSkin) { 511 this.startX = startX; 512 this.endX = endX; 513 this.skin = new WeakReference<>(progressBarSkin); 514 this.flip = progressBarSkin.getIndeterminateBarFlip(); 515 progressBarSkin.getIndeterminateBarEscape(); 516 setCycleDuration(Duration.seconds(progressBarSkin.getIndeterminateBarAnimationTime() * (flip ? 2 : 1))); 517 } 518 519 @Override 520 protected void interpolate(double frac) { 521 ProgressBarSkin s = skin.get(); 522 if (s == null) { 523 stop(); 524 } else { 525 if (frac <= 0.5 || !flip) { 526 s.bar.setScaleX(-1); 527 s.bar.setTranslateX(startX + (flip ? 2 : 1) * frac * (endX - startX)); 528 } else { 529 s.bar.setScaleX(1); 530 s.bar.setTranslateX(startX + 2 * (1 - frac) * (endX - startX)); 531 } 532 } 533 } 534 } 535 }