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