OSDN Git Service

Rework Outline API, remove isolatedZVolume remnants
authorChris Craik <ccraik@google.com>
Thu, 20 Mar 2014 19:44:20 +0000 (12:44 -0700)
committerChris Craik <ccraik@google.com>
Mon, 24 Mar 2014 18:37:06 +0000 (11:37 -0700)
Change-Id: I30c2fe832dcb98fa6329b1a595b3d3aafbdcad6b

13 files changed:
api/current.txt
core/java/android/view/RenderNode.java
core/java/android/view/View.java
core/java/android/view/ViewGroup.java
core/jni/android_view_RenderNode.cpp
core/res/res/values/attrs.xml
graphics/java/android/graphics/Outline.java [new file with mode: 0644]
graphics/java/android/graphics/drawable/Drawable.java
graphics/java/android/graphics/drawable/GradientDrawable.java
libs/hwui/Outline.h [new file with mode: 0644]
libs/hwui/RenderNode.cpp
libs/hwui/RenderProperties.cpp
libs/hwui/RenderProperties.h

index a3d39c8..21d5cf8 100644 (file)
@@ -9792,6 +9792,13 @@ package android.graphics {
     method public void setPaint(android.graphics.Paint);
   }
 
+  public class Outline {
+    ctor public Outline();
+    method public final boolean isValid();
+    method public void set(android.graphics.Outline);
+    method public void setRoundRect(int, int, int, int, float);
+  }
+
   public class Paint {
     ctor public Paint();
     ctor public Paint(int);
@@ -10501,6 +10508,7 @@ package android.graphics.drawable {
     method public int getMinimumHeight();
     method public int getMinimumWidth();
     method public abstract int getOpacity();
+    method public android.graphics.Outline getOutline();
     method public boolean getPadding(android.graphics.Rect);
     method public int[] getState();
     method public android.graphics.Region getTransparentRegion();
@@ -29097,7 +29105,6 @@ package android.view {
     method public int getNextFocusRightId();
     method public int getNextFocusUpId();
     method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
-    method public final void getOutline(android.graphics.Path);
     method public int getOverScrollMode();
     method public android.view.ViewOverlay getOverlay();
     method public int getPaddingBottom();
@@ -29361,7 +29368,7 @@ package android.view {
     method public void setOnLongClickListener(android.view.View.OnLongClickListener);
     method public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener);
     method public void setOnTouchListener(android.view.View.OnTouchListener);
-    method public void setOutline(android.graphics.Path);
+    method public void setOutline(android.graphics.Outline);
     method public void setOverScrollMode(int);
     method public void setPadding(int, int, int, int);
     method public void setPaddingRelative(int, int, int, int);
index 87ab20e..60fb7ac 100644 (file)
@@ -17,7 +17,7 @@
 package android.view;
 
 import android.graphics.Matrix;
-import android.graphics.Path;
+import android.graphics.Outline;
 
 /**
  * <p>A display list records a series of graphics related operations and can replay
@@ -311,16 +311,6 @@ public class RenderNode {
     }
 
     /**
-     * Set whether the display list should collect and Z order all 3d composited descendents, and
-     * draw them in order with the default Z=0 content.
-     *
-     * @param isolatedZVolume true if the display list should collect and Z order descendents.
-     */
-    public void setIsolatedZVolume(boolean isolatedZVolume) {
-        nSetIsolatedZVolume(mNativeDisplayList, isolatedZVolume);
-    }
-
-    /**
      * Sets whether the display list should be drawn immediately after the
      * closest ancestor display list where isolateZVolume is true. If the
      * display list itself satisfies this constraint, changing this attribute
@@ -346,13 +336,19 @@ public class RenderNode {
      * Sets the outline, defining the shape that casts a shadow, and the path to
      * be clipped if setClipToOutline is set.
      *
-     * Deep copies the native path to simplify reference ownership.
-     *
-     * @param outline Convex, CW Path to store in the DisplayList. May be null.
-     */
-    public void setOutline(Path outline) {
-        long nativePath = (outline == null) ? 0 : outline.mNativePath;
-        nSetOutline(mNativeDisplayList, nativePath);
+     * Deep copies the data into native to simplify reference ownership.
+     */
+    public void setOutline(Outline outline) {
+        if (outline == null) {
+            nSetOutlineEmpty(mNativeDisplayList);
+        } else if (!outline.isValid()) {
+            throw new IllegalArgumentException("Outline must be valid");
+        } else if (outline.mRect != null) {
+            nSetOutlineRoundRect(mNativeDisplayList, outline.mRect.left, outline.mRect.top,
+                    outline.mRect.right, outline.mRect.bottom, outline.mRadius);
+        } else if (outline.mPath != null) {
+            nSetOutlineConvexPath(mNativeDisplayList, outline.mPath.mNativePath);
+        }
     }
 
     /**
@@ -855,8 +851,10 @@ public class RenderNode {
     private static native void nSetClipToBounds(long displayList, boolean clipToBounds);
     private static native void nSetProjectBackwards(long displayList, boolean shouldProject);
     private static native void nSetProjectionReceiver(long displayList, boolean shouldRecieve);
-    private static native void nSetIsolatedZVolume(long displayList, boolean isolateZVolume);
-    private static native void nSetOutline(long displayList, long nativePath);
+    private static native void nSetOutlineRoundRect(long displayList, int left, int top,
+            int right, int bottom, float radius);
+    private static native void nSetOutlineConvexPath(long displayList, long nativePath);
+    private static native void nSetOutlineEmpty(long displayList);
     private static native void nSetClipToOutline(long displayList, boolean clipToOutline);
     private static native void nSetAlpha(long displayList, float alpha);
     private static native void nSetHasOverlappingRendering(long displayList,
index a8ccd49..9f912db 100644 (file)
@@ -31,6 +31,7 @@ import android.graphics.Insets;
 import android.graphics.Interpolator;
 import android.graphics.LinearGradient;
 import android.graphics.Matrix;
+import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PixelFormat;
@@ -2377,14 +2378,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     static final int PFLAG3_CLIP_TO_OUTLINE = 0x20;
 
     /**
+     * Flag indicating that a view's outline has been specifically defined.
+     */
+    static final int PFLAG3_OUTLINE_DEFINED = 0x40;
+
+    /**
      * Flag indicating that we're in the process of applying window insets.
      */
-    static final int PFLAG3_APPLYING_INSETS = 0x40;
+    static final int PFLAG3_APPLYING_INSETS = 0x80;
 
     /**
      * Flag indicating that we're in the process of fitting system windows using the old method.
      */
-    static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
+    static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x100;
 
     /* End of masks for mPrivateFlags3 */
 
@@ -3335,8 +3341,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     /**
      * Stores the outline of the view, passed down to the DisplayList level for
      * defining shadow shape and clipping.
+     *
+     * TODO: once RenderNode is long-lived, remove this and rely on native copy.
      */
-    private Path mOutline;
+    private Outline mOutline;
 
     /**
      * When this view has focus and the next focus is {@link #FOCUS_LEFT},
@@ -10802,66 +10810,44 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     }
 
     /**
-     * Copies the Outline of the View into the Path parameter.
-     * <p>
-     * If the outline is not set, the parameter Path is set to empty.
-     *
-     * @param outline Path into which View's outline will be copied. Must be non-null.
-     *
-     * @see #setOutline(Path)
-     * @see #getClipToOutline()
-     * @see #setClipToOutline(boolean)
-     */
-    public final void getOutline(@NonNull Path outline) {
-        if (outline == null) {
-            throw new IllegalArgumentException("Path must be non-null");
-        }
-        if (mOutline == null) {
-            outline.reset();
-        } else {
-            outline.set(mOutline);
-        }
-    }
-
-    /**
      * Sets the outline of the view, which defines the shape of the shadow it
      * casts, and can used for clipping.
      * <p>
-     * The outline path of a View must be {@link android.graphics.Path#isConvex() convex}.
-     * <p>
-     * If the outline is not set, or {@link Path#isEmpty()}, shadows will be
-     * cast from the bounds of the View, and clipToOutline will be ignored.
+     * If the outline is not set or is null, shadows will be cast from the
+     * bounds of the View, and clipToOutline will be ignored.
      *
-     * @param outline The new outline of the view. Must be non-null, and convex.
+     * @param outline The new outline of the view.
+     *         Must be {@link android.view.Outline#isValid() valid.}
      *
-     * @see #getOutline(Path)
      * @see #getClipToOutline()
      * @see #setClipToOutline(boolean)
      */
-    public void setOutline(@NonNull Path outline) {
-        if (outline == null) {
-            throw new IllegalArgumentException("Path must be non-null");
+    public void setOutline(@Nullable Outline outline) {
+        if (outline != null && !outline.isValid()) {
+            throw new IllegalArgumentException("Outline must not be invalid");
         }
-        if (!outline.isConvex()) {
-            throw new IllegalArgumentException("Path must be convex");
-        }
-        // always copy the path since caller may reuse
-        if (mOutline == null) {
-            mOutline = new Path(outline);
+
+        mPrivateFlags3 |= PFLAG3_OUTLINE_DEFINED;
+
+        if (outline == null) {
+            mOutline = null;
         } else {
+            // always copy the path since caller may reuse
+            if (mOutline == null) {
+                mOutline = new Outline();
+            }
             mOutline.set(outline);
         }
 
         if (mDisplayList != null) {
-            mDisplayList.setOutline(outline);
+            mDisplayList.setOutline(mOutline);
         }
     }
 
     /**
      * Returns whether the outline of the View will be used for clipping.
      *
-     * @see #getOutline(Path)
-     * @see #setOutline(Path)
+     * @see #setOutline(Outline)
      */
     public final boolean getClipToOutline() {
         return ((mPrivateFlags3 & PFLAG3_CLIP_TO_OUTLINE) != 0);
@@ -10879,8 +10865,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * If the outline of the view is not set or is empty, no clipping will be
      * performed.
      *
-     * @see #getOutline(Path)
-     * @see #setOutline(Path)
+     * @see #setOutline(Outline)
      */
     public void setClipToOutline(boolean clipToOutline) {
         // TODO : Add a fast invalidation here.
@@ -11460,7 +11445,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
 
             // Damage the entire IsolatedZVolume recieving this view's shadow.
             if (getTranslationZ() != 0) {
-                damageIsolatedZVolume();
+                damageShadowReceiver();
             }
         }
     }
@@ -11489,24 +11474,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     }
 
     /**
-     * Damage area of the screen covered by the current isolated Z volume
+     * Damage area of the screen that can be covered by this View's shadow.
      *
      * This method will guarantee that any changes to shadows cast by a View
      * are damaged on the screen for future redraw.
      */
-    private void damageIsolatedZVolume() {
+    private void damageShadowReceiver() {
         final AttachInfo ai = mAttachInfo;
         if (ai != null) {
             ViewParent p = getParent();
-            while (p != null) {
-                if (p instanceof ViewGroup) {
-                    final ViewGroup vg = (ViewGroup) p;
-                    if (vg.hasIsolatedZVolume()) {
-                        vg.damageInParent();
-                        return;
-                    }
-                }
-                p = p.getParent();
+            if (p != null && p instanceof ViewGroup) {
+                final ViewGroup vg = (ViewGroup) p;
+                vg.damageInParent();
             }
         }
     }
@@ -11540,7 +11519,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             damageInParent();
         }
         if (invalidateParent && getTranslationZ() != 0) {
-            damageIsolatedZVolume();
+            damageShadowReceiver();
         }
     }
 
@@ -14571,10 +14550,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                 displayList.setClipToBounds(
                         (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0);
             }
-            if (this instanceof ViewGroup) {
-                displayList.setIsolatedZVolume(
-                        (((ViewGroup) this).mGroupFlags & ViewGroup.FLAG_ISOLATED_Z_VOLUME) != 0);
-            }
             displayList.setOutline(mOutline);
             displayList.setClipToOutline(getClipToOutline());
             float alpha = 1;
@@ -15178,6 +15153,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         if (mBackgroundSizeChanged) {
             background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
             mBackgroundSizeChanged = false;
+            if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
+                // Outline not currently define, query from background
+                mOutline = background.getOutline();
+                if (mDisplayList != null) {
+                    mDisplayList.setOutline(mOutline);
+                }
+            }
         }
 
         // Attempt to use a display list if requested.
index 22fbbd6..aadaa7f 100644 (file)
@@ -357,15 +357,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
      */
     private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000;
 
-    /**
-     * When true, indicates that all 3d composited descendents are contained within this group, and
-     * will not be interleaved with other 3d composited content.
-     */
-    static final int FLAG_ISOLATED_Z_VOLUME = 0x1000000;
-
-    static final int FLAG_IS_TRANSITION_GROUP = 0x2000000;
+    static final int FLAG_IS_TRANSITION_GROUP = 0x1000000;
 
-    static final int FLAG_IS_TRANSITION_GROUP_SET = 0x4000000;
+    static final int FLAG_IS_TRANSITION_GROUP_SET = 0x2000000;
 
     /**
      * Indicates which types of drawing caches are to be kept in memory.
@@ -499,7 +493,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
         mGroupFlags |= FLAG_ANIMATION_DONE;
         mGroupFlags |= FLAG_ANIMATION_CACHE;
         mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
-        mGroupFlags |= FLAG_ISOLATED_Z_VOLUME;
 
         if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
             mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
@@ -528,9 +521,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                 case R.styleable.ViewGroup_clipToPadding:
                     setClipToPadding(a.getBoolean(attr, true));
                     break;
-                case R.styleable.ViewGroup_isolatedZVolume:
-                    setIsolatedZVolume(a.getBoolean(attr, true));
-                    break;
                 case R.styleable.ViewGroup_animationCache:
                     setAnimationCacheEnabled(a.getBoolean(attr, true));
                     break;
@@ -3159,43 +3149,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     }
 
     /**
-     * Returns whether this group's descendents are drawn in their own
-     * independent Z volume. Views drawn in one contained volume will not
-     * interleave with views in another, even if their Z values are interleaved.
-     * The default value is true.
-     * @see #setIsolatedZVolume(boolean)
-     *
-     * @return True if the ViewGroup has an isolated Z volume.
-     *
-     * @hide
-     */
-    public boolean hasIsolatedZVolume() {
-        return ((mGroupFlags & FLAG_ISOLATED_Z_VOLUME) != 0);
-    }
-
-    /**
-     * By default, only direct children of a group can interleave drawing order
-     * by interleaving Z values. Set to false on individual groups to enable Z
-     * interleaving of views that aren't direct siblings.
-     *
-     * @return True if the group should be an isolated Z volume with its own Z
-     *         ordering space, false if its decendents should inhabit the
-     *         inherited Z ordering volume.
-     * @attr ref android.R.styleable#ViewGroup_isolatedZVolume
-     *
-     * @hide
-     */
-    public void setIsolatedZVolume(boolean isolateZVolume) {
-        boolean previousValue = (mGroupFlags & FLAG_ISOLATED_Z_VOLUME) != 0;
-        if (isolateZVolume != previousValue) {
-            setBooleanFlag(FLAG_ISOLATED_Z_VOLUME, isolateZVolume);
-            if (mDisplayList != null) {
-                mDisplayList.setIsolatedZVolume(isolateZVolume);
-            }
-        }
-    }
-
-    /**
      * Returns whether this group's children are clipped to their bounds before drawing.
      * The default value is true.
      * @see #setClipChildren(boolean)
index fa08a78..f96be22 100644 (file)
@@ -69,7 +69,7 @@ static void android_view_RenderNode_destroyDisplayList(JNIEnv* env,
 }
 
 // ----------------------------------------------------------------------------
-// DisplayList view properties
+// RenderProperties
 // ----------------------------------------------------------------------------
 
 static void android_view_RenderNode_setCaching(JNIEnv* env,
@@ -98,11 +98,6 @@ static void android_view_RenderNode_setClipToBounds(JNIEnv* env,
     displayList->properties().setClipToBounds(clipToBounds);
 }
 
-static void android_view_RenderNode_setIsolatedZVolume(JNIEnv* env,
-        jobject clazz, jlong displayListPtr, jboolean shouldIsolate) {
-    // No-op, TODO: Remove Java usage of this method
-}
-
 static void android_view_RenderNode_setProjectBackwards(JNIEnv* env,
         jobject clazz, jlong displayListPtr, jboolean shouldProject) {
     RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr);
@@ -115,17 +110,28 @@ static void android_view_RenderNode_setProjectionReceiver(JNIEnv* env,
     displayList->properties().setProjectionReceiver(shouldRecieve);
 }
 
-static void android_view_RenderNode_setOutline(JNIEnv* env,
+static void android_view_RenderNode_setOutlineRoundRect(JNIEnv* env,
+        jobject clazz, jlong displayListPtr, jint left, jint top,
+        jint right, jint bottom, jfloat radius) {
+    RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr);
+    displayList->properties().outline().setRoundRect(left, top, right, bottom, radius);
+}
+static void android_view_RenderNode_setOutlineConvexPath(JNIEnv* env,
         jobject clazz, jlong displayListPtr, jlong outlinePathPtr) {
     RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr);
-    SkPath* outline = reinterpret_cast<SkPath*>(outlinePathPtr);
-    displayList->properties().setOutline(outline);
+    SkPath* outlinePath = reinterpret_cast<SkPath*>(outlinePathPtr);
+    displayList->properties().outline().setConvexPath(outlinePath);
+}
+static void android_view_RenderNode_setOutlineEmpty(JNIEnv* env,
+        jobject clazz, jlong displayListPtr) {
+    RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr);
+    displayList->properties().outline().setEmpty();
 }
 
 static void android_view_RenderNode_setClipToOutline(JNIEnv* env,
         jobject clazz, jlong displayListPtr, jboolean clipToOutline) {
     RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr);
-    displayList->properties().setClipToOutline(clipToOutline);
+    displayList->properties().outline().setShouldClip(clipToOutline);
 }
 
 static void android_view_RenderNode_setAlpha(JNIEnv* env,
@@ -381,11 +387,14 @@ static JNINativeMethod gMethods[] = {
     { "nSetStaticMatrix",      "(JJ)V",  (void*) android_view_RenderNode_setStaticMatrix },
     { "nSetAnimationMatrix",   "(JJ)V",  (void*) android_view_RenderNode_setAnimationMatrix },
     { "nSetClipToBounds",      "(JZ)V",  (void*) android_view_RenderNode_setClipToBounds },
-    { "nSetIsolatedZVolume",   "(JZ)V",  (void*) android_view_RenderNode_setIsolatedZVolume },
     { "nSetProjectBackwards",  "(JZ)V",  (void*) android_view_RenderNode_setProjectBackwards },
     { "nSetProjectionReceiver","(JZ)V",  (void*) android_view_RenderNode_setProjectionReceiver },
-    { "nSetOutline",           "(JJ)V",  (void*) android_view_RenderNode_setOutline },
+
+    { "nSetOutlineRoundRect",  "(JIIIIF)V", (void*) android_view_RenderNode_setOutlineRoundRect },
+    { "nSetOutlineConvexPath", "(JJ)V",  (void*) android_view_RenderNode_setOutlineConvexPath },
+    { "nSetOutlineEmpty",      "(J)V",   (void*) android_view_RenderNode_setOutlineEmpty },
     { "nSetClipToOutline",     "(JZ)V",  (void*) android_view_RenderNode_setClipToOutline },
+
     { "nSetAlpha",             "(JF)V",  (void*) android_view_RenderNode_setAlpha },
     { "nSetHasOverlappingRendering", "(JZ)V",
             (void*) android_view_RenderNode_setHasOverlappingRendering },
index 582ed1b..f1b1f6b 100644 (file)
         <!-- Defines whether the ViewGroup will clip its drawing surface so as to exclude
              the padding area. This property is set to true by default. -->
         <attr name="clipToPadding" format="boolean" />
-        <!-- Defines whether 3d composited descendents of the ViewGroup will be reordered into their
-             own independent Z volume. This property is set to true by default. -->
-        <attr name="isolatedZVolume" format="boolean" />
         <!-- Defines the layout animation to use the first time the ViewGroup is laid out.
              Layout animations can also be started manually after the first layout. -->
         <attr name="layoutAnimation" format="reference" />
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
new file mode 100644 (file)
index 0000000..a65ac87
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+/**
+ * Defines an area of content.
+ *
+ * Can be used with a View or Drawable to drive the shape of shadows cast by a
+ * View, and allowing Views to clip inner content.
+ *
+ * @see View#setOutline(Outline)
+ * @see View#setClipToOutline(boolean)
+ */
+public class Outline {
+    /** @hide */
+    public Rect mRect;
+
+    /** @hide */
+    public float mRadius;
+
+    /** @hide */
+    public Path mPath;
+
+    /**
+     * Constructs an invalid Outline. Call one of the setter methods to make
+     * the outline valid for use with a View.
+     */
+    public Outline() {}
+
+    /**
+     * Returns whether the Outline is valid for use with a View.
+     * <p>
+     * Outlines are invalid when constructed until a setter method is called.
+     */
+    public final boolean isValid() {
+        return mRect != null || mPath != null;
+    }
+
+    /**
+     * @hide
+     */
+    public final boolean canClip() {
+        return mPath == null;
+    }
+
+    /**
+     * Replace the contents of this Outline with the contents of src.
+     */
+    public void set(Outline src) {
+        if (src.mPath != null) {
+            if (mPath == null) {
+                mPath = new Path();
+            }
+            mPath.set(src.mPath);
+            mRect = null;
+        }
+        if (src.mRect != null) {
+            if (mRect == null) {
+                mRect = new Rect();
+            }
+            mRect.set(src.mRect);
+        }
+        mRadius = src.mRadius;
+    }
+
+    /**
+     * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
+     * <p>
+     * Outlines produced by this method support
+     * {@link View#setClipToOutline(boolean) View clipping.}
+     */
+    public void setRoundRect(int left, int top, int right, int bottom, float radius) {
+        if (mRect == null) mRect = new Rect();
+        mRect.set(left, top, right, bottom);
+        mRadius = radius;
+        mPath = null;
+    }
+
+    /**
+     * Sets the Constructs an Outline from a {@link android.graphics.Path#isConvex() convex path}.
+     *
+     * @hide
+     */
+    public void setConvexPath(Path convexPath) {
+        if (!convexPath.isConvex()) {
+            throw new IllegalArgumentException("path must be convex");
+        }
+        if (mPath == null) mPath = new Path();
+
+        mRect = null;
+        mRadius = -1.0f;
+        mPath.set(convexPath);
+    }
+}
index 84211ef..eb6b536 100644 (file)
@@ -30,6 +30,7 @@ import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.NinePatch;
+import android.graphics.Outline;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
@@ -860,6 +861,24 @@ public abstract class Drawable {
     }
 
     /**
+     * Returns the outline for this drawable if defined, null if not.
+     * <p>
+     * This method will be called by a View on its background Drawable after
+     * bounds change, if the View's Outline isn't set explicitly. This allows
+     * the background Drawable to provide the shape of the shadow casting
+     * portion of the View. It can also serve to clip the area of the View if
+     * if {@link View#setClipToOutline(boolean)} is set on the View.
+     * <p>
+     * The Outline queried by the View will not be modified, and is treated as
+     * a static shape that only needs to be requeried when the drawable's bounds
+     * change.
+     *
+     * @see View#setOutline(android.view.Outline)
+     * @see View#setClipToOutline(boolean)
+     */
+    public Outline getOutline() { return null; }
+
+    /**
      * Make this drawable mutable. This operation cannot be reversed. A mutable
      * drawable is guaranteed to not share its state with any other drawable.
      * This is especially useful when you need to modify properties of drawables
index 46d57ad..8f22add 100644 (file)
@@ -24,6 +24,7 @@ import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.DashPathEffect;
 import android.graphics.LinearGradient;
+import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PixelFormat;
@@ -133,6 +134,7 @@ public class GradientDrawable extends Drawable {
 
     private final Path mPath = new Path();
     private final RectF mRect = new RectF();
+    private Outline mOutline;
     
     private Paint mLayerPaint;    // internal, used if we use saveLayer()
     private boolean mRectIsDirty;   // internal state
@@ -585,11 +587,8 @@ public class GradientDrawable extends Drawable {
                     // to show it. If we did nothing, Skia would clamp the rad
                     // independently along each axis, giving us a thin ellipse
                     // if the rect were very wide but not very tall
-                    float rad = st.mRadius;
-                    float r = Math.min(mRect.width(), mRect.height()) * 0.5f;
-                    if (rad > r) {
-                        rad = r;
-                    }
+                    float rad = Math.min(st.mRadius,
+                            Math.min(mRect.width(), mRect.height()) * 0.5f);
                     canvas.drawRoundRect(mRect, rad, rad, mFillPaint);
                     if (haveStroke) {
                         canvas.drawRoundRect(mRect, rad, rad, mStrokePaint);
@@ -661,7 +660,7 @@ public class GradientDrawable extends Drawable {
         if (mRingPath == null) {
             mRingPath = new Path();
         } else {
-            mRingPath.reset();            
+            mRingPath.reset();
         }
 
         final Path ringPath = mRingPath;
@@ -1243,6 +1242,46 @@ public class GradientDrawable extends Drawable {
     }
 
     @Override
+    public Outline getOutline() {
+        final GradientState st = mGradientState;
+        final Rect bounds = getBounds();
+
+        switch (st.mShape) {
+            case RECTANGLE:
+                if (st.mRadiusArray != null) {
+                    return null;
+                }
+                float rad = 0;
+                if (st.mRadius > 0.0f) {
+                    // clamp the radius based on width & height, matching behavior in draw()
+                    rad = Math.min(st.mRadius,
+                            Math.min(bounds.width(), bounds.height()) * 0.5f);
+                }
+                if (mOutline == null) {
+                    mOutline = new Outline();
+                }
+                mOutline.setRoundRect(bounds.left, bounds.top,
+                        bounds.right, bounds.bottom, rad);
+                return mOutline;
+            case LINE: {
+                float halfStrokeWidth = mStrokePaint.getStrokeWidth() * 0.5f;
+                float centerY = bounds.centerY();
+                int top = (int) Math.floor(centerY - halfStrokeWidth);
+                int bottom = (int) Math.ceil(centerY + halfStrokeWidth);
+
+                if (mOutline == null) {
+                    mOutline = new Outline();
+                }
+                mOutline.setRoundRect(bounds.left, top, bounds.right, bottom, 0);
+                return mOutline;
+            }
+            default:
+                // TODO: investigate
+                return null;
+        }
+    }
+
+    @Override
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
             mGradientState = new GradientState(mGradientState);
diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h
new file mode 100644 (file)
index 0000000..f42be50
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef OUTLINE_H
+#define OUTLINE_H
+
+#include <SkPath.h>
+
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+class Outline {
+public:
+    Outline()
+            : mShouldClip(false)
+            , mType(kOutlineType_None)
+            , mRadius(0) {}
+
+    void setRoundRect(int left, int top, int right, int bottom, int radius) {
+        mType = kOutlineType_RoundRect;
+        mBounds.set(left, top, right, bottom);
+        mRadius = radius;
+        mPath.reset();
+        mPath.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom),
+                radius, radius);
+    }
+
+    void setConvexPath(const SkPath* outline) {
+        if (!outline) {
+            setEmpty();
+            return;
+        }
+        mType = kOutlineType_ConvexPath;
+        mPath = *outline;
+        mBounds.set(outline->getBounds());
+    }
+
+    void setEmpty() {
+        mType = kOutlineType_None;
+        mPath.reset();
+    }
+
+    void setShouldClip(bool clip) {
+        mShouldClip = clip;
+    }
+
+
+    bool willClip() const {
+        // only round rect outlines can be used for clipping
+        return mShouldClip && (mType == kOutlineType_RoundRect);
+    }
+
+    const SkPath* getPath() {
+        if (mType == kOutlineType_None) return NULL;
+
+        return &mPath;
+    }
+
+private:
+    enum OutlineType {
+        kOutlineType_None = 0,
+        kOutlineType_ConvexPath = 1,
+        kOutlineType_RoundRect = 2
+    };
+
+    bool mShouldClip;
+    OutlineType mType;
+    Rect mBounds;
+    float mRadius;
+    SkPath mPath;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* OUTLINE_H */
index e371590..8aed857 100644 (file)
@@ -128,14 +128,14 @@ void RenderNode::outputViewProperties(const int level) {
                 flags |= SkCanvas::kClipToLayer_SaveFlag;
                 clipToBoundsNeeded = false; // clipping done by save layer
             }
-            ALOGD("%*sSaveLayerAlpha %.2f, %.2f, %.2f, %.2f, %d, 0x%x", level * 2, "",
-                    (float) 0, (float) 0, (float) properties().mRight - properties().mLeft, (float) properties().mBottom - properties().mTop,
+            ALOGD("%*sSaveLayerAlpha %d, %d, %d, %d, %d, 0x%x", level * 2, "",
+                    0, 0, properties().mWidth, properties().mHeight,
                     (int)(properties().mAlpha * 255), flags);
         }
     }
     if (clipToBoundsNeeded) {
-        ALOGD("%*sClipRect %.2f, %.2f, %.2f, %.2f", level * 2, "", 0.0f, 0.0f,
-                (float) properties().mRight - properties().mLeft, (float) properties().mBottom - properties().mTop);
+        ALOGD("%*sClipRect %d, %d, %d, %d", level * 2, "",
+                0, 0, properties().mWidth, properties().mHeight);
     }
 }
 
@@ -185,17 +185,20 @@ void RenderNode::setViewProperties(OpenGLRenderer& renderer, T& handler,
             }
 
             SaveLayerOp* op = new (handler.allocator()) SaveLayerOp(
-                    0, 0, properties().mRight - properties().mLeft, properties().mBottom - properties().mTop, properties().mAlpha * 255, saveFlags);
+                    0, 0, properties().mWidth, properties().mHeight,
+                    properties().mAlpha * 255, saveFlags);
             handler(op, PROPERTY_SAVECOUNT, properties().mClipToBounds);
         }
     }
     if (clipToBoundsNeeded) {
-        ClipRectOp* op = new (handler.allocator()) ClipRectOp(0, 0,
-                properties().mRight - properties().mLeft, properties().mBottom - properties().mTop, SkRegion::kIntersect_Op);
+        ClipRectOp* op = new (handler.allocator()) ClipRectOp(
+                0, 0, properties().mWidth, properties().mHeight, SkRegion::kIntersect_Op);
         handler(op, PROPERTY_SAVECOUNT, properties().mClipToBounds);
     }
-    if (CC_UNLIKELY(properties().mClipToOutline && !properties().mOutline.isEmpty())) {
-        ClipPathOp* op = new (handler.allocator()) ClipPathOp(&properties().mOutline, SkRegion::kIntersect_Op);
+    if (CC_UNLIKELY(properties().mOutline.willClip())) {
+        // TODO: optimize RR case
+        ClipPathOp* op = new (handler.allocator()) ClipPathOp(properties().mOutline.getPath(),
+                SkRegion::kIntersect_Op);
         handler(op, PROPERTY_SAVECOUNT, properties().mClipToBounds);
     }
 }
@@ -443,7 +446,7 @@ void RenderNode::iterate3dChildren(const Vector<ZDrawDisplayListOpPair>& zTransl
 
                     DisplayListOp* shadowOp  = new (alloc) DrawShadowOp(
                             shadowMatrixXY, shadowMatrixZ,
-                            caster->properties().mAlpha, &(caster->properties().mOutline),
+                            caster->properties().mAlpha, caster->properties().mOutline.getPath(),
                             caster->properties().mWidth, caster->properties().mHeight);
                     handler(shadowOp, PROPERTY_SAVECOUNT, properties().mClipToBounds);
                 }
index 233aace..902a748 100644 (file)
@@ -26,7 +26,6 @@ RenderProperties::RenderProperties()
         : mClipToBounds(true)
         , mProjectBackwards(false)
         , mProjectionReceiver(false)
-        , mClipToOutline(false)
         , mAlpha(1)
         , mHasOverlappingRendering(true)
         , mTranslationX(0), mTranslationY(0), mTranslationZ(0)
@@ -47,7 +46,6 @@ RenderProperties::RenderProperties()
         , mStaticMatrix(NULL)
         , mAnimationMatrix(NULL)
         , mCaching(false) {
-    mOutline.rewind();
 }
 
 RenderProperties::~RenderProperties() {
index 6e3b8ae..57fa4ba 100644 (file)
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef RENDERNODEPROPERTIES_H_
-#define RENDERNODEPROPERTIES_H_
+#ifndef RENDERNODEPROPERTIES_H
+#define RENDERNODEPROPERTIES_H
 
 #include <stddef.h>
 #include <cutils/compiler.h>
@@ -22,7 +22,9 @@
 
 #include <SkCamera.h>
 #include <SkMatrix.h>
-#include <SkPath.h>
+
+#include "Rect.h"
+#include "Outline.h"
 
 #define TRANSLATION 0x0001
 #define ROTATION    0x0002
@@ -64,18 +66,6 @@ public:
         return mProjectionReceiver;
     }
 
-    void setOutline(const SkPath* outline) {
-        if (!outline) {
-            mOutline.reset();
-        } else {
-            mOutline = *outline;
-        }
-    }
-
-    void setClipToOutline(bool clipToOutline) {
-        mClipToOutline = clipToOutline;
-    }
-
     void setStaticMatrix(SkMatrix* matrix) {
         delete mStaticMatrix;
         mStaticMatrix = new SkMatrix(*matrix);
@@ -375,14 +365,18 @@ public:
         mCaching = caching;
     }
 
-    int getWidth() {
+    int getWidth() const {
         return mWidth;
     }
 
-    int getHeight() {
+    int getHeight() const {
         return mHeight;
     }
 
+    Outline& outline() {
+        return mOutline;
+    }
+
 private:
     void onTranslationUpdate() {
         mMatrixDirty = true;
@@ -396,11 +390,10 @@ private:
     void updateMatrix();
 
     // Rendering properties
+    Outline mOutline;
     bool mClipToBounds;
     bool mProjectBackwards;
     bool mProjectionReceiver;
-    SkPath mOutline;
-    bool mClipToOutline;
     float mAlpha;
     bool mHasOverlappingRendering;
     float mTranslationX, mTranslationY, mTranslationZ;
@@ -436,4 +429,4 @@ private:
 } /* namespace uirenderer */
 } /* namespace android */
 
-#endif /* RENDERNODEPROPERTIES_H_ */
+#endif /* RENDERNODEPROPERTIES_H */