Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 07f0b8ee authored by Chris Craik's avatar Chris Craik Committed by Android (Google) Code Review
Browse files

Merge "Refactor Drawable outline production, flesh out Outline methods"

parents c806d539 e6a39b12
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -10023,11 +10023,16 @@ package android.graphics {
    method public void setPaint(android.graphics.Paint);
  }
  public class Outline {
  public final class Outline {
    ctor public Outline();
    method public final boolean isValid();
    ctor public Outline(android.graphics.Outline);
    method public boolean isValid();
    method public void set(android.graphics.Outline);
    method public void setConvexPath(android.graphics.Path);
    method public void setRect(int, int, int, int);
    method public void setRect(android.graphics.Rect);
    method public void setRoundRect(int, int, int, int, float);
    method public void setRoundRect(android.graphics.Rect, float);
  }
  public class Paint {
@@ -10750,7 +10755,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 getOutline(android.graphics.Outline);
    method public boolean getPadding(android.graphics.Rect);
    method public int[] getState();
    method public android.graphics.Region getTransparentRegion();
+30 −54
Original line number Diff line number Diff line
@@ -2374,25 +2374,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    static final int PFLAG3_CALLED_SUPER = 0x10;
    /**
     * Flag indicating that an view will be clipped to its outline.
     */
    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;
    static final int PFLAG3_OUTLINE_DEFINED = 0x20;
    /**
     * Flag indicating that we're in the process of applying window insets.
     */
    static final int PFLAG3_APPLYING_INSETS = 0x80;
    static final int PFLAG3_APPLYING_INSETS = 0x40;
    /**
     * Flag indicating that we're in the process of fitting system windows using the old method.
     */
    static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x100;
    static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
    /**
     * Flag indicating that nested scrolling is enabled for this view.
@@ -3258,9 +3253,7 @@ 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.
     * defining shadow shape.
     */
    private Outline mOutline;
@@ -10572,16 +10565,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    /**
     * Sets the outline of the view, which defines the shape of the shadow it
     * casts, and can used for clipping.
     * casts.
     * <p>
     * If the outline is not set or is null, shadows will be cast from the
     * bounds of the View, and clipToOutline will be ignored.
     * bounds of the View.
     *
     * @param outline The new outline of the view.
     *         Must be {@link android.graphics.Outline#isValid() valid.}
     *
     * @see #getClipToOutline()
     * @see #setClipToOutline(boolean)
     */
    public void setOutline(@Nullable Outline outline) {
        if (outline != null && !outline.isValid()) {
@@ -10595,46 +10585,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        } else {
            // always copy the path since caller may reuse
            if (mOutline == null) {
                mOutline = new Outline();
                mOutline = new Outline(outline);
            }
            mOutline.set(outline);
        }
        mRenderNode.setOutline(mOutline);
    }
    /**
     * Returns whether the outline of the View will be used for clipping.
     *
     * @see #setOutline(Outline)
     */
    public final boolean getClipToOutline() {
        return ((mPrivateFlags3 & PFLAG3_CLIP_TO_OUTLINE) != 0);
    }
    // TODO: remove
    public final boolean getClipToOutline() { return false; }
    public void setClipToOutline(boolean clipToOutline) {}
    /**
     * Sets whether the outline of the View will be used for clipping.
     * <p>
     * The current implementation of outline clipping uses
     * {@link Canvas#clipPath(Path) path clipping},
     * and thus does not support anti-aliasing, and is expensive in terms of
     * graphics performance. Therefore, it is strongly recommended that this
     * property only be set temporarily, as in an animation. For the same
     * reasons, there is no parallel XML attribute for this property.
     * <p>
     * If the outline of the view is not set or is empty, no clipping will be
     * performed.
     *
     * @see #setOutline(Outline)
     */
    public void setClipToOutline(boolean clipToOutline) {
        // TODO : Add a fast invalidation here.
        if (getClipToOutline() != clipToOutline) {
            if (clipToOutline) {
                mPrivateFlags3 |= PFLAG3_CLIP_TO_OUTLINE;
    private void queryOutlineFromBackgroundIfUndefined() {
        if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
            // Outline not currently defined, query from background
            if (mOutline == null) {
                mOutline = new Outline();
            } else {
                mPrivateFlags3 &= ~PFLAG3_CLIP_TO_OUTLINE;
                mOutline.markInvalid();
            }
            if (mBackground.getOutline(mOutline)) {
                if (!mOutline.isValid()) {
                    throw new IllegalStateException("Background drawable failed to build outline");
                }
                mRenderNode.setOutline(mOutline);
            } else {
                mRenderNode.setOutline(null);
            }
            mRenderNode.setClipToOutline(clipToOutline);
        }
    }
@@ -14893,11 +14869,7 @@ 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();
                mRenderNode.setOutline(mOutline);
            }
            queryOutlineFromBackgroundIfUndefined();
        }
        // Attempt to use a display list if requested.
@@ -15299,7 +15271,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     * @param drawable the drawable to invalidate
     */
    @Override
    public void invalidateDrawable(Drawable drawable) {
    public void invalidateDrawable(@NonNull Drawable drawable) {
        if (verifyDrawable(drawable)) {
            final Rect dirty = drawable.getDirtyBounds();
            final int scrollX = mScrollX;
@@ -15307,6 +15279,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            invalidate(dirty.left + scrollX, dirty.top + scrollY,
                    dirty.right + scrollX, dirty.bottom + scrollY);
            if (drawable == mBackground) {
                queryOutlineFromBackgroundIfUndefined();
            }
        }
    }
+42 −16
Original line number Diff line number Diff line
@@ -16,18 +16,19 @@

package android.graphics;

import android.graphics.drawable.Drawable;
import android.view.View;

/**
 * Defines an area of content.
 *
 * Can be used with a View or Drawable to drive the shape of shadows cast by a
 * Can be used with a View, or computed by a 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)
 * @see Drawable#getOutline(Outline)
 */
public class Outline {
public final class Outline {
    /** @hide */
    public Rect mRect;

@@ -44,19 +45,26 @@ public class Outline {
    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.
     * Constructs an Outline with a copy of the data in src.
     */
    public final boolean isValid() {
        return mRect != null || mPath != null;
    public Outline(Outline src) {
        set(src);
    }

    /** @hide */
    public void markInvalid() {
        mRadius = 0;
        mRect = null;
        mPath = null;
    }

    /**
     * @hide
     * 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 canClip() {
        return mPath == null;
    public boolean isValid() {
        return mRect != null || mPath != null;
    }

    /**
@@ -81,9 +89,20 @@ public class Outline {

    /**
     * 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 setRect(int left, int top, int right, int bottom) {
        setRoundRect(left, top, right, bottom, 0.0f);
    }

    /**
     * Convenience for {@link #setRect(int, int, int, int)}
     */
    public void setRect(Rect rect) {
        setRect(rect.left, rect.top, rect.right, rect.bottom);
    }

    /**
     * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
     */
    public void setRoundRect(int left, int top, int right, int bottom, float radius) {
        if (mRect == null) mRect = new Rect();
@@ -92,10 +111,17 @@ public class Outline {
        mPath = null;
    }

    /**
     * Convenience for {@link #setRoundRect(int, int, int, int, float)}
     * @param rect
     * @param radius
     */
    public void setRoundRect(Rect rect, float radius) {
        setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius);
    }

    /**
     * Sets the Constructs an Outline from a {@link android.graphics.Path#isConvex() convex path}.
     *
     * @hide
     */
    public void setConvexPath(Path convexPath) {
        if (!convexPath.isConvex()) {
+12 −13
Original line number Diff line number Diff line
@@ -864,22 +864,21 @@ public abstract class Drawable {
    }

    /**
     * Returns the outline for this drawable if defined, null if not.
     * Called to get the drawable to populate the Outline.
     * <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.
     * This method will be called by a View on its background Drawable after bounds change, or its
     * Drawable is invalidated, if the View's Outline isn't set explicitly. This allows the
     * background Drawable to define the shape of the shadow cast by the View.
     *
     * The default behavior defines the outline to be the bounding rectangle. Subclasses that wish
     * to convey a different shape must override this method.
     *
     * @see View#setOutline(android.view.Outline)
     * @see View#setClipToOutline(boolean)
     * @see View#setOutline(android.graphics.Outline)
     */
    public Outline getOutline() { return null; }
    public boolean getOutline(Outline outline) {
        outline.setRect(getBounds());
        return true;
    }

    /**
     * Make this drawable mutable. This operation cannot be reversed. A mutable
+23 −22
Original line number Diff line number Diff line
@@ -139,7 +139,6 @@ 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
@@ -577,11 +576,7 @@ public class GradientDrawable extends Drawable {
        switch (st.mShape) {
            case RECTANGLE:
                if (st.mRadiusArray != null) {
                    if (mPathIsDirty || mRectIsDirty) {
                        mPath.reset();
                        mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
                        mPathIsDirty = mRectIsDirty = false;
                    }
                    buildPathIfDirty();
                    canvas.drawPath(mPath, mFillPaint);
                    if (haveStroke) {
                        canvas.drawPath(mPath, mStrokePaint);
@@ -639,6 +634,15 @@ public class GradientDrawable extends Drawable {
        }
    }

    private void buildPathIfDirty() {
        final GradientState st = mGradientState;
        if (mPathIsDirty || mRectIsDirty) {
            mPath.reset();
            mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
            mPathIsDirty = mRectIsDirty = false;
        }
    }

    private Path buildRing(GradientState st) {
        if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
        mPathIsDirty = false;
@@ -1428,42 +1432,39 @@ public class GradientDrawable extends Drawable {
    }

    @Override
    public Outline getOutline() {
    public boolean getOutline(Outline outline) {
        final GradientState st = mGradientState;
        final Rect bounds = getBounds();

        switch (st.mShape) {
            case RECTANGLE:
                if (st.mRadiusArray != null) {
                    return null;
                    buildPathIfDirty();
                    outline.setConvexPath(mPath);
                    return true;
                }

                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,
                outline.setRoundRect(bounds.left, bounds.top,
                        bounds.right, bounds.bottom, rad);
                return mOutline;
                return true;
            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;
                outline.setRect(bounds.left, top, bounds.right, bottom);
                return true;
            }
            default:
                // TODO: investigate
                return null;
                return false;
        }
    }