Path_Delegate.java revision d348b6eaa98e23cb38d90906df109aaa2d20ea7f
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.layoutlib.bridge.Bridge;
20import com.android.layoutlib.bridge.impl.DelegateManager;
21
22import android.graphics.Path.Direction;
23import android.graphics.Path.FillType;
24
25import java.awt.Shape;
26import java.awt.geom.AffineTransform;
27import java.awt.geom.Arc2D;
28import java.awt.geom.Area;
29import java.awt.geom.GeneralPath;
30import java.awt.geom.PathIterator;
31import java.awt.geom.Point2D;
32import java.awt.geom.Rectangle2D;
33
34/**
35 * Delegate implementing the native methods of android.graphics.Path
36 *
37 * Through the layoutlib_create tool, the original native methods of Path have been replaced
38 * by calls to methods of the same name in this delegate class.
39 *
40 * This class behaves like the original native implementation, but in Java, keeping previously
41 * native data into its own objects and mapping them to int that are sent back and forth between
42 * it and the original Path class.
43 *
44 * @see DelegateManager
45 *
46 */
47public final class Path_Delegate {
48
49    // ---- delegate manager ----
50    private static final DelegateManager<Path_Delegate> sManager =
51            new DelegateManager<Path_Delegate>();
52
53    // ---- delegate data ----
54    private FillType mFillType = FillType.WINDING;
55    private GeneralPath mPath = new GeneralPath();
56
57    private float mLastX = 0;
58    private float mLastY = 0;
59
60    // ---- Public Helper methods ----
61
62    public static Path_Delegate getDelegate(int nPath) {
63        return sManager.getDelegate(nPath);
64    }
65
66    public Shape getJavaShape() {
67        return mPath;
68    }
69
70    // ---- native methods ----
71
72    /*package*/ static int init1() {
73        // create the delegate
74        Path_Delegate newDelegate = new Path_Delegate();
75
76        return sManager.addDelegate(newDelegate);
77    }
78
79    /*package*/ static int init2(int nPath) {
80        // create the delegate
81        Path_Delegate newDelegate = new Path_Delegate();
82
83        // get the delegate to copy
84        if (nPath > 0) {
85            Path_Delegate pathDelegate = sManager.getDelegate(nPath);
86            if (pathDelegate == null) {
87                assert false;
88                return 0;
89            }
90
91            newDelegate.set(pathDelegate);
92        }
93
94        return sManager.addDelegate(newDelegate);
95    }
96
97    /*package*/ static void native_reset(int nPath) {
98        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
99        if (pathDelegate == null) {
100            assert false;
101            return;
102        }
103
104        pathDelegate.mPath.reset();
105    }
106
107    /*package*/ static void native_rewind(int nPath) {
108        // call out to reset since there's nothing to optimize in
109        // terms of data structs.
110        native_reset(nPath);
111    }
112
113    /*package*/ static void native_set(int native_dst, int native_src) {
114        Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst);
115        if (pathDstDelegate == null) {
116            assert false;
117            return;
118        }
119
120        Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src);
121        if (pathSrcDelegate == null) {
122            assert false;
123            return;
124        }
125
126        pathDstDelegate.set(pathSrcDelegate);
127    }
128
129    /*package*/ static int native_getFillType(int nPath) {
130        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
131        if (pathDelegate == null) {
132            assert false;
133            return 0;
134        }
135
136        return pathDelegate.mFillType.nativeInt;
137    }
138
139    /*package*/ static void native_setFillType(int nPath, int ft) {
140        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
141        if (pathDelegate == null) {
142            assert false;
143            return;
144        }
145
146        pathDelegate.mFillType = Path.sFillTypeArray[ft];
147    }
148
149    /*package*/ static boolean native_isEmpty(int nPath) {
150        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
151        if (pathDelegate == null) {
152            assert false;
153            return true;
154        }
155
156        return pathDelegate.isEmpty();
157    }
158
159    /*package*/ static boolean native_isRect(int nPath, RectF rect) {
160        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
161        if (pathDelegate == null) {
162            assert false;
163            return false;
164        }
165
166        // create an Area that can test if the path is a rect
167        Area area = new Area(pathDelegate.mPath);
168        if (area.isRectangular()) {
169            if (rect != null) {
170                pathDelegate.fillBounds(rect);
171            }
172
173            return true;
174        }
175
176        return false;
177    }
178
179    /*package*/ static void native_computeBounds(int nPath, RectF bounds) {
180        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
181        if (pathDelegate == null) {
182            assert false;
183            return;
184        }
185
186        pathDelegate.fillBounds(bounds);
187    }
188
189    /*package*/ static void native_incReserve(int nPath, int extraPtCount) {
190        // since we use a java2D path, there's no way to pre-allocate new points,
191        // so we do nothing.
192    }
193
194    /*package*/ static void native_moveTo(int nPath, float x, float y) {
195        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
196        if (pathDelegate == null) {
197            assert false;
198            return;
199        }
200
201        pathDelegate.moveTo(x, y);
202    }
203
204    /*package*/ static void native_rMoveTo(int nPath, float dx, float dy) {
205        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
206        if (pathDelegate == null) {
207            assert false;
208            return;
209        }
210
211        pathDelegate.rMoveTo(dx, dy);
212    }
213
214    /*package*/ static void native_lineTo(int nPath, float x, float y) {
215        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
216        if (pathDelegate == null) {
217            assert false;
218            return;
219        }
220
221        pathDelegate.lineTo(x, y);
222    }
223
224    /*package*/ static void native_rLineTo(int nPath, float dx, float dy) {
225        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
226        if (pathDelegate == null) {
227            assert false;
228            return;
229        }
230
231        pathDelegate.rLineTo(dx, dy);
232    }
233
234    /*package*/ static void native_quadTo(int nPath, float x1, float y1, float x2, float y2) {
235        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
236        if (pathDelegate == null) {
237            assert false;
238            return;
239        }
240
241        pathDelegate.quadTo(x1, y1, x2, y2);
242    }
243
244    /*package*/ static void native_rQuadTo(int nPath, float dx1, float dy1,
245                                              float dx2, float dy2) {
246        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
247        if (pathDelegate == null) {
248            assert false;
249            return;
250        }
251
252        pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
253    }
254
255    /*package*/ static void native_cubicTo(int nPath, float x1, float y1,
256                float x2, float y2, float x3, float y3) {
257        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
258        if (pathDelegate == null) {
259            assert false;
260            return;
261        }
262
263        pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
264    }
265
266    /*package*/ static void native_rCubicTo(int nPath, float x1, float y1,
267                float x2, float y2, float x3, float y3) {
268        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
269        if (pathDelegate == null) {
270            assert false;
271            return;
272        }
273
274        pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
275    }
276
277    /*package*/ static void native_arcTo(int nPath, RectF oval,
278                    float startAngle, float sweepAngle, boolean forceMoveTo) {
279        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
280        if (pathDelegate == null) {
281            assert false;
282            return;
283        }
284
285        pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
286    }
287
288    /*package*/ static void native_close(int nPath) {
289        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
290        if (pathDelegate == null) {
291            assert false;
292            return;
293        }
294
295        pathDelegate.close();
296    }
297
298    /*package*/ static void native_addRect(int nPath, RectF rect, int dir) {
299        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
300        if (pathDelegate == null) {
301            assert false;
302            return;
303        }
304
305        pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
306    }
307
308    /*package*/ static void native_addRect(int nPath, float left, float top,
309                                            float right, float bottom, int dir) {
310        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
311        if (pathDelegate == null) {
312            assert false;
313            return;
314        }
315
316        pathDelegate.addRect(left, top, right, bottom, dir);
317    }
318
319    /*package*/ static void native_addOval(int nPath, RectF oval, int dir) {
320        // FIXME
321        throw new UnsupportedOperationException();
322    }
323
324    /*package*/ static void native_addCircle(int nPath, float x, float y,
325                                                float radius, int dir) {
326        // FIXME
327        throw new UnsupportedOperationException();
328    }
329
330    /*package*/ static void native_addArc(int nPath, RectF oval,
331                                            float startAngle, float sweepAngle) {
332        // FIXME
333        throw new UnsupportedOperationException();
334    }
335
336    /*package*/ static void native_addRoundRect(int nPath, RectF rect,
337                                                   float rx, float ry, int dir) {
338        // FIXME
339        throw new UnsupportedOperationException();
340    }
341
342    /*package*/ static void native_addRoundRect(int nPath, RectF r,
343                                                   float[] radii, int dir) {
344        // FIXME
345        throw new UnsupportedOperationException();
346    }
347
348    /*package*/ static void native_addPath(int nPath, int src, float dx,
349                                              float dy) {
350        // FIXME
351        throw new UnsupportedOperationException();
352    }
353
354    /*package*/ static void native_addPath(int nPath, int src) {
355        // FIXME
356        throw new UnsupportedOperationException();
357    }
358
359    /*package*/ static void native_addPath(int nPath, int src, int matrix) {
360        // FIXME
361        throw new UnsupportedOperationException();
362    }
363
364    /*package*/ static void native_offset(int nPath, float dx, float dy,
365                                             int dst_path) {
366        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
367        if (pathDelegate == null) {
368            assert false;
369            return;
370        }
371
372        Path_Delegate dstDelegate = null;
373        if (dst_path > 0) {
374            dstDelegate = sManager.getDelegate(dst_path);
375            if (dstDelegate == null) {
376                assert false;
377                return;
378            }
379        }
380
381        pathDelegate.offset(dx, dy, dstDelegate);
382    }
383
384    /*package*/ static void native_offset(int nPath, float dx, float dy) {
385        native_offset(nPath, dx, dy, 0);
386    }
387
388    /*package*/ static void native_setLastPoint(int nPath, float dx, float dy) {
389        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
390        if (pathDelegate == null) {
391            assert false;
392            return;
393        }
394
395        pathDelegate.mLastX = dx;
396        pathDelegate.mLastY = dy;
397    }
398
399    /*package*/ static void native_transform(int nPath, int matrix,
400                                                int dst_path) {
401        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
402        if (pathDelegate == null) {
403            assert false;
404            return;
405        }
406
407        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
408        if (matrixDelegate == null) {
409            assert false;
410            return;
411        }
412
413        Path_Delegate dstDelegate = null;
414        if (dst_path > 0) {
415            dstDelegate = sManager.getDelegate(dst_path);
416            if (dstDelegate == null) {
417                assert false;
418                return;
419            }
420        }
421
422        pathDelegate.transform(matrixDelegate, dstDelegate);
423    }
424
425    /*package*/ static void native_transform(int nPath, int matrix) {
426        native_transform(nPath, matrix, 0);
427    }
428
429    /*package*/ static void finalizer(int nPath) {
430        sManager.removeDelegate(nPath);
431    }
432
433
434    // ---- Private helper methods ----
435
436    private void set(Path_Delegate delegate) {
437        mPath.reset();
438        setFillType(delegate.mFillType);
439        mPath.append(delegate.mPath, false /*connect*/);
440    }
441
442    private void setFillType(FillType fillType) {
443        mFillType = fillType;
444        mPath.setWindingRule(getWindingRule(fillType));
445    }
446
447    /**
448     * Returns the Java2D winding rules matching a given Android {@link FillType}.
449     * @param type the android fill type
450     * @return the matching java2d winding rule.
451     */
452    private static int getWindingRule(FillType type) {
453        switch (type) {
454            case WINDING:
455            case INVERSE_WINDING:
456                return GeneralPath.WIND_NON_ZERO;
457            case EVEN_ODD:
458            case INVERSE_EVEN_ODD:
459                return GeneralPath.WIND_EVEN_ODD;
460        }
461
462        assert false;
463        throw new IllegalArgumentException();
464    }
465
466    private static Direction getDirection(int direction) {
467        for (Direction d : Direction.values()) {
468            if (direction == d.nativeInt) {
469                return d;
470            }
471        }
472
473        assert false;
474        return null;
475    }
476
477    /**
478     * Returns whether the path is empty.
479     * @return true if the path is empty.
480     */
481    private boolean isEmpty() {
482        return mPath.getCurrentPoint() == null;
483    }
484
485    /**
486     * Fills the given {@link RectF} with the path bounds.
487     * @param bounds the RectF to be filled.
488     */
489    private void fillBounds(RectF bounds) {
490        Rectangle2D rect = mPath.getBounds2D();
491        bounds.left = (float)rect.getMinX();
492        bounds.right = (float)rect.getMaxX();
493        bounds.top = (float)rect.getMinY();
494        bounds.bottom = (float)rect.getMaxY();
495    }
496
497    /**
498     * Set the beginning of the next contour to the point (x,y).
499     *
500     * @param x The x-coordinate of the start of a new contour
501     * @param y The y-coordinate of the start of a new contour
502     */
503    private void moveTo(float x, float y) {
504        mPath.moveTo(mLastX = x, mLastY = y);
505    }
506
507    /**
508     * Set the beginning of the next contour relative to the last point on the
509     * previous contour. If there is no previous contour, this is treated the
510     * same as moveTo().
511     *
512     * @param dx The amount to add to the x-coordinate of the end of the
513     *           previous contour, to specify the start of a new contour
514     * @param dy The amount to add to the y-coordinate of the end of the
515     *           previous contour, to specify the start of a new contour
516     */
517    private void rMoveTo(float dx, float dy) {
518        dx += mLastX;
519        dy += mLastY;
520        mPath.moveTo(mLastX = dx, mLastY = dy);
521    }
522
523    /**
524     * Add a line from the last point to the specified point (x,y).
525     * If no moveTo() call has been made for this contour, the first point is
526     * automatically set to (0,0).
527     *
528     * @param x The x-coordinate of the end of a line
529     * @param y The y-coordinate of the end of a line
530     */
531    private void lineTo(float x, float y) {
532        mPath.lineTo(mLastX = x, mLastY = y);
533    }
534
535    /**
536     * Same as lineTo, but the coordinates are considered relative to the last
537     * point on this contour. If there is no previous point, then a moveTo(0,0)
538     * is inserted automatically.
539     *
540     * @param dx The amount to add to the x-coordinate of the previous point on
541     *           this contour, to specify a line
542     * @param dy The amount to add to the y-coordinate of the previous point on
543     *           this contour, to specify a line
544     */
545    private void rLineTo(float dx, float dy) {
546        if (isEmpty()) {
547            mPath.moveTo(mLastX = 0, mLastY = 0);
548        }
549        dx += mLastX;
550        dy += mLastY;
551        mPath.lineTo(mLastX = dx, mLastY = dy);
552    }
553
554    /**
555     * Add a quadratic bezier from the last point, approaching control point
556     * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
557     * this contour, the first point is automatically set to (0,0).
558     *
559     * @param x1 The x-coordinate of the control point on a quadratic curve
560     * @param y1 The y-coordinate of the control point on a quadratic curve
561     * @param x2 The x-coordinate of the end point on a quadratic curve
562     * @param y2 The y-coordinate of the end point on a quadratic curve
563     */
564    private void quadTo(float x1, float y1, float x2, float y2) {
565        mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
566    }
567
568    /**
569     * Same as quadTo, but the coordinates are considered relative to the last
570     * point on this contour. If there is no previous point, then a moveTo(0,0)
571     * is inserted automatically.
572     *
573     * @param dx1 The amount to add to the x-coordinate of the last point on
574     *            this contour, for the control point of a quadratic curve
575     * @param dy1 The amount to add to the y-coordinate of the last point on
576     *            this contour, for the control point of a quadratic curve
577     * @param dx2 The amount to add to the x-coordinate of the last point on
578     *            this contour, for the end point of a quadratic curve
579     * @param dy2 The amount to add to the y-coordinate of the last point on
580     *            this contour, for the end point of a quadratic curve
581     */
582    private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
583        if (isEmpty()) {
584            mPath.moveTo(mLastX = 0, mLastY = 0);
585        }
586        dx1 += mLastX;
587        dy1 += mLastY;
588        dx2 += mLastX;
589        dy2 += mLastY;
590        mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
591    }
592
593    /**
594     * Add a cubic bezier from the last point, approaching control points
595     * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
596     * made for this contour, the first point is automatically set to (0,0).
597     *
598     * @param x1 The x-coordinate of the 1st control point on a cubic curve
599     * @param y1 The y-coordinate of the 1st control point on a cubic curve
600     * @param x2 The x-coordinate of the 2nd control point on a cubic curve
601     * @param y2 The y-coordinate of the 2nd control point on a cubic curve
602     * @param x3 The x-coordinate of the end point on a cubic curve
603     * @param y3 The y-coordinate of the end point on a cubic curve
604     */
605    private void cubicTo(float x1, float y1, float x2, float y2,
606                        float x3, float y3) {
607        mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
608    }
609
610    /**
611     * Same as cubicTo, but the coordinates are considered relative to the
612     * current point on this contour. If there is no previous point, then a
613     * moveTo(0,0) is inserted automatically.
614     */
615    private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
616                         float dx3, float dy3) {
617        if (isEmpty()) {
618            mPath.moveTo(mLastX = 0, mLastY = 0);
619        }
620        dx1 += mLastX;
621        dy1 += mLastY;
622        dx2 += mLastX;
623        dy2 += mLastY;
624        dx3 += mLastX;
625        dy3 += mLastY;
626        mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
627    }
628
629    /**
630     * Append the specified arc to the path as a new contour. If the start of
631     * the path is different from the path's current last point, then an
632     * automatic lineTo() is added to connect the current contour to the
633     * start of the arc. However, if the path is empty, then we call moveTo()
634     * with the first point of the arc. The sweep angle is tread mod 360.
635     *
636     * @param oval        The bounds of oval defining shape and size of the arc
637     * @param startAngle  Starting angle (in degrees) where the arc begins
638     * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
639     *                    mod 360.
640     * @param forceMoveTo If true, always begin a new contour with the arc
641     */
642    private void arcTo(RectF oval, float startAngle, float sweepAngle,
643                      boolean forceMoveTo) {
644        Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), startAngle,
645                sweepAngle, Arc2D.OPEN);
646        mPath.append(arc, true /*connect*/);
647
648        resetLastPointFromPath();
649    }
650
651    /**
652     * Close the current contour. If the current point is not equal to the
653     * first point of the contour, a line segment is automatically added.
654     */
655    private void close() {
656        mPath.closePath();
657    }
658
659    private void resetLastPointFromPath() {
660        Point2D last = mPath.getCurrentPoint();
661        mLastX = (float) last.getX();
662        mLastY = (float) last.getY();
663    }
664
665    /**
666     * Add a closed rectangle contour to the path
667     *
668     * @param left   The left side of a rectangle to add to the path
669     * @param top    The top of a rectangle to add to the path
670     * @param right  The right side of a rectangle to add to the path
671     * @param bottom The bottom of a rectangle to add to the path
672     * @param dir    The direction to wind the rectangle's contour
673     */
674    private void addRect(float left, float top, float right, float bottom,
675                        int dir) {
676        moveTo(left, top);
677
678        Direction direction = getDirection(dir);
679
680        switch (direction) {
681            case CW:
682                lineTo(right, top);
683                lineTo(right, bottom);
684                lineTo(left, bottom);
685                break;
686            case CCW:
687                lineTo(left, bottom);
688                lineTo(right, bottom);
689                lineTo(right, top);
690                break;
691        }
692
693        close();
694
695        resetLastPointFromPath();
696    }
697
698    /**
699     * Offset the path by (dx,dy), returning true on success
700     *
701     * @param dx  The amount in the X direction to offset the entire path
702     * @param dy  The amount in the Y direction to offset the entire path
703     * @param dst The translated path is written here. If this is null, then
704     *            the original path is modified.
705     */
706    public void offset(float dx, float dy, Path_Delegate dst) {
707        GeneralPath newPath = new GeneralPath();
708
709        PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
710
711        newPath.append(iterator, false /*connect*/);
712
713        if (dst != null) {
714            dst.mPath = newPath;
715        } else {
716            mPath = newPath;
717        }
718    }
719
720    /**
721     * Transform the points in this path by matrix, and write the answer
722     * into dst. If dst is null, then the the original path is modified.
723     *
724     * @param matrix The matrix to apply to the path
725     * @param dst    The transformed path is written here. If dst is null,
726     *               then the the original path is modified
727     */
728    public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
729        if (matrix.hasPerspective()) {
730            assert false;
731            Bridge.getLog().fidelityWarning(null,
732                    "android.graphics.Path#transform() only " +
733                    "supports affine transformations in the Layout Preview.", null);
734        }
735
736        GeneralPath newPath = new GeneralPath();
737
738        PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
739
740        newPath.append(iterator, false /*connect*/);
741
742        if (dst != null) {
743            dst.mPath = newPath;
744        } else {
745            mPath = newPath;
746        }
747    }
748}
749