1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
19import com.android.annotations.NonNull;
20import com.android.layoutlib.bridge.android.BridgeContext;
21import com.android.resources.Density;
22import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
23
24import android.content.Context;
25import android.graphics.Bitmap;
26import android.graphics.Bitmap_Delegate;
27import android.graphics.Canvas;
28import android.graphics.Outline;
29import android.graphics.Path_Delegate;
30import android.graphics.Rect;
31import android.graphics.Region.Op;
32import android.util.DisplayMetrics;
33import android.util.TypedValue;
34import android.view.animation.Transformation;
35
36import java.awt.Graphics2D;
37import java.awt.image.BufferedImage;
38
39/**
40 * Delegate used to provide new implementation of a select few methods of {@link ViewGroup}
41 * <p/>
42 * Through the layoutlib_create tool, the original  methods of ViewGroup have been replaced by calls
43 * to methods of the same name in this delegate class.
44 */
45public class ViewGroup_Delegate {
46
47    /**
48     * Overrides the original drawChild call in ViewGroup to draw the shadow.
49     */
50    @LayoutlibDelegate
51    /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child,
52            long drawingTime) {
53        boolean retVal = thisVG.drawChild_Original(canvas, child, drawingTime);
54        if (child.getZ() > thisVG.getZ()) {
55            ViewOutlineProvider outlineProvider = child.getOutlineProvider();
56            Outline outline = new Outline();
57            outlineProvider.getOutline(child, outline);
58
59            if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
60                int restoreTo = transformCanvas(thisVG, canvas, child);
61                drawShadow(thisVG, canvas, child, outline);
62                canvas.restoreToCount(restoreTo);
63            }
64        }
65        return retVal;
66    }
67
68    private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
69            Outline outline) {
70        BufferedImage shadow = null;
71        int x = 0;
72        if (outline.mRect != null) {
73            Shadow s = getRectShadow(parent, canvas, child, outline);
74            shadow = s.mShadow;
75            x = -s.mShadowWidth;
76        } else if (outline.mPath != null) {
77            shadow = getPathShadow(child, outline, canvas);
78        }
79        if (shadow == null) {
80            return;
81        }
82        Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false,
83                Density.getEnum(canvas.getDensity()));
84        Rect clipBounds = canvas.getClipBounds();
85        Rect newBounds = new Rect(clipBounds);
86        newBounds.left = newBounds.left + x;
87        canvas.clipRect(newBounds, Op.REPLACE);
88        canvas.drawBitmap(bitmap, x, 0, null);
89        canvas.clipRect(clipBounds, Op.REPLACE);
90    }
91
92    private static Shadow getRectShadow(ViewGroup parent, Canvas canvas, View child,
93            Outline outline) {
94        BufferedImage shadow;
95        Rect clipBounds = canvas.getClipBounds();
96        if (clipBounds.isEmpty()) {
97            return null;
98        }
99        float height = child.getZ() - parent.getZ();
100        // Draw large shadow if difference in z index is more than 10dp
101        float largeShadowThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f,
102                getMetrics(child));
103        boolean largeShadow = height > largeShadowThreshold;
104        int shadowSize = largeShadow ? ShadowPainter.SHADOW_SIZE : ShadowPainter.SMALL_SHADOW_SIZE;
105        shadow = new BufferedImage(clipBounds.width() + shadowSize, clipBounds.height(),
106                BufferedImage.TYPE_INT_ARGB);
107        Graphics2D graphics = shadow.createGraphics();
108        Rect rect = outline.mRect;
109        if (largeShadow) {
110            ShadowPainter.drawRectangleShadow(graphics,
111                    rect.left + shadowSize, rect.top, rect.width(), rect.height());
112        } else {
113            ShadowPainter.drawSmallRectangleShadow(graphics,
114                    rect.left + shadowSize, rect.top, rect.width(), rect.height());
115        }
116        graphics.dispose();
117        return new Shadow(shadow, shadowSize);
118    }
119
120    @NonNull
121    private static DisplayMetrics getMetrics(View view) {
122        Context context = view.getContext();
123        while (context instanceof ContextThemeWrapper) {
124            context = ((ContextThemeWrapper) context).getBaseContext();
125        }
126        if (context instanceof BridgeContext) {
127            return ((BridgeContext) context).getMetrics();
128        }
129        throw new RuntimeException("View " + view.getClass().getName() + " not created with the " +
130                "right context");
131    }
132
133    private static BufferedImage getPathShadow(View child, Outline outline, Canvas canvas) {
134        Rect clipBounds = canvas.getClipBounds();
135        BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(),
136                BufferedImage.TYPE_INT_ARGB);
137        Graphics2D graphics = image.createGraphics();
138        graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
139        graphics.dispose();
140        return ShadowPainter.createDropShadow(image, ((int) child.getZ()));
141    }
142
143    // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
144    // which were never taken. Ideally, we should hook up the shadow code in the same method so
145    // that we don't have to transform the canvas twice.
146    private static int transformCanvas(ViewGroup thisVG, Canvas canvas, View child) {
147        final int restoreTo = canvas.save();
148        final boolean childHasIdentityMatrix = child.hasIdentityMatrix();
149        int flags = thisVG.mGroupFlags;
150        Transformation transformToApply = null;
151        boolean concatMatrix = false;
152        if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
153            final Transformation t = thisVG.getChildTransformation();
154            final boolean hasTransform = thisVG.getChildStaticTransformation(child, t);
155            if (hasTransform) {
156                final int transformType = t.getTransformationType();
157                transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
158                concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
159            }
160        }
161        concatMatrix |= childHasIdentityMatrix;
162
163        child.computeScroll();
164        int sx = child.mScrollX;
165        int sy = child.mScrollY;
166
167        canvas.translate(child.mLeft - sx, child.mTop - sy);
168        float alpha = child.getAlpha() * child.getTransitionAlpha();
169
170        if (transformToApply != null || alpha < 1 || !childHasIdentityMatrix) {
171            if (transformToApply != null || !childHasIdentityMatrix) {
172                int transX = -sx;
173                int transY = -sy;
174
175                if (transformToApply != null) {
176                    if (concatMatrix) {
177                        // Undo the scroll translation, apply the transformation matrix,
178                        // then redo the scroll translate to get the correct result.
179                        canvas.translate(-transX, -transY);
180                        canvas.concat(transformToApply.getMatrix());
181                        canvas.translate(transX, transY);
182                    }
183                    if (!childHasIdentityMatrix) {
184                        canvas.translate(-transX, -transY);
185                        canvas.concat(child.getMatrix());
186                        canvas.translate(transX, transY);
187                    }
188                }
189
190            }
191        }
192        return restoreTo;
193    }
194
195    private static class Shadow {
196        public BufferedImage mShadow;
197        public int mShadowWidth;
198
199        public Shadow(BufferedImage shadow, int shadowWidth) {
200            mShadow = shadow;
201            mShadowWidth = shadowWidth;
202        }
203
204    }
205}
206