/******************************************************************************* * Copyright 2012-present Pixate, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ /** * Copyright (c) 2012-2013 Pixate, Inc. All rights reserved. */ package com.pixate.freestyle.cg.shapes; import java.util.ArrayList; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.RectF; /** * A PXShape sub-class used to render collections of shapes. */ public class PXShapeGroup extends PXShape { public static enum AlignViewPortType { kAlignViewPortNone("none"), XMIN_YMIN("xMinYMin"), XMIN_YMID("xMinYMid"), XMIN_YMAX("xMinYMax"), XMID_YMIN("xMidYMin"), XMID_YMID("xMidYMid"), XMID_YMAX("xMidYMax"), XMAX_YMIN("xMaxYMin"), XMAX_YMID("xMaxYMid"), XMAX_YMAX("xMaxYMax"); private String name; private AlignViewPortType(String name) { this.name = name; } @Override public String toString() { return name; } } public static enum CropType { MEET, SLICE } private ArrayList<PXRenderable> shapes; private float width; private float height; private RectF viewport; private AlignViewPortType viewportAlignment; private CropType viewportCrop; private Matrix viewPortTransform; /** * Constructs a renderable shapes group. */ public PXShapeGroup() { shapes = new ArrayList<PXRenderable>(5); viewport = new RectF(); viewportAlignment = AlignViewPortType.XMID_YMID; viewportCrop = CropType.MEET; } /** * Sets the viewport of this shape group. * * @param viewport */ public void setViewport(RectF viewport) { this.viewport = viewport; } /** * Returns the viewport of this shape group. * * @return The viewport rectangle. */ public RectF getViewport() { return viewport; } /** * Set the width of this shape group. * * @param width A new width * @deprecated */ public void setWidth(float width) { this.width = width; } /** * Returns the width of this shape group. * * @deprecated */ public float getWidth() { return width; } /** * Set the height of this shape group. * * @param height A new height * @deprecated */ public void setHeight(float height) { this.height = height; } /** * Returns the height of this shape group. * * @return the height * @deprecated */ public float getHeight() { return height; } /** * Returns how many child shapes this group contains. * * @return The group size. */ public int getShapeCount() { if (shapes != null) { return shapes.size(); } return 0; } /** * Adds a shape to this shape group. <code>null</code> values are ignored. * * @param shape The shape to add */ public void addShape(PXRenderable shape) { if (shape != null) { // add shape to child list shapes.add(shape); // set child's parent shape.setParent(this); } } /** * Removes the specified shape from the shape group. * * @param shape The shape to remove */ public void removeShape(PXRenderable shape) { if (shape != null) { shapes.remove(shape); // TODO: verify this is in this group shape.setParent(null); } } /** * Returns the shape at the specified index. <code>null</code> is returned * for index values that are out of range. * * @param index The index of the shape to return. * @returns A PXRenderable or <code>null</code> */ public PXRenderable getShapeAtIndex(int index) { return (shapes != null && index > -1 && index <= shapes.size() - 1) ? shapes.get(index) : null; } /** * Returns the alignment to use when mapping a shape group's viewport to the * screen. */ public AlignViewPortType getViewportAlignment() { return viewportAlignment; } /** * Sets the alignment to use when mapping a shape group's viewport to the * screen. */ public void setViewportAlignment(AlignViewPortType viewportAlignment) { this.viewportAlignment = viewportAlignment; } /** * Returns type of crop to use when applying the shape group's viewport to * the screen */ public CropType getViewportCrop() { return viewportCrop; } /** * Sets type of crop to use when applying the shape group's viewport to the * screen */ public void setViewportCrop(CropType viewportCrop) { this.viewportCrop = viewportCrop; } /** * Returns the transform that would need to be applied to this shape group * in order for its viewport to fit within the specified shape group width * and height. * * @return A {@link Matrix} transformation. */ public Matrix getViewPortTransform() { if (viewport != null) { float viewportWidth = viewport.width(); float viewportHeight = viewport.height(); if (viewportWidth > 0 && viewportHeight > 0 && width > 0 && height > 0) { float ratioX = width / viewportWidth; float ratioY = height / viewportHeight; // Create the Matrix transformation Matrix matrix = new Matrix(); matrix.preTranslate(viewport.left, viewport.top); if (viewportAlignment == AlignViewPortType.kAlignViewPortNone) { matrix.preScale(ratioX, ratioY); } else { if ((ratioX > ratioY && viewportCrop == CropType.MEET) || (ratioX < ratioY && viewportCrop == CropType.SLICE)) { float tx = 0; float diffX = width - viewportWidth * ratioY; switch (viewportAlignment) { case XMID_YMIN: case XMID_YMID: case XMID_YMAX: tx = diffX * 0.5F; break; case XMAX_YMIN: case XMAX_YMID: case XMAX_YMAX: tx = diffX; break; default: break; } matrix.preTranslate(tx, 0); matrix.preScale(ratioY, ratioY); } else if ((ratioX < ratioY && viewportCrop == CropType.MEET) || (ratioX > ratioY && viewportCrop == CropType.SLICE)) { float ty = 0; float diffY = height - viewportHeight * ratioX; switch (viewportAlignment) { case XMIN_YMID: case XMID_YMID: case XMAX_YMID: ty = diffY * 0.5F; break; case XMIN_YMAX: case XMID_YMAX: case XMAX_YMAX: ty = diffY; break; default: break; } matrix.preTranslate(0, ty); matrix.preScale(ratioX, ratioX); } else { matrix.preScale(ratioX, ratioX); } } viewPortTransform = matrix; } } return viewPortTransform; } /** * Render the group. * * @param context A {@link Canvas} context. */ public void renderChildren(Canvas context) { Matrix transform = getViewPortTransform(); if (transform != null) { context.concat(transform); } else { // TODO - Do we want to log this? // Log.w(TAG, "PXShapeGroup#getViewPortTransform() returned null"); } // Render the group for (PXRenderable renderable : shapes) { renderable.render(context, false); } } // TODO - We'll need to manually call this one to clear up the childern's // parent. I really don't want to put this in a finalize() method... public void dealloc() { if (shapes != null) { try { for (PXRenderable shape : shapes) { shape.setParent(null); } shapes = null; } catch (Throwable t) { // ignore } } } }