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(int 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 int nativeConstructor() {
268        Region_Delegate newDelegate = new Region_Delegate();
269        return sManager.addNewDelegate(newDelegate);
270    }
271
272    @LayoutlibDelegate
273    /*package*/ static void nativeDestructor(int native_region) {
274        sManager.removeJavaReferenceFor(native_region);
275    }
276
277    @LayoutlibDelegate
278    /*package*/ static boolean nativeSetRegion(int native_dst, int native_src) {
279        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
280        if (dstRegion == null) {
281            return true;
282        }
283
284        Region_Delegate srcRegion = sManager.getDelegate(native_src);
285        if (srcRegion == null) {
286            return true;
287        }
288
289        dstRegion.mArea.reset();
290        dstRegion.mArea.add(srcRegion.mArea);
291
292        return true;
293    }
294
295    @LayoutlibDelegate
296    /*package*/ static boolean nativeSetRect(int native_dst,
297            int left, int top, int right, int bottom) {
298        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
299        if (dstRegion == null) {
300            return true;
301        }
302
303        dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top));
304        return dstRegion.mArea.getBounds().isEmpty() == false;
305    }
306
307    @LayoutlibDelegate
308    /*package*/ static boolean nativeSetPath(int native_dst, int native_path, int native_clip) {
309        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
310        if (dstRegion == null) {
311            return true;
312        }
313
314        Path_Delegate path = Path_Delegate.getDelegate(native_path);
315        if (path == null) {
316            return true;
317        }
318
319        dstRegion.mArea = new Area(path.getJavaShape());
320
321        Region_Delegate clip = sManager.getDelegate(native_clip);
322        if (clip != null) {
323            dstRegion.mArea.subtract(clip.getJavaArea());
324        }
325
326        return dstRegion.mArea.getBounds().isEmpty() == false;
327    }
328
329    @LayoutlibDelegate
330    /*package*/ static boolean nativeGetBounds(int native_region, Rect rect) {
331        Region_Delegate region = sManager.getDelegate(native_region);
332        if (region == null) {
333            return true;
334        }
335
336        Rectangle bounds = region.mArea.getBounds();
337        if (bounds.isEmpty()) {
338            rect.left = rect.top = rect.right = rect.bottom = 0;
339            return false;
340        }
341
342        rect.left = bounds.x;
343        rect.top = bounds.y;
344        rect.right = bounds.x + bounds.width;
345        rect.bottom = bounds.y + bounds.height;
346        return true;
347    }
348
349    @LayoutlibDelegate
350    /*package*/ static boolean nativeGetBoundaryPath(int native_region, int native_path) {
351        Region_Delegate region = sManager.getDelegate(native_region);
352        if (region == null) {
353            return false;
354        }
355
356        Path_Delegate path = Path_Delegate.getDelegate(native_path);
357        if (path == null) {
358            return false;
359        }
360
361        if (region.mArea.isEmpty()) {
362            path.reset();
363            return false;
364        }
365
366        path.setPathIterator(region.mArea.getPathIterator(new AffineTransform()));
367        return true;
368    }
369
370    @LayoutlibDelegate
371    /*package*/ static boolean nativeOp(int native_dst,
372            int left, int top, int right, int bottom, int op) {
373        Region_Delegate region = sManager.getDelegate(native_dst);
374        if (region == null) {
375            return false;
376        }
377
378        region.mArea = combineShapes(region.mArea,
379                new Rectangle2D.Float(left, top, right - left, bottom - top), op);
380
381        assert region.mArea != null;
382        if (region.mArea != null) {
383            region.mArea = new Area();
384        }
385
386        return region.mArea.getBounds().isEmpty() == false;
387    }
388
389    @LayoutlibDelegate
390    /*package*/ static boolean nativeOp(int native_dst, Rect rect, int native_region, int op) {
391        Region_Delegate region = sManager.getDelegate(native_dst);
392        if (region == null) {
393            return false;
394        }
395
396        region.mArea = combineShapes(region.mArea,
397                new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op);
398
399        assert region.mArea != null;
400        if (region.mArea != null) {
401            region.mArea = new Area();
402        }
403
404        return region.mArea.getBounds().isEmpty() == false;
405    }
406
407    @LayoutlibDelegate
408    /*package*/ static boolean nativeOp(int native_dst,
409            int native_region1, int native_region2, int op) {
410        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
411        if (dstRegion == null) {
412            return true;
413        }
414
415        Region_Delegate region1 = sManager.getDelegate(native_region1);
416        if (region1 == null) {
417            return false;
418        }
419
420        Region_Delegate region2 = sManager.getDelegate(native_region2);
421        if (region2 == null) {
422            return false;
423        }
424
425        dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op);
426
427        assert dstRegion.mArea != null;
428        if (dstRegion.mArea != null) {
429            dstRegion.mArea = new Area();
430        }
431
432        return dstRegion.mArea.getBounds().isEmpty() == false;
433
434    }
435
436    @LayoutlibDelegate
437    /*package*/ static int nativeCreateFromParcel(Parcel p) {
438        // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only
439        // used during aidl call so really this should not be called.
440        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
441                "AIDL is not suppored, and therefore Regions cannot be created from parcels.",
442                null /*data*/);
443        return 0;
444    }
445
446    @LayoutlibDelegate
447    /*package*/ static boolean nativeWriteToParcel(int native_region,
448                                                      Parcel p) {
449        // This is only called when sending a region through aidl, so really this should not
450        // be called.
451        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
452                "AIDL is not suppored, and therefore Regions cannot be written to parcels.",
453                null /*data*/);
454        return false;
455    }
456
457    @LayoutlibDelegate
458    /*package*/ static boolean nativeEquals(int native_r1, int native_r2) {
459        Region_Delegate region1 = sManager.getDelegate(native_r1);
460        if (region1 == null) {
461            return false;
462        }
463
464        Region_Delegate region2 = sManager.getDelegate(native_r2);
465        if (region2 == null) {
466            return false;
467        }
468
469        return region1.mArea.equals(region2.mArea);
470    }
471
472    @LayoutlibDelegate
473    /*package*/ static String nativeToString(int native_region) {
474        Region_Delegate region = sManager.getDelegate(native_region);
475        if (region == null) {
476            return "not found";
477        }
478
479        return region.mArea.toString();
480    }
481
482    // ---- Private delegate/helper methods ----
483
484}
485