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