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 if ((control.getScene() != null) && (control.getScene().getWindow() != null)) { 197 registerChangeListener(control.getScene().getWindow().showingProperty(), "WINDOWSHOWING"); 198 } 199 200 initialize(); 201 } 202 203 204 /*************************************************************************** 205 * * 206 * API (for ProgressBarSkin) * 207 * * 208 **************************************************************************/ 209 210 @Override protected void handleControlPropertyChanged(String p) { 211 super.handleControlPropertyChanged(p); 212 213 if ("INDETERMINATE".equals(p)) { 214 initialize(); 215 } else if ("PROGRESS".equals(p)) { 216 updateProgress(); 217 } else if ("VISIBLE".equals(p)) { 218 updateAnimation(); 219 } else if ("PARENT".equals(p)) { 220 updateAnimation(); 221 } else if ("SCENE".equals(p)) { 222 updateAnimation(); 223 } else if ("WINDOWSHOWING".equals(p)) { 224 updateAnimation(); 225 } 226 } 227 228 protected void initialize() { 229 boolean isIndeterminate = control.isIndeterminate(); 230 if (isIndeterminate) { 231 // clean up determinateIndicator 232 determinateIndicator = null; 233 234 // create spinner 235 spinner = new IndeterminateSpinner(spinEnabled.get(), progressColor.get()); 236 getChildren().setAll(spinner); 237 if (control.impl_isTreeVisible()) { 238 if (indeterminateTransition != null) { 239 indeterminateTransition.play(); 240 } 241 } 242 } else { 243 // clean up after spinner 244 if (spinner != null) { 245 if (indeterminateTransition != null) { 246 indeterminateTransition.stop(); 247 } 248 spinner = null; 249 } 250 251 // create determinateIndicator 252 determinateIndicator = new DeterminateIndicator(control, this, progressColor.get()); 253 getChildren().setAll(determinateIndicator); 254 } 255 } 256 257 @Override public void dispose() { 258 super.dispose(); 259 260 if (indeterminateTransition != null) { 261 indeterminateTransition.stop(); 262 indeterminateTransition = null; 263 } 264 265 if (spinner != null) { 266 spinner = null; 267 } 268 269 control = null; 270 } 271 272 protected void updateProgress() { 273 if (determinateIndicator != null) { 274 determinateIndicator.updateProgress(control.getProgress()); 275 } 276 } 277 278 protected void createIndeterminateTimeline() { 279 if (spinner != null) { 280 spinner.rebuildTimeline(); 281 } 282 } 283 284 protected void pauseTimeline(boolean pause) { 285 if (getSkinnable().isIndeterminate()) { 286 if (indeterminateTransition == null) { 287 createIndeterminateTimeline(); 288 } 289 if (pause) { 290 indeterminateTransition.pause(); 291 } else { 292 indeterminateTransition.play(); 293 } 294 } 295 } 296 297 protected void updateAnimation() { 298 ProgressIndicator control = getSkinnable(); 299 final boolean isTreeVisible = control.isVisible() && 300 control.getParent() != null && 301 control.getScene() != null && 302 control.getScene().getWindow() != null && 303 control.getScene().getWindow().isShowing(); 304 if (indeterminateTransition != null) { 305 pauseTimeline(! isTreeVisible); 306 } else if (isTreeVisible) { 307 createIndeterminateTimeline(); 308 } 309 } 310 311 312 313 /*************************************************************************** 314 * * 315 * Listeners * 316 * * 317 **************************************************************************/ 318 319 320 321 322 /*************************************************************************** 323 * * 324 * Layout * 325 * * 326 **************************************************************************/ 327 328 @Override protected void layoutChildren(final double x, final double y, 329 final double w, final double h) { 330 if (spinner != null && control.isIndeterminate()) { 331 spinner.layoutChildren(); 332 spinner.resizeRelocate(0, 0, w, h); 333 } else if (determinateIndicator != null) { 334 determinateIndicator.layoutChildren(); 335 determinateIndicator.resizeRelocate(0, 0, w, h); 336 } 337 } 338 339 340 341 /*************************************************************************** 342 * * 343 * DeterminateIndicator * 344 * * 345 **************************************************************************/ 346 347 private class DeterminateIndicator extends Region { 348 private double textGap = 2.0F; 349 350 // only update progress text on whole percentages 351 private int intProgress; 352 353 // only update pie arc to nearest degree 354 private int degProgress; 355 private Text text; 356 private StackPane indicator; 357 private StackPane progress; 358 private StackPane tick; 359 private Arc arcShape; 360 private Circle indicatorCircle; 361 362 public DeterminateIndicator(ProgressIndicator control, ProgressIndicatorSkin s, Paint fillOverride) { 363 364 getStyleClass().add("determinate-indicator"); 365 366 intProgress = (int) Math.round(control.getProgress() * 100.0) ; 367 degProgress = (int) (360 * control.getProgress()); 368 369 getChildren().clear(); 370 371 text = new Text((control.getProgress() >= 1) ? (DONE) : ("" + intProgress + "%")); 372 text.setTextOrigin(VPos.TOP); 373 text.getStyleClass().setAll("text", "percentage"); 374 375 // The circular background for the progress pie piece 376 indicator = new StackPane(); 377 indicator.setScaleShape(false); 378 indicator.setCenterShape(false); 379 indicator.getStyleClass().setAll("indicator"); 380 indicatorCircle = new Circle(); 381 indicator.setShape(indicatorCircle); 382 383 // The shape for our progress pie piece 384 arcShape = new Arc(); 385 arcShape.setType(ArcType.ROUND); 386 arcShape.setStartAngle(90.0F); 387 388 // Our progress pie piece 389 progress = new StackPane(); 390 progress.getStyleClass().setAll("progress"); 391 progress.setScaleShape(false); 392 progress.setCenterShape(false); 393 progress.setShape(arcShape); 394 progress.getChildren().clear(); 395 setFillOverride(fillOverride); 396 397 // The check mark that's drawn at 100% 398 tick = new StackPane(); 399 tick.getStyleClass().setAll("tick"); 400 401 getChildren().setAll(indicator, progress, text, tick); 402 updateProgress(control.getProgress()); 403 } 404 405 private void setFillOverride(Paint fillOverride) { 406 if (fillOverride instanceof Color) { 407 Color c = (Color)fillOverride; 408 progress.setStyle("-fx-background-color: rgba("+((int)(255*c.getRed()))+","+((int)(255*c.getGreen()))+","+((int)(255*c.getBlue()))+","+c.getOpacity()+");"); 409 } else { 410 progress.setStyle(null); 411 } 412 } 413 414 @Override public boolean usesMirroring() { 415 // This is used instead of setting NodeOrientation, 416 // allowing the Text node to inherit the current 417 // orientation. 418 return false; 419 } 420 421 private void updateProgress(double progress) { 422 intProgress = (int) Math.round(progress * 100.0) ; 423 text.setText((progress >= 1) ? (DONE) : ("" + intProgress + "%")); 424 425 degProgress = (int) (360 * progress); 426 arcShape.setLength(-degProgress); 427 requestLayout(); 428 } 429 430 @Override protected void layoutChildren() { 431 // Position and size the circular background 432 double doneTextHeight = doneText.getLayoutBounds().getHeight(); 433 final double left = control.snappedLeftInset(); 434 final double right = control.snappedRightInset(); 435 final double top = control.snappedTopInset(); 436 final double bottom = control.snappedBottomInset(); 437 438 /* 439 ** use the min of width, or height, keep it a circle 440 */ 441 final double areaW = control.getWidth() - left - right; 442 final double areaH = control.getHeight() - top - bottom - textGap - doneTextHeight; 443 final double radiusW = areaW / 2; 444 final double radiusH = areaH / 2; 445 final double radius = Math.floor(Math.min(radiusW, radiusH)); 446 final double centerX = snapPosition(left + radiusW); 447 final double centerY = snapPosition(top + radius); 448 449 // find radius that fits inside radius - insetsPadding 450 final double iLeft = indicator.snappedLeftInset(); 451 final double iRight = indicator.snappedRightInset(); 452 final double iTop = indicator.snappedTopInset(); 453 final double iBottom = indicator.snappedBottomInset(); 454 final double progressRadius = snapSize(Math.min( 455 Math.min(radius - iLeft, radius - iRight), 456 Math.min(radius - iTop, radius - iBottom))); 457 458 indicatorCircle.setRadius(radius); 459 indicator.setLayoutX(centerX); 460 indicator.setLayoutY(centerY); 461 462 arcShape.setRadiusX(progressRadius); 463 arcShape.setRadiusY(progressRadius); 464 progress.setLayoutX(centerX); 465 progress.setLayoutY(centerY); 466 467 // find radius that fits inside progressRadius - progressInsets 468 final double pLeft = progress.snappedLeftInset(); 469 final double pRight = progress.snappedRightInset(); 470 final double pTop = progress.snappedTopInset(); 471 final double pBottom = progress.snappedBottomInset(); 472 final double indicatorRadius = snapSize(Math.min( 473 Math.min(progressRadius - pLeft, progressRadius - pRight), 474 Math.min(progressRadius - pTop, progressRadius - pBottom))); 475 476 // find size of spare box that fits inside indicator radius 477 double squareBoxHalfWidth = Math.ceil(Math.sqrt((indicatorRadius * indicatorRadius) / 2)); 478 double squareBoxHalfWidth2 = indicatorRadius * (Math.sqrt(2)/2); 479 480 tick.setLayoutX(centerX - squareBoxHalfWidth); 481 tick.setLayoutY(centerY - squareBoxHalfWidth); 482 tick.resize(squareBoxHalfWidth + squareBoxHalfWidth, squareBoxHalfWidth + squareBoxHalfWidth); 483 tick.setVisible(control.getProgress() >= 1); 484 485 // if the % text can't fit anywhere in the bounds then don't display it 486 double textWidth = text.getLayoutBounds().getWidth(); 487 double textHeight = text.getLayoutBounds().getHeight(); 488 if (control.getWidth() >= textWidth && control.getHeight() >= textHeight) { 489 if (!text.isVisible()) text.setVisible(true); 490 text.setLayoutY(snapPosition(centerY + radius + textGap)); 491 text.setLayoutX(snapPosition(centerX - (textWidth/2))); 492 } else { 493 if (text.isVisible()) text.setVisible(false); 494 } 495 } 496 497 @Override protected double computePrefWidth(double height) { 498 final double left = control.snappedLeftInset(); 499 final double right = control.snappedRightInset(); 500 final double iLeft = indicator.snappedLeftInset(); 501 final double iRight = indicator.snappedRightInset(); 502 final double iTop = indicator.snappedTopInset(); 503 final double iBottom = indicator.snappedBottomInset(); 504 final double indicatorMax = snapSize(Math.max(Math.max(iLeft, iRight), Math.max(iTop, iBottom))); 505 final double pLeft = progress.snappedLeftInset(); 506 final double pRight = progress.snappedRightInset(); 507 final double pTop = progress.snappedTopInset(); 508 final double pBottom = progress.snappedBottomInset(); 509 final double progressMax = snapSize(Math.max(Math.max(pLeft, pRight), Math.max(pTop, pBottom))); 510 final double tLeft = tick.snappedLeftInset(); 511 final double tRight = tick.snappedRightInset(); 512 final double indicatorWidth = indicatorMax + progressMax + tLeft + tRight + progressMax + indicatorMax; 513 return left + Math.max(indicatorWidth, doneText.getLayoutBounds().getWidth()) + right; 514 } 515 516 @Override protected double computePrefHeight(double width) { 517 final double top = control.snappedTopInset(); 518 final double bottom = control.snappedBottomInset(); 519 final double iLeft = indicator.snappedLeftInset(); 520 final double iRight = indicator.snappedRightInset(); 521 final double iTop = indicator.snappedTopInset(); 522 final double iBottom = indicator.snappedBottomInset(); 523 final double indicatorMax = snapSize(Math.max(Math.max(iLeft, iRight), Math.max(iTop, iBottom))); 524 final double pLeft = progress.snappedLeftInset(); 525 final double pRight = progress.snappedRightInset(); 526 final double pTop = progress.snappedTopInset(); 527 final double pBottom = progress.snappedBottomInset(); 528 final double progressMax = snapSize(Math.max(Math.max(pLeft, pRight), Math.max(pTop, pBottom))); 529 final double tTop = tick.snappedTopInset(); 530 final double tBottom = tick.snappedBottomInset(); 531 final double indicatorHeight = indicatorMax + progressMax + tTop + tBottom + progressMax + indicatorMax; 532 return top + indicatorHeight + textGap + doneText.getLayoutBounds().getHeight() + bottom; 533 } 534 535 @Override protected double computeMaxWidth(double height) { 536 return computePrefWidth(height); 537 } 538 539 @Override protected double computeMaxHeight(double width) { 540 return computePrefHeight(width); 541 } 542 } 543 544 545 546 /*************************************************************************** 547 * * 548 * IndeterminateSpinner * 549 * * 550 **************************************************************************/ 551 552 protected final Duration CLIPPED_DELAY = new Duration(300); 553 protected final Duration UNCLIPPED_DELAY = new Duration(0); 554 555 private final class IndeterminateSpinner extends Region { 556 private IndicatorPaths pathsG; 557 private final List<Double> opacities = new ArrayList<>(); 558 private boolean spinEnabled = false; 559 private Paint fillOverride = null; 560 561 private IndeterminateSpinner(boolean spinEnabled, Paint fillOverride) { 562 this.spinEnabled = spinEnabled; 563 this.fillOverride = fillOverride; 564 565 setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); 566 getStyleClass().setAll("spinner"); 567 568 pathsG = new IndicatorPaths(); 569 getChildren().add(pathsG); 570 rebuild(); 571 572 rebuildTimeline(); 573 574 } 575 576 public void setFillOverride(Paint fillOverride) { 577 this.fillOverride = fillOverride; 578 rebuild(); 579 } 580 581 public void setSpinEnabled(boolean spinEnabled) { 582 this.spinEnabled = spinEnabled; 583 rebuildTimeline(); 584 } 585 586 private void rebuildTimeline() { 587 if (spinEnabled) { 588 if (indeterminateTransition == null) { 589 indeterminateTransition = new Timeline(); 590 indeterminateTransition.setCycleCount(Timeline.INDEFINITE); 591 indeterminateTransition.setDelay(UNCLIPPED_DELAY); 592 } else { 593 indeterminateTransition.stop(); 594 ((Timeline)indeterminateTransition).getKeyFrames().clear(); 595 } 596 final ObservableList<KeyFrame> keyFrames = FXCollections.<KeyFrame>observableArrayList(); 597 598 keyFrames.add(new KeyFrame(Duration.millis(1), new KeyValue(pathsG.rotateProperty(), 360))); 599 keyFrames.add(new KeyFrame(Duration.millis(3900), new KeyValue(pathsG.rotateProperty(), 0))); 600 601 for (int i = 100; i <= 3900; i += 100) { 602 keyFrames.add(new KeyFrame(Duration.millis(i), event -> shiftColors())); 603 } 604 605 ((Timeline)indeterminateTransition).getKeyFrames().setAll(keyFrames); 606 indeterminateTransition.playFromStart(); 607 } else { 608 if (indeterminateTransition != null) { 609 indeterminateTransition.stop(); 610 ((Timeline)indeterminateTransition).getKeyFrames().clear(); 611 indeterminateTransition = null; 612 } 613 } 614 } 615 616 private class IndicatorPaths extends Pane { 617 @Override protected double computePrefWidth(double height) { 618 double w = 0; 619 for(Node child: getChildren()) { 620 if (child instanceof Region) { 621 Region region = (Region)child; 622 if (region.getShape() != null) { 623 w = Math.max(w,region.getShape().getLayoutBounds().getMaxX()); 624 } else { 625 w = Math.max(w,region.prefWidth(height)); 626 } 627 } 628 } 629 return w; 630 } 631 632 @Override protected double computePrefHeight(double width) { 633 double h = 0; 634 for(Node child: getChildren()) { 635 if (child instanceof Region) { 636 Region region = (Region)child; 637 if (region.getShape() != null) { 638 h = Math.max(h,region.getShape().getLayoutBounds().getMaxY()); 639 } else { 640 h = Math.max(h,region.prefHeight(width)); 641 } 642 } 643 } 644 return h; 645 } 646 647 @Override protected void layoutChildren() { 648 // calculate scale 649 double scale = getWidth() / computePrefWidth(-1); 650 for(Node child: getChildren()) { 651 if (child instanceof Region) { 652 Region region = (Region)child; 653 if (region.getShape() != null) { 654 region.resize( 655 region.getShape().getLayoutBounds().getMaxX(), 656 region.getShape().getLayoutBounds().getMaxY() 657 ); 658 region.getTransforms().setAll(new Scale(scale,scale,0,0)); 659 } else { 660 region.autosize(); 661 } 662 } 663 } 664 } 665 } 666 667 @Override protected void layoutChildren() { 668 final double w = control.getWidth() - control.snappedLeftInset() - control.snappedRightInset(); 669 final double h = control.getHeight() - control.snappedTopInset() - control.snappedBottomInset(); 670 final double prefW = pathsG.prefWidth(-1); 671 final double prefH = pathsG.prefHeight(-1); 672 double scaleX = w / prefW; 673 double scale = scaleX; 674 if ((scaleX * prefH) > h) { 675 scale = h / prefH; 676 } 677 double indicatorW = prefW * scale; 678 double indicatorH = prefH * scale; 679 pathsG.resizeRelocate((w - indicatorW) / 2, (h - indicatorH) / 2, indicatorW, indicatorH); 680 } 681 682 private void rebuild() { 683 // update indeterminate indicator 684 final int segments = indeterminateSegmentCount.get(); 685 opacities.clear(); 686 pathsG.getChildren().clear(); 687 final double step = 0.8/(segments-1); 688 for (int i = 0; i < segments; i++) { 689 Region region = new Region(); 690 region.setScaleShape(false); 691 region.setCenterShape(false); 692 region.getStyleClass().addAll("segment", "segment" + i); 693 if (fillOverride instanceof Color) { 694 Color c = (Color)fillOverride; 695 region.setStyle("-fx-background-color: rgba("+((int)(255*c.getRed()))+","+((int)(255*c.getGreen()))+","+((int)(255*c.getBlue()))+","+c.getOpacity()+");"); 696 } else { 697 region.setStyle(null); 698 } 699 pathsG.getChildren().add(region); 700 opacities.add(Math.max(0.1, (1.0 - (step*i)))); 701 } 702 } 703 704 private void shiftColors() { 705 if (opacities.size() <= 0) return; 706 final int segments = indeterminateSegmentCount.get(); 707 Collections.rotate(opacities, -1); 708 for (int i = 0; i < segments; i++) { 709 pathsG.getChildren().get(i).setOpacity(opacities.get(i)); 710 } 711 } 712 } 713 714 715 716 /*************************************************************************** 717 * * 718 * Stylesheet Handling * 719 * * 720 **************************************************************************/ 721 722 private static final CssMetaData<ProgressIndicator,Paint> PROGRESS_COLOR = 723 new CssMetaData<ProgressIndicator,Paint>("-fx-progress-color", 724 PaintConverter.getInstance(), null) { 725 726 @Override 727 public boolean isSettable(ProgressIndicator n) { 728 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin(); 729 return skin.progressColor == null || 730 !skin.progressColor.isBound(); 731 } 732 733 @Override 734 public StyleableProperty<Paint> getStyleableProperty(ProgressIndicator n) { 735 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin(); 736 return (StyleableProperty<Paint>)(WritableValue<Paint>)skin.progressColor; 737 } 738 }; 739 private static final CssMetaData<ProgressIndicator,Number> INDETERMINATE_SEGMENT_COUNT = 740 new CssMetaData<ProgressIndicator,Number>("-fx-indeterminate-segment-count", 741 SizeConverter.getInstance(), 8) { 742 743 @Override public boolean isSettable(ProgressIndicator n) { 744 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin(); 745 return skin.indeterminateSegmentCount == null || 746 !skin.indeterminateSegmentCount.isBound(); 747 } 748 749 @Override public StyleableProperty<Number> getStyleableProperty(ProgressIndicator n) { 750 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin(); 751 return (StyleableProperty<Number>)(WritableValue<Number>)skin.indeterminateSegmentCount; 752 } 753 }; 754 private static final CssMetaData<ProgressIndicator,Boolean> SPIN_ENABLED = 755 new CssMetaData<ProgressIndicator,Boolean>("-fx-spin-enabled", BooleanConverter.getInstance(), Boolean.FALSE) { 756 757 @Override public boolean isSettable(ProgressIndicator node) { 758 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) node.getSkin(); 759 return skin.spinEnabled == null || !skin.spinEnabled.isBound(); 760 } 761 762 @Override public StyleableProperty<Boolean> getStyleableProperty(ProgressIndicator node) { 763 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) node.getSkin(); 764 return (StyleableProperty<Boolean>)(WritableValue<Boolean>)skin.spinEnabled; 765 } 766 }; 767 768 public static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 769 static { 770 final List<CssMetaData<? extends Styleable, ?>> styleables = 771 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData()); 772 styleables.add(PROGRESS_COLOR); 773 styleables.add(INDETERMINATE_SEGMENT_COUNT); 774 styleables.add(SPIN_ENABLED); 775 STYLEABLES = Collections.unmodifiableList(styleables); 776 } 777 778 /** 779 * @return The CssMetaData associated with this class, which may include the 780 * CssMetaData of its super classes. 781 */ 782 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 783 return STYLEABLES; 784 } 785 786 /** 787 * {@inheritDoc} 788 */ 789 @Override 790 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 791 return getClassCssMetaData(); 792 } 793 }