CarouselController.java revision fb179e7afd8f02be63061b478b0283e3085fc25f
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 com.android.ex.carousel;
18
19import com.android.ex.carousel.CarouselRS.CarouselCallback;
20import com.android.ex.carousel.CarouselView.DetailAlignment;
21
22import android.content.res.Resources;
23import android.graphics.Bitmap;
24import android.renderscript.FileA3D;
25import android.renderscript.Float4;
26import android.renderscript.Mesh;
27import android.renderscript.RenderScriptGL;
28import android.util.Log;
29import android.view.SurfaceHolder;
30
31/**
32 * <p>
33 * This class represents the basic building block for using a 3D Carousel. The Carousel is
34 * basically a scene of cards and slots.  The spacing between cards is dictated by the number
35 * of slots and the radius. The number of visible cards dictates how far the Carousel can be moved.
36 * If the number of cards exceeds the number of slots, then the Carousel will continue to go
37 * around until the last card can be seen.
38 */
39public class CarouselController {
40    private final int DEFAULT_SLOT_COUNT = 10;
41    private final float DEFAULT_RADIUS = 20.0f;
42    private final int DEFAULT_VISIBLE_DETAIL_COUNT = 3;
43    private final int DEFAULT_PREFETCH_CARD_COUNT = 2;
44    private final float DEFAULT_SWAY_SENSITIVITY = 0.0f;
45    private final float DEFAULT_FRICTION_COEFFICIENT = 10.0f;
46    private final float DEFAULT_DRAG_FACTOR = 0.25f;
47    private final int DEFAULT_DETAIL_ALIGNMENT =
48            DetailAlignment.VIEW_TOP | DetailAlignment.LEFT;
49    private CarouselRS mRenderScript;
50    private RenderScriptGL mRS;
51    private static final String TAG = "CarouselController";
52    private static final boolean DBG = false;
53    private boolean mTracking;
54
55    // These shadow the state of the renderer in case the surface changes so the surface
56    // can be restored to its previous state.
57    private Bitmap mDefaultBitmap;
58    private Bitmap mLoadingBitmap;
59    private Bitmap mBackgroundBitmap;
60    private Bitmap mDefaultLineBitmap = Bitmap.createBitmap(
61            new int[] {0x00000000, 0xffffffff, 0x00000000}, 0, 3, 3, 1, Bitmap.Config.ARGB_4444);
62    private Mesh mDefaultGeometry;
63    private Mesh mLoadingGeometry;
64    private int mCardCount = 0;
65    private int mVisibleSlots = 0;
66    private int mVisibleDetails = DEFAULT_VISIBLE_DETAIL_COUNT;
67    private int mPrefetchCardCount = DEFAULT_PREFETCH_CARD_COUNT;
68    private int mDetailTextureAlignment = DEFAULT_DETAIL_ALIGNMENT;
69    private boolean mForceBlendCardsWithZ = false;
70    private boolean mDrawRuler = true;
71    private float mStartAngle;
72    private float mCarouselRotationAngle;
73    private float mRadius = DEFAULT_RADIUS;
74    private float mCardRotation = 0.0f;
75    private boolean mCardsFaceTangent = false;
76    private float mSwaySensitivity = DEFAULT_SWAY_SENSITIVITY;
77    private float mFrictionCoefficient = DEFAULT_FRICTION_COEFFICIENT;
78    private float mDragFactor = DEFAULT_DRAG_FACTOR;
79    private int mSlotCount = DEFAULT_SLOT_COUNT;
80    private float mEye[] = { 20.6829f, 2.77081f, 16.7314f };
81    private float mAt[] = { 14.7255f, -3.40001f, -1.30184f };
82    private float mUp[] = { 0.0f, 1.0f, 0.0f };
83    private Float4 mBackgroundColor = new Float4(0.0f, 0.0f, 0.0f, 1.0f);
84    private CarouselCallback mCarouselCallback;
85    private float mRezInCardCount = 0.0f;
86    private long mFadeInDuration = 250L;
87    private Bitmap mDetailLoadingBitmap = Bitmap.createBitmap(
88            new int[] {0}, 0, 1, 1, 1, Bitmap.Config.ARGB_4444);
89
90    public CarouselController() {
91        boolean useDepthBuffer = true;
92    }
93
94    public void setRS(RenderScriptGL rs, CarouselRS renderScript) {
95        mRS = rs;
96        mRenderScript = renderScript;
97    }
98
99    public void onSurfaceChanged() {
100        setSlotCount(mSlotCount);
101        createCards(mCardCount);
102        setVisibleSlots(mVisibleSlots);
103        setVisibleDetails(mVisibleDetails);
104        setPrefetchCardCount(mPrefetchCardCount);
105        setDetailTextureAlignment(mDetailTextureAlignment);
106        setForceBlendCardsWithZ(mForceBlendCardsWithZ);
107        setDrawRuler(mDrawRuler);
108        setCallback(mCarouselCallback);
109        setDefaultBitmap(mDefaultBitmap);
110        setLoadingBitmap(mLoadingBitmap);
111        setDefaultGeometry(mDefaultGeometry);
112        setLoadingGeometry(mLoadingGeometry);
113        setBackgroundColor(mBackgroundColor.x, mBackgroundColor.y, mBackgroundColor.z,
114                mBackgroundColor.w);
115        setBackgroundBitmap(mBackgroundBitmap);
116        setDetailLineBitmap(mDefaultLineBitmap);
117        setStartAngle(mStartAngle);
118        setCarouselRotationAngle(mCarouselRotationAngle);
119        setRadius(mRadius);
120        setCardRotation(mCardRotation);
121        setCardsFaceTangent(mCardsFaceTangent);
122        setSwaySensitivity(mSwaySensitivity);
123        setFrictionCoefficient(mFrictionCoefficient);
124        setDragFactor(mDragFactor);
125        setLookAt(mEye, mAt, mUp);
126        setRezInCardCount(mRezInCardCount);
127        setFadeInDuration(mFadeInDuration);
128        setDetailLoadingBitmap(mDetailLoadingBitmap);
129    }
130
131    /**
132     * Loads geometry from a resource id.
133     *
134     * @param resId
135     * @return the loaded mesh or null if it cannot be loaded
136     */
137    public Mesh loadGeometry(Resources res, int resId) {
138        FileA3D model = FileA3D.createFromResource(mRS, res, resId);
139        FileA3D.IndexEntry entry = model.getIndexEntry(0);
140        if(entry == null || entry.getClassID() != FileA3D.ClassID.MESH) {
141            return null;
142        }
143        return (Mesh) entry.getObject();
144    }
145
146    /**
147     * Load A3D file from resource.  If resId == 0, will clear geometry for this item.
148     * @param n
149     * @param resId
150     */
151    public void setGeometryForItem(int n, Mesh mesh) {
152        if (mRenderScript != null) {
153            mRenderScript.setGeometry(n, mesh);
154        }
155    }
156
157    /**
158     * Set the number of slots around the Carousel. Basically equivalent to the poles horses
159     * might attach to on a real Carousel.
160     *
161     * @param n the number of slots
162     */
163    public void setSlotCount(int n) {
164        mSlotCount = n;
165        if (mRenderScript != null) {
166            mRenderScript.setSlotCount(n);
167        }
168    }
169
170    /**
171     * Sets the number of visible slots around the Carousel.  This is primarily used as a cheap
172     * form of clipping. The Carousel will never show more than this many cards.
173     * @param n the number of visible slots
174     */
175    public void setVisibleSlots(int n) {
176        mVisibleSlots = n;
177        if (mRenderScript != null) {
178            mRenderScript.setVisibleSlots(n);
179        }
180    }
181
182    /**
183     * Set the number of detail textures that can be visible at one time.
184     *
185     * @param n the number of slots
186     */
187    public void setVisibleDetails(int n) {
188        mVisibleDetails = n;
189        if (mRenderScript != null) {
190            mRenderScript.setVisibleDetails(n);
191        }
192    }
193
194    /**
195     * Set the number of cards to pre-load that are outside of the visible region, as determined by
196     * setVisibleSlots(). This number gets added to the number of visible slots and used to
197     * determine when resources for cards should be loaded. This number should be small (n <= 4)
198     * for systems with limited texture memory or views that show more than half dozen cards in the
199     * view.
200     *
201     * @param n the number of cards; should be even, so the count is the same on each side
202     */
203    public void setPrefetchCardCount(int n) {
204        mPrefetchCardCount = n;
205        if (mRenderScript != null) {
206            mRenderScript.setPrefetchCardCount(n);
207        }
208    }
209
210    /**
211     * Sets how detail textures are aligned with respect to the card.
212     *
213     * @param alignment a bitmask of DetailAlignment flags.
214     */
215    public void setDetailTextureAlignment(int alignment) {
216        int xBits = alignment & DetailAlignment.HORIZONTAL_ALIGNMENT_MASK;
217        if (xBits == 0 || ((xBits & (xBits - 1)) != 0)) {
218            throw new IllegalArgumentException(
219                    "Must specify exactly one horizontal alignment flag");
220        }
221        int yBits = alignment & DetailAlignment.VERTICAL_ALIGNMENT_MASK;
222        if (yBits == 0 || ((yBits & (yBits - 1)) != 0)) {
223            throw new IllegalArgumentException(
224                    "Must specify exactly one vertical alignment flag");
225        }
226
227        mDetailTextureAlignment = alignment;
228        if (mRenderScript != null) {
229            mRenderScript.setDetailTextureAlignment(alignment);
230        }
231    }
232
233    /**
234     * Set whether depth is enabled while blending. Generally, this is discouraged because
235     * it causes bad artifacts. Careful attention to geometry and alpha transparency of
236     * textures can mitigate much of this. Geometry for an individual item must be drawn
237     * back-to-front, for example.
238     *
239     * @param enabled True to enable depth while blending, and false to disable it.
240     */
241    public void setForceBlendCardsWithZ(boolean enabled) {
242        mForceBlendCardsWithZ = enabled;
243        if (mRenderScript != null) {
244            mRenderScript.setForceBlendCardsWithZ(enabled);
245        }
246    }
247
248    /**
249     * Set whether to draw a ruler from the card to the detail texture
250     *
251     * @param drawRuler True to draw a ruler, false to draw nothing where the ruler would go.
252     */
253    public void setDrawRuler(boolean drawRuler) {
254        mDrawRuler = drawRuler;
255        if (mRenderScript != null) {
256            mRenderScript.setDrawRuler(drawRuler);
257        }
258    }
259
260    /**
261     * This dictates how many cards are in the deck.  If the number of cards is greater than the
262     * number of slots, then the Carousel goes around n / slot_count times.
263     *
264     * Can be called again to increase or decrease the number of cards.
265     *
266     * @param n the number of cards to create.
267     */
268    public void createCards(int n) {
269        mCardCount = n;
270        if (mRenderScript != null) {
271            mRenderScript.createCards(n);
272        }
273    }
274
275    public int getCardCount() {
276        return mCardCount;
277    }
278
279    /**
280     * This sets the texture on card n.  It should only be called in response to
281     * {@link CarouselCallback#onRequestTexture(int)}.  Since there's no guarantee
282     * that a given texture is still on the screen, replacing this texture should be done
283     * by first setting it to null and then waiting for the next
284     * {@link CarouselCallback#onRequestTexture(int)} to swap it with the new one.
285     *
286     * @param n the card given by {@link CarouselCallback#onRequestTexture(int)}
287     * @param bitmap the bitmap image to show
288     */
289    public void setTextureForItem(int n, Bitmap bitmap) {
290        // Also check against mRS, to handle the case where the result is being delivered by a
291        // background thread but the sender no longer exists.
292        if (mRenderScript != null && mRS != null) {
293            if (DBG) Log.v(TAG, "setTextureForItem(" + n + ")");
294            mRenderScript.setTexture(n, bitmap);
295            if (DBG) Log.v(TAG, "done");
296        }
297    }
298
299    /**
300     * This sets the detail texture that floats above card n. It should only be called in response
301     * to {@link CarouselCallback#onRequestDetailTexture(int)}.  Since there's no guarantee
302     * that a given texture is still on the screen, replacing this texture should be done
303     * by first setting it to null and then waiting for the next
304     * {@link CarouselCallback#onRequestDetailTexture(int)} to swap it with the new one.
305     *
306     * @param n the card to set detail texture for
307     * @param offx an optional offset to apply to the texture (in pixels) from top of detail line
308     * @param offy an optional offset to apply to the texture (in pixels) from top of detail line
309     * @param loffx an optional offset to apply to the line (in pixels) from left edge of card
310     * @param loffy an optional offset to apply to the line (in pixels) from top of screen
311     * @param bitmap the bitmap to show as the detail
312     */
313    public void setDetailTextureForItem(int n, float offx, float offy, float loffx, float loffy,
314            Bitmap bitmap) {
315        if (mRenderScript != null && mRS != null) {
316            if (DBG) Log.v(TAG, "setDetailTextureForItem(" + n + ")");
317            mRenderScript.setDetailTexture(n, offx, offy, loffx, loffy, bitmap);
318            if (DBG) Log.v(TAG, "done");
319        }
320    }
321
322    /**
323     * Sets the bitmap to show on a card when the card draws the very first time.
324     * Generally, this bitmap will only be seen during the first few frames of startup
325     * or when the number of cards are changed.  It can be ignored in most cases,
326     * as the cards will generally only be in the loading or loaded state.
327     *
328     * @param bitmap
329     */
330    public void setDefaultBitmap(Bitmap bitmap) {
331        mDefaultBitmap = bitmap;
332        if (mRenderScript != null) {
333            mRenderScript.setDefaultBitmap(bitmap);
334        }
335    }
336
337    /**
338     * Sets the bitmap to show on the card while the texture is loading. It is set to this
339     * value just before {@link CarouselCallback#onRequestTexture(int)} is called and changed
340     * when {@link CarouselView#setTextureForItem(int, Bitmap)} is called. It is shared by all
341     * cards.
342     *
343     * @param bitmap
344     */
345    public void setLoadingBitmap(Bitmap bitmap) {
346        mLoadingBitmap = bitmap;
347        if (mRenderScript != null) {
348            mRenderScript.setLoadingBitmap(bitmap);
349        }
350    }
351
352    /**
353     * Sets background to specified color.  If a background texture is specified with
354     * {@link CarouselView#setBackgroundBitmap(Bitmap)}, then this call has no effect.
355     *
356     * @param red the amount of red
357     * @param green the amount of green
358     * @param blue the amount of blue
359     * @param alpha the amount of alpha
360     */
361    public void setBackgroundColor(float red, float green, float blue, float alpha) {
362        mBackgroundColor = new Float4(red, green, blue, alpha);
363        if (mRenderScript != null) {
364            mRenderScript.setBackgroundColor(mBackgroundColor);
365        }
366    }
367
368    /**
369     * Can be used to optionally set the background to a bitmap. When set to something other than
370     * null, this overrides {@link CarouselController#setBackgroundColor(Float4)}.
371     *
372     * @param bitmap
373     */
374    public void setBackgroundBitmap(Bitmap bitmap) {
375        mBackgroundBitmap = bitmap;
376        if (mRenderScript != null) {
377            mRenderScript.setBackgroundTexture(bitmap);
378        }
379    }
380
381    /**
382     * Can be used to optionally set a "loading" detail bitmap. Typically, this is just a black
383     * texture with alpha = 0 to allow details to slowly fade in.
384     *
385     * @param bitmap
386     */
387    public void setDetailLoadingBitmap(Bitmap bitmap) {
388        mDetailLoadingBitmap = bitmap;
389        if (mRenderScript != null) {
390            mRenderScript.setDetailLoadingTexture(bitmap);
391        }
392    }
393
394    /**
395     * This texture is used to draw a line from the card alongside the texture detail. The line
396     * will be as wide as the texture. It can be used to give the line glow effects as well as
397     * allowing other blending effects. It is typically one dimensional, e.g. 3x1.
398     *
399     * @param bitmap
400     */
401    public void setDetailLineBitmap(Bitmap bitmap) {
402        mDefaultLineBitmap = bitmap;
403        if (mRenderScript != null) {
404            mRenderScript.setDetailLineTexture(bitmap);
405        }
406    }
407
408    /**
409     * This geometry will be shown when no geometry has been loaded for a given slot. If not set,
410     * a quad will be drawn in its place. It is shared for all cards. If something other than
411     * simple planar geometry is used, consider enabling depth test with
412     * {@link CarouselController#setForceBlendCardsWithZ(boolean)}
413     *
414     * @param mesh
415     */
416    public void setDefaultGeometry(Mesh mesh) {
417        mDefaultGeometry = mesh;
418        if (mRenderScript != null) {
419            mRenderScript.setDefaultGeometry(mesh);
420        }
421    }
422
423    /**
424     * This is an intermediate version of the object to show while geometry is loading. If not set,
425     * a quad will be drawn in its place.  It is shared for all cards. If something other than
426     * simple planar geometry is used, consider enabling depth test with
427     * {@link CarouselView#setForceBlendCardsWithZ(boolean)}
428     *
429     * @param mesh
430     */
431    public void setLoadingGeometry(Mesh mesh) {
432        mLoadingGeometry = mesh;
433        if (mRenderScript != null) {
434            mRenderScript.setLoadingGeometry(mesh);
435        }
436    }
437
438    /**
439     * Sets the callback for receiving events from RenderScript.
440     *
441     * @param callback
442     */
443    public void setCallback(CarouselCallback callback)
444    {
445        mCarouselCallback = callback;
446        if (mRenderScript != null) {
447            mRenderScript.setCallback(callback);
448        }
449    }
450
451    /**
452     * Gets the callback for receiving events from Renderscript.
453     */
454    public CarouselCallback getCallback() {
455        return mCarouselCallback;
456    }
457
458    /**
459     * Sets the startAngle for the Carousel. The start angle is the first position of the first
460     * slot draw.  Cards will be drawn from this angle in a counter-clockwise manner around the
461     * Carousel.
462     *
463     * @param angle the angle, in radians.
464     */
465    public void setStartAngle(float angle)
466    {
467        mStartAngle = angle;
468        if (mRenderScript != null) {
469            mRenderScript.setStartAngle(angle);
470        }
471    }
472
473    /**
474     * Set the current carousel rotation angle, in card units.
475     * This is measured in card positions, not in radians or degrees.
476     *
477     * A value of 0.0 means that card 0 is in the home position.
478     * A value of 1.0 means that card 1 is in the home position, and so on.
479     * The maximum value will be somewhat less than the total number of cards.
480     *
481     * @param angle
482     */
483    public void setCarouselRotationAngle(float angle) {
484        mCarouselRotationAngle = angle;
485        if (mRenderScript != null) {
486            mRenderScript.setCarouselRotationAngle(angle);
487        }
488    }
489
490    public void setRadius(float radius) {
491        mRadius = radius;
492        if (mRenderScript != null) {
493            mRenderScript.setRadius(radius);
494        }
495    }
496
497    public void setCardRotation(float cardRotation) {
498        mCardRotation = cardRotation;
499        if (mRenderScript != null) {
500            mRenderScript.setCardRotation(cardRotation);
501        }
502    }
503
504    public void setCardsFaceTangent(boolean faceTangent) {
505        mCardsFaceTangent = faceTangent;
506        if (mRenderScript != null) {
507            mRenderScript.setCardsFaceTangent(faceTangent);
508        }
509    }
510
511    public void setSwaySensitivity(float swaySensitivity) {
512        mSwaySensitivity = swaySensitivity;
513        if (mRenderScript != null) {
514            mRenderScript.setSwaySensitivity(swaySensitivity);
515        }
516    }
517
518    public void setFrictionCoefficient(float frictionCoefficient) {
519        mFrictionCoefficient = frictionCoefficient;
520        if (mRenderScript != null) {
521            mRenderScript.setFrictionCoefficient(frictionCoefficient);
522        }
523    }
524
525    public void setDragFactor(float dragFactor) {
526        mDragFactor = dragFactor;
527        if (mRenderScript != null) {
528            mRenderScript.setDragFactor(dragFactor);
529        }
530    }
531
532    public void setLookAt(float[] eye, float[] at, float[] up) {
533        mEye = eye;
534        mAt = at;
535        mUp = up;
536        if (mRenderScript != null) {
537            mRenderScript.setLookAt(eye, at, up);
538        }
539    }
540
541    /**
542     * This sets the number of cards in the distance that will be shown "rezzing in".
543     * These alpha values will be faded in from the background to the foreground over
544     * 'n' cards.  A floating point value is used to allow subtly changing the rezzing in
545     * position.
546     *
547     * @param n the number of cards to rez in.
548     */
549    public void setRezInCardCount(float n) {
550        mRezInCardCount = n;
551        if (mRenderScript != null) {
552            mRenderScript.setRezInCardCount(n);
553        }
554    }
555
556    /**
557     * This sets the duration (in ms) that a card takes to fade in when loaded via a call
558     * to {@link CarouselView#setTextureForItem(int, Bitmap)}. The timer starts the
559     * moment {@link CarouselView#setTextureForItem(int, Bitmap)} is called and continues
560     * until all of the cards have faded in.  Note: using large values will extend the
561     * animation until all cards have faded in.
562     *
563     * @param t
564     */
565    public void setFadeInDuration(long t) {
566        mFadeInDuration = t;
567        if (mRenderScript != null) {
568            mRenderScript.setFadeInDuration(t);
569        }
570    }
571
572    /**
573     * Tells the carousel that a touch event has started at the designated location.
574     * @param x The number of pixels from the left edge that the event occurred
575     * @param y The number of pixels from the top edge that the event occurred
576     */
577    public void onTouchStarted(float x, float y) {
578        mRenderScript.doStart(x, y);
579    }
580
581    /**
582     * Tells the carousel that a touch event has moved to the designated location.
583     * @param x The number of pixels from the left edge that the event occurred
584     * @param y The number of pixels from the top edge that the event occurred
585     */
586    public void onTouchMoved(float x, float y) {
587        mRenderScript.doMotion(x, y);
588    }
589
590    /**
591     * Tells the carousel that the user has long-pressed.
592     */
593    public void onLongPress() {
594        mRenderScript.doLongPress();
595    }
596
597    /**
598     * Tells the carousel that a touch event has stopped at the designated location.
599     * @param x The number of pixels from the left edge that the event occurred
600     * @param y The number of pixels from the top edge that the event occurred
601     */
602    public void onTouchStopped(float x, float y) {
603        mRenderScript.doStop(x, y);
604    }
605}
606