1 /* 2 * Copyright (c) 2010, 2014, 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 java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 32 import javafx.animation.Animation; 33 import javafx.animation.KeyFrame; 34 import javafx.animation.KeyValue; 35 import javafx.animation.Timeline; 36 import javafx.beans.property.BooleanProperty; 37 import javafx.beans.property.IntegerProperty; 38 import javafx.beans.property.ObjectProperty; 39 import javafx.beans.value.WritableValue; 40 import javafx.collections.FXCollections; 41 import javafx.collections.ObservableList; 42 import javafx.geometry.NodeOrientation; 43 import javafx.geometry.VPos; 44 import javafx.scene.Node; 45 import javafx.scene.control.Control; 46 import javafx.scene.control.ProgressIndicator; 47 import javafx.scene.control.SkinBase; 48 import javafx.scene.layout.Pane; 49 import javafx.scene.layout.Region; 50 import javafx.scene.layout.StackPane; 51 import javafx.scene.paint.Color; 52 import javafx.scene.paint.Paint; 53 import javafx.scene.shape.Arc; 54 import javafx.scene.shape.ArcType; 55 import javafx.scene.shape.Circle; 56 import javafx.scene.text.Text; 57 import javafx.scene.transform.Scale; 58 import javafx.util.Duration; 59 import javafx.css.CssMetaData; 60 import javafx.css.StyleableObjectProperty; 61 import javafx.css.StyleableProperty; 62 import javafx.css.StyleableBooleanProperty; 63 import javafx.css.StyleableIntegerProperty; 64 import javafx.css.converter.BooleanConverter; 65 import javafx.css.converter.PaintConverter; 66 import javafx.css.converter.SizeConverter; 67 import com.sun.javafx.scene.control.skin.resources.ControlResources; 68 import javafx.css.Styleable; 69 70 /** 71 * Default skin implementation for the {@link ProgressIndicator} control. 72 * 73 * @see ProgressIndicator 74 * @since 9 75 */ 76 public class ProgressIndicatorSkin extends SkinBase<ProgressIndicator> { 77 78 /*************************************************************************** 79 * * 80 * Static fields * 81 * * 82 **************************************************************************/ 83 84 private static final String DONE = ControlResources.getString("ProgressIndicator.doneString"); 85 86 /** doneText is just used to know the size of done as that is the biggest text we need to allow for */ 87 private static final Text doneText = new Text(DONE); 88 static { 89 doneText.getStyleClass().add("text"); 90 } 91 92 93 94 /*************************************************************************** 95 * * 96 * Private fields * 97 * * 98 **************************************************************************/ 99 100 final Duration CLIPPED_DELAY = new Duration(300); 101 final Duration UNCLIPPED_DELAY = new Duration(0); 102 103 private IndeterminateSpinner spinner; 104 private DeterminateIndicator determinateIndicator; 105 private ProgressIndicator control; 106 107 Animation indeterminateTransition; 108 109 110 111 /*************************************************************************** 112 * * 113 * Constructors * 114 * * 115 **************************************************************************/ 116 117 /** 118 * Creates a new ProgressIndicatorSkin instance, installing the necessary child 119 * nodes into the Control {@link Control#getChildren() children} list, as 120 * well as the necessary {@link Node#getInputMap() input mappings} for 121 * handling key, mouse, etc events. 122 * 123 * @param control The control that this skin should be installed onto. 124 */ 125 public ProgressIndicatorSkin(ProgressIndicator control) { 126 super(control); 127 128 this.control = control; 129 130 // register listeners 131 registerChangeListener(control.indeterminateProperty(), e -> initialize()); 132 registerChangeListener(control.progressProperty(), e -> updateProgress()); 133 registerChangeListener(control.visibleProperty(), e -> updateAnimation()); 134 registerChangeListener(control.parentProperty(), e -> updateAnimation()); 135 registerChangeListener(control.sceneProperty(), e -> updateAnimation()); 136 137 initialize(); 138 } 139 140 141 142 /*************************************************************************** 143 * * 144 * Properties * 145 * * 146 **************************************************************************/ 147 148 /** 149 * The colour of the progress segment. 150 */ 151 private ObjectProperty<Paint> progressColor = new StyleableObjectProperty<Paint>(null) { 152 @Override protected void invalidated() { 153 final Paint value = get(); 154 if (value != null && !(value instanceof Color)) { 155 if (isBound()) { 156 unbind(); 157 } 158 set(null); 159 throw new IllegalArgumentException("Only Color objects are supported"); 160 } 161 if (spinner!=null) spinner.setFillOverride(value); 162 if (determinateIndicator!=null) determinateIndicator.setFillOverride(value); 163 } 164 165 @Override public Object getBean() { 166 return ProgressIndicatorSkin.this; 167 } 168 169 @Override public String getName() { 170 return "progressColorProperty"; 171 } 172 173 @Override public CssMetaData<ProgressIndicator,Paint> getCssMetaData() { 174 return PROGRESS_COLOR; 175 } 176 }; 177 178 Paint getProgressColor() { 179 return progressColor.get(); 180 } 181 182 /** 183 * The number of segments in the spinner. 184 */ 185 private IntegerProperty indeterminateSegmentCount = new StyleableIntegerProperty(8) { 186 @Override protected void invalidated() { 187 if (spinner!=null) spinner.rebuild(); 188 } 189 190 @Override public Object getBean() { 191 return ProgressIndicatorSkin.this; 192 } 193 194 @Override public String getName() { 195 return "indeterminateSegmentCount"; 196 } 197 198 @Override public CssMetaData<ProgressIndicator,Number> getCssMetaData() { 199 return INDETERMINATE_SEGMENT_COUNT; 200 } 201 }; 202 203 /** 204 * True if the progress indicator should rotate as well as animate opacity. 205 */ 206 private final BooleanProperty spinEnabled = new StyleableBooleanProperty(false) { 207 @Override protected void invalidated() { 208 if (spinner!=null) spinner.setSpinEnabled(get()); 209 } 210 211 @Override public CssMetaData<ProgressIndicator,Boolean> getCssMetaData() { 212 return SPIN_ENABLED; 213 } 214 215 @Override public Object getBean() { 216 return ProgressIndicatorSkin.this; 217 } 218 219 @Override public String getName() { 220 return "spinEnabled"; 221 } 222 }; 223 224 225 226 /*************************************************************************** 227 * * 228 * Public API * 229 * * 230 **************************************************************************/ 231 232 /** {@inheritDoc} */ 233 @Override public void dispose() { 234 super.dispose(); 235 236 if (indeterminateTransition != null) { 237 indeterminateTransition.stop(); 238 indeterminateTransition = null; 239 } 240 241 if (spinner != null) { 242 spinner = null; 243 } 244 245 control = null; 246 } 247 248 /** {@inheritDoc} */ 249 @Override protected void layoutChildren(final double x, final double y, 250 final double w, final double h) { 251 if (spinner != null && control.isIndeterminate()) { 252 spinner.layoutChildren(); 253 spinner.resizeRelocate(0, 0, w, h); 254 } else if (determinateIndicator != null) { 255 determinateIndicator.layoutChildren(); 256 determinateIndicator.resizeRelocate(0, 0, w, h); 257 } 258 } 259 260 261 262 /*************************************************************************** 263 * * 264 * Private implementation * 265 * * 266 **************************************************************************/ 267 268 void initialize() { 269 boolean isIndeterminate = control.isIndeterminate(); 270 if (isIndeterminate) { 271 // clean up determinateIndicator 272 determinateIndicator = null; 273 274 // create spinner 275 spinner = new IndeterminateSpinner(spinEnabled.get(), progressColor.get()); 276 getChildren().setAll(spinner); 277 if (control.impl_isTreeVisible()) { 278 if (indeterminateTransition != null) { 279 indeterminateTransition.play(); 280 } 281 } 282 } else { 283 // clean up after spinner 284 if (spinner != null) { 285 if (indeterminateTransition != null) { 286 indeterminateTransition.stop(); 287 } 288 spinner = null; 289 } 290 291 // create determinateIndicator 292 determinateIndicator = new DeterminateIndicator(control, this, progressColor.get()); 293 getChildren().setAll(determinateIndicator); 294 } 295 } 296 297 void updateProgress() { 298 if (determinateIndicator != null) { 299 determinateIndicator.updateProgress(control.getProgress()); 300 } 301 } 302 303 void createIndeterminateTimeline() { 304 if (spinner != null) { 305 spinner.rebuildTimeline(); 306 } 307 } 308 309 void pauseTimeline(boolean pause) { 310 if (getSkinnable().isIndeterminate()) { 311 if (indeterminateTransition == null) { 312 createIndeterminateTimeline(); 313 } 314 if (pause) { 315 indeterminateTransition.pause(); 316 } else { 317 indeterminateTransition.play(); 318 } 319 } 320 } 321 322 void updateAnimation() { 323 ProgressIndicator control = getSkinnable(); 324 final boolean isTreeVisible = control.isVisible() && 325 control.getParent() != null && 326 control.getScene() != null; 327 if (indeterminateTransition != null) { 328 pauseTimeline(!isTreeVisible); 329 } else if (isTreeVisible) { 330 createIndeterminateTimeline(); 331 } 332 } 333 334 335 336 /*************************************************************************** 337 * * 338 * Stylesheet Handling * 339 * * 340 **************************************************************************/ 341 342 private static final CssMetaData<ProgressIndicator,Paint> PROGRESS_COLOR = 343 new CssMetaData<ProgressIndicator,Paint>("-fx-progress-color", 344 PaintConverter.getInstance(), null) { 345 346 @Override 347 public boolean isSettable(ProgressIndicator n) { 348 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin(); 349 return skin.progressColor == null || 350 !skin.progressColor.isBound(); 351 } 352 353 @Override 354 public StyleableProperty<Paint> getStyleableProperty(ProgressIndicator n) { 355 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin(); 356 return (StyleableProperty<Paint>)(WritableValue<Paint>)skin.progressColor; 357 } 358 }; 359 private static final CssMetaData<ProgressIndicator,Number> INDETERMINATE_SEGMENT_COUNT = 360 new CssMetaData<ProgressIndicator,Number>("-fx-indeterminate-segment-count", 361 SizeConverter.getInstance(), 8) { 362 363 @Override public boolean isSettable(ProgressIndicator n) { 364 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin(); 365 return skin.indeterminateSegmentCount == null || 366 !skin.indeterminateSegmentCount.isBound(); 367 } 368 369 @Override public StyleableProperty<Number> getStyleableProperty(ProgressIndicator n) { 370 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin(); 371 return (StyleableProperty<Number>)(WritableValue<Number>)skin.indeterminateSegmentCount; 372 } 373 }; 374 private static final CssMetaData<ProgressIndicator,Boolean> SPIN_ENABLED = 375 new CssMetaData<ProgressIndicator,Boolean>("-fx-spin-enabled", BooleanConverter.getInstance(), Boolean.FALSE) { 376 377 @Override public boolean isSettable(ProgressIndicator node) { 378 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) node.getSkin(); 379 return skin.spinEnabled == null || !skin.spinEnabled.isBound(); 380 } 381 382 @Override public StyleableProperty<Boolean> getStyleableProperty(ProgressIndicator node) { 383 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) node.getSkin(); 384 return (StyleableProperty<Boolean>)(WritableValue<Boolean>)skin.spinEnabled; 385 } 386 }; 387 388 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 389 static { 390 final List<CssMetaData<? extends Styleable, ?>> styleables = 391 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData()); 392 styleables.add(PROGRESS_COLOR); 393 styleables.add(INDETERMINATE_SEGMENT_COUNT); 394 styleables.add(SPIN_ENABLED); 395 STYLEABLES = Collections.unmodifiableList(styleables); 396 } 397 398 /** 399 * Returns the CssMetaData associated with this class, which may include the 400 * CssMetaData of its super classes. 401 */ 402 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 403 return STYLEABLES; 404 } 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override 410 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 411 return getClassCssMetaData(); 412 } 413 414 415 416 /*************************************************************************** 417 * * 418 * Support classes * 419 * * 420 **************************************************************************/ 421 422 private final class DeterminateIndicator extends Region { 423 private double textGap = 2.0F; 424 425 // only update progress text on whole percentages 426 private int intProgress; 427 428 // only update pie arc to nearest degree 429 private int degProgress; 430 private Text text; 431 private StackPane indicator; 432 private StackPane progress; 433 private StackPane tick; 434 private Arc arcShape; 435 private Circle indicatorCircle; 436 437 public DeterminateIndicator(ProgressIndicator control, ProgressIndicatorSkin s, Paint fillOverride) { 438 439 getStyleClass().add("determinate-indicator"); 440 441 intProgress = (int) Math.round(control.getProgress() * 100.0) ; 442 degProgress = (int) (360 * control.getProgress()); 443 444 getChildren().clear(); 445 446 text = new Text((control.getProgress() >= 1) ? (DONE) : ("" + intProgress + "%")); 447 text.setTextOrigin(VPos.TOP); 448 text.getStyleClass().setAll("text", "percentage"); 449 450 // The circular background for the progress pie piece 451 indicator = new StackPane(); 452 indicator.setScaleShape(false); 453 indicator.setCenterShape(false); 454 indicator.getStyleClass().setAll("indicator"); 455 indicatorCircle = new Circle(); 456 indicator.setShape(indicatorCircle); 457 458 // The shape for our progress pie piece 459 arcShape = new Arc(); 460 arcShape.setType(ArcType.ROUND); 461 arcShape.setStartAngle(90.0F); 462 463 // Our progress pie piece 464 progress = new StackPane(); 465 progress.getStyleClass().setAll("progress"); 466 progress.setScaleShape(false); 467 progress.setCenterShape(false); 468 progress.setShape(arcShape); 469 progress.getChildren().clear(); 470 setFillOverride(fillOverride); 471 472 // The check mark that's drawn at 100% 473 tick = new StackPane(); 474 tick.getStyleClass().setAll("tick"); 475 476 getChildren().setAll(indicator, progress, text, tick); 477 updateProgress(control.getProgress()); 478 } 479 480 private void setFillOverride(Paint fillOverride) { 481 if (fillOverride instanceof Color) { 482 Color c = (Color)fillOverride; 483 progress.setStyle("-fx-background-color: rgba("+((int)(255*c.getRed()))+","+((int)(255*c.getGreen()))+","+((int)(255*c.getBlue()))+","+c.getOpacity()+");"); 484 } else { 485 progress.setStyle(null); 486 } 487 } 488 489 @Override public boolean usesMirroring() { 490 // This is used instead of setting NodeOrientation, 491 // allowing the Text node to inherit the current 492 // orientation. 493 return false; 494 } 495 496 private void updateProgress(double progress) { 497 intProgress = (int) Math.round(progress * 100.0) ; 498 text.setText((progress >= 1) ? (DONE) : ("" + intProgress + "%")); 499 500 degProgress = (int) (360 * progress); 501 arcShape.setLength(-degProgress); 502 requestLayout(); 503 } 504 505 @Override protected void layoutChildren() { 506 // Position and size the circular background 507 double doneTextHeight = doneText.getLayoutBounds().getHeight(); 508 final double left = control.snappedLeftInset(); 509 final double right = control.snappedRightInset(); 510 final double top = control.snappedTopInset(); 511 final double bottom = control.snappedBottomInset(); 512 513 /* 514 ** use the min of width, or height, keep it a circle 515 */ 516 final double areaW = control.getWidth() - left - right; 517 final double areaH = control.getHeight() - top - bottom - textGap - doneTextHeight; 518 final double radiusW = areaW / 2; 519 final double radiusH = areaH / 2; 520 final double radius = Math.floor(Math.min(radiusW, radiusH)); 521 final double centerX = snapPosition(left + radiusW); 522 final double centerY = snapPosition(top + radius); 523 524 // find radius that fits inside radius - insetsPadding 525 final double iLeft = indicator.snappedLeftInset(); 526 final double iRight = indicator.snappedRightInset(); 527 final double iTop = indicator.snappedTopInset(); 528 final double iBottom = indicator.snappedBottomInset(); 529 final double progressRadius = snapSize(Math.min( 530 Math.min(radius - iLeft, radius - iRight), 531 Math.min(radius - iTop, radius - iBottom))); 532 533 indicatorCircle.setRadius(radius); 534 indicator.setLayoutX(centerX); 535 indicator.setLayoutY(centerY); 536 537 arcShape.setRadiusX(progressRadius); 538 arcShape.setRadiusY(progressRadius); 539 progress.setLayoutX(centerX); 540 progress.setLayoutY(centerY); 541 542 // find radius that fits inside progressRadius - progressInsets 543 final double pLeft = progress.snappedLeftInset(); 544 final double pRight = progress.snappedRightInset(); 545 final double pTop = progress.snappedTopInset(); 546 final double pBottom = progress.snappedBottomInset(); 547 final double indicatorRadius = snapSize(Math.min( 548 Math.min(progressRadius - pLeft, progressRadius - pRight), 549 Math.min(progressRadius - pTop, progressRadius - pBottom))); 550 551 // find size of spare box that fits inside indicator radius 552 double squareBoxHalfWidth = Math.ceil(Math.sqrt((indicatorRadius * indicatorRadius) / 2)); 553 double squareBoxHalfWidth2 = indicatorRadius * (Math.sqrt(2)/2); 554 555 tick.setLayoutX(centerX - squareBoxHalfWidth); 556 tick.setLayoutY(centerY - squareBoxHalfWidth); 557 tick.resize(squareBoxHalfWidth + squareBoxHalfWidth, squareBoxHalfWidth + squareBoxHalfWidth); 558 tick.setVisible(control.getProgress() >= 1); 559 560 // if the % text can't fit anywhere in the bounds then don't display it 561 double textWidth = text.getLayoutBounds().getWidth(); 562 double textHeight = text.getLayoutBounds().getHeight(); 563 if (control.getWidth() >= textWidth && control.getHeight() >= textHeight) { 564 if (!text.isVisible()) text.setVisible(true); 565 text.setLayoutY(snapPosition(centerY + radius + textGap)); 566 text.setLayoutX(snapPosition(centerX - (textWidth/2))); 567 } else { 568 if (text.isVisible()) text.setVisible(false); 569 } 570 } 571 572 @Override protected double computePrefWidth(double height) { 573 final double left = control.snappedLeftInset(); 574 final double right = control.snappedRightInset(); 575 final double iLeft = indicator.snappedLeftInset(); 576 final double iRight = indicator.snappedRightInset(); 577 final double iTop = indicator.snappedTopInset(); 578 final double iBottom = indicator.snappedBottomInset(); 579 final double indicatorMax = snapSize(Math.max(Math.max(iLeft, iRight), Math.max(iTop, iBottom))); 580 final double pLeft = progress.snappedLeftInset(); 581 final double pRight = progress.snappedRightInset(); 582 final double pTop = progress.snappedTopInset(); 583 final double pBottom = progress.snappedBottomInset(); 584 final double progressMax = snapSize(Math.max(Math.max(pLeft, pRight), Math.max(pTop, pBottom))); 585 final double tLeft = tick.snappedLeftInset(); 586 final double tRight = tick.snappedRightInset(); 587 final double indicatorWidth = indicatorMax + progressMax + tLeft + tRight + progressMax + indicatorMax; 588 return left + Math.max(indicatorWidth, doneText.getLayoutBounds().getWidth()) + right; 589 } 590 591 @Override protected double computePrefHeight(double width) { 592 final double top = control.snappedTopInset(); 593 final double bottom = control.snappedBottomInset(); 594 final double iLeft = indicator.snappedLeftInset(); 595 final double iRight = indicator.snappedRightInset(); 596 final double iTop = indicator.snappedTopInset(); 597 final double iBottom = indicator.snappedBottomInset(); 598 final double indicatorMax = snapSize(Math.max(Math.max(iLeft, iRight), Math.max(iTop, iBottom))); 599 final double pLeft = progress.snappedLeftInset(); 600 final double pRight = progress.snappedRightInset(); 601 final double pTop = progress.snappedTopInset(); 602 final double pBottom = progress.snappedBottomInset(); 603 final double progressMax = snapSize(Math.max(Math.max(pLeft, pRight), Math.max(pTop, pBottom))); 604 final double tTop = tick.snappedTopInset(); 605 final double tBottom = tick.snappedBottomInset(); 606 final double indicatorHeight = indicatorMax + progressMax + tTop + tBottom + progressMax + indicatorMax; 607 return top + indicatorHeight + textGap + doneText.getLayoutBounds().getHeight() + bottom; 608 } 609 610 @Override protected double computeMaxWidth(double height) { 611 return computePrefWidth(height); 612 } 613 614 @Override protected double computeMaxHeight(double width) { 615 return computePrefHeight(width); 616 } 617 } 618 619 620 private final class IndeterminateSpinner extends Region { 621 private IndicatorPaths pathsG; 622 private final List<Double> opacities = new ArrayList<>(); 623 private boolean spinEnabled = false; 624 private Paint fillOverride = null; 625 626 private IndeterminateSpinner(boolean spinEnabled, Paint fillOverride) { 627 this.spinEnabled = spinEnabled; 628 this.fillOverride = fillOverride; 629 630 setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); 631 getStyleClass().setAll("spinner"); 632 633 pathsG = new IndicatorPaths(); 634 getChildren().add(pathsG); 635 rebuild(); 636 637 rebuildTimeline(); 638 639 } 640 641 public void setFillOverride(Paint fillOverride) { 642 this.fillOverride = fillOverride; 643 rebuild(); 644 } 645 646 public void setSpinEnabled(boolean spinEnabled) { 647 this.spinEnabled = spinEnabled; 648 rebuildTimeline(); 649 } 650 651 private void rebuildTimeline() { 652 if (spinEnabled) { 653 if (indeterminateTransition == null) { 654 indeterminateTransition = new Timeline(); 655 indeterminateTransition.setCycleCount(Timeline.INDEFINITE); 656 indeterminateTransition.setDelay(UNCLIPPED_DELAY); 657 } else { 658 indeterminateTransition.stop(); 659 ((Timeline)indeterminateTransition).getKeyFrames().clear(); 660 } 661 final ObservableList<KeyFrame> keyFrames = FXCollections.<KeyFrame>observableArrayList(); 662 663 keyFrames.add(new KeyFrame(Duration.millis(1), new KeyValue(pathsG.rotateProperty(), 360))); 664 keyFrames.add(new KeyFrame(Duration.millis(3900), new KeyValue(pathsG.rotateProperty(), 0))); 665 666 for (int i = 100; i <= 3900; i += 100) { 667 keyFrames.add(new KeyFrame(Duration.millis(i), event -> shiftColors())); 668 } 669 670 ((Timeline)indeterminateTransition).getKeyFrames().setAll(keyFrames); 671 indeterminateTransition.playFromStart(); 672 } else { 673 if (indeterminateTransition != null) { 674 indeterminateTransition.stop(); 675 ((Timeline)indeterminateTransition).getKeyFrames().clear(); 676 indeterminateTransition = null; 677 } 678 } 679 } 680 681 private class IndicatorPaths extends Pane { 682 @Override protected double computePrefWidth(double height) { 683 double w = 0; 684 for(Node child: getChildren()) { 685 if (child instanceof Region) { 686 Region region = (Region)child; 687 if (region.getShape() != null) { 688 w = Math.max(w,region.getShape().getLayoutBounds().getMaxX()); 689 } else { 690 w = Math.max(w,region.prefWidth(height)); 691 } 692 } 693 } 694 return w; 695 } 696 697 @Override protected double computePrefHeight(double width) { 698 double h = 0; 699 for(Node child: getChildren()) { 700 if (child instanceof Region) { 701 Region region = (Region)child; 702 if (region.getShape() != null) { 703 h = Math.max(h,region.getShape().getLayoutBounds().getMaxY()); 704 } else { 705 h = Math.max(h,region.prefHeight(width)); 706 } 707 } 708 } 709 return h; 710 } 711 712 @Override protected void layoutChildren() { 713 // calculate scale 714 double scale = getWidth() / computePrefWidth(-1); 715 for(Node child: getChildren()) { 716 if (child instanceof Region) { 717 Region region = (Region)child; 718 if (region.getShape() != null) { 719 region.resize( 720 region.getShape().getLayoutBounds().getMaxX(), 721 region.getShape().getLayoutBounds().getMaxY() 722 ); 723 region.getTransforms().setAll(new Scale(scale,scale,0,0)); 724 } else { 725 region.autosize(); 726 } 727 } 728 } 729 } 730 } 731 732 @Override protected void layoutChildren() { 733 final double w = control.getWidth() - control.snappedLeftInset() - control.snappedRightInset(); 734 final double h = control.getHeight() - control.snappedTopInset() - control.snappedBottomInset(); 735 final double prefW = pathsG.prefWidth(-1); 736 final double prefH = pathsG.prefHeight(-1); 737 double scaleX = w / prefW; 738 double scale = scaleX; 739 if ((scaleX * prefH) > h) { 740 scale = h / prefH; 741 } 742 double indicatorW = prefW * scale; 743 double indicatorH = prefH * scale; 744 pathsG.resizeRelocate((w - indicatorW) / 2, (h - indicatorH) / 2, indicatorW, indicatorH); 745 } 746 747 private void rebuild() { 748 // update indeterminate indicator 749 final int segments = indeterminateSegmentCount.get(); 750 opacities.clear(); 751 pathsG.getChildren().clear(); 752 final double step = 0.8/(segments-1); 753 for (int i = 0; i < segments; i++) { 754 Region region = new Region(); 755 region.setScaleShape(false); 756 region.setCenterShape(false); 757 region.getStyleClass().addAll("segment", "segment" + i); 758 if (fillOverride instanceof Color) { 759 Color c = (Color)fillOverride; 760 region.setStyle("-fx-background-color: rgba("+((int)(255*c.getRed()))+","+((int)(255*c.getGreen()))+","+((int)(255*c.getBlue()))+","+c.getOpacity()+");"); 761 } else { 762 region.setStyle(null); 763 } 764 pathsG.getChildren().add(region); 765 opacities.add(Math.max(0.1, (1.0 - (step*i)))); 766 } 767 } 768 769 private void shiftColors() { 770 if (opacities.size() <= 0) return; 771 final int segments = indeterminateSegmentCount.get(); 772 Collections.rotate(opacities, -1); 773 for (int i = 0; i < segments; i++) { 774 pathsG.getChildren().get(i).setOpacity(opacities.get(i)); 775 } 776 } 777 } 778 }