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