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