AI2 Component  (Version nb184)
Canvas.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2009-2011 Google, All Rights reserved
3 // Copyright 2011-2020 MIT, All rights reserved
4 // Released under the Apache License, Version 2.0
5 // http://www.apache.org/licenses/LICENSE-2.0
6 
7 package com.google.appinventor.components.runtime;
8 
9 import android.Manifest;
10 import android.app.Activity;
11 
12 import android.content.Context;
13 
14 import android.graphics.Bitmap;
15 import android.graphics.BitmapFactory;
16 import android.graphics.Color;
17 import android.graphics.Paint;
18 import android.graphics.Path;
19 import android.graphics.PorterDuff;
20 import android.graphics.Rect;
21 import android.graphics.RectF;
22 import android.graphics.drawable.BitmapDrawable;
23 import android.graphics.drawable.ColorDrawable;
24 import android.graphics.drawable.Drawable;
25 
26 import android.text.TextUtils;
27 
28 import android.util.Base64;
29 import android.util.Log;
30 
31 import android.view.GestureDetector;
32 import android.view.MotionEvent;
33 import android.view.View;
34 
35 import androidx.annotation.RequiresApi;
36 
46 
51 
53 
55 
64 
65 import java.io.File;
66 import java.io.FileNotFoundException;
67 import java.io.FileOutputStream;
68 import java.io.IOException;
69 
70 import java.util.ArrayList;
71 import java.util.LinkedList;
72 import java.util.List;
73 import java.util.Set;
74 
112 @DesignerComponent(version = YaVersion.CANVAS_COMPONENT_VERSION,
113  description = "<p>A two-dimensional touch-sensitive rectangular panel on " +
114  "which drawing can be done and sprites can be moved.</p> " +
115  "<p>The <code>BackgroundColor</code>, <code>PaintColor</code>, " +
116  "<code>BackgroundImage</code>, <code>Width</code>, and " +
117  "<code>Height</code> of the Canvas can be set in either the Designer or " +
118  "in the Blocks Editor. The <code>Width</code> and <code>Height</code> " +
119  "are measured in pixels and must be positive.</p>" +
120  "<p>Any location on the Canvas can be specified as a pair of " +
121  "(X, Y) values, where <ul> " +
122  "<li>X is the number of pixels away from the left edge of the Canvas</li>" +
123  "<li>Y is the number of pixels away from the top edge of the Canvas</li>" +
124  "</ul>.</p> " +
125  "<p>There are events to tell when and where a Canvas has been touched or " +
126  "a <code>Sprite</code> (<code>ImageSprite</code> or <code>Ball</code>) " +
127  "has been dragged. There are also methods for drawing points, lines, " +
128  "and circles.</p>",
129  category = ComponentCategory.ANIMATION)
130 @SimpleObject
131 @UsesPermissions(permissionNames = "android.permission.INTERNET")
132 public final class Canvas extends AndroidViewComponent implements ComponentContainer {
133  private static final String LOG_TAG = "Canvas";
134 
135  private final Activity context;
136  private final CanvasView view;
137 
138  // Android can't correctly give the width and height of a canvas until
139  // something has been drawn on it.
140  private boolean drawn;
141 
142  // Variables behind properties
143  private int paintColor;
144  private final Paint paint;
145  private int backgroundColor;
146  private String backgroundImagePath = "";
147  private int textAlignment;
148  private boolean extendMovesOutsideCanvas = false;
149 
150  // Default values
151  private static final int MIN_WIDTH_HEIGHT = 1;
152  private static final float DEFAULT_LINE_WIDTH = 2;
153  private static final int DEFAULT_PAINT_COLOR = Component.COLOR_BLACK;
154  private static final int DEFAULT_BACKGROUND_COLOR = Component.COLOR_WHITE;
155  private static final int DEFAULT_TEXTALIGNMENT = Component.ALIGNMENT_CENTER;
156  private static final int FLING_INTERVAL = 1000; // ms
157 
158  // Keep track of enclosed sprites. This list should always be
159  // sorted by increasing sprite.Z().
160  private final List<Sprite> sprites;
161 
162  // Handle touches and drags
163  private final MotionEventParser motionEventParser;
164 
165  // Handle fling events
166  private final GestureDetector mGestureDetector;
167 
168  // The canvas has built-in detectors that trigger on touch, drag, touchDown,
169  // TouchUp and Fling gestures. It also maintains a set of additional gesture detectors
170  // that can respond to motion events. These detectors
171  // will typically be implemented by extension components that add the detector to this set.
172 
173  private final Set<ExtensionGestureDetector> extensionGestureDetectors = Sets.newHashSet();
174 
175  private Form form = $form();
176 
177  // Do we have storage permission?
178  private boolean havePermission = false;
179 
180  // additional gesture detectors must implement this interface
181  public interface ExtensionGestureDetector {
182  boolean onTouchEvent(MotionEvent event);
183  };
184 
214  class MotionEventParser {
220  // This used to be 30 and people complained that they could not draw small circles.
221  // If the threshold is too small, then touches might be misinterpreted as drags,
222  // this might require more experimentation. We might also want to take screen resolution
223  // into account and/or try to make a more clever motion parser.
224  public static final int TAP_THRESHOLD = 15;
225 
232  public static final int FINGER_WIDTH = 24;
233 
240  public static final int FINGER_HEIGHT = 24;
241 
242  private static final int HALF_FINGER_WIDTH = FINGER_WIDTH / 2;
243  private static final int HALF_FINGER_HEIGHT = FINGER_HEIGHT / 2;
244 
249  private final List<Sprite> draggedSprites = new ArrayList<Sprite>();
250 
251  // startX and startY hold the coordinates of where a touch/drag started
252  private static final int UNSET = -1;
253  private float startX = UNSET;
254  private float startY = UNSET;
255 
256  // lastX and lastY hold the coordinates of the previous step of a drag
257  private float lastX = UNSET;
258  private float lastY = UNSET;
259 
260  // Is this sequence of events a drag? I.e., has the touch point moved away
261  // from the start point?
262  private boolean isDrag = false;
263 
264  private boolean drag = false;
265 
266  void parse(MotionEvent event) {
267  int width = Width();
268  int height = Height();
269 
270  // Coordinates less than 0 can be returned if a move begins within a
271  // view and ends outside of it. Because negative coordinates would
272  // probably confuse the user (as they did me) and would not be useful,
273  // we replace any negative values with zero.
274  float x = Math.max(0, (int) event.getX() / $form().deviceDensity());
275  float y = Math.max(0, (int) event.getY() / $form().deviceDensity());
276 
277  // Also make sure that by adding or subtracting a half finger that
278  // we don't go out of bounds.
279  BoundingBox rect = new BoundingBox(
280  Math.max(0, (int) x - HALF_FINGER_HEIGHT),
281  Math.max(0, (int) y - HALF_FINGER_WIDTH),
282  Math.min(width - 1, (int) x + HALF_FINGER_WIDTH),
283  Math.min(height - 1, (int) y + HALF_FINGER_HEIGHT));
284 
285  switch (event.getAction()) {
286  case MotionEvent.ACTION_DOWN:
287  draggedSprites.clear();
288  startX = x;
289  startY = y;
290  lastX = x;
291  lastY = y;
292  drag = false;
293  isDrag = false;
294  for (Sprite sprite : sprites) {
295  if (sprite.Enabled() && sprite.Visible() && sprite.intersectsWith(rect)) {
296  draggedSprites.add(sprite);
297  sprite.TouchDown(startX, startY);
298  }
299  }
300  TouchDown(startX, startY);
301  break;
302 
303  case MotionEvent.ACTION_MOVE:
304  // Ensure that this was preceded by an ACTION_DOWN
305  if (startX == UNSET || startY == UNSET || lastX == UNSET || lastY == UNSET) {
306  Log.w(LOG_TAG, "In Canvas.MotionEventParser.parse(), " +
307  "an ACTION_MOVE was passed without a preceding ACTION_DOWN: " + event);
308  }
309 
310  // If the new point is near the start point, it may just be a tap
311  if (!isDrag &&
312  (Math.abs(x - startX) < TAP_THRESHOLD && Math.abs(y - startY) < TAP_THRESHOLD)) {
313  break;
314  }
315  // Otherwise, it's a drag.
316  isDrag = true;
317  drag = true;
318 
319  // Don't let MOVE extend beyond the bounds of the canvas
320  // if ExtendMovesOutsideCanvas is false
321  if (((x <= 0) || (x > width) || (y <= 0) || (y > height))
322  && (! extendMovesOutsideCanvas)) {
323  break;
324  }
325 
326  // Update draggedSprites by adding any that are currently being
327  // touched.
328  for (Sprite sprite : sprites) {
329  if (!draggedSprites.contains(sprite)
330  && sprite.Enabled() && sprite.Visible()
331  && sprite.intersectsWith(rect)) {
332  draggedSprites.add(sprite);
333  }
334  }
335 
336  // Raise a Dragged event for any affected sprites
337  boolean handled = false;
338  for (Sprite sprite : draggedSprites) {
339  if (sprite.Enabled() && sprite.Visible()) {
340  sprite.Dragged(startX, startY, lastX, lastY, x, y);
341  handled = true;
342  }
343  }
344 
345  // Last argument indicates whether a sprite handled the drag
346  Dragged(startX, startY, lastX, lastY, x, y, handled);
347  lastX = x;
348  lastY = y;
349  break;
350 
351  case MotionEvent.ACTION_UP:
352  // If we never strayed far from the start point, it's a tap. (If we
353  // did stray far, we've already handled the movements in the ACTION_MOVE
354  // case.)
355  if (!drag) {
356  // It's a tap
357  handled = false;
358  for (Sprite sprite : draggedSprites) {
359  if (sprite.Enabled() && sprite.Visible()) {
360  sprite.Touched(x, y);
361  sprite.TouchUp(x, y);
362  handled = true;
363  }
364  }
365  // Last argument indicates that one or more sprites handled the tap
366  Touched(x, y, handled);
367  }
368  else {
369  for (Sprite sprite : draggedSprites) {
370  if (sprite.Enabled() && sprite.Visible()) {
371  sprite.Touched(x, y);
372  sprite.TouchUp(x, y);
373  }
374  }
375  }
376  // This is intentionally outside the if (!drag) block.
377  // Even the release of a drag on the canvas should fire
378  // a touch-up event.
379  TouchUp(x, y);
380 
381  // Prepare for next drag
382  drag = false;
383  startX = UNSET;
384  startY = UNSET;
385  lastX = UNSET;
386  lastY = UNSET;
387  break;
388  }
389  }
390  }
391 
396  private final class CanvasView extends View {
397  // Variables to implement View
398  private android.graphics.Canvas canvas;
399  private Bitmap bitmap; // Bitmap backing Canvas
400 
401  // Support for background images
402  private BitmapDrawable backgroundDrawable;
403 
404  // Support for GetBackgroundPixelColor() and GetPixelColor().
405 
406  // scaledBackgroundBitmap is a scaled version of backgroundDrawable that
407  // is created only if getBackgroundPixelColor() is called. It is set back
408  // to null whenever the canvas size or backgroundDrawable changes.
409  private Bitmap scaledBackgroundBitmap;
410 
411  // completeCache is created if the user calls getPixelColor(). It is set
412  // back to null whenever the view is redrawn. If available, it is used
413  // when the Canvas is saved to a file.
414  private Bitmap completeCache;
415 
416  public CanvasView(Context context) {
417  super(context);
418  bitmap = Bitmap.createBitmap(ComponentConstants.CANVAS_PREFERRED_WIDTH,
419  ComponentConstants.CANVAS_PREFERRED_HEIGHT,
420  Bitmap.Config.ARGB_8888);
421  canvas = new android.graphics.Canvas(bitmap);
422  }
423 
424  /*
425  * Create a bitmap showing the background (image or color) and drawing
426  * (points, lines, circles, text) layer of the view but not any sprites.
427  */
428  private Bitmap buildCache() {
429  // First, try building drawing cache.
430  setDrawingCacheEnabled(true);
431  destroyDrawingCache(); // clear any earlier versions we have requested
432  Bitmap cache = getDrawingCache(); // may return null if size is too large
433 
434  // If drawing cache can't be built, build a cache manually.
435  if (cache == null) {
436  int width = getWidth();
437  int height = getHeight();
438  cache = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
439  android.graphics.Canvas c = new android.graphics.Canvas(cache);
440  layout(0, 0, width, height);
441  draw(c);
442  }
443  return cache;
444  }
445 
446  @Override
447  public void onDraw(android.graphics.Canvas canvas0) {
448  completeCache = null;
449 
450  // This will draw the background image and color, if present.
451  super.onDraw(canvas0);
452 
453  // Redraw anything that had been directly drawn on the old Canvas,
454  // such as lines and circles but not Sprites.
455  canvas0.drawBitmap(bitmap, 0, 0, null);
456 
457  // sprites is sorted by Z level, so sprites with low Z values will be
458  // drawn first, potentially being hidden by Sprites with higher Z values.
459  for (Sprite sprite : sprites) {
460  sprite.onDraw(canvas0);
461  }
462  drawn = true;
463  }
464 
465  @Override
466  protected void onSizeChanged(int w, int h, int oldW, int oldH) {
467  int oldBitmapWidth = bitmap.getWidth();
468  int oldBitmapHeight = bitmap.getHeight();
469  if (w != oldBitmapWidth || h != oldBitmapHeight) {
470  Bitmap oldBitmap = bitmap;
471 
472  // Create a new bitmap by scaling the old bitmap that contained the
473  // drawing layer (points, lines, text, etc.).
474 
475  // The documentation for Bitmap.createScaledBitmap doesn't specify whether it creates a
476  // mutable or immutable bitmap. Looking at the source code shows that it calls
477  // Bitmap.createBitmap(Bitmap, int, int, int, int, Matrix, boolean), which is documented as
478  // returning an immutable bitmap. However, it actually returns a mutable bitmap.
479  // It's possible that the behavior could change in the future if they "fix" that bug.
480  // Try Bitmap.createScaledBitmap, but if it gives us an immutable bitmap, we'll have to
481  // create a mutable bitmap and scale the old bitmap using Canvas.drawBitmap.
482  try {
483  // See comment at the catch below
484  Bitmap scaledBitmap = Bitmap.createScaledBitmap(oldBitmap, w, h, false);
485 
486  if (scaledBitmap.isMutable()) {
487  // scaledBitmap is mutable; we can use it in a canvas.
488  bitmap = scaledBitmap;
489  // NOTE(lizlooney) - I tried just doing canvas.setBitmap(bitmap), but after that the
490  // canvas.drawCircle() method did not work correctly. So, we need to create a whole new
491  // canvas.
492  canvas = new android.graphics.Canvas(bitmap);
493 
494  } else {
495  // scaledBitmap is immutable; we can't use it in a canvas.
496 
497  bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
498  // NOTE(lizlooney) - I tried just doing canvas.setBitmap(bitmap), but after that the
499  // canvas.drawCircle() method did not work correctly. So, we need to create a whole new
500  // canvas.
501  canvas = new android.graphics.Canvas(bitmap);
502 
503  // Draw the old bitmap into the new canvas, scaling as necessary.
504  Rect src = new Rect(0, 0, oldBitmapWidth, oldBitmapHeight);
505  RectF dst = new RectF(0, 0, w, h);
506  canvas.drawBitmap(oldBitmap, src, dst, null);
507  }
508 
509  } catch (IllegalArgumentException ioe) {
510  // There's some kind of order of events issue that results in w or h being zero.
511  // I'm guessing that this is a result of specifying width or height as FILL_PARRENT on an
512  // opening screen. In any case, w<=0 or h<=0 causes the call to createScaledBitmap
513  // to throw an illegal argument. If this happens we simply don't draw the bitmap
514  // (which would be of width or height 0)
515  // TODO(hal): Investigate this further to see what is causes the w=0 or h=0 and see if
516  // there is a more high-level fix.
517 
518  Log.e(LOG_TAG, "Bad values to createScaledBimap w = " + w + ", h = " + h);
519  }
520 
521  // The following has nothing to do with the scaling in this method.
522  // It has to do with scaling the background image for GetColor().
523  // Specifically, it says we need to regenerate the bitmap representing
524  // the background color/image if a call to GetColor() is made.
525  scaledBackgroundBitmap = null;
526  }
527  }
528 
529  @Override
530  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
531  int preferredWidth;
532  int preferredHeight;
533  if (backgroundDrawable != null) {
534  // Drawable.getIntrinsicWidth/Height gives weird values, but Bitmap.getWidth/Height works.
535  Bitmap bitmap = backgroundDrawable.getBitmap();
536  preferredWidth = bitmap.getWidth();
537  preferredHeight = bitmap.getHeight();
538  } else {
539  preferredWidth = ComponentConstants.CANVAS_PREFERRED_WIDTH;
540  preferredHeight = ComponentConstants.CANVAS_PREFERRED_HEIGHT;
541  }
542  setMeasuredDimension(getSize(widthMeasureSpec, preferredWidth),
543  getSize(heightMeasureSpec, preferredHeight));
544  }
545 
546  private int getSize(int measureSpec, int preferredSize) {
547  int result;
548  int specMode = MeasureSpec.getMode(measureSpec);
549  int specSize = MeasureSpec.getSize(measureSpec);
550 
551  if (specMode == MeasureSpec.EXACTLY) {
552  // We were told how big to be
553  result = specSize;
554  } else {
555  // Use the preferred size.
556  result = preferredSize;
557  if (specMode == MeasureSpec.AT_MOST) {
558  // Respect AT_MOST value if that was what is called for by measureSpec
559  result = Math.min(result, specSize);
560  }
561  }
562 
563  return result;
564  }
565 
566  @Override
567  public boolean onTouchEvent(MotionEvent event) {
568  // The following call results in the Form not grabbing our events and
569  // handling dragging on its own, which it wants to do to handle scrolling.
570  // Its effect only lasts long as the current set of motion events
571  // generated during this touch and drag sequence. Consequently, it needs
572  // to be called here, so that it happens for each touch-drag sequence.
573  container.$form().dontGrabTouchEventsForComponent();
574  motionEventParser.parse(event);
575  mGestureDetector.onTouchEvent(event); // handle onFling here
576  // let each detector in the custom list handle the event
577  for (ExtensionGestureDetector g : extensionGestureDetectors) {
578  // Log.i("Canvas", "Calling detector: " + g.toString());
579  // Log.i("Canvas", "sending motion event " + event.toString());
580  g.onTouchEvent(event);
581  }
582  return true;
583  }
584 
585  // Methods supporting properties
586 
587  // This mutates backgroundImagePath in the outer class
588  // and backgroundDrawable in this class.
589  //
590  // This erases the drawing layer (lines, text, etc.), whether or not
591  // a valid image is loaded, to be compatible with earlier versions
592  // of App Inventor.
593  void setBackgroundImage(String path) {
594  backgroundImagePath = (path == null) ? "" : path;
595  backgroundDrawable = null;
596  scaledBackgroundBitmap = null;
597 
598  if (!TextUtils.isEmpty(backgroundImagePath)) {
599  try {
600  backgroundDrawable = MediaUtil.getBitmapDrawable(container.$form(), backgroundImagePath);
601  } catch (IOException ioe) {
602  Log.e(LOG_TAG, "Unable to load " + backgroundImagePath);
603  }
604  }
605 
606  setBackground();
607 
608  clearDrawingLayer(); // will call invalidate()
609  }
610 
611  @RequiresApi(api = android.os.Build.VERSION_CODES.FROYO)
612  void setBackgroundImageBase64(String imageUrl) {
613  backgroundImagePath = (imageUrl == null) ? "" : imageUrl;
614  backgroundDrawable = null;
615  scaledBackgroundBitmap = null;
616 
617  if (!TextUtils.isEmpty(backgroundImagePath)) {
618  byte[] decodedString = Base64.decode(backgroundImagePath, Base64.DEFAULT);
619  android.graphics.Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
620  backgroundDrawable = new BitmapDrawable(decodedByte);
621  }
622 
623  setBackground();
624  clearDrawingLayer(); // will call invalidate()
625  }
626 
627  private void setBackground() {
628  Drawable setDraw = backgroundDrawable;
629  if (backgroundImagePath != "" && backgroundDrawable != null) {
630  setDraw = backgroundDrawable.getConstantState().newDrawable();
631  setDraw.setColorFilter((backgroundColor != Component.COLOR_DEFAULT) ? backgroundColor : Component.COLOR_WHITE,
632  PorterDuff.Mode.DST_OVER);
633  }
634  else {
635  setDraw = new ColorDrawable(
636  (backgroundColor != Component.COLOR_DEFAULT) ? backgroundColor : Component.COLOR_WHITE);
637  }
638  setBackgroundDrawable(setDraw);
639  }
640 
641  private void clearDrawingLayer() {
642  canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
643  invalidate();
644  }
645 
646  // This mutates backgroundColor in the outer class.
647  // This erases the drawing layer (lines, text, etc.) to be compatible
648  // with earlier versions of App Inventor.
649  @Override
650  public void setBackgroundColor(int color) {
651  backgroundColor = color;
652 
653  setBackground();
654 
655  clearDrawingLayer();
656  }
657 
658  // These methods support SimpleFunctions.
659  private void drawTextAtAngle(String text, int x, int y, float angle) {
660  canvas.save();
661  canvas.rotate(-angle, x, y);
662  canvas.drawText(text, x, y, paint);
663  canvas.restore();
664  invalidate();
665  }
666 
667  // This intentionally ignores sprites.
668  private int getBackgroundPixelColor(int x, int y) {
669  // If the request is out of bounds, return COLOR_NONE.
670  if (x < 0 || x >= bitmap.getWidth() ||
671  y < 0 || y >= bitmap.getHeight()) {
672  return Component.COLOR_NONE;
673  }
674 
675  try {
676  // First check if anything has been drawn on the bitmap
677  // (such as by DrawPoint, DrawCircle, etc.).
678  int color = bitmap.getPixel(x, y);
679  if (color != Color.TRANSPARENT) {
680  return color;
681  }
682 
683  // If nothing has been drawn on the bitmap at that location,
684  // check if there is a background image.
685  if (backgroundDrawable != null) {
686  if (scaledBackgroundBitmap == null) {
687  scaledBackgroundBitmap = Bitmap.createScaledBitmap(
688  backgroundDrawable.getBitmap(),
689  bitmap.getWidth(), bitmap.getHeight(),
690  false); // false argument indicates not to filter
691  }
692  color = scaledBackgroundBitmap.getPixel(x, y);
693  return color;
694  }
695 
696  // If there is no background image, use the background color.
697  if (Color.alpha(backgroundColor) != 0) {
698  return backgroundColor;
699  }
700  return Component.COLOR_NONE;
701  } catch (IllegalArgumentException e) {
702  // This should never occur, since we have checked bounds.
703  Log.e(LOG_TAG,
704  String.format("Returning COLOR_NONE (exception) from getBackgroundPixelColor."));
705  return Component.COLOR_NONE;
706  }
707  }
708 
709  private int getPixelColor(int x, int y) {
710  // If the request is out of bounds, return COLOR_NONE.
711  if (x < 0 || x >= bitmap.getWidth() ||
712  y < 0 || y >= bitmap.getHeight()) {
713  return Component.COLOR_NONE;
714  }
715 
716  // If the cache isn't available, try to avoid rebuilding it.
717  if (completeCache == null) {
718  // If there are no visible sprites, just call getBackgroundPixelColor().
719  boolean anySpritesVisible = false;
720  for (Sprite sprite : sprites) {
721  if (sprite.Visible()) {
722  anySpritesVisible = true;
723  break;
724  }
725  }
726  if (!anySpritesVisible) {
727  return getBackgroundPixelColor(x, y);
728  }
729 
730  // TODO(user): If needed for efficiency, check whether there are any
731  // sprites overlapping (x, y). If not, we can just call getBackgroundPixelColor().
732  // If so, maybe we can just draw those sprites instead of building a full
733  // cache of the view.
734 
735  completeCache = buildCache();
736  }
737 
738  // Check the complete cache.
739  try {
740  return completeCache.getPixel(x, y);
741  } catch (IllegalArgumentException e) {
742  // This should never occur, since we have checked bounds.
743  Log.e(LOG_TAG,
744  String.format("Returning COLOR_NONE (exception) from getPixelColor."));
745  return Component.COLOR_NONE;
746  }
747  }
748  }
749 
750  public Canvas(ComponentContainer container) {
751  super(container);
752  context = container.$context();
753 
754  // Create view and add it to its designated container.
755  view = new CanvasView(context);
756  container.$add(this);
757 
758  paint = new Paint();
759  paint.setFlags(Paint.ANTI_ALIAS_FLAG);
760 
761  // Set default properties.
762  paint.setStrokeWidth(DEFAULT_LINE_WIDTH);
763  PaintColor(DEFAULT_PAINT_COLOR);
764  BackgroundColor(DEFAULT_BACKGROUND_COLOR);
765  TextAlignment(DEFAULT_TEXTALIGNMENT);
766  FontSize(Component.FONT_DEFAULT_SIZE);
767 
768  sprites = new LinkedList<Sprite>();
769  motionEventParser = new MotionEventParser();
770  mGestureDetector = new GestureDetector(context, new FlingGestureListener());
771  }
772 
773  public void Initialize() {
774  // Note: The code below does not call ourselves after the
775  // onGranted because we don't do anything beyond getting
776  // permissions. If we ever add code to this Initialize method,
777  // that requires permissions, then be sure to call ourselves in
778  // onGranted().
779  if (!havePermission && form.doesAppDeclarePermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
780  final Canvas me = this;
781  form.askPermission(new BulkPermissionRequest(this, "Canvas",
782  Manifest.permission.WRITE_EXTERNAL_STORAGE) {
783  @Override
784  public void onGranted() {
785  me.havePermission = true;
786  }
787  });
788  }
789  }
790 
791  @Override
792  public View getView() {
793  return view;
794  }
795 
796  public Activity getContext() {
797  return context;
798  }
799 
800  // add a new custom gesture detector, typically by means of a component extension
802  // Log.i("Canvas", "Adding custom detector " + detector.toString());
803  extensionGestureDetectors.add(detector);
804  }
805 
806  public void removeCustomGestureDetector(Object detector) {
807  extensionGestureDetectors.remove(detector);
808  }
809 
810 
811  // Methods related to getting the dimensions of this Canvas
812 
820  public boolean ready() {
821  return drawn;
822  }
823 
824  // Implementation of container methods
825 
832  void addSprite(Sprite sprite) {
833  // Add before first element with greater Z value.
834  // This ensures not only that items are in increasing Z value
835  // but that sprites whose Z values are always equal are
836  // ordered by creation time. While we don't wish to guarantee
837  // this behavior going forward, it does provide consistency
838  // with how things worked before Z layering was added.
839  for (int i = 0; i < sprites.size(); i++) {
840  if (sprites.get(i).Z() > sprite.Z()) {
841  sprites.add(i, sprite);
842  return;
843  }
844  }
845 
846  // Add to end if it has the highest Z value.
847  sprites.add(sprite);
848  }
849 
855  void removeSprite(Sprite sprite) {
856  sprites.remove(sprite);
857  }
858 
865  void changeSpriteLayer(Sprite sprite) {
866  removeSprite(sprite);
867  addSprite(sprite);
868  view.invalidate();
869  }
870 
871  @Override
872  public Activity $context() {
873  return context;
874  }
875 
876  @Override
877  public Form $form() {
878  return container.$form();
879  }
880 
881  @Override
882  public void $add(AndroidViewComponent component) {
883  throw new UnsupportedOperationException("Canvas.$add() called");
884  }
885 
886  @Override
887  public void setChildWidth(AndroidViewComponent component, int width) {
888  throw new UnsupportedOperationException("Canvas.setChildWidth() called");
889  }
890 
891  @Override
892  public void setChildHeight(AndroidViewComponent component, int height) {
893  throw new UnsupportedOperationException("Canvas.setChildHeight() called");
894  }
895 
896  // Methods executed when a child sprite has changed its location or appearance
897 
904  void registerChange(Sprite sprite) {
905  view.invalidate();
906  findSpriteCollisions(sprite);
907  }
908 
909 
910  // Methods for detecting collisions
911 
927  protected void findSpriteCollisions(Sprite movedSprite) {
928  for (Sprite sprite : sprites) {
929  if (sprite != movedSprite) {
930  // Check whether we already raised an event for their collision.
931  if (movedSprite.CollidingWith(sprite)) {
932  // If they no longer conflict, note that.
933  if (!movedSprite.Visible() || !movedSprite.Enabled() ||
934  !sprite.Visible() || !sprite.Enabled() ||
935  !Sprite.colliding(sprite, movedSprite)) {
936  movedSprite.NoLongerCollidingWith(sprite);
937  sprite.NoLongerCollidingWith(movedSprite);
938  } else {
939  // If they still conflict, do nothing.
940  }
941  } else {
942  // Check if they now conflict.
943  if (movedSprite.Visible() && movedSprite.Enabled() &&
944  sprite.Visible() && sprite.Enabled() &&
945  Sprite.colliding(sprite, movedSprite)) {
946  // If so, raise two CollidedWith events.
947  movedSprite.CollidedWith(sprite);
948  sprite.CollidedWith(movedSprite);
949  } else {
950  // If they still don't conflict, do nothing.
951  }
952  }
953  }
954  }
955  }
956 
957 
958  // Properties
959 
970  @Override
972  // the bitmap routines will crash if the width is set to 0
973  public void Width(int width) {
974  if ((width > 0) || (width==LENGTH_FILL_PARENT) || (width==LENGTH_PREFERRED) ||
975  (width <= LENGTH_PERCENT_TAG)) {
976  super.Width(width);
977  }
978  else {
979  container.$form().dispatchErrorOccurredEvent(this, "Width",
981  }
982  }
983 
994  @Override
996  // the bitmap routines will crash if the height is set to 0
997  public void Height(int height) {
998  if ((height > 0) || (height==LENGTH_FILL_PARENT) || (height==LENGTH_PREFERRED) ||
999  (height <= LENGTH_PERCENT_TAG)) {
1000  super.Height(height);
1001  }
1002  else {
1003  container.$form().dispatchErrorOccurredEvent(this, "Height",
1005  }
1006  }
1007 
1008 
1017  @SimpleProperty(
1018  description = "The color of the canvas background.",
1019  category = PropertyCategory.APPEARANCE)
1020  @IsColor
1021  public int BackgroundColor() {
1022  return backgroundColor;
1023  }
1024 
1035  defaultValue = Component.DEFAULT_VALUE_COLOR_WHITE)
1037  public void BackgroundColor(int argb) {
1038  view.setBackgroundColor(argb);
1039  }
1040 
1046  @SimpleProperty(
1047  description = "The name of a file containing the background image for the canvas",
1048  category = PropertyCategory.APPEARANCE)
1049  public String BackgroundImage() {
1050  return backgroundImagePath;
1051  }
1052 
1063  defaultValue = "")
1065  public void BackgroundImage(String path) {
1066  view.setBackgroundImage(path);
1067  }
1068 
1075  @RequiresApi(api = android.os.Build.VERSION_CODES.FROYO)
1076  @SimpleProperty (
1077  description = "Set the background image in Base64 format. This requires API level >= 8. For "
1078  + "devices with API level less than 8, setting this will end up with an empty background."
1079  )
1080  public void BackgroundImageinBase64(String imageUrl) {
1082  view.setBackgroundImageBase64(imageUrl);
1083  } else {
1084  view.setBackgroundImageBase64("");
1085  }
1086 
1087  }
1088 
1097  @SimpleProperty(
1098  description = "The color in which lines are drawn",
1099  category = PropertyCategory.APPEARANCE)
1100  @IsColor
1101  public int PaintColor() {
1102  return paintColor;
1103  }
1104 
1114  defaultValue = Component.DEFAULT_VALUE_COLOR_BLACK)
1116  public void PaintColor(int argb) {
1117  paintColor = argb;
1118  changePaint(paint, argb);
1119  }
1120 
1121  private void changePaint(Paint paint, int argb) {
1122  if (argb == Component.COLOR_DEFAULT) {
1123  // The default paint color is black.
1125  } else if (argb == Component.COLOR_NONE) {
1126  PaintUtil.changePaintTransparent(paint);
1127  } else {
1128  PaintUtil.changePaint(paint, argb);
1129  }
1130  }
1131 
1132  @SimpleProperty(
1133  description = "The font size of text drawn on the canvas.",
1134  category = PropertyCategory.APPEARANCE)
1135  public float FontSize() {
1136  float scale = $form().deviceDensity();
1137  return paint.getTextSize() / scale;
1138  }
1139 
1145  defaultValue = Component.FONT_DEFAULT_SIZE + "")
1147  public void FontSize(float size) {
1148  float scale = $form().deviceDensity();
1149  paint.setTextSize(size * scale);
1150  }
1151 
1156  @SimpleProperty(
1157  description = "The width of lines drawn on the canvas.",
1158  category = PropertyCategory.APPEARANCE)
1159  public float LineWidth() {
1160  return paint.getStrokeWidth() / $form().deviceDensity();
1161  }
1162 
1169  defaultValue = DEFAULT_LINE_WIDTH + "")
1171  public void LineWidth(float width) {
1172  paint.setStrokeWidth(width * $form().deviceDensity());
1173  }
1174 
1184  @SimpleProperty(description = "Determines the alignment of the " +
1185  "text drawn by DrawText() or DrawAngle() with respect to the " +
1186  "point specified by that command: point at the left of the text, point at the center " +
1187  "of the text, or point at the right of the text.",
1188 // TODO: (Hal) Check that this is still correct for RTL languages.
1189  category = PropertyCategory.APPEARANCE,
1190  userVisible = true)
1191  public int TextAlignment() {
1192  return textAlignment;
1193  }
1194 
1208  defaultValue = DEFAULT_TEXTALIGNMENT + "")
1209  @SimpleProperty(userVisible = true)
1210  public void TextAlignment(int alignment) {
1211  this.textAlignment = alignment;
1212  switch (alignment) {
1214  paint.setTextAlign(Paint.Align.LEFT);
1215  break;
1217  paint.setTextAlign(Paint.Align.CENTER);
1218  break;
1220  paint.setTextAlign(Paint.Align.RIGHT);
1221  break;
1222  }
1223  }
1224 
1225  @SimpleProperty(description =
1226  "Determines whether moves can extend beyond the canvas borders. " +
1227  " Default is false. This should normally be false, and the property " +
1228  "is provided for backwards compatibility.",
1229  category = PropertyCategory.BEHAVIOR,
1230  userVisible = true)
1231  public boolean ExtendMovesOutsideCanvas() {
1232  return extendMovesOutsideCanvas;
1233  }
1234 
1239  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "False")
1240  @SimpleProperty(userVisible = true)
1241  public void ExtendMovesOutsideCanvas(boolean extend){
1242  extendMovesOutsideCanvas = extend;
1243  }
1244 
1245  // Methods supporting event handling
1246 
1257  @SimpleEvent
1258  public void Touched(float x, float y, boolean touchedAnySprite) {
1259  EventDispatcher.dispatchEvent(this, "Touched", x, y, touchedAnySprite);
1260  }
1261 
1270  @SimpleEvent
1271  public void TouchDown(float x, float y) {
1272  EventDispatcher.dispatchEvent(this, "TouchDown", x, y);
1273  }
1274 
1283  @SimpleEvent
1284  public void TouchUp(float x, float y) {
1285  EventDispatcher.dispatchEvent(this, "TouchUp", x, y);
1286  }
1287 
1306  @SimpleEvent
1307  public void Flung(float x, float y, float speed, float heading,float xvel, float yvel,
1308  boolean flungSprite) {
1309  EventDispatcher.dispatchEvent(this, "Flung", x, y, speed, heading, xvel, yvel, flungSprite);
1310  }
1311 
1329  @SimpleEvent
1330  public void Dragged(float startX, float startY, float prevX, float prevY,
1331  float currentX, float currentY, boolean draggedAnySprite) {
1332  EventDispatcher.dispatchEvent(this, "Dragged", startX, startY,
1333  prevX, prevY, currentX, currentY, draggedAnySprite);
1334  }
1335 
1336  // Functions
1337 
1342  @SimpleFunction(description = "Clears anything drawn on this Canvas but " +
1343  "not any background color or image.")
1344  public void Clear() {
1345  view.clearDrawingLayer();
1346  }
1347 
1355  public void DrawPoint(int x, int y) {
1356  float correctedX = x * $form().deviceDensity();
1357  float correctedY = y * $form().deviceDensity();
1358  view.canvas.drawPoint(correctedX, correctedY, paint);
1359  view.invalidate();
1360  }
1361 
1372  public void DrawCircle(int centerX, int centerY, float radius, boolean fill) {
1373  float correctedX = centerX * $form().deviceDensity();
1374  float correctedY = centerY * $form().deviceDensity();
1375  float correctedR = radius * $form().deviceDensity();
1376  Paint p = new Paint(paint);
1377  p.setStyle(fill ? Paint.Style.FILL : Paint.Style.STROKE);
1378  view.canvas.drawCircle(correctedX, correctedY, correctedR, p);
1379  view.invalidate();
1380  }
1381 
1391  public void DrawLine(int x1, int y1, int x2, int y2) {
1392  float correctedX1 = x1 * $form().deviceDensity();
1393  float correctedY1 = y1 * $form().deviceDensity();
1394  float correctedX2 = x2 * $form().deviceDensity();
1395  float correctedY2 = y2 * $form().deviceDensity();
1396  view.canvas.drawLine(correctedX1, correctedY1, correctedX2, correctedY2, paint);
1397  view.invalidate();
1398  }
1399 
1410  @SimpleFunction(description =
1411  "Draws a shape on the canvas. " +
1412  "pointList should be a list contains sub-lists with two number which represents a coordinate. " +
1413  "The first point and last point does not need to be the same. e.g. ((x1 y1) (x2 y2) (x3 y3)) " +
1414  "When fill is true, the shape will be filled.")
1415  public void DrawShape(YailList pointList, boolean fill) {
1416  Path path;
1417  try {
1418  path = parsePath(parsePointList(pointList));
1419  } catch (IllegalArgumentException e) {
1420  $form().dispatchErrorOccurredEvent(this, "DrawShape", ErrorMessages.ERROR_CANVAS_DRAW_SHAPE_BAD_ARGUMENT);
1421  return;
1422  }
1423  path.close();
1424  Paint p = new Paint(paint);
1425  p.setStyle(fill ? Paint.Style.FILL : Paint.Style.STROKE);
1426  view.canvas.drawPath(path, p);
1427  view.invalidate();
1428  }
1429 
1430  private Path parsePath(float[][] points) throws IllegalArgumentException {
1431  if (points == null || points.length == 0) {
1432  throw new IllegalArgumentException();
1433  }
1434  float scalingFactor = $form().deviceDensity();
1435 
1436  Path path = new Path();
1437  path.moveTo(points[0][0] * scalingFactor, points[0][1] * scalingFactor);
1438  for (int i = 1; i < points.length; i++) {
1439  path.lineTo(points[i][0] * scalingFactor, points[i][1] * scalingFactor);
1440  }
1441 
1442  return path;
1443  }
1444 
1445  private float[][] parsePointList(YailList pointList) throws IllegalArgumentException {
1446  if (pointList == null || pointList.size() == 0) {
1447  throw new IllegalArgumentException();
1448  }
1449  float[][] points = new float[pointList.size()][2];
1450  int index = 0;
1451  YailList pointYailList;
1452  for (Object pointObject : pointList.toArray()) {
1453  if (pointObject instanceof YailList) {
1454  pointYailList = (YailList) pointObject;
1455  if (pointYailList.size() == 2) {
1456  try {
1457  points[index][0] = Float.parseFloat(pointYailList.getString(0));
1458  points[index][1] = Float.parseFloat(pointYailList.getString(1));
1459  index++;
1460  } catch (NullPointerException e) {
1461  throw new IllegalArgumentException(e.fillInStackTrace());
1462  } catch (NumberFormatException e) {
1463  throw new IllegalArgumentException(e.fillInStackTrace());
1464  }
1465  } else {
1466  throw new IllegalArgumentException("length of item YailList("+ index +") is not 2");
1467  }
1468  } else {
1469  throw new IllegalArgumentException("item("+ index +") in YailList is not a YailList");
1470  }
1471  }
1472  return points;
1473  }
1474 
1490  @SimpleFunction(description =
1491  "Draw an arc on Canvas, by drawing an arc from a specified oval (specified by left, top, right & bottom). " +
1492  "Start angle is 0 when heading to the right, and increase when rotate clockwise. " +
1493  "When useCenter is true, a sector will be drawed instead of an arc. " +
1494  "When fill is true, a filled arc (or sector) will be drawed instead of just an outline.")
1495  public void DrawArc(int left, int top, int right, int bottom,
1496  float startAngle, float sweepAngle, boolean useCenter, boolean fill) {
1497  float scalingFactor = $form().deviceDensity();
1498  Paint p = new Paint(paint);
1499  p.setStyle(fill ? Paint.Style.FILL : Paint.Style.STROKE);
1500  view.canvas.drawArc(
1501  new RectF(scalingFactor * left, scalingFactor * top,
1502  scalingFactor * right, scalingFactor * bottom),
1503  startAngle, sweepAngle, useCenter, p);
1504  view.invalidate();
1505  }
1506 
1516  @SimpleFunction(description = "Draws the specified text relative to the specified coordinates "
1517  + "using the values of the FontSize and TextAlignment properties.")
1518  public void DrawText(String text, int x, int y) {
1519  float fontScalingFactor = $form().deviceDensity();
1520  float correctedX = x * fontScalingFactor;
1521  float correctedY = y * fontScalingFactor;
1522  view.canvas.drawText(text, correctedX, correctedY, paint);
1523  view.invalidate();
1524  }
1525 
1536  @SimpleFunction(description = "Draws the specified text starting at the specified coordinates "
1537  + "at the specified angle using the values of the FontSize and TextAlignment properties.")
1538  public void DrawTextAtAngle(String text, int x, int y, float angle) {
1539  int correctedX = (int) (x * $form().deviceDensity());
1540  int correctedY = (int) (y * $form().deviceDensity());
1541  view.drawTextAtAngle(text, correctedX, correctedY, angle);
1542  }
1543 
1552  @SimpleFunction(description = "Gets the color of the specified point. "
1553  + "This includes the background and any drawn points, lines, or "
1554  + "circles but not sprites.")
1555  @IsColor
1556  public int GetBackgroundPixelColor(int x, int y) {
1557  int correctedX = (int) (x * $form().deviceDensity());
1558  int correctedY = (int) (y * $form().deviceDensity());
1559  return view.getBackgroundPixelColor(correctedX, correctedY);
1560  }
1561 
1570  @SimpleFunction(description = "Sets the color of the specified point. "
1571  + "This differs from DrawPoint by having an argument for color.")
1572  public void SetBackgroundPixelColor(int x, int y, @IsColor int color) {
1573  Paint pixelPaint = new Paint();
1574  PaintUtil.changePaint(pixelPaint, color);
1575  int correctedX = (int) (x * $form().deviceDensity());
1576  int correctedY = (int) (y * $form().deviceDensity());
1577  view.canvas.drawPoint(correctedX, correctedY, pixelPaint);
1578  view.invalidate();
1579  }
1580 
1589  @SimpleFunction(description = "Gets the color of the specified point.")
1590  @IsColor
1591  public int GetPixelColor(int x, int y) {
1592  int correctedX = (int) (x * $form().deviceDensity());
1593  int correctedY = (int) (y * $form().deviceDensity());
1594  return view.getPixelColor(correctedX, correctedY);
1595  }
1596 
1604  @UsesPermissions({Manifest.permission.WRITE_EXTERNAL_STORAGE})
1605  @SimpleFunction(description = "Saves a picture of this Canvas to the " +
1606  "device's external storage. If an error occurs, the Screen's ErrorOccurred " +
1607  "event will be called.")
1608  public String Save() {
1609  try {
1610  File file = FileUtil.getPictureFile($form(), "png");
1611  return saveFile(file, Bitmap.CompressFormat.PNG, "Save");
1612  } catch (PermissionException e) {
1613  container.$form().dispatchPermissionDeniedEvent(this, "Save", e);
1614  } catch (IOException e) {
1615  container.$form().dispatchErrorOccurredEvent(this, "Save",
1616  ErrorMessages.ERROR_MEDIA_FILE_ERROR, e.getMessage());
1617  } catch (FileUtil.FileException e) {
1618  container.$form().dispatchErrorOccurredEvent(this, "Save",
1619  e.getErrorMessageNumber());
1620  }
1621  return "";
1622  }
1623 
1632  @UsesPermissions({Manifest.permission.WRITE_EXTERNAL_STORAGE})
1633  @SimpleFunction(description = "Saves a picture of this Canvas to the device's " +
1634  "external storage in the file " +
1635  "named fileName. fileName must end with one of .jpg, .jpeg, or .png, " +
1636  "which determines the file type.")
1637  public String SaveAs(String fileName) {
1638  // Figure out desired file format
1639  Bitmap.CompressFormat format;
1640  if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
1641  format = Bitmap.CompressFormat.JPEG;
1642  } else if (fileName.endsWith(".png")) {
1643  format = Bitmap.CompressFormat.PNG;
1644  } else if (!fileName.contains(".")) { // make PNG the default to match Save behavior
1645  fileName = fileName + ".png";
1646  format = Bitmap.CompressFormat.PNG;
1647  } else {
1648  container.$form().dispatchErrorOccurredEvent(this, "SaveAs",
1650  return "";
1651  }
1652  try {
1653  File file = FileUtil.getExternalFile($form(), fileName);
1654  return saveFile(file, format, "SaveAs");
1655  } catch (PermissionException e) {
1656  container.$form().dispatchPermissionDeniedEvent(this, "SaveAs", e);
1657  } catch (IOException e) {
1658  container.$form().dispatchErrorOccurredEvent(this, "SaveAs",
1659  ErrorMessages.ERROR_MEDIA_FILE_ERROR, e.getMessage());
1660  } catch (FileUtil.FileException e) {
1661  container.$form().dispatchErrorOccurredEvent(this, "SaveAs",
1662  e.getErrorMessageNumber());
1663  }
1664  return "";
1665  }
1666 
1667  // Helper method for Save and SaveAs
1668  private String saveFile(File file, Bitmap.CompressFormat format, String method) {
1669  try {
1670  boolean success = false;
1671  FileOutputStream fos = new FileOutputStream(file);
1672  // Don't cache, in order to save memory. It seems unlikely to be used again soon.
1673  Bitmap bitmap = (view.completeCache == null ? view.buildCache() : view.completeCache);
1674  try {
1675  success = bitmap.compress(format,
1676  100, // quality: ignored for png
1677  fos);
1678  } finally {
1679  fos.close();
1680  }
1681  if (success) {
1682  return file.getAbsolutePath();
1683  } else {
1684  container.$form().dispatchErrorOccurredEvent(this, method,
1685  ErrorMessages.ERROR_CANVAS_BITMAP_ERROR);
1686  }
1687  } catch (FileNotFoundException e) {
1688  container.$form().dispatchErrorOccurredEvent(this, method,
1689  ErrorMessages.ERROR_MEDIA_CANNOT_OPEN, file.getAbsolutePath());
1690  } catch (IOException e) {
1691  container.$form().dispatchErrorOccurredEvent(this, method,
1692  ErrorMessages.ERROR_MEDIA_FILE_ERROR, e.getMessage());
1693  }
1694  return "";
1695  }
1696 
1697  class FlingGestureListener extends GestureDetector.SimpleOnGestureListener {
1698  @Override
1699  public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
1700  float velocityY) {
1701  float x = Math.max(0, (int)(e1.getX() / $form().deviceDensity())); // set to zero if negative
1702  float y = Math.max(0, (int)(e1.getY() / $form().deviceDensity())); // set to zero if negative
1703 
1704  // Normalize the velocity: Change from pixels/sec to pixels/ms
1705  float vx = velocityX / FLING_INTERVAL;
1706  float vy = velocityY / FLING_INTERVAL;
1707 
1708  float speed = (float) Math.sqrt(vx * vx + vy * vy);
1709  float heading = (float) -Math.toDegrees(Math.atan2(vy, vx));
1710 
1711  int width = Width();
1712  int height = Height();
1713 
1714  // Also make sure that by adding or subtracting a half finger that
1715  // we don't go out of bounds.
1716  BoundingBox rect = new BoundingBox(
1717  Math.max(0, (int) x - MotionEventParser.HALF_FINGER_HEIGHT),
1718  Math.max(0, (int) y - MotionEventParser.HALF_FINGER_WIDTH),
1719  Math.min(width - 1, (int) x + MotionEventParser.HALF_FINGER_WIDTH),
1720  Math.min(height - 1, (int) y + MotionEventParser.HALF_FINGER_HEIGHT));
1721 
1722  boolean spriteHandledFling = false;
1723 
1724  for (Sprite sprite : sprites) {
1725  if (sprite.Enabled() && sprite.Visible() &&
1726  sprite.intersectsWith(rect)) {
1727  sprite.Flung(x, y, speed, heading, vx, vy);
1728  spriteHandledFling = true;
1729  }
1730  }
1731  Flung(x, y, speed, heading, vx, vy, spriteHandledFling);
1732  return true;
1733  }
1734  }
1735 }
1736 
com.google.appinventor.components.runtime.EventDispatcher
Definition: EventDispatcher.java:22
com.google.appinventor.components.runtime.Form.askPermission
void askPermission(final String permission, final PermissionResultHandler responseRequestor)
Definition: Form.java:2633
com.google.appinventor.components.runtime.Canvas.$add
void $add(AndroidViewComponent component)
Definition: Canvas.java:882
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_TEXTALIGNMENT
static final String PROPERTY_TYPE_TEXTALIGNMENT
Definition: PropertyTypeConstants.java:260
com.google.appinventor.components.runtime.util.YailList
Definition: YailList.java:26
com.google.appinventor.components.runtime.Canvas.DrawPoint
void DrawPoint(int x, int y)
Definition: Canvas.java:1355
com.google.appinventor.components.runtime.Canvas.TouchUp
void TouchUp(float x, float y)
Definition: Canvas.java:1284
com.google.appinventor.components.runtime.Component.COLOR_DEFAULT
static final int COLOR_DEFAULT
Definition: Component.java:68
com.google.appinventor.components.annotations.SimpleFunction
Definition: SimpleFunction.java:23
com.google.appinventor.components.runtime.Sprite.CollidingWith
boolean CollidingWith(Sprite other)
Definition: Sprite.java:657
com.google.appinventor.components.runtime.util.ErrorMessages
Definition: ErrorMessages.java:17
com.google.appinventor.components.runtime.util.PaintUtil.changePaint
static void changePaint(Paint paint, int argb)
Definition: PaintUtil.java:27
com.google.appinventor.components.runtime.util
-*- mode: java; c-basic-offset: 2; -*-
Definition: AccountChooser.java:7
com.google.appinventor.components.runtime.Canvas.Canvas
Canvas(ComponentContainer container)
Definition: Canvas.java:750
com.google.appinventor.components.runtime.Canvas.Flung
void Flung(float x, float y, float speed, float heading, float xvel, float yvel, boolean flungSprite)
Definition: Canvas.java:1307
com.google.appinventor.components.runtime.util.PaintUtil
Definition: PaintUtil.java:17
com.google.appinventor.components.runtime.util.FileUtil
Definition: FileUtil.java:37
com.google.appinventor.components.common.YaVersion
Definition: YaVersion.java:14
com.google.appinventor.components.runtime.util.BoundingBox
Definition: BoundingBox.java:13
com.google.appinventor.components.annotations.DesignerProperty
Definition: DesignerProperty.java:25
com.google.appinventor.components.runtime.Sprite.colliding
static boolean colliding(Sprite sprite1, Sprite sprite2)
Definition: Sprite.java:922
com.google.appinventor.components.runtime.Canvas.$form
Form $form()
Definition: Canvas.java:877
com.google.appinventor.components
com.google.appinventor.components.runtime.Component.DEFAULT_VALUE_COLOR_WHITE
static final String DEFAULT_VALUE_COLOR_WHITE
Definition: Component.java:82
com.google.appinventor.components.runtime.Sprite.Z
void Z(double layer)
Definition: Sprite.java:382
com.google.appinventor.components.runtime.Sprite.Flung
void Flung(float x, float y, float speed, float heading, float xvel, float yvel)
Definition: Sprite.java:553
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN
static final String PROPERTY_TYPE_BOOLEAN
Definition: PropertyTypeConstants.java:35
com.google.appinventor.components.runtime.Sprite
Definition: Sprite.java:37
com.google.appinventor.components.runtime.Canvas.getView
View getView()
Definition: Canvas.java:792
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_MEDIA_IMAGE_FILE_FORMAT
static final int ERROR_MEDIA_IMAGE_FILE_FORMAT
Definition: ErrorMessages.java:98
com.google.appinventor.components.runtime.util.MediaUtil
Definition: MediaUtil.java:53
com.google.appinventor.components.annotations.DesignerComponent
Definition: DesignerComponent.java:22
com.google.appinventor.components.annotations.SimpleEvent
Definition: SimpleEvent.java:20
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_CANVAS_WIDTH_ERROR
static final int ERROR_CANVAS_WIDTH_ERROR
Definition: ErrorMessages.java:123
com.google.appinventor.components.runtime.Canvas.BackgroundColor
int BackgroundColor()
Definition: Canvas.java:1021
com.google.appinventor.components.annotations.PropertyCategory.BEHAVIOR
BEHAVIOR
Definition: PropertyCategory.java:15
com.google.appinventor.components.runtime.Canvas.setChildWidth
void setChildWidth(AndroidViewComponent component, int width)
Definition: Canvas.java:887
com.google.appinventor.components.runtime.util.FileUtil.getPictureFile
static File getPictureFile(String extension)
Definition: FileUtil.java:375
com.google.appinventor.components.runtime.Canvas.removeCustomGestureDetector
void removeCustomGestureDetector(Object detector)
Definition: Canvas.java:806
com.google.appinventor.components.runtime.File
Definition: File.java:53
com.google.appinventor.components.runtime.Canvas.getContext
Activity getContext()
Definition: Canvas.java:796
com.google.appinventor.components.runtime.collect
Definition: Lists.java:7
com.google.appinventor.components.runtime.ComponentContainer.$add
void $add(AndroidViewComponent component)
com.google.appinventor.components.runtime.collect.Sets.newHashSet
static< K > HashSet< K > newHashSet()
Definition: Sets.java:36
com.google.appinventor.components.runtime.Sprite.CollidedWith
void CollidedWith(Sprite other)
Definition: Sprite.java:435
com.google.appinventor.components.runtime.Canvas.$context
Activity $context()
Definition: Canvas.java:872
com.google.appinventor.components.annotations.UsesPermissions
Definition: UsesPermissions.java:21
com.google.appinventor.components.runtime.Canvas.GetPixelColor
int GetPixelColor(int x, int y)
Definition: Canvas.java:1591
com.google.appinventor.components.runtime.Canvas.BackgroundImage
void BackgroundImage(String path)
Definition: Canvas.java:1065
com.google.appinventor.components.runtime.Canvas.BackgroundColor
void BackgroundColor(int argb)
Definition: Canvas.java:1037
com.google.appinventor.components.runtime.Canvas.LineWidth
void LineWidth(float width)
Definition: Canvas.java:1171
com.google.appinventor.components.runtime.EventDispatcher.dispatchEvent
static boolean dispatchEvent(Component component, String eventName, Object...args)
Definition: EventDispatcher.java:188
com.google.appinventor.components.runtime.Canvas.PaintColor
int PaintColor()
Definition: Canvas.java:1101
com.google.appinventor.components.runtime.util.SdkLevel
Definition: SdkLevel.java:19
com.google.appinventor.components.runtime.Canvas.TouchDown
void TouchDown(float x, float y)
Definition: Canvas.java:1271
com.google.appinventor.components.runtime.Canvas.Touched
void Touched(float x, float y, boolean touchedAnySprite)
Definition: Canvas.java:1258
com.google.appinventor.components.annotations.SimpleProperty
Definition: SimpleProperty.java:23
com.google.appinventor.components.runtime.util.BulkPermissionRequest
Definition: BulkPermissionRequest.java:22
com.google.appinventor.components.runtime.Form.doesAppDeclarePermission
boolean doesAppDeclarePermission(String permissionName)
Definition: Form.java:2730
com.google.appinventor.components.annotations.PropertyCategory
Definition: PropertyCategory.java:13
com.google.appinventor.components.runtime.Canvas.DrawLine
void DrawLine(int x1, int y1, int x2, int y2)
Definition: Canvas.java:1391
com.google.appinventor.components.runtime.Canvas.Dragged
void Dragged(float startX, float startY, float prevX, float prevY, float currentX, float currentY, boolean draggedAnySprite)
Definition: Canvas.java:1330
com.google.appinventor.components.runtime.Canvas.Initialize
void Initialize()
Definition: Canvas.java:773
com.google.appinventor.components.runtime.Component.COLOR_BLACK
static final int COLOR_BLACK
Definition: Component.java:55
com.google.appinventor.components.runtime.errors.PermissionException
Definition: PermissionException.java:16
com.google.appinventor.components.runtime.Canvas.registerCustomGestureDetector
void registerCustomGestureDetector(ExtensionGestureDetector detector)
Definition: Canvas.java:801
com.google.appinventor.components.runtime.ComponentContainer
Definition: ComponentContainer.java:16
com.google.appinventor.components.runtime.util.SdkLevel.getLevel
static int getLevel()
Definition: SdkLevel.java:45
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.Component
Definition: Component.java:17
com.google.appinventor.components.runtime.Sprite.intersectsWith
boolean intersectsWith(BoundingBox rect)
Definition: Sprite.java:950
com.google.appinventor.components.runtime.Canvas.ExtensionGestureDetector
Definition: Canvas.java:181
com.google.appinventor.components.runtime.Component.ALIGNMENT_NORMAL
static final int ALIGNMENT_NORMAL
Definition: Component.java:32
com.google.appinventor.components.runtime.Sprite.Visible
boolean Visible()
Definition: Sprite.java:287
com.google.appinventor.components.common
Definition: ComponentCategory.java:7
com.google.appinventor.components.runtime.Canvas.findSpriteCollisions
void findSpriteCollisions(Sprite movedSprite)
Definition: Canvas.java:927
com.google.appinventor.components.runtime.Form.$form
Form $form()
Definition: Form.java:2110
com.google.appinventor.components.common.ComponentCategory
Definition: ComponentCategory.java:48
com.google.appinventor.components.runtime.util.SdkLevel.LEVEL_FROYO
static final int LEVEL_FROYO
Definition: SdkLevel.java:25
com.google.appinventor.components.runtime.Sprite.NoLongerCollidingWith
void NoLongerCollidingWith(Sprite other)
Definition: Sprite.java:513
com.google.appinventor.components.runtime.Canvas.PaintColor
void PaintColor(int argb)
Definition: Canvas.java:1116
com.google.appinventor.components.runtime.Canvas
Definition: Canvas.java:132
com.google.appinventor.components.annotations.SimpleObject
Definition: SimpleObject.java:23
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_COLOR
static final String PROPERTY_TYPE_COLOR
Definition: PropertyTypeConstants.java:63
com.google.appinventor.components.runtime.Component.COLOR_WHITE
static final int COLOR_WHITE
Definition: Component.java:66
com.google
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_NON_NEGATIVE_FLOAT
static final String PROPERTY_TYPE_NON_NEGATIVE_FLOAT
Definition: PropertyTypeConstants.java:200
com
com.google.appinventor.components.runtime.Canvas.ready
boolean ready()
Definition: Canvas.java:820
com.google.appinventor.components.runtime.util.FileUtil.FileException
Definition: FileUtil.java:578
com.google.appinventor.components.runtime.errors
Definition: ArrayIndexOutOfBoundsError.java:7
com.google.appinventor.components.runtime.Canvas.Height
void Height(int height)
Definition: Canvas.java:997
com.google.appinventor.components.runtime.ComponentContainer.$context
Activity $context()
com.google.appinventor.components.common.ComponentConstants
Definition: ComponentConstants.java:13
com.google.appinventor.components.runtime.Canvas.FontSize
void FontSize(float size)
Definition: Canvas.java:1147
com.google.appinventor.components.runtime.AndroidViewComponent
Definition: AndroidViewComponent.java:27
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_CANVAS_DRAW_SHAPE_BAD_ARGUMENT
static final int ERROR_CANVAS_DRAW_SHAPE_BAD_ARGUMENT
Definition: ErrorMessages.java:125
com.google.appinventor.components.annotations.IsColor
Definition: IsColor.java:13
com.google.appinventor.components.runtime.Form
Definition: Form.java:126
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_MEDIA_FILE_ERROR
static final int ERROR_MEDIA_FILE_ERROR
Definition: ErrorMessages.java:100
com.google.appinventor.components.runtime.Canvas.setChildHeight
void setChildHeight(AndroidViewComponent component, int height)
Definition: Canvas.java:892
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_ASSET
static final String PROPERTY_TYPE_ASSET
Definition: PropertyTypeConstants.java:22
com.google.appinventor.components.runtime.Sprite.Enabled
boolean Enabled()
Definition: Sprite.java:166
com.google.appinventor.components.annotations.PropertyCategory.APPEARANCE
APPEARANCE
Definition: PropertyCategory.java:16
com.google.appinventor.components.runtime.Canvas.Width
void Width(int width)
Definition: Canvas.java:973
com.google.appinventor.components.common.PropertyTypeConstants
Definition: PropertyTypeConstants.java:14
com.google.appinventor.components.runtime.Component.FONT_DEFAULT_SIZE
static final float FONT_DEFAULT_SIZE
Definition: Component.java:89
com.google.appinventor.components.runtime.util.FileUtil.getExternalFile
static File getExternalFile(String fileName)
Definition: FileUtil.java:519
com.google.appinventor.components.annotations
com.google.appinventor.components.runtime.Component.DEFAULT_VALUE_COLOR_BLACK
static final String DEFAULT_VALUE_COLOR_BLACK
Definition: Component.java:71
com.google.appinventor.components.runtime.Component.ALIGNMENT_OPPOSITE
static final int ALIGNMENT_OPPOSITE
Definition: Component.java:34
com.google.appinventor.components.runtime.Canvas.DrawCircle
void DrawCircle(int centerX, int centerY, float radius, boolean fill)
Definition: Canvas.java:1372
com.google.appinventor.components.runtime.Canvas.GetBackgroundPixelColor
int GetBackgroundPixelColor(int x, int y)
Definition: Canvas.java:1556
com.google.appinventor
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_CANVAS_HEIGHT_ERROR
static final int ERROR_CANVAS_HEIGHT_ERROR
Definition: ErrorMessages.java:124
com.google.appinventor.components.runtime.Component.ALIGNMENT_CENTER
static final int ALIGNMENT_CENTER
Definition: Component.java:33
com.google.appinventor.components.runtime.collect.Sets
Definition: Sets.java:23