changeset 7799:6efbcb758363

RT-38391: [CSS] Add support for specifying durations
author David Grieve<david.grieve@oracle.com>
date Tue, 26 Aug 2014 15:54:28 -0400
parents 5705e881dc1f
children 6feed77a2bfa
files apps/toys/Hello/src/main/java/hello/HelloCSS.java modules/graphics/src/main/docs/javafx/scene/doc-files/cssref.html modules/graphics/src/main/java/com/sun/javafx/css/SizeUnits.java modules/graphics/src/main/java/com/sun/javafx/css/converters/DurationConverter.java modules/graphics/src/main/java/com/sun/javafx/css/parser/CSSLexer.java modules/graphics/src/main/java/com/sun/javafx/css/parser/CSSParser.java modules/graphics/src/main/java/javafx/css/StyleConverter.java modules/graphics/src/main/java/javafx/css/StyleablePropertyFactory.java modules/graphics/src/test/java/com/sun/javafx/css/SizeTest.java modules/graphics/src/test/java/com/sun/javafx/css/parser/CSSLexerTest.java modules/graphics/src/test/java/javafx/css/StyleablePropertyFactoryTest.java modules/graphics/src/test/java/javafx/css/StyleablePropertyFactory_createMethod_Test.java
diffstat 12 files changed, 465 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/apps/toys/Hello/src/main/java/hello/HelloCSS.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/apps/toys/Hello/src/main/java/hello/HelloCSS.java	Tue Aug 26 15:54:28 2014 -0400
@@ -25,14 +25,31 @@
 
 package hello;
 
+import javafx.animation.Animation;
+import javafx.animation.FadeTransition;
 import javafx.application.Application;
+import javafx.beans.binding.Bindings;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.Property;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.value.WritableValue;
+import javafx.css.CssMetaData;
+import javafx.css.Styleable;
+import javafx.css.StyleableProperty;
+import javafx.css.StyleablePropertyFactory;
+import javafx.geometry.Insets;
 import javafx.geometry.Orientation;
+import javafx.geometry.Pos;
 import javafx.scene.Group;
 import javafx.scene.Node;
 import javafx.scene.Scene;
 import javafx.scene.SubScene;
 import javafx.scene.control.Button;
 import javafx.scene.control.Label;
+import javafx.scene.control.Slider;
+import javafx.scene.layout.BorderPane;
 import javafx.scene.layout.FlowPane;
 import javafx.scene.layout.StackPane;
 import javafx.scene.layout.VBox;
@@ -40,6 +57,9 @@
 import javafx.scene.shape.Rectangle;
 import javafx.scene.text.Text;
 import javafx.stage.Stage;
+import javafx.util.Duration;
+
+import java.util.List;
 
 public class HelloCSS extends Application {
     /**
@@ -82,10 +102,14 @@
         SubScene caspianSubScene = new SubScene(subSceneRoot, 300, 100);
         caspianSubScene.setUserAgentStylesheet("com/sun/javafx/scene/control/skin/caspian/caspian.css");
 
-        caspianSubScene.setLayoutX(25);
-        caspianSubScene.setLayoutY(210);
+        caspianSubScene.setLayoutX(275);
+        caspianSubScene.setLayoutY(40);
 
-        ((Group)scene.getRoot()).getChildren().addAll(rect,rect2,swapTest,caspianSubScene);
+        Node durationTest = createDurationTest();
+        durationTest.setLayoutX(25);
+        durationTest.setLayoutY(210);
+
+        ((Group)scene.getRoot()).getChildren().addAll(rect,rect2,swapTest,caspianSubScene, durationTest);
         stage.setScene(scene);
         stage.show();
     }
@@ -117,4 +141,93 @@
 
         return hBox;
     }
+
+    private static class TestNode extends Rectangle {
+
+        public TestNode() {
+            super(100, 100);
+        }
+
+        @Override public void impl_processCSS(WritableValue<Boolean> foo) {
+            super.impl_processCSS(foo);
+        }
+        StyleablePropertyFactory<TestNode> factory = new StyleablePropertyFactory<>(Rectangle.getClassCssMetaData());
+        StyleableProperty<Duration> myDuration = factory.createStyleableDurationProperty(this, "myDuration", "-my-duration", (s) -> s.myDuration, Duration.millis(1000));
+
+        @Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
+            return factory.getCssMetaData();
+        }
+    }
+
+
+    BooleanProperty fadeIn = new SimpleBooleanProperty(false);
+    private Node createDurationTest() {
+
+        final BorderPane pane = new BorderPane();
+        pane.setStyle("-fx-border-color: blue;");
+        pane.setPadding(new Insets(10,10,10,10));
+
+        Slider slider = new Slider();
+        slider.setPadding(new Insets(5,5,5,10));
+        slider.setMin(500d);
+        slider.setMax(1500d);
+        slider.setBlockIncrement(50);
+        slider.setValue(1000d);
+        slider.setShowTickLabels(true);
+        slider.setShowTickMarks(true);
+        slider.setSnapToTicks(true);
+        slider.setOrientation(Orientation.VERTICAL);
+
+        pane.setRight(slider);
+
+        final TestNode testNode = new TestNode();
+        slider.valueProperty().addListener(o -> testNode.setStyle("-my-duration: " + ((Property<Number>)o).getValue().intValue() + "ms;"));
+
+        final Button fadeButton = new Button();
+        fadeButton.textProperty().bind(Bindings.when(fadeIn).then("Fade In").otherwise("Fade Out"));
+        fadeButton.setOnAction(e -> {
+            Duration duration = testNode.myDuration.getValue();
+            FadeTransition transition = new FadeTransition(duration, testNode);
+            transition.setFromValue(testNode.getOpacity());
+            transition.statusProperty().addListener(o -> {
+                if (((ReadOnlyObjectProperty<Animation.Status>) o).getValue() == Animation.Status.STOPPED) {
+                    fadeButton.setDisable(false);
+                } else {
+                    fadeButton.setDisable(true);
+                }
+            });
+            if (fadeIn.get()) {
+                transition.setToValue(1.0);
+                transition.setByValue(5);
+                transition.setOnFinished(a -> fadeIn.set(false));
+            } else {
+                transition.setToValue(0.1);
+                transition.setByValue(-5);
+                transition.setOnFinished(a -> fadeIn.set(true));
+            }
+            transition.playFromStart();
+        });
+
+        VBox vbox = new VBox(5, testNode, fadeButton);
+        vbox.setAlignment(Pos.CENTER);
+        pane.setCenter(vbox);
+
+        Label label = new Label("Use slider to adjust duration of the\nFadeTransition, then click the button.");
+        pane.setTop(label);
+
+        Label status = new Label();
+        status.textProperty().bind(Bindings.createStringBinding(
+                () -> testNode.myDuration.getValue().toString(),
+                (ObjectProperty<Duration>)testNode.myDuration
+        ));
+        pane.setBottom(status);
+
+        BorderPane.setAlignment(label, Pos.CENTER);
+        BorderPane.setAlignment(slider, Pos.CENTER);
+        BorderPane.setAlignment(vbox, Pos.CENTER);
+        BorderPane.setAlignment(status, Pos.BOTTOM_RIGHT);
+
+        return pane;
+    }
+
 }
--- a/modules/graphics/src/main/docs/javafx/scene/doc-files/cssref.html	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/main/docs/javafx/scene/doc-files/cssref.html	Tue Aug 26 15:54:28 2014 -0400
@@ -812,6 +812,16 @@
       <li><strong>grad</strong>: angle in gradians</li>
       <li><strong>turn</strong>: angle in turns</li>
     </ul>
+    <h3><a name="typeduration" id="typeduration">&lt;duration&gt;</a></h3>
+    <p>A duration is a <a href="#typenumber" class="typeref">&lt;number&gt;</a>
+        with one of the following units.</p>
+    <p> </p>
+    <p class="grammar"><a href="#typenumber">&lt;number&gt;</a>[ s | ms ]</p>
+    <ul>
+        <li><strong>s</strong>: duration in seconds</li>
+        <li><strong>ms</strong>: duration in milliseconds. One second is 1000 milliseconds.</li>
+    </ul>
+    <p>See also <a href="http://www.w3.org/TR/css3-values/#time" class="typelink">W3C time units</a>.</p>
     <h3><a name="typepoint" id="typepoint">&lt;point&gt;</a></h3>
     <p>A point is an {x,y} coordinate.</p>
     <p class="grammar">[ [ &lt;length&gt; &lt;length&gt; ]&nbsp;|&nbsp;[
--- a/modules/graphics/src/main/java/com/sun/javafx/css/SizeUnits.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/javafx/css/SizeUnits.java	Tue Aug 26 15:54:28 2014 -0400
@@ -246,6 +246,41 @@
             return round(value*360);
         }
 
+    },
+
+
+    S(true) {
+
+        @Override
+        public String toString() { return "s"; }
+
+        @Override
+        public double points(double value, double multiplier_not_used, Font font_not_used) {
+            return value;
+        }
+
+        @Override
+        public double pixels(double value, double multiplier_not_used, Font font_not_used) {
+            return value;
+        }
+
+    },
+
+    MS(true) {
+
+        @Override
+        public String toString() { return "ms"; }
+
+        @Override
+        public double points(double value, double multiplier_not_used, Font font_not_used) {
+            return value;
+        }
+
+        @Override
+        public double pixels(double value, double multiplier_not_used, Font font_not_used) {
+            return value;
+        }
+
     };
 
     abstract double points(double value, double multiplier, Font font);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/javafx/css/converters/DurationConverter.java	Tue Aug 26 15:54:28 2014 -0400
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.css.converters;
+
+import com.sun.javafx.css.Size;
+import com.sun.javafx.css.StyleConverterImpl;
+import javafx.css.ParsedValue;
+import javafx.css.StyleConverter;
+import javafx.scene.text.Font;
+import javafx.util.Duration;
+
+/* Convert a Size to Duration */
+public final class DurationConverter extends StyleConverterImpl<ParsedValue<?, Size>, Duration> {
+
+    // lazy, thread-safe instatiation
+    private static class Holder {
+        static final DurationConverter INSTANCE = new DurationConverter();
+    }
+
+    public static StyleConverter<ParsedValue<?, Size>, Duration> getInstance() {
+        return Holder.INSTANCE;
+    }
+
+    private DurationConverter() {
+        super();
+    }
+
+    @Override
+    public Duration convert(ParsedValue<ParsedValue<?, Size>, Duration> value, Font font) {
+        ParsedValue<?, Size> parsedValue = value.getValue();
+        Size size = parsedValue.convert(font);
+        double time = size.getValue();
+        Duration duration = null;
+        switch (size.getUnits()) {
+            case S: duration = Duration.seconds(time); break;
+            case MS: duration = Duration.millis(time); break;
+            default: duration = Duration.UNKNOWN;
+        }
+        return duration;
+    }
+
+    @Override
+    public String toString() {
+        return "DurationConverter";
+    }
+
+}
--- a/modules/graphics/src/main/java/com/sun/javafx/css/parser/CSSLexer.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/javafx/css/parser/CSSLexer.java	Tue Aug 26 15:54:28 2014 -0400
@@ -77,6 +77,8 @@
     final static int FONT_FACE = 42;
     final static int URL = 43;
     final static int IMPORT = 44;
+    final static int SECONDS = 45;
+    final static int MS = 46;
 
     private final Recognizer A = (c) -> c == 'a' || c == 'A';
     private final Recognizer B = (c) -> c == 'b' || c == 'B';
@@ -604,17 +606,18 @@
             { G, R, A, D },
             { I, N },
             { M, M },
+            { M, S },
             { P, C },
             { P, T },
             { P, X },
             { R, A, D },
+            { S },
             { T, U, R, N },
             { (c) -> c == '%'}
-            
         };
         
         // One bit per unit
-        private int unitsMask = 0x1FFF;
+        private int unitsMask = 0x7FFF;
 
         // Offset into inner array of units
         private int index = -1;
@@ -638,17 +641,19 @@
                 case 0x10: type = GRAD; break;
                 case 0x20: type = IN; break;
                 case 0x40: type = MM; break;
-                case 0x80: type = PC; break;
-                case 0x100: type = PT; break;
-                case 0x200: type = PX; break;
-                case 0x400: type = RAD; break;
-                case 0x800: type = TURN; break;
-                case 0x1000: type = PERCENTAGE; break;
+                case 0x80: type = MS; break;
+                case 0x100: type = PC; break;
+                case 0x200: type = PT; break;
+                case 0x400: type = PX; break;
+                case 0x800: type = RAD; break;
+                case 0x1000: type = SECONDS; break;
+                case 0x2000: type = TURN; break;
+                case 0x4000: type = PERCENTAGE; break;
                 default: type = Token.INVALID;
             }
              
             // reset
-            unitsMask = 0x1fff;
+            unitsMask = 0x7fff;
             index = -1;
             
             return type;
@@ -670,22 +675,22 @@
             if (unitsMask == 0) return true;
             
             index += 1;
-            
+
             for (int n=0 ; n < units.length; n++) {
                 
                 final int u = 1 << n;
                 
                 // the unit at this index already failed. Move on.
                 if ((unitsMask & u) == 0) continue;
-                
+
                 if ((index >= units[n].length) || !(units[n][index].recognize(c))) {
                     // not a match, turn off this bit
-                    unitsMask &= ~u; 
+                    unitsMask &= ~u;
                 }
                     
             }
 
-            
+
             return true;
         }
 
--- a/modules/graphics/src/main/java/com/sun/javafx/css/parser/CSSParser.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/main/java/com/sun/javafx/css/parser/CSSParser.java	Tue Aug 26 15:54:28 2014 -0400
@@ -40,6 +40,7 @@
 import com.sun.javafx.css.StyleManager;
 import com.sun.javafx.css.Stylesheet;
 import com.sun.javafx.css.converters.BooleanConverter;
+import com.sun.javafx.css.converters.DurationConverter;
 import com.sun.javafx.css.converters.EffectConverter;
 import com.sun.javafx.css.converters.EnumConverter;
 import com.sun.javafx.css.converters.FontConverter;
@@ -89,6 +90,7 @@
 import javafx.scene.text.Font;
 import javafx.scene.text.FontPosture;
 import javafx.scene.text.FontWeight;
+import javafx.util.Duration;
 import sun.util.logging.PlatformLogger;
 import sun.util.logging.PlatformLogger.Level;
 
@@ -613,7 +615,14 @@
             break;
         case CSSLexer.TURN:
             units = SizeUnits.TURN;
-            trim = 5;
+            trim = 4;
+            break;
+        case CSSLexer.SECONDS:
+            units = SizeUnits.S;
+            trim = 1;
+            break;
+        case CSSLexer.MS:
+            units = SizeUnits.MS;
             break;
         default:
             if (LOGGER.isLoggable(Level.FINEST)) {
@@ -853,6 +862,12 @@
                 value = new ParsedValueImpl<ParsedValue[],Number[]>(sizeValue, SizeConverter.SequenceConverter.getInstance());
             }
             break;
+        case CSSLexer.SECONDS:
+        case CSSLexer.MS: {
+            ParsedValue<Size, Size> sizeValue = new ParsedValueImpl<Size, Size>(size(token), null);
+            value = new ParsedValueImpl<ParsedValue<?, Size>, Duration>(sizeValue, DurationConverter.getInstance());
+            break;
+        }
         case CSSLexer.STRING:
         case CSSLexer.IDENT:
             boolean isIdent = ttype == CSSLexer.IDENT;
@@ -4596,6 +4611,8 @@
             case CSSLexer.RAD:
             case CSSLexer.TURN:
             case CSSLexer.PERCENTAGE:
+            case CSSLexer.SECONDS:
+            case CSSLexer.MS:
                 break;
 
             case CSSLexer.STRING:
--- a/modules/graphics/src/main/java/javafx/css/StyleConverter.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/main/java/javafx/css/StyleConverter.java	Tue Aug 26 15:54:28 2014 -0400
@@ -27,6 +27,7 @@
 
 import com.sun.javafx.css.converters.BooleanConverter;
 import com.sun.javafx.css.converters.ColorConverter;
+import com.sun.javafx.css.converters.DurationConverter;
 import com.sun.javafx.css.converters.EffectConverter;
 import com.sun.javafx.css.converters.EnumConverter;
 import com.sun.javafx.css.converters.FontConverter;
@@ -40,6 +41,7 @@
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
 import javafx.scene.text.Font;
+import javafx.util.Duration;
 
 /**
  * Converter converts {@code ParsedValue&tl;F,T&gt;} from type F to type T. the
@@ -80,11 +82,20 @@
      * representation of a web color to a {@code Color}
      * @see Color#web(java.lang.String) 
      */
+    public static StyleConverter<?,Duration> getDurationConverter() {
+        return DurationConverter.getInstance();
+    }
+
+    /**
+     * @return A {@code StyleConverter} that converts a String
+     * representation of a web color to a {@code Color}
+     * @see Color#web(java.lang.String)
+     */
     public static StyleConverter<String,Color> getColorConverter() {
         return ColorConverter.getInstance();
     }
-    
-    /** 
+
+    /**
      * @return A {@code StyleConverter} that converts a parsed representation
      * of an {@code Effect} to an {@code Effect}
      * @see Effect
--- a/modules/graphics/src/main/java/javafx/css/StyleablePropertyFactory.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/main/java/javafx/css/StyleablePropertyFactory.java	Tue Aug 26 15:54:28 2014 -0400
@@ -32,6 +32,7 @@
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
 import javafx.scene.text.Font;
+import javafx.util.Duration;
 import javafx.util.Pair;
 
 import java.util.ArrayList;
@@ -358,6 +359,88 @@
 
     //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     //
+    // create StyleableProperty<Duration>
+    //
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Create a StyleableProperty&lt;Duration&gt; with initial value and inherit flag.
+     * @param styleable The <code>this</code> reference of the returned property. This is also the property bean.
+     * @param propertyName The field name of the StyleableProperty&lt;Duration&gt;
+     * @param cssProperty The CSS property name
+     * @param function A function that returns the StyleableProperty&lt;Duration&gt; that was created by this method call.
+     * @param initialValue The initial value of the property. CSS may reset the property to this value. 
+     * @param inherits Whether or not the CSS style can be inherited by child nodes                     
+     */
+    public final StyleableProperty<Duration> createStyleableDurationProperty(
+            S styleable,
+            String propertyName,
+            String cssProperty,
+            Function<S, StyleableProperty<Duration>> function,
+            Duration initialValue,
+            boolean inherits) {
+
+        CssMetaData<S,Duration> cssMetaData = createDurationCssMetaData(cssProperty, function, initialValue, inherits);
+        return new SimpleStyleableObjectProperty<Duration>(cssMetaData, styleable, propertyName, initialValue);
+    }
+
+    /**
+     * Create a StyleableProperty&lt;Duration&gt; with initial value. The inherit flag defaults to false.
+     * @param styleable The <code>this</code> reference of the returned property. This is also the property bean.
+     * @param propertyName The field name of the StyleableProperty&lt;Duration&gt;
+     * @param cssProperty The CSS property name
+     * @param function A function that returns the StyleableProperty&lt;Duration&gt; that was created by this method call.
+     * @param initialValue The initial value of the property. CSS may reset the property to this value. 
+     */
+    public final StyleableProperty<Duration> createStyleableDurationProperty(
+            S styleable,
+            String propertyName,
+            String cssProperty,
+            Function<S, StyleableProperty<Duration>> function,
+            Duration initialValue) {
+        return createStyleableDurationProperty(styleable, propertyName, cssProperty, function, initialValue, false);
+    }
+
+    /**
+     * Create a StyleableProperty&lt;Duration&gt;. The initial value defaults to Duration.BLACK and the 
+     * inherit flag defaults to false.
+     * @param styleable The <code>this</code> reference of the returned property. This is also the property bean.
+     * @param propertyName The field name of the StyleableProperty&lt;Duration&gt;
+     * @param cssProperty The CSS property name
+     * @param function A function that returns the StyleableProperty&lt;Duration&gt; that was created by this method call.
+     */
+    public final StyleableProperty<Duration> createStyleableDurationProperty(
+            S styleable,
+            String propertyName,
+            String cssProperty,
+            Function<S, StyleableProperty<Duration>> function) {
+        return createStyleableDurationProperty(styleable, propertyName, cssProperty, function, Duration.UNKNOWN, false);
+    }
+
+    /**
+     * Create a StyleableProperty&lt;Duration&gt; using previously created CssMetaData for the given <code>cssProperty</code>.
+     * @param styleable The <code>this</code> reference of the returned property. This is also the property bean.
+     * @param propertyName The field name of the StyleableProperty&lt;Duration&gt;
+     * @param cssProperty The CSS property name
+     * @throws java.lang.IllegalArgumentException if <code>cssProperty</code> is null or empty
+     * @throws java.util.NoSuchElementException if the CssMetaData for <code>cssProperty</code> was not created prior to this method invocation
+     */
+    public final StyleableProperty<Duration> createStyleableDurationProperty(
+            S styleable,
+            String propertyName,
+            String cssProperty) {
+
+        if (cssProperty == null || cssProperty.isEmpty()) {
+            throw new IllegalArgumentException("cssProperty cannot be null or empty string");
+        }
+
+        @SuppressWarnings("unchecked")
+        CssMetaData<S,Duration> cssMetaData = (CssMetaData<S,Duration>)getCssMetaData(Duration.class, cssProperty);
+        return new SimpleStyleableObjectProperty<Duration>(cssMetaData, styleable, propertyName, cssMetaData.getInitialValue(styleable));
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //
     // create StyleableProperty<Effect>
     //
     //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -433,7 +516,7 @@
         }
 
         @SuppressWarnings("unchecked")
-        CssMetaData<S,Effect> cssMetaData = (CssMetaData<S,Effect>)getCssMetaData(Color.class, cssProperty);
+        CssMetaData<S,Effect> cssMetaData = (CssMetaData<S,Effect>)getCssMetaData(Effect.class, cssProperty);
         return new SimpleStyleableObjectProperty<Effect>(cssMetaData, styleable, propertyName, cssMetaData.getInitialValue(styleable));
     }
     
@@ -1173,6 +1256,65 @@
 
     //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     //                                                                                                              //
+    // create CssMetaData<S, Duration>                                                                                 //
+    //                                                                                                              //
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Create a CssMetaData&lt;S, Duration&gt; with initial value, and inherit flag.
+     * @param property The CSS property name.
+     * @param function A function that returns the StyleableProperty&lt;Duration&gt; that corresponds to this CssMetaData.
+     * @param initialValue The initial value of the property. CSS may reset the property to this value.
+     * @param inherits Whether or not the CSS style can be inherited by child nodes
+     * @throws java.lang.IllegalArgumentException if <code>property</code> is null or an empty string, or <code>function</code> is null.
+     */
+    public final CssMetaData<S, Duration>
+    createDurationCssMetaData(final String property, final Function<S,StyleableProperty<Duration>> function, final Duration initialValue, final boolean inherits)
+    {
+        if (property == null || property.isEmpty()) {
+            throw new IllegalArgumentException("property cannot be null or empty string");
+        }
+
+        if (function == null) {
+            throw new IllegalArgumentException("function cannot be null");
+        }
+
+        @SuppressWarnings("unchecked") // getCssMetaData checks and will throw a ClassCastException
+                CssMetaData<S, Duration> cssMetaData =
+                (CssMetaData<S, Duration>)getCssMetaData(Duration.class, property, key -> {
+                    final StyleConverter<?,Duration> converter = StyleConverter.getDurationConverter();
+                    return new SimpleCssMetaData<S, Duration>(property, function, converter, initialValue, inherits);
+                });
+        return cssMetaData;
+    }
+
+    /**
+     * Create a CssMetaData&lt;S, Duration&gt; with initial value, and inherit flag defaulting to false.
+     * @param property The CSS property name.
+     * @param function A function that returns the StyleableProperty&lt;Duration&gt; that corresponds to this CssMetaData.
+     * @param initialValue The initial value of the property. CSS may reset the property to this value.
+     * @throws java.lang.IllegalArgumentException if <code>property</code> is null or an empty string, or <code>function</code> is null.
+     */
+    public final CssMetaData<S, Duration>
+    createDurationCssMetaData(final String property, final Function<S,StyleableProperty<Duration>> function, final Duration initialValue)
+    {
+        return createDurationCssMetaData(property, function, initialValue, false);
+    }
+
+    /**
+     * Create a CssMetaData&lt;S, Duration&gt; with initial value of Duration.BLACK, and inherit flag defaulting to false.
+     * @param property The CSS property name.
+     * @param function A function that returns the StyleableProperty&lt;Duration&gt; that corresponds to this CssMetaData.
+     * @throws java.lang.IllegalArgumentException if <code>property</code> is null or an empty string, or <code>function</code> is null.
+     */
+    public final CssMetaData<S, Duration>
+    createDurationCssMetaData(final String property, final Function<S,StyleableProperty<Duration>> function)
+    {
+        return createDurationCssMetaData(property, function, Duration.UNKNOWN, false);
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //                                                                                                              //
     // create CssMetaData<S, Effect>                                                                                 //
     //                                                                                                              //
     //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
--- a/modules/graphics/src/test/java/com/sun/javafx/css/SizeTest.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/test/java/com/sun/javafx/css/SizeTest.java	Tue Aug 26 15:54:28 2014 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -180,6 +180,25 @@
     }
 
     /**
+     * Test of sizes with time units.
+     */
+    @Test
+    public void testTime() {
+
+        double expResult = 90;
+
+        Size instance = new Size(90, SizeUnits.S);
+        double result = instance.pixels();
+        assertEquals("90s", expResult, result, 0.01);
+
+        instance = new Size(90, SizeUnits.MS);
+        result = instance.pixels();
+        assertEquals("90ms", expResult, result, 0.01);
+
+    }
+
+
+    /**
      * Test of equals method, of class Size.
      */
     @Test
--- a/modules/graphics/src/test/java/com/sun/javafx/css/parser/CSSLexerTest.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/test/java/com/sun/javafx/css/parser/CSSLexerTest.java	Tue Aug 26 15:54:28 2014 -0400
@@ -183,7 +183,19 @@
         lexDigitsWithUnits("turn", CSSLexer.TURN);
         // case should be ignored
         lexDigitsWithUnits("TurN", CSSLexer.TURN);
-    }        
+    }
+    @Test
+    public void testLexValidDigitsWithS() {
+        lexDigitsWithUnits("s", CSSLexer.SECONDS);
+        // case should be ignored
+        lexDigitsWithUnits("S", CSSLexer.SECONDS);
+    }
+    @Test
+    public void testLexValidDigitsWithMS() {
+        lexDigitsWithUnits("ms", CSSLexer.MS);
+        // case should be ignored
+        lexDigitsWithUnits("mS", CSSLexer.MS);
+    }
     @Test
     public void testLexValidDigitsWithPCT() {        
         lexDigitsWithUnits("%", CSSLexer.PERCENTAGE);
--- a/modules/graphics/src/test/java/javafx/css/StyleablePropertyFactoryTest.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/test/java/javafx/css/StyleablePropertyFactoryTest.java	Tue Aug 26 15:54:28 2014 -0400
@@ -16,6 +16,7 @@
 import javafx.scene.paint.Paint;
 import javafx.scene.paint.Stop;
 import javafx.scene.text.Font;
+import javafx.util.Duration;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.CoreMatchers;
 import org.hamcrest.Description;
@@ -65,6 +66,7 @@
         return Arrays.asList(new Data[][]{
                 {new Data("myBoolean", "-my-boolean: true;", Boolean.TRUE)},
                 {new Data("myColor", "-my-color: red;", Color.RED)},
+                {new Data("myDuration", "-my-duration: 30ms;", Duration.millis(30))},
                 {new Data("myEffect", "-my-effect: innershadow(gaussian, red, 10, .5, 1, 1);",
                         new InnerShadow(BlurType.GAUSSIAN, Color.RED, 10, .5, 1, 1),
                         new BaseMatcher<InnerShadow>() {
@@ -129,6 +131,11 @@
         public void setMyColor(Color value) { myColor.setValue(value); }
         private final StyleableProperty<Color> myColor = fac.createStyleableColorProperty(this, "myColor", "-my-color", s -> ((MyStyleable) s).myColor);
 
+        public ObservableValue<Duration> myDurationProperty () { return (ObservableValue<Duration>) myDuration; }
+        public Duration getMyDuration() { return myDuration.getValue(); }
+        public void setMyDuration(Duration value) { myDuration.setValue(value); }
+        private final StyleableProperty<Duration> myDuration = fac.createStyleableDurationProperty(this, "myDuration", "-my-duration", s -> ((MyStyleable) s).myDuration);
+
         public ObservableValue<Effect> myEffectProperty () { return (ObservableValue<Effect>) myEffect; }
         public Effect getMyEffect() { return myEffect.getValue(); }
         public void setMyEffect(Effect value) { myEffect.setValue(value); }
--- a/modules/graphics/src/test/java/javafx/css/StyleablePropertyFactory_createMethod_Test.java	Tue Aug 26 11:34:41 2014 -0700
+++ b/modules/graphics/src/test/java/javafx/css/StyleablePropertyFactory_createMethod_Test.java	Tue Aug 26 15:54:28 2014 -0400
@@ -35,6 +35,7 @@
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
 import javafx.scene.text.Font;
+import javafx.util.Duration;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -86,6 +87,7 @@
         return Arrays.asList(new Data[][] {
                 { new Data("createStyleableBooleanProperty", StyleConverter.getBooleanConverter(), Boolean.TRUE) },
                 { new Data("createStyleableColorProperty",   StyleConverter.getColorConverter(), Color.YELLOW)   },
+                { new Data("createStyleableDurationProperty",   StyleConverter.getDurationConverter(), Duration.millis(30))   },
                 { new Data("createStyleableEffectProperty",  StyleConverter.getEffectConverter(), new InnerShadow(10d, Color.RED)) },
                 { new Data("createStyleableEnumProperty", StyleConverter.getEnumConverter(Pos.class), Pos.CENTER) },
                 { new Data("createStyleableFontProperty", StyleConverter.getFontConverter(), Font.font(18)) },