// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.components.runtime; import java.io.IOException; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.util.Log; import com.google.appinventor.components.annotations.DesignerComponent; import com.google.appinventor.components.annotations.DesignerProperty; import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.annotations.UsesPermissions; import com.google.appinventor.components.common.ComponentCategory; import com.google.appinventor.components.common.PropertyTypeConstants; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.runtime.util.MediaUtil; /** * Simple image-based Sprite. * */ @DesignerComponent(version = YaVersion.IMAGESPRITE_COMPONENT_VERSION, description = "<p>A 'sprite' that can be placed on a " + "<code>Canvas</code>, where it can react to touches and drags, " + "interact with other sprites (<code>Ball</code>s and other " + "<code>ImageSprite</code>s) and the edge of the Canvas, and move " + "according to its property values. Its appearance is that of the " + "image specified in its <code>Picture</code> property (unless its " + "<code>Visible</code> property is <code>False</code>.</p> " + "<p>To have an <code>ImageSprite</code> move 10 pixels to the left " + "every 1000 milliseconds (one second), for example, " + "you would set the <code>Speed</code> property to 10 [pixels], the " + "<code>Interval</code> property to 1000 [milliseconds], the " + "<code>Heading</code> property to 180 [degrees], and the " + "<code>Enabled</code> property to <code>True</code>. A sprite whose " + "<code>Rotates</code> property is <code>True</code> will rotate its " + "image as the sprite's <code>Heading</code> changes. Checking for collisions " + "with a rotated sprite currently checks the sprite's unrotated position " + "so that collision checking will be inaccurate for tall narrow or short " + "wide sprites that are rotated. Any of the sprite properties " + "can be changed at any time under program control.</p> ", category = ComponentCategory.ANIMATION) @SimpleObject @UsesPermissions(permissionNames = "android.permission.INTERNET") public class ImageSprite extends Sprite { private final Form form; private BitmapDrawable drawable; private int widthHint = LENGTH_PREFERRED; private int heightHint = LENGTH_PREFERRED; private String picturePath = ""; // Picture property private boolean rotates; /** * Constructor for ImageSprite. * * @param container */ public ImageSprite(ComponentContainer container) { super(container); form = container.$form(); rotates = true; } /** * This method uses getWidth and getHeight directly from the bitmap, * so we apply corrections for density for coordinates and size. * @param canvas the canvas on which to draw */ public void onDraw(android.graphics.Canvas canvas) { if (drawable != null && visible) { int xinit = (int) (Math.round(xLeft) * form.deviceDensity()); int yinit = (int) (Math.round(yTop) * form.deviceDensity()); int w = (int)(Width() * form.deviceDensity()); int h = (int)(Height() * form.deviceDensity()); drawable.setBounds(xinit, yinit, xinit + w, yinit + h); // If the sprite doesn't rotate, just draw the drawable // within the bounds of the sprite rectangle if (!rotates) { drawable.draw(canvas); } else { // if the sprite does rotate, draw the sprite on the canvas // that has been rotated in the opposite direction // Still within those same image bounds. canvas.save(); // rotate the canvas for drawing. This pivot point of the // rotation will be the center of the sprite canvas.rotate((float) (- Heading()), xinit + w/2, yinit + h/2); drawable.draw(canvas); canvas.restore(); } } } /** * Returns the path of the sprite's picture * * @return the path of the sprite's picture */ @SimpleProperty( description = "The picture that determines the sprite's appearence", category = PropertyCategory.APPEARANCE) public String Picture() { return picturePath; } /** * Specifies the path of the sprite's picture * * <p/>See {@link MediaUtil#determineMediaSource} for information about what * a path can be. * * @param path the path of the sprite's picture */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_ASSET, defaultValue = "") @SimpleProperty public void Picture(String path) { picturePath = (path == null) ? "" : path; try { drawable = MediaUtil.getBitmapDrawable(form, picturePath); } catch (IOException ioe) { Log.e("ImageSprite", "Unable to load " + picturePath); drawable = null; } // note: drawable can be null! registerChange(); } // The actual width/height of an ImageSprite whose Width/Height property is set to Automatic or // Fill Parent will be the width/height of the image. @Override @SimpleProperty public int Height() { if (heightHint == LENGTH_PREFERRED || heightHint == LENGTH_FILL_PARENT || heightHint <= LENGTH_PERCENT_TAG) { // Drawable.getIntrinsicWidth/Height gives weird values, but Bitmap.getWidth/Height works. return drawable == null ? 0 : (int)(drawable.getBitmap().getHeight() / form.deviceDensity()); } return heightHint; } @Override @SimpleProperty public void Height(int height) { heightHint = height; registerChange(); } @Override public void HeightPercent(int pCent) { // Ignore } @Override @SimpleProperty public int Width() { if (widthHint == LENGTH_PREFERRED || widthHint == LENGTH_FILL_PARENT || widthHint <= LENGTH_PERCENT_TAG) { // Drawable.getIntrinsicWidth/Height gives weird values, but Bitmap.getWidth/Height works. return drawable == null ? 0 : (int)(drawable.getBitmap().getWidth() / form.deviceDensity()); } return widthHint; } @Override @SimpleProperty public void Width(int width) { widthHint = width; registerChange(); } @Override public void WidthPercent(int pCent) { // Ignore } /** * Rotates property getter method. * * @return {@code true} indicates that the image rotates to match the sprite's heading * {@code false} indicates that the sprite image doesn't rotate. */ @SimpleProperty( description = "If true, the sprite image rotates to match the sprite's heading. " + "If false, the sprite image does not rotate when the sprite changes heading. " + "The sprite rotates around its centerpoint.", category = PropertyCategory.BEHAVIOR) public boolean Rotates() { return rotates; } /** * Rotates property setter method * * @param rotates {@code true} indicates that the image rotates to match the sprite's heading * {@code false} indicates that the sprite image doesn't rotate. */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "True") @SimpleProperty public void Rotates(boolean rotates) { this.rotates = rotates; registerChange(); } }