changeset 2150:263bf0a65815

Fixed RT-27538: ProgressIndicator is not properly styleable from CSS
author "Jasper Potts"
date Fri, 04 Jan 2013 22:06:00 -0800
parents 1664eb23626f
children 3f1a87f8b460
files javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ProgressIndicatorSkin.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/caspian.css
diffstat 2 files changed, 314 insertions(+), 196 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ProgressIndicatorSkin.java	Fri Jan 04 12:31:00 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ProgressIndicatorSkin.java	Fri Jan 04 22:06:00 2013 -0800
@@ -24,18 +24,28 @@
  */
 package com.sun.javafx.scene.control.skin;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
 import javafx.animation.Timeline;
 import javafx.beans.InvalidationListener;
 import javafx.beans.Observable;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.value.WritableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.geometry.NodeOrientation;
 import javafx.geometry.VPos;
-import javafx.scene.Group;
+import javafx.scene.Node;
 import javafx.scene.control.ProgressIndicator;
+import javafx.scene.control.SkinBase;
+import javafx.scene.layout.Pane;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.StackPane;
 import javafx.scene.paint.Color;
@@ -43,26 +53,19 @@
 import javafx.scene.shape.Arc;
 import javafx.scene.shape.ArcType;
 import javafx.scene.shape.Circle;
-import javafx.scene.shape.SVGPath;
-import javafx.scene.text.Font;
 import javafx.scene.text.Text;
-import javafx.scene.transform.Rotate;
 import javafx.scene.transform.Scale;
 import javafx.util.Duration;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import com.sun.javafx.Utils;
+import com.sun.javafx.css.CssMetaData;
+import com.sun.javafx.css.Origin;
+import com.sun.javafx.css.StyleableBooleanProperty;
+import com.sun.javafx.css.StyleableIntegerProperty;
 import com.sun.javafx.css.StyleableObjectProperty;
-import com.sun.javafx.css.CssMetaData;
+import com.sun.javafx.css.converters.BooleanConverter;
 import com.sun.javafx.css.converters.PaintConverter;
+import com.sun.javafx.css.converters.SizeConverter;
 import com.sun.javafx.scene.control.behavior.ProgressIndicatorBehavior;
 import com.sun.javafx.scene.control.skin.resources.ControlResources;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.value.WritableValue;
-import javafx.scene.control.SkinBase;
 
 public class ProgressIndicatorSkin extends BehaviorSkinBase<ProgressIndicator, ProgressIndicatorBehavior<ProgressIndicator>> {
 
@@ -80,13 +83,6 @@
         doneText.getStyleClass().add("text");
     }
 
-
-    private static final ObservableList<String> paths;
-    static {
-        paths = FXCollections.<String>observableArrayList();
-        paths.addAll("M 18.19152 4.2642355 L 15.734064 5.984965 L 15.734064 5.984965 C  16.55803 7.1617074 17.0 8.563462 17.0 10.0 L 20.0 10.0 C  20.0 7.9478035 19.368612 5.9452963 18.19152 4.2642355 Z", "M 11.736482 0.15192246 L 11.215537 3.1063457 L 11.215537 3.1063457 C  12.63025 3.3557978 13.933962 4.034467 14.949747 5.0502524 L 10.0 10.0 L 17.071068 2.9289322 C  15.619946 1.4778103 13.757501 0.5082826 11.736482 0.15192246 Z", "M 10.0 0.0 C  7.9478035 0.0 5.9452963 0.6313881 4.2642355 1.8084795 L 5.984965 4.265936 L 5.984965 4.265936 C  7.1617074 3.4419718 8.563462 3.0 10.0 3.0 L 10.0 0.0 Z", "M 2.9289322 2.9289322 C  1.4778103 4.380054 0.5082826 6.2424994 0.15192246 8.263518 L 3.1063457 8.784463 L 3.1063457 8.784463 C  3.3557978 7.3697495 4.034467 6.0660377 5.0502524 5.0502524 L 5.0502524 5.0502524 L 2.9289322 2.9289322 Z", "M 0.0 10.0 C  0.0 12.0521965 0.6313881 14.054704 1.8084795 15.7357645 L 10.0 10.0 L 4.265936 14.015035 C  3.4419718 12.838292 3.0 11.436538 3.0 10.0 Z", "M 10.0 10.0 L 8.784463 16.893654 C  7.3697495 16.644201 6.0660377 15.965533 5.050253 14.949747 L 5.0502524 14.949747 L 2.9289322 17.071068 C  4.380054 18.52219 6.2424994 19.491718 8.263518 19.848078 L 10.0 10.0 Z", "M 10.0 10.0 L 14.015035 15.734064 C  12.838292 16.55803 11.436538 17.0 10.0 17.0 L 10.0 20.0 C  12.0521965 20.0 14.054704 19.368612 15.7357645 18.19152 L 10.0 10.0 Z", "M 10.0 10.0 L 16.893654 11.215537 C  16.644201 12.63025 15.965533 13.933962 14.949747 14.949747 L 17.071068 17.071068 C  18.52219 15.619946 19.491718 13.757501 19.848078 11.736482 L 10.0 10.0 Z");
-    }
-
     private IndeterminateSpinner spinner;
     private DeterminateIndicator determinateIndicator;
     private boolean timelineNulled = false;
@@ -111,7 +107,7 @@
             @Override public void invalidated(Observable valueModel) {
                 if (getSkinnable().isIndeterminate() && timelineNulled && spinner == null) {
                     timelineNulled = false;
-                    spinner = new IndeterminateSpinner(getSkinnable(), ProgressIndicatorSkin.this);
+                    spinner = new IndeterminateSpinner(getSkinnable(), ProgressIndicatorSkin.this, spinEnabled.get(), progressColor.get());
                     getChildren().add(spinner);
                 }
                 
@@ -144,7 +140,7 @@
                 else {
                     if (getSkinnable().getScene() != null && getSkinnable().isIndeterminate()) {
                         timelineNulled = false;
-                        spinner = new IndeterminateSpinner(getSkinnable(), ProgressIndicatorSkin.this);
+                        spinner = new IndeterminateSpinner(getSkinnable(), ProgressIndicatorSkin.this, spinEnabled.get(), progressColor.get());
                         getChildren().add(spinner);
                         if (getSkinnable().impl_isTreeVisible()) {
                             spinner.indeterminateTimeline.play();
@@ -167,7 +163,7 @@
             // clean up determinateIndicator
             determinateIndicator = null;
             // create spinner
-            spinner = new IndeterminateSpinner(control, this);
+            spinner = new IndeterminateSpinner(control, this, spinEnabled.get(), progressColor.get());
             getChildren().clear();
             getChildren().add(spinner);
             if (getSkinnable().impl_isTreeVisible()) {
@@ -180,7 +176,7 @@
                 spinner = null;
             }
             // create determinateIndicator
-            determinateIndicator = new com.sun.javafx.scene.control.skin.ProgressIndicatorSkin.DeterminateIndicator(control, this);
+            determinateIndicator = new com.sun.javafx.scene.control.skin.ProgressIndicatorSkin.DeterminateIndicator(control, this, progressColor.get());
             getChildren().clear();
             getChildren().add(determinateIndicator);
         }
@@ -188,7 +184,6 @@
     
     @Override public void dispose() {
         super.dispose();
-        
         if (spinner != null) {
             spinner.indeterminateTimeline.stop();
             spinner = null;
@@ -213,7 +208,6 @@
      **************************************************************************/
 
     static class DeterminateIndicator extends Region {
-        private Font font;
         private double textGap = 2.0F;
 
         // only update progress text on whole percentages
@@ -227,10 +221,9 @@
         private StackPane indicator;
         private StackPane progress;
         private StackPane tick;
-        Arc arcShape;
-        Arc arcProgress;
+        private Arc arcShape;
 
-        public DeterminateIndicator(ProgressIndicator control, ProgressIndicatorSkin s) {
+        public DeterminateIndicator(ProgressIndicator control, ProgressIndicatorSkin s, Paint fillOverride) {
             this.control = control;
             this.skin = s;
             
@@ -261,24 +254,14 @@
             arcShape.setType(ArcType.ROUND);
             arcShape.setStartAngle(90.0F);
 
-            arcProgress = new Arc();
-            arcProgress.setType(ArcType.ROUND);
-            arcProgress.setStartAngle(90.0F);
-            arcProgress.setFill(skin.getProgressColor());
-
             // Our progress pie piece
-            progress = new StackPane() {
-                @Override protected void layoutChildren() {
-                    arcProgress.setFill(skin.getProgressColor());
-                }
-            };
-
+            progress = new StackPane();
             progress.getStyleClass().setAll("progress");
             progress.setScaleShape(false);
             progress.setCenterShape(false);
             progress.setShape(arcShape);
             progress.getChildren().clear();
-            progress.getChildren().addAll(arcProgress);
+            setFillOverride(fillOverride);
 
             // The check mark that's drawn at 100%
             tick = new StackPane();
@@ -288,6 +271,15 @@
             updateProgress();
         }
 
+        private void setFillOverride(Paint fillOverride) {
+            if (fillOverride instanceof Color) {
+                Color c = (Color)fillOverride;
+                progress.setStyle("-fx-background-color: rgba("+((int)(255*c.getRed()))+","+((int)(255*c.getGreen()))+","+((int)(255*c.getBlue()))+","+c.getOpacity()+");");
+            } else {
+                progress.setStyle(null);
+            }
+        }
+
         @Override public boolean isAutomaticallyMirrored() {
             // This is used instead of setting NodeOrientation,
             // allowing the Text node to inherit the current
@@ -301,7 +293,6 @@
 
             degProgress = (int) (360 * control.getProgress());
             arcShape.setLength(-degProgress);
-            arcProgress.setLength(-degProgress);
             requestLayout();
         }
 
@@ -331,8 +322,6 @@
 
             arcShape.setRadiusX(((indicator.getWidth() - indicator.getInsets().getLeft() - indicator.getInsets().getRight()) / 2));
             arcShape.setRadiusY(arcShape.getRadiusX());
-            arcProgress.setRadiusX(arcShape.getRadiusX()-1);
-            arcProgress.setRadiusY(arcShape.getRadiusY()-1);
 
 
             progress.setLayoutX(indicator.getLayoutX() + radius);
@@ -346,8 +335,8 @@
             /*
             ** if the % text can't fit anywhere in the bounds then don't display it
             */
-            double textWidth = com.sun.javafx.scene.control.skin.Utils.computeTextWidth(font, text.getText(), 0.0);
-            double textHeight = com.sun.javafx.scene.control.skin.Utils.computeTextHeight(font, text.getText(), 0.0);
+            double textWidth = com.sun.javafx.scene.control.skin.Utils.computeTextWidth(text.getFont(), text.getText(), 0.0);
+            double textHeight = com.sun.javafx.scene.control.skin.Utils.computeTextHeight(text.getFont(), text.getText(), 0.0);
             if (control.getWidth() >= textWidth && control.getHeight() >= textHeight) {
                 if (!text.isVisible()) {
                     text.setVisible(true);
@@ -381,7 +370,6 @@
             return getInsets().getTop() + indH + textGap + doneText.getLayoutBounds().getHeight() + getInsets().getBottom();
         }
 
-
         @Override protected double computeMaxWidth(double height) {
             return computePrefWidth(height);
         }
@@ -398,55 +386,63 @@
      **************************************************************************/
 
     static class IndeterminateSpinner extends Region {
+        private ProgressIndicator control;
+        private ProgressIndicatorSkin skin;
+        private IndicatorPaths pathsG;
+        private Timeline indeterminateTimeline;
+        private double angle = 0.0F;
+        private final List<Double> opacities = new ArrayList<Double>();
+        private boolean spinEnabled = false;
+        private Paint fillOverride = null;
 
-        private ProgressIndicator control;
-        protected ProgressIndicatorSkin skin;
-        private IndicatorPaths pathsG;
-        Scale scaleTransform;
-        Rotate rotateTransform;
-
-        public IndeterminateSpinner(ProgressIndicator control, ProgressIndicatorSkin s) {
+        public IndeterminateSpinner(ProgressIndicator control, ProgressIndicatorSkin s, boolean spinEnabled, Paint fillOverride) {
             this.control = control;
             this.skin = s;
+            this.spinEnabled = spinEnabled;
+            this.fillOverride = fillOverride;
 
             setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT);
             getStyleClass().setAll("spinner");
 
-            skin.segmentColors = FXCollections.<Color>observableArrayList();
-            skin.svgpaths = FXCollections.<SVGPath>observableArrayList();
-            skin.setColors(skin.getProgressColor());
-
             pathsG = new IndicatorPaths(this);
-
-            scaleTransform = new Scale();
-
-            rotateTransform = new Rotate();
-            rotateTransform.setAngle(angle);
-
-            pathsG.getChildren().clear();
-            pathsG.getChildren().addAll(skin.svgpaths);
+            getChildren().add(pathsG);
 
             indeterminateTimeline = new Timeline();
             indeterminateTimeline.setCycleCount(Timeline.INDEFINITE);
+            rebuildTimeline();
 
-            ObservableList<KeyFrame> keyframes = FXCollections.<KeyFrame>observableArrayList();
-            for (int i = 100; i <= 3900; i += 100) {
-                keyframes.add(
-                      new KeyFrame(Duration.millis(i), new EventHandler<ActionEvent>() {
-                              @Override public void handle(ActionEvent event) {
-                                  skin.shiftColors();
-                              }
-                      }));
-            }
-            indeterminateTimeline.getKeyFrames().clear();
-            indeterminateTimeline.getKeyFrames().addAll(keyframes);
-
-            getChildren().clear();
-            getChildren().addAll(pathsG);
-            requestLayout();
+            rebuild();
         }
 
-        void pauseIndicator(boolean pause) {
+        public void setFillOverride(Paint fillOverride) {
+            this.fillOverride = fillOverride;
+            rebuild();
+        }
+
+        public void setSpinEnabled(boolean spinEnabled) {
+            this.spinEnabled = spinEnabled;
+            rebuildTimeline();
+        }
+
+        private void rebuildTimeline() {
+            final ObservableList<KeyFrame> keyFrames = FXCollections.<KeyFrame>observableArrayList();
+            if(spinEnabled) {
+                keyFrames.add(new KeyFrame(Duration.millis(0), new KeyValue(pathsG.rotateProperty(), 360)));
+                keyFrames.add(new KeyFrame(Duration.millis(3900), new KeyValue(pathsG.rotateProperty(), 0)));
+            }
+            for (int i = 100; i <= 3900; i += 100) {
+                keyFrames.add(
+                        new KeyFrame(
+                                Duration.millis(i), new EventHandler<ActionEvent>() {
+                            @Override public void handle(ActionEvent event) {
+                                shiftColors();
+                            }
+                        }));
+            }
+            indeterminateTimeline.getKeyFrames().setAll(keyFrames);
+        }
+
+        private void pauseIndicator(boolean pause) {
             if (indeterminateTimeline != null) {
                 if (pause) {
                     indeterminateTimeline.pause();
@@ -457,99 +453,118 @@
             }
         }
 
+        private class IndicatorPaths extends Pane {
+            IndeterminateSpinner piSkin;
+            IndicatorPaths(IndeterminateSpinner pi) {
+                super();
+                piSkin = pi;
+                InvalidationListener treeVisibilityListener = new InvalidationListener() {
+                        @Override public void invalidated(Observable valueModel) {
+                            if (piSkin.skin.getSkinnable().impl_isTreeVisible()) {
+                                piSkin.pauseIndicator(false);
+                            }
+                            else {
+                                piSkin.pauseIndicator(true);
+                            }
+                        }
+                    };
+                impl_treeVisibleProperty().addListener(treeVisibilityListener);
+            }
 
-    class IndicatorPaths extends Group {
-        IndeterminateSpinner piSkin;
-        IndicatorPaths(IndeterminateSpinner pi) {
-            super();
-            piSkin = pi;
-            InvalidationListener treeVisibilityListener = new InvalidationListener() {
-                    @Override public void invalidated(Observable valueModel) {
-                        if (piSkin.skin.getSkinnable().impl_isTreeVisible()) {
-                            piSkin.pauseIndicator(false);
-                        }
-                        else {
-                            piSkin.pauseIndicator(true);
+            @Override protected double computePrefWidth(double height) {
+                double w = 0;
+                for(Node child: getChildren()) {
+                    if (child instanceof Region) {
+                        Region region = (Region)child;
+                        if (region.getShape() != null) {
+                            w = Math.max(w,region.getShape().getLayoutBounds().getMaxX());
+                        } else {
+                            w = Math.max(w,region.prefWidth(height));
                         }
                     }
-                };
-            impl_treeVisibleProperty().addListener(treeVisibilityListener);
+                }
+                return w;
+            }
+
+            @Override protected double computePrefHeight(double width) {
+                double h = 0;
+                for(Node child: getChildren()) {
+                    if (child instanceof Region) {
+                        Region region = (Region)child;
+                        if (region.getShape() != null) {
+                            h = Math.max(h,region.getShape().getLayoutBounds().getMaxY());
+                        } else {
+                            h = Math.max(h,region.prefHeight(width));
+                        }
+                    }
+                }
+                return h;
+            }
+
+            @Override protected void layoutChildren() {
+                // calculate scale
+                double scale = getWidth() / computePrefWidth(-1);
+                for(Node child: getChildren()) {
+                    if (child instanceof Region) {
+                        Region region = (Region)child;
+                        if (region.getShape() != null) {
+                            region.resize(
+                                region.getShape().getLayoutBounds().getMaxX(),
+                                region.getShape().getLayoutBounds().getMaxY()
+                            );
+                            region.getTransforms().setAll(new Scale(scale,scale,0,0));
+                        } else {
+                            region.autosize();
+                        }
+                    }
+                }
+            }
         }
-    }
-
 
         @Override protected void layoutChildren() {
-            double radiusW = (control.getWidth() - (skin.getInsets().getLeft() + skin.getInsets().getRight())) / 2;
-            double radiusH = (control.getHeight() - (skin.getInsets().getTop() + skin.getInsets().getBottom())) / 2;
-            double radius = Math.min(radiusW, radiusH);
-
-            scaleTransform.setX(radius/10);
-            scaleTransform.setY(radius/10);
-
-            rotateTransform.setPivotX(radius);
-            rotateTransform.setPivotY(radius);
-
-            pathsG.getTransforms().clear();
-            pathsG.getTransforms().addAll(scaleTransform, rotateTransform);
-
-            double diameter = radius*2;
-            pathsG.resize(diameter, diameter);
-
-            pathsG.setLayoutX(skin.getInsets().getLeft()+(radiusW - radius));
-            pathsG.setLayoutY(skin.getInsets().getTop()+(radiusH - radius));
+            final double w = control.getWidth() - skin.getInsets().getLeft() - skin.getInsets().getRight();
+            final double h = control.getHeight() - skin.getInsets().getTop() - skin.getInsets().getBottom();
+            final double prefW = pathsG.prefWidth(-1);
+            final double prefH = pathsG.prefHeight(-1);
+            double scaleX = w / prefW;
+            double scale = scaleX;
+            if ((scaleX * prefH) > h) {
+                scale = h / prefH;
+            }
+            double indicatorW = prefW * scale;
+            double indicatorH = prefH * scale;
+            pathsG.resizeRelocate((w - indicatorW) / 2, (h - indicatorH) / 2, indicatorW, indicatorH);
         }
 
-        private Timeline indeterminateTimeline;
-        private double angle = 0.0F;
-
-
-        @Override protected double computePrefWidth(double height) {
-            return getInsets().getLeft() + doneText.getLayoutBounds().getHeight() + getInsets().getRight();
+        private void rebuild() {
+            // update indeterminate indicator
+            final int segments = skin.indeterminateSegmentCount.get();
+            opacities.clear();
+            pathsG.getChildren().clear();
+            final double step = 0.8/(segments-1);
+            for (int i = 0; i < segments; i++) {
+                Region region = new Region();
+                region.setScaleShape(false);
+                region.setCenterShape(false);
+                region.getStyleClass().addAll("segment", "segment" + i);
+                if (fillOverride instanceof Color) {
+                    Color c = (Color)fillOverride;
+                    region.setStyle("-fx-background-color: rgba("+((int)(255*c.getRed()))+","+((int)(255*c.getGreen()))+","+((int)(255*c.getBlue()))+","+c.getOpacity()+");");
+                } else {
+                    region.setStyle(null);
+                }
+                pathsG.getChildren().add(region);
+                opacities.add(Math.min(1, 0.2 + (i * step)));
+            }
         }
 
-        @Override protected double computePrefHeight(double width) {
-            /*
-            ** use the same as the width, to keep it square
-            */
-            return getInsets().getTop() + doneText.getLayoutBounds().getHeight() + getInsets().getBottom();
-        }
-
-        @Override protected double computeMaxWidth(double height) {
-            return computePrefWidth(-1);
-        }
-
-        @Override protected double computeMaxHeight(double width) {
-            return computePrefHeight(-1);
-        }
-    }
-
-    private ObservableList<Color> segmentColors;
-    private ObservableList<SVGPath> svgpaths;
-
-    private void setColors(Paint seedColor) {
-        if (segmentColors != null) {
-            segmentColors.clear();
-            if (seedColor instanceof Color) {
-                final Color c = (Color) seedColor;
-                for (int i = 0; i <= 7; i++) segmentColors.add(Utils.deriveColor(c, -0.2F + 1.2F / 7.0F * i));
-            } else {
-                // as it a Paint we can not derive colors so just use as it
-                for (int i = 0; i <= 7; i++) segmentColors.add((Color)seedColor);
+        private void shiftColors() {
+            if (opacities.size() <= 0) return;
+            final int segments = skin.indeterminateSegmentCount.get();
+            Collections.rotate(opacities, -1);
+            for (int i = 0; i < segments; i++) {
+                pathsG.getChildren().get(i).setOpacity(opacities.get(i));
             }
-
-            for (int i = 0; i <= 7; i++) {
-                SVGPath svgpath = new SVGPath();
-                svgpath.setContent(paths.get(i));
-                svgpath.setFill(segmentColors.get(i));
-                svgpaths.add(svgpath);
-            }
-        }
-    }
-
-    private void shiftColors() {
-        FXCollections.rotate(segmentColors, -1);
-        for (int i = 0; i <= 7; i++) {
-            svgpaths.get(i).setFill(segmentColors.get(i));
         }
     }
 
@@ -558,42 +573,82 @@
     }
 
     /**
-     * The colour of the progress segment
+     * The colour of the progress segment.
      */
-    private ObjectProperty<Paint> progressColor =            
-            new StyleableObjectProperty<Paint>(Color.DODGERBLUE) {
+    private ObjectProperty<Paint> progressColor =
+            new StyleableObjectProperty<Paint>(null) {
 
-        @Override public void set(Paint newProgressColor) {
-            final Paint color = (newProgressColor instanceof Color)
-                    ? newProgressColor 
-                    : Color.DODGERBLUE;
-            super.set(color);
-        }
-        
-        @Override
-        protected void invalidated() {
-            setColors((Color)progressColor.get());
+                @Override public void set(Paint newProgressColor) {
+                    final Paint color = (newProgressColor instanceof Color)
+                            ? newProgressColor
+                            : null;
+                    super.set(color);
+                }
+
+                @Override protected void invalidated() {
+                    if (spinner!=null) spinner.setFillOverride(get());
+                    if (determinateIndicator!=null) determinateIndicator.setFillOverride(get());
+                }
+
+                @Override public Object getBean() {
+                    return ProgressIndicatorSkin.this;
+                }
+
+                @Override public String getName() {
+                    return "progressColorProperty";
+                }
+
+                @Override public CssMetaData getCssMetaData() {
+                    return StyleableProperties.PROGRESS_COLOR;
+                }
+            };
+
+    /**
+     * The number of segments in the spinner.
+     */
+    private IntegerProperty indeterminateSegmentCount =
+            new StyleableIntegerProperty(8) {
+
+                @Override protected void invalidated() {
+                    if (spinner!=null) spinner.rebuild();
+                }
+
+                @Override public Object getBean() {
+                    return ProgressIndicatorSkin.this;
+                }
+
+                @Override public String getName() {
+                    return "indeterminateSegmentCount";
+                }
+
+                @Override public CssMetaData getCssMetaData() {
+                    return StyleableProperties.PROGRESS_COLOR;
+                }
+            };
+
+    /**
+     * True if the progress indicator should rotate as well as animate opacity.
+     */
+    private final BooleanProperty spinEnabled = new StyleableBooleanProperty(false) {
+        @Override protected void invalidated() {
+            if (spinner!=null) spinner.setSpinEnabled(get());
         }
 
-        @Override
-        public Object getBean() {
+        @Override public CssMetaData getCssMetaData() {
+            return StyleableProperties.LEGEND_VISIBLE;
+        }
+
+        @Override public Object getBean() {
             return ProgressIndicatorSkin.this;
         }
 
-        @Override
-        public String getName() {
-            return "progressColorProperty";
-        }
-
-        @Override
-        public CssMetaData getCssMetaData() {
-            return StyleableProperties.PROGRESS_COLOR;
+        @Override public String getName() {
+            return "spinEnabled";
         }
     };
-        
 
     // *********** Stylesheet Handling *****************************************
-    
+
     /**
      * Super-lazy instantiation pattern from Bill Pugh.
      * @treatAsPrivate implementation detail
@@ -601,12 +656,12 @@
     private static class StyleableProperties {
         private static final CssMetaData<ProgressIndicator,Paint> PROGRESS_COLOR =
             new CssMetaData<ProgressIndicator,Paint>("-fx-progress-color",
-                PaintConverter.getInstance(), Color.DODGERBLUE) {
+                PaintConverter.getInstance(), null) {
 
             @Override
             public boolean isSettable(ProgressIndicator n) {
                 final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin();
-                return skin.progressColor == null || 
+                return skin.progressColor == null ||
                         !skin.progressColor.isBound();
             }
 
@@ -616,13 +671,48 @@
                 return skin.progressColor;
             }
         };
+        private static final CssMetaData<ProgressIndicator,Number> INDETERMINATE_SEGMENT_COUNT =
+            new CssMetaData<ProgressIndicator,Number>("-fx-indeterminate-segment-count",
+                                                     SizeConverter.getInstance(), 8) {
+
+            @Override public void set(ProgressIndicator node, Number value, Origin origin) {
+                super.set(node, value.intValue(), origin);
+            }
+
+            @Override public boolean isSettable(ProgressIndicator n) {
+                final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin();
+                return skin.indeterminateSegmentCount == null ||
+                        !skin.indeterminateSegmentCount.isBound();
+            }
+
+            @Override public WritableValue<Number> getWritableValue(ProgressIndicator n) {
+                final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) n.getSkin();
+                return skin.indeterminateSegmentCount;
+            }
+        };
+        private static final CssMetaData<ProgressIndicator,Boolean> LEGEND_VISIBLE =
+            new CssMetaData<ProgressIndicator,Boolean>("-fx-spin-enabled",
+                                           BooleanConverter.getInstance(), Boolean.FALSE) {
+
+                @Override public boolean isSettable(ProgressIndicator node) {
+                    final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) node.getSkin();
+                    return skin.spinEnabled == null || !skin.spinEnabled.isBound();
+                }
+
+                @Override public WritableValue<Boolean> getWritableValue(ProgressIndicator node) {
+                    final ProgressIndicatorSkin skin = (ProgressIndicatorSkin) node.getSkin();
+                    return skin.spinEnabled;
+                }
+            };
 
         public static final List<CssMetaData> STYLEABLES;
         static {
             final List<CssMetaData> styleables = 
                 new ArrayList<CssMetaData>(SkinBase.getClassCssMetaData());
             Collections.addAll(styleables,
-                               PROGRESS_COLOR
+                               PROGRESS_COLOR,
+                               INDETERMINATE_SEGMENT_COUNT,
+                               LEGEND_VISIBLE
             );
             STYLEABLES = Collections.unmodifiableList(styleables);
         }
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/caspian.css	Fri Jan 04 12:31:00 2013 -0800
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/caspian.css	Fri Jan 04 22:06:00 2013 -0800
@@ -1998,7 +1998,7 @@
  ******************************************************************************/
 
 .progress-indicator {
-    -fx-progress-color: dodgerblue;
+    -fx-indeterminate-segment-count: 8;
 }
 
 .progress-indicator > .determinate-indicator > .indicator {
@@ -2013,8 +2013,8 @@
 
 .progress-indicator > .determinate-indicator > .progress {
     -fx-background-color:
-        linear-gradient(to bottom, derive(-fx-box-border,38%), derive(-fx-box-border,-60%)),
-        linear-gradient(to bottom, derive(-fx-box-border,38%), -fx-box-border);
+        linear-gradient(to bottom, derive(-fx-accent,38%), derive(-fx-accent,-60%)),
+        linear-gradient(to bottom, derive(-fx-accent,20%), -fx-accent);
     -fx-background-insets: 0, 1;
     -fx-padding: 0.75em; /* 9 */
 }
@@ -2041,6 +2041,34 @@
     -fx-opacity: -fx-disabled-opacity;
 }
 
+.progress-indicator:indeterminate .segment {
+    -fx-background-color: -fx-accent;
+}
+.progress-indicator:indeterminate .segment0 {
+    -fx-shape:"M18.191,4.264l-2.457,1.721l0,0C16.559,7.161,17,8.564,17,10h3C20,7.948,19.369,5.946,18.191,4.264z";
+}
+.progress-indicator:indeterminate .segment1 {
+    -fx-shape:"M11.736,0.152l-0.521,2.955l0,0c1.416,0.249,2.719,0.928,3.734,1.943l2.122-2.121C15.62,1.478,13.758,0.508,11.736,0.152z";
+}
+.progress-indicator:indeterminate .segment2 {
+    -fx-shape:"M10,0C7.947,0,5.945,0.631,4.264,1.809l1.72,2.457l0,0C7.162,3.442,8.563,3,10,3V0z";
+}
+.progress-indicator:indeterminate .segment3 {
+    -fx-shape:"M2.929,2.929C1.478,4.38,0.508,6.242,0.152,8.264l2.955,0.521l0,0C3.356,7.369,4.035,6.066,5.05,5.05l0,0L2.929,2.929z";
+}
+.progress-indicator:indeterminate .segment4 {
+    -fx-shape:"M0,10c0,2.052,0.631,4.055,1.809,5.735l2.458-1.721C3.442,12.838,3,11.437,3,10H0z";
+}
+.progress-indicator:indeterminate .segment5 {
+    -fx-shape:"M8.785,16.894c-1.416-0.25-2.719-0.929-3.735-1.944l0,0l-2.122,2.122c1.451,1.45,3.313,2.42,5.335,2.776L8.785,16.894z";
+}
+.progress-indicator:indeterminate .segment6 {
+    -fx-shape:"M14.016,15.734C12.838,16.558,11.437,17,10,17v3c2.053,0,4.055-0.632,5.736-1.809L14.016,15.734z";
+}
+.progress-indicator:indeterminate .segment7 {
+    -fx-shape:"M16.894,11.215c-0.249,1.415-0.929,2.719-1.944,3.734l2.122,2.122c1.45-1.451,2.421-3.314,2.776-5.335L16.894,11.215z";
+}
+
 /*******************************************************************************
  *                                                                             *
  * TableView                                                                   *