1/*
2 * Copyright (C) 2010 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.graphics;
18
19import com.android.ide.common.rendering.api.LayoutLog;
20import com.android.layoutlib.bridge.Bridge;
21import com.android.layoutlib.bridge.impl.DelegateManager;
22import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
23
24import android.os.Parcel;
25
26import java.awt.Rectangle;
27import java.awt.Shape;
28import java.awt.geom.AffineTransform;
29import java.awt.geom.Area;
30import java.awt.geom.Rectangle2D;
31
32/**
33 * Delegate implementing the native methods of android.graphics.Region
34 *
35 * Through the layoutlib_create tool, the original native methods of Region have been replaced
36 * by calls to methods of the same name in this delegate class.
37 *
38 * This class behaves like the original native implementation, but in Java, keeping previously
39 * native data into its own objects and mapping them to int that are sent back and forth between
40 * it and the original Region class.
41 *
42 * This also serve as a base class for all Region delegate classes.
43 *
44 * @see DelegateManager
45 *
46 */
47public class Region_Delegate {
48
49    // ---- delegate manager ----
50    protected static final DelegateManager<Region_Delegate> sManager =
51            new DelegateManager<Region_Delegate>(Region_Delegate.class);
52
53    // ---- delegate helper data ----
54
55    // ---- delegate data ----
56    private Area mArea = new Area();
57
58    // ---- Public Helper methods ----
59
60    public static Region_Delegate getDelegate(long nativeShader) {
61        return sManager.getDelegate(nativeShader);
62    }
63
64    public Area getJavaArea() {
65        return mArea;
66    }
67
68    /**
69     * Combines two {@link Shape} into another one (actually an {@link Area}), according
70     * to the given {@link Region.Op}.
71     *
72     * If the Op is not one that combines two shapes, then this return null
73     *
74     * @param shape1 the firt shape to combine which can be null if there's no original clip.
75     * @param shape2 the 2nd shape to combine
76     * @param regionOp the operande for the combine
77     * @return a new area or null.
78     */
79    public static Area combineShapes(Shape shape1, Shape shape2, int regionOp) {
80        if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
81            // if shape1 is null (empty), then the result is null.
82            if (shape1 == null) {
83                return null;
84            }
85
86            // result is always a new area.
87            Area result = new Area(shape1);
88            result.subtract(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
89            return result;
90
91        } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
92            // if shape1 is null, then the result is simply shape2.
93            if (shape1 == null) {
94                return new Area(shape2);
95            }
96
97            // result is always a new area.
98            Area result = new Area(shape1);
99            result.intersect(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
100            return result;
101
102        } else if (regionOp == Region.Op.UNION.nativeInt) {
103            // if shape1 is null, then the result is simply shape2.
104            if (shape1 == null) {
105                return new Area(shape2);
106            }
107
108            // result is always a new area.
109            Area result = new Area(shape1);
110            result.add(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
111            return result;
112
113        } else if (regionOp == Region.Op.XOR.nativeInt) {
114            // if shape1 is null, then the result is simply shape2
115            if (shape1 == null) {
116                return new Area(shape2);
117            }
118
119            // result is always a new area.
120            Area result = new Area(shape1);
121            result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
122            return result;
123
124        } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
125            // result is always a new area.
126            Area result = new Area(shape2);
127
128            if (shape1 != null) {
129                result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1));
130            }
131
132            return result;
133        }
134
135        return null;
136    }
137
138    // ---- native methods ----
139
140    @LayoutlibDelegate
141    /*package*/ static boolean isEmpty(Region thisRegion) {
142        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
143        if (regionDelegate == null) {
144            return true;
145        }
146
147        return regionDelegate.mArea.isEmpty();
148    }
149
150    @LayoutlibDelegate
151    /*package*/ static boolean isRect(Region thisRegion) {
152        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
153        if (regionDelegate == null) {
154            return true;
155        }
156
157        return regionDelegate.mArea.isRectangular();
158    }
159
160    @LayoutlibDelegate
161    /*package*/ static boolean isComplex(Region thisRegion) {
162        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
163        if (regionDelegate == null) {
164            return true;
165        }
166
167        return regionDelegate.mArea.isSingular() == false;
168    }
169
170    @LayoutlibDelegate
171    /*package*/ static boolean contains(Region thisRegion, int x, int y) {
172        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
173        if (regionDelegate == null) {
174            return false;
175        }
176
177        return regionDelegate.mArea.contains(x, y);
178    }
179
180    @LayoutlibDelegate
181    /*package*/ static boolean quickContains(Region thisRegion,
182            int left, int top, int right, int bottom) {
183        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
184        if (regionDelegate == null) {
185            return false;
186        }
187
188        return regionDelegate.mArea.isRectangular() &&
189                regionDelegate.mArea.contains(left, top, right - left, bottom - top);
190    }
191
192    @LayoutlibDelegate
193    /*package*/ static boolean quickReject(Region thisRegion,
194            int left, int top, int right, int bottom) {
195        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
196        if (regionDelegate == null) {
197            return false;
198        }
199
200        return regionDelegate.mArea.isEmpty() ||
201                regionDelegate.mArea.intersects(left, top, right - left, bottom - top) == false;
202    }
203
204    @LayoutlibDelegate
205    /*package*/ static boolean quickReject(Region thisRegion, Region rgn) {
206        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
207        if (regionDelegate == null) {
208            return false;
209        }
210
211        Region_Delegate targetRegionDelegate = sManager.getDelegate(rgn.mNativeRegion);
212        if (targetRegionDelegate == null) {
213            return false;
214        }
215
216        return regionDelegate.mArea.isEmpty() ||
217                regionDelegate.mArea.getBounds().intersects(
218                        targetRegionDelegate.mArea.getBounds()) == false;
219
220    }
221
222    @LayoutlibDelegate
223    /*package*/ static void translate(Region thisRegion, int dx, int dy, Region dst) {
224        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
225        if (regionDelegate == null) {
226            return;
227        }
228
229        Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
230        if (targetRegionDelegate == null) {
231            return;
232        }
233
234        if (regionDelegate.mArea.isEmpty()) {
235            targetRegionDelegate.mArea = new Area();
236        } else {
237            targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
238            AffineTransform mtx = new AffineTransform();
239            mtx.translate(dx, dy);
240            targetRegionDelegate.mArea.transform(mtx);
241        }
242    }
243
244    @LayoutlibDelegate
245    /*package*/ static void scale(Region thisRegion, float scale, Region dst) {
246        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
247        if (regionDelegate == null) {
248            return;
249        }
250
251        Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
252        if (targetRegionDelegate == null) {
253            return;
254        }
255
256        if (regionDelegate.mArea.isEmpty()) {
257            targetRegionDelegate.mArea = new Area();
258        } else {
259            targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
260            AffineTransform mtx = new AffineTransform();
261            mtx.scale(scale, scale);
262            targetRegionDelegate.mArea.transform(mtx);
263        }
264    }
265
266    @LayoutlibDelegate
267    /*package*/ static long nativeConstructor() {
268        Region_Delegate newDelegate = new Region_Delegate();
269        return sManager.addNewDelegate(newDelegate);
270    }
271
272    @LayoutlibDelegate
273    /*package*/ static void nativeDestructor(long native_region) {
274        sManager.removeJavaReferenceFor(native_region);
275    }
276
277    @LayoutlibDelegate
278    /*package*/ static void nativeSetRegion(long native_dst, long native_src) {
279        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
280        if (dstRegion == null) {
281            return;
282        }
283
284        Region_Delegate srcRegion = sManager.getDelegate(native_src);
285        if (srcRegion == null) {
286            return;
287        }
288
289        dstRegion.mArea.reset();
290        dstRegion.mArea.add(srcRegion.mArea);
291
292    }
293
294    @LayoutlibDelegate
295    /*package*/ static boolean nativeSetRect(long native_dst,
296            int left, int top, int right, int bottom) {
297        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
298        if (dstRegion == null) {
299            return true;
300        }
301
302        dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top));
303        return dstRegion.mArea.getBounds().isEmpty() == false;
304    }
305
306    @LayoutlibDelegate
307    /*package*/ static boolean nativeSetPath(long native_dst, long native_path, long native_clip) {
308        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
309        if (dstRegion == null) {
310            return true;
311        }
312
313        Path_Delegate path = Path_Delegate.getDelegate(native_path);
314        if (path == null) {
315            return true;
316        }
317
318        dstRegion.mArea = new Area(path.getJavaShape());
319
320        Region_Delegate clip = sManager.getDelegate(native_clip);
321        if (clip != null) {
322            dstRegion.mArea.subtract(clip.getJavaArea());
323        }
324
325        return dstRegion.mArea.getBounds().isEmpty() == false;
326    }
327
328    @LayoutlibDelegate
329    /*package*/ static boolean nativeGetBounds(long native_region, Rect rect) {
330        Region_Delegate region = sManager.getDelegate(native_region);
331        if (region == null) {
332            return true;
333        }
334
335        Rectangle bounds = region.mArea.getBounds();
336        if (bounds.isEmpty()) {
337            rect.left = rect.top = rect.right = rect.bottom = 0;
338            return false;
339        }
340
341        rect.left = bounds.x;
342        rect.top = bounds.y;
343        rect.right = bounds.x + bounds.width;
344        rect.bottom = bounds.y + bounds.height;
345        return true;
346    }
347
348    @LayoutlibDelegate
349    /*package*/ static boolean nativeGetBoundaryPath(long native_region, long native_path) {
350        Region_Delegate region = sManager.getDelegate(native_region);
351        if (region == null) {
352            return false;
353        }
354
355        Path_Delegate path = Path_Delegate.getDelegate(native_path);
356        if (path == null) {
357            return false;
358        }
359
360        if (region.mArea.isEmpty()) {
361            path.reset();
362            return false;
363        }
364
365        path.setPathIterator(region.mArea.getPathIterator(new AffineTransform()));
366        return true;
367    }
368
369    @LayoutlibDelegate
370    /*package*/ static boolean nativeOp(long native_dst,
371            int left, int top, int right, int bottom, int op) {
372        Region_Delegate region = sManager.getDelegate(native_dst);
373        if (region == null) {
374            return false;
375        }
376
377        region.mArea = combineShapes(region.mArea,
378                new Rectangle2D.Float(left, top, right - left, bottom - top), op);
379
380        assert region.mArea != null;
381        if (region.mArea != null) {
382            region.mArea = new Area();
383        }
384
385        return region.mArea.getBounds().isEmpty() == false;
386    }
387
388    @LayoutlibDelegate
389    /*package*/ static boolean nativeOp(long native_dst, Rect rect, long native_region, int op) {
390        Region_Delegate region = sManager.getDelegate(native_dst);
391        if (region == null) {
392            return false;
393        }
394
395        region.mArea = combineShapes(region.mArea,
396                new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op);
397
398        assert region.mArea != null;
399        if (region.mArea != null) {
400            region.mArea = new Area();
401        }
402
403        return region.mArea.getBounds().isEmpty() == false;
404    }
405
406    @LayoutlibDelegate
407    /*package*/ static boolean nativeOp(long native_dst,
408            long native_region1, long native_region2, int op) {
409        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
410        if (dstRegion == null) {
411            return true;
412        }
413
414        Region_Delegate region1 = sManager.getDelegate(native_region1);
415        if (region1 == null) {
416            return false;
417        }
418
419        Region_Delegate region2 = sManager.getDelegate(native_region2);
420        if (region2 == null) {
421            return false;
422        }
423
424        dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op);
425
426        assert dstRegion.mArea != null;
427        if (dstRegion.mArea != null) {
428            dstRegion.mArea = new Area();
429        }
430
431        return dstRegion.mArea.getBounds().isEmpty() == false;
432
433    }
434
435    @LayoutlibDelegate
436    /*package*/ static long nativeCreateFromParcel(Parcel p) {
437        // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only
438        // used during aidl call so really this should not be called.
439        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
440                "AIDL is not suppored, and therefore Regions cannot be created from parcels.",
441                null /*data*/);
442        return 0;
443    }
444
445    @LayoutlibDelegate
446    /*package*/ static boolean nativeWriteToParcel(long native_region,
447                                                      Parcel p) {
448        // This is only called when sending a region through aidl, so really this should not
449        // be called.
450        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
451                "AIDL is not suppored, and therefore Regions cannot be written to parcels.",
452                null /*data*/);
453        return false;
454    }
455
456    @LayoutlibDelegate
457    /*package*/ static boolean nativeEquals(long native_r1, long native_r2) {
458        Region_Delegate region1 = sManager.getDelegate(native_r1);
459        if (region1 == null) {
460            return false;
461        }
462
463        Region_Delegate region2 = sManager.getDelegate(native_r2);
464        if (region2 == null) {
465            return false;
466        }
467
468        return region1.mArea.equals(region2.mArea);
469    }
470
471    @LayoutlibDelegate
472    /*package*/ static String nativeToString(long native_region) {
473        Region_Delegate region = sManager.getDelegate(native_region);
474        if (region == null) {
475            return "not found";
476        }
477
478        return region.mArea.toString();
479    }
480
481    // ---- Private delegate/helper methods ----
482
483}
484