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 }