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 android.view.View;
20import com.android.ex.carousel.CarouselRS.CarouselCallback;
21
22import android.content.Context;
23import android.graphics.Bitmap;
24import android.renderscript.Float4;
25import android.renderscript.Mesh;
26import android.renderscript.RSSurfaceView;
27import android.renderscript.RenderScriptGL;
28import android.util.AttributeSet;
29import android.view.MotionEvent;
30import android.view.SurfaceHolder;
31
32/**
33 * <p>
34 * This class represents the basic building block for using a 3D Carousel. The Carousel is
35 * basically a scene of cards and slots.  The spacing between cards is dictated by the number
36 * of slots and the radius. The number of visible cards dictates how far the Carousel can be moved.
37 * If the number of cards exceeds the number of slots, then the Carousel will continue to go
38 * around until the last card can be seen.
39 */
40public abstract class CarouselView extends RSSurfaceView {
41    private static final boolean USE_DEPTH_BUFFER = true;
42    private static final String TAG = "CarouselView";
43    private CarouselRS mRenderScript;
44    private RenderScriptGL mRS;
45    private Context mContext;
46    private boolean mTracking;
47
48    CarouselController mController;
49
50    // Drag relative to x coordinate of motion on screen
51    public static final int DRAG_MODEL_SCREEN_DELTA = CarouselRS.DRAG_MODEL_SCREEN_DELTA;
52    // Drag relative to projected point on plane of carousel
53    public static final int DRAG_MODEL_PLANE = CarouselRS.DRAG_MODEL_PLANE;
54    // Drag relative to projected point on inside (far point) of cylinder centered around carousel
55    public static final int DRAG_MODEL_CYLINDER_INSIDE = CarouselRS.DRAG_MODEL_CYLINDER_INSIDE;
56    // Drag relative to projected point on outside (near point) of cylinder centered around carousel
57    public static final int DRAG_MODEL_CYLINDER_OUTSIDE = CarouselRS.DRAG_MODEL_CYLINDER_OUTSIDE;
58
59    // Draw cards counterclockwise around the carousel
60    public static final int FILL_DIRECTION_CCW = CarouselRS.FILL_DIRECTION_CCW;
61    // Draw cards clockwise around the carousel
62    public static final int FILL_DIRECTION_CW = CarouselRS.FILL_DIRECTION_CW;
63
64    // Note: remember to update carousel.rs when changing the values below
65    public static class InterpolationMode {
66        /** y= x **/
67        public static final int LINEAR = 0;
68        /** The quadratic curve y= 1 - (1 - x)^2 moves quickly towards the target
69         * while decelerating constantly. **/
70        public static final int DECELERATE_QUADRATIC = 1;
71        /** The cubic curve y= (3-2x)*x^2 gradually accelerates at the origin,
72         * and decelerates near the target. **/
73        public static final int ACCELERATE_DECELERATE_CUBIC = 2;
74    }
75
76    // Note: remember to update carousel.rs when changing the values below
77    public static class DetailAlignment {
78        /** Detail is centered vertically with respect to the card **/
79        public static final int CENTER_VERTICAL = 1;
80        /** Detail is aligned with the top edge of the carousel view **/
81        public static final int VIEW_TOP = 1 << 1;
82        /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/
83        public static final int VIEW_BOTTOM = 1 << 2;
84        /** Detail is positioned above the card (not yet implemented) **/
85        public static final int ABOVE = 1 << 3;
86        /** Detail is positioned below the card **/
87        public static final int BELOW = 1 << 4;
88        /** Mask that selects those bits that control vertical alignment **/
89        public static final int VERTICAL_ALIGNMENT_MASK = 0xff;
90
91        /**
92         * Detail is centered horizontally with respect to either the top or bottom
93         * extent of the card, depending on whether the detail is above or below the card.
94         */
95        public static final int CENTER_HORIZONTAL = 1 << 8;
96        /**
97         * Detail is aligned with the left edge of either the top or the bottom of
98         * the card, depending on whether the detail is above or below the card.
99         */
100        public static final int LEFT = 1 << 9;
101        /**
102         * Detail is aligned with the right edge of either the top or the bottom of
103         * the card, depending on whether the detail is above or below the card.
104         * (not yet implemented)
105         */
106        public static final int RIGHT = 1 << 10;
107        /** Mask that selects those bits that control horizontal alignment **/
108        public static final int HORIZONTAL_ALIGNMENT_MASK = 0xff00;
109    }
110
111    public static class Info {
112        public Info(int _resId) { resId = _resId; }
113        public int resId; // resource for renderscript resource (e.g. R.raw.carousel)
114    }
115
116    public abstract Info getRenderScriptInfo();
117
118    public CarouselView(Context context) {
119        this(context, new CarouselController());
120    }
121
122    public CarouselView(Context context, CarouselController controller) {
123        this(context, null, controller);
124    }
125
126    /**
127     * Constructor used when this widget is created from a layout file.
128     */
129    public CarouselView(Context context, AttributeSet attrs) {
130        this(context, attrs, new CarouselController());
131    }
132
133    public CarouselView(Context context, AttributeSet attrs, CarouselController controller) {
134        super(context, attrs);
135        mContext = context;
136        mController = controller;
137        boolean useDepthBuffer = true;
138        ensureRenderScript();
139        // TODO: add parameters to layout
140
141        setOnLongClickListener(new View.OnLongClickListener() {
142            public boolean onLongClick(View v) {
143                if (interpretLongPressEvents()) {
144                    mController.onLongPress();
145                    return true;
146                } else {
147                    return false;
148                }
149            }
150        });
151    }
152
153    private void ensureRenderScript() {
154        if (mRS == null) {
155            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
156            if (USE_DEPTH_BUFFER) {
157                sc.setDepth(16, 24);
158            }
159            mRS = createRenderScriptGL(sc);
160        }
161        if (mRenderScript == null) {
162            mRenderScript = new CarouselRS(mRS, mContext.getResources(),
163                    getRenderScriptInfo().resId);
164            mRenderScript.resumeRendering();
165        }
166        mController.setRS(mRS, mRenderScript);
167    }
168
169    @Override
170    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
171        super.surfaceChanged(holder, format, w, h);
172        // setZOrderOnTop(true);
173        mController.onSurfaceChanged();
174    }
175
176    public CarouselController getController() {
177        return mController;
178    }
179
180    public void setController(CarouselController controller) {
181        mController = controller;
182        mController.setRS(mRS, mRenderScript);
183    }
184
185    /**
186     * Do I want to interpret the long-press gesture? If so, long-presses will cancel the
187     * current selection and call the appropriate callbacks. Otherwise, a long press will
188     * not be handled any way other than as a continued drag.
189     *
190     * @return True if we interpret long-presses
191     */
192    public boolean interpretLongPressEvents() {
193        return false;
194    }
195
196    /**
197     * Loads geometry from a resource id.
198     *
199     * @param resId
200     * @return the loaded mesh or null if it cannot be loaded
201     */
202    public Mesh loadGeometry(int resId) {
203        return mController.loadGeometry(resId);
204    }
205
206    /**
207     * Set the geometry for a given item.
208     * @param n
209     * @param mesh
210     */
211    public void setGeometryForItem(int n, Mesh mesh) {
212        mController.setGeometryForItem(n, mesh);
213    }
214
215    /**
216     * Set the matrix for a given item.
217     * @param n
218     * @param matrix the requested matrix; null to just use the default
219     */
220    public void setMatrixForItem(int n, float[] matrix) {
221        mController.setMatrixForItem(n, matrix);
222    }
223
224    /**
225     * Set the number of slots around the Carousel. Basically equivalent to the poles horses
226     * might attach to on a real Carousel.
227     *
228     * @param n the number of slots
229     */
230    public void setSlotCount(int n) {
231        mController.setSlotCount(n);
232    }
233
234    /**
235     * Sets the number of visible slots around the Carousel.  This is primarily used as a cheap
236     * form of clipping. The Carousel will never show more than this many cards.
237     * @param n the number of visible slots
238     */
239    public void setVisibleSlots(int n) {
240        mController.setVisibleSlots(n);
241    }
242
243    /**
244     * Set the number of cards to pre-load that are outside of the visible region, as determined by
245     * setVisibleSlots(). This number gets added to the number of visible slots and used to
246     * determine when resources for cards should be loaded. This number should be small (n <= 4)
247     * for systems with limited texture memory or views that show more than half dozen cards in the
248     * view.
249     *
250     * @param n the number of cards; should be even, so the count is the same on each side
251     */
252    public void setPrefetchCardCount(int n) {
253        mController.setPrefetchCardCount(n);
254    }
255
256    /**
257     * Sets the number of rows of cards to show in each slot.
258     */
259    public void setRowCount(int n) {
260        mController.setRowCount(n);
261    }
262
263    /**
264     * Sets the spacing between each row of cards when rowCount > 1.
265     */
266    public void setRowSpacing(float s) {
267        mController.setRowSpacing(s);
268    }
269
270    /**
271     * Sets the position of the first card when rowCount > 1.
272     */
273    public void setFirstCardTop(boolean f) {
274        mController.setFirstCardTop(f);
275    }
276
277    /**
278     * Sets the amount of allowed overscroll (in slots)
279     */
280    public void setOverscrollSlots(float slots) {
281        mController.setOverscrollSlots(slots);
282    }
283
284    /**
285     * Set the number of detail textures that can be visible at one time.
286     *
287     * @param n the number of slots
288     */
289    public void setVisibleDetails(int n) {
290        mController.setVisibleDetails(n);
291    }
292
293    /**
294     * Sets how detail textures are aligned with respect to the card.
295     *
296     * @param alignment a bitmask of DetailAlignment flags.
297     */
298    public void setDetailTextureAlignment(int alignment) {
299        mController.setDetailTextureAlignment(alignment);
300    }
301
302    /**
303     * Set whether depth is enabled while blending. Generally, this is discouraged because
304     * it causes bad artifacts. Careful attention to geometry and alpha transparency of
305     * textures can mitigate much of this. For example, geometry for an item must be drawn
306     * back-to-front if any edges overlap.
307     *
308     * @param enabled True to enable depth while blending, and false to disable it.
309     */
310    public void setForceBlendCardsWithZ(boolean enabled) {
311        mController.setForceBlendCardsWithZ(enabled);
312    }
313
314    /**
315     * Set whether to draw a ruler from the card to the detail texture
316     *
317     * @param drawRuler True to draw a ruler, false to draw nothing where the ruler would go.
318     */
319    public void setDrawRuler(boolean drawRuler) {
320        mController.setDrawRuler(drawRuler);
321    }
322
323    /**
324     * This dictates how many cards are in the deck.  If the number of cards is greater than the
325     * number of slots, then the Carousel goes around n / slot_count times.
326     *
327     * Can be called again to increase or decrease the number of cards.
328     *
329     * @param n the number of cards to create.
330     */
331    public void createCards(int n) {
332        mController.createCards(n);
333    }
334
335    public int getCardCount() {
336        return mController.getCardCount();
337    }
338
339    /**
340     * This sets the texture on card n.  It should only be called in response to
341     * {@link CarouselCallback#onRequestTexture(int)}.  Since there's no guarantee
342     * that a given texture is still on the screen, replacing this texture should be done
343     * by first setting it to null and then waiting for the next
344     * {@link CarouselCallback#onRequestTexture(int)} to swap it with the new one.
345     *
346     * @param n the card given by {@link CarouselCallback#onRequestTexture(int)}
347     * @param bitmap the bitmap image to show
348     */
349    public void setTextureForItem(int n, Bitmap bitmap) {
350        mController.setTextureForItem(n, bitmap);
351    }
352
353    /**
354     * This sets the detail texture that floats above card n. It should only be called in response
355     * to {@link CarouselCallback#onRequestDetailTexture(int)}.  Since there's no guarantee
356     * that a given texture is still on the screen, replacing this texture should be done
357     * by first setting it to null and then waiting for the next
358     * {@link CarouselCallback#onRequestDetailTexture(int)} to swap it with the new one.
359     *
360     * @param n the card to set detail texture for
361     * @param offx an optional offset to apply to the texture (in pixels) from top of detail line
362     * @param offy an optional offset to apply to the texture (in pixels) from top of detail line
363     * @param loffx an optional offset to apply to the line (in pixels) from left edge of card
364     * @param loffy an optional offset to apply to the line (in pixels) from top of screen
365     * @param bitmap the bitmap to show as the detail
366     */
367    public void setDetailTextureForItem(int n, float offx, float offy, float loffx, float loffy,
368            Bitmap bitmap) {
369        mController.setDetailTextureForItem(n, offx, offy, loffx, loffy, bitmap);
370    }
371
372    /**
373     * Sets the bitmap to show on a card when the card draws the very first time.
374     * Generally, this bitmap will only be seen during the first few frames of startup
375     * or when the number of cards are changed.  It can be ignored in most cases,
376     * as the cards will generally only be in the loading or loaded state.
377     *
378     * @param bitmap
379     */
380    public void setDefaultBitmap(Bitmap bitmap) {
381        mController.setDefaultBitmap(bitmap);
382    }
383
384    /**
385     * Sets the bitmap to show on the card while the texture is loading. It is set to this
386     * value just before {@link CarouselCallback#onRequestTexture(int)} is called and changed
387     * when {@link CarouselView#setTextureForItem(int, Bitmap)} is called. It is shared by all
388     * cards.
389     *
390     * @param bitmap
391     */
392    public void setLoadingBitmap(Bitmap bitmap) {
393        mController.setLoadingBitmap(bitmap);
394    }
395
396    /**
397     * Sets background to specified color.  If a background texture is specified with
398     * {@link CarouselView#setBackgroundBitmap(Bitmap)}, then this call has no effect.
399     *
400     * @param red the amount of red
401     * @param green the amount of green
402     * @param blue the amount of blue
403     * @param alpha the amount of alpha
404     */
405    public void setBackgroundColor(float red, float green, float blue, float alpha) {
406        mController.setBackgroundColor(red, green, blue, alpha);
407    }
408
409    /**
410     * Can be used to optionally set the background to a bitmap. When set to something other than
411     * null, this overrides {@link CarouselView#setBackgroundColor(Float4)}.
412     *
413     * @param bitmap
414     */
415    public void setBackgroundBitmap(Bitmap bitmap) {
416        mController.setBackgroundBitmap(bitmap);
417    }
418
419    /**
420     * Can be used to optionally set a "loading" detail bitmap. Typically, this is just a black
421     * texture with alpha = 0 to allow details to slowly fade in.
422     *
423     * @param bitmap
424     */
425    public void setDetailLoadingBitmap(Bitmap bitmap) {
426        mController.setDetailLoadingBitmap(bitmap);
427    }
428
429    /**
430     * This texture is used to draw a line from the card alongside the texture detail. The line
431     * will be as wide as the texture. It can be used to give the line glow effects as well as
432     * allowing other blending effects. It is typically one dimensional, e.g. 3x1.
433     *
434     * @param bitmap
435     */
436    public void setDetailLineBitmap(Bitmap bitmap) {
437        mController.setDetailLineBitmap(bitmap);
438    }
439
440    /**
441     * This geometry will be shown when no geometry has been loaded for a given slot. If not set,
442     * a quad will be drawn in its place. It is shared for all cards. If something other than
443     * simple planar geometry is used, consider enabling depth test with
444     * {@link CarouselView#setForceBlendCardsWithZ(boolean)}
445     *
446     * @param resId
447     */
448    public void setDefaultGeometry(int resId) {
449        mController.setDefaultGeometry(resId);
450    }
451
452    /**
453     * Sets the matrix used to transform card geometries.  By default, this
454     * is the identity matrix, but you can specify a different matrix if you
455     * want to scale, translate and / or rotate the card before drawing.
456     *
457     * @param matrix array of 9 or 16 floats representing a 3x3 or 4x4 matrix,
458     * or null as a shortcut for an identity matrix.
459     */
460    public void setDefaultCardMatrix(float[] matrix) {
461        mController.setDefaultCardMatrix(matrix);
462    }
463
464    /**
465     * This is an intermediate version of the object to show while geometry is loading. If not set,
466     * a quad will be drawn in its place.  It is shared for all cards. If something other than
467     * simple planar geometry is used, consider enabling depth test with
468     * {@link CarouselView#setForceBlendCardsWithZ(boolean)}
469     *
470     * @param resId
471     */
472    public void setLoadingGeometry(int resId) {
473        mController.setLoadingGeometry(resId);
474    }
475
476    /**
477     * Sets the callback for receiving events from RenderScript.
478     *
479     * @param callback
480     */
481    public void setCallback(CarouselCallback callback)
482    {
483        mController.setCallback(callback);
484    }
485
486    /**
487     * Sets the startAngle for the Carousel. The start angle is the first position of the first
488     * slot draw.  Cards will be drawn from this angle in a counter-clockwise manner around the
489     * Carousel.
490     *
491     * @param angle the angle, in radians.
492     */
493    public void setStartAngle(float angle)
494    {
495        mController.setStartAngle(angle);
496    }
497
498    public void setRadius(float radius) {
499        mController.setRadius(radius);
500    }
501
502    public void setCardRotation(float cardRotation) {
503        mController.setCardRotation(cardRotation);
504    }
505
506    public void setCardsFaceTangent(boolean faceTangent) {
507        mController.setCardsFaceTangent(faceTangent);
508    }
509
510    public void setSwaySensitivity(float swaySensitivity) {
511        mController.setSwaySensitivity(swaySensitivity);
512    }
513
514    public void setFrictionCoefficient(float frictionCoefficient) {
515        mController.setFrictionCoefficient(frictionCoefficient);
516    }
517
518    public void setDragFactor(float dragFactor) {
519        mController.setDragFactor(dragFactor);
520    }
521
522    public void setDragModel(int model) {
523        mController.setDragModel(model);
524    }
525
526    public void setLookAt(float[] eye, float[] at, float[] up) {
527        mController.setLookAt(eye, at, up);
528    }
529
530    /**
531     * This sets the number of cards in the distance that will be shown "rezzing in".
532     * These alpha values will be faded in from the background to the foreground over
533     * 'n' cards.  A floating point value is used to allow subtly changing the rezzing in
534     * position.
535     *
536     * @param n the number of cards to rez in.
537     */
538    public void setRezInCardCount(float n) {
539        mController.setRezInCardCount(n);
540    }
541
542    /**
543     * This sets the duration (in ms) that a card takes to fade in when loaded via a call
544     * to {@link CarouselView#setTextureForItem(int, Bitmap)}. The timer starts the
545     * moment {@link CarouselView#setTextureForItem(int, Bitmap)} is called and continues
546     * until all of the cards have faded in.  Note: using large values will extend the
547     * animation until all cards have faded in.
548     *
549     * @param t
550     */
551    public void setFadeInDuration(long t) {
552        mController.setFadeInDuration(t);
553    }
554
555    @Override
556    protected void onDetachedFromWindow() {
557        super.onDetachedFromWindow();
558        mRenderScript = null;
559        if (mRS != null) {
560            mRS = null;
561            destroyRenderScriptGL();
562        }
563        mController.setRS(mRS, mRenderScript);
564    }
565
566    @Override
567    protected void onAttachedToWindow() {
568        super.onAttachedToWindow();
569        ensureRenderScript();
570    }
571
572    @Override
573    public boolean onTouchEvent(MotionEvent event) {
574        super.onTouchEvent(event);
575        final int action = event.getAction();
576
577        if (mRenderScript == null) {
578            return true;
579        }
580
581        switch (action) {
582            case MotionEvent.ACTION_DOWN:
583                mTracking = true;
584                mController.onTouchStarted(event.getX(), event.getY(), event.getEventTime());
585                break;
586
587            case MotionEvent.ACTION_MOVE:
588                if (mTracking) {
589                    for (int i = 0; i < event.getHistorySize(); i++) {
590                        mController.onTouchMoved(event.getHistoricalX(i), event.getHistoricalY(i),
591                                event.getHistoricalEventTime(i));
592                    }
593                    mController.onTouchMoved(event.getX(), event.getY(), event.getEventTime());
594                }
595                break;
596
597            case MotionEvent.ACTION_UP:
598                mController.onTouchStopped(event.getX(), event.getY(), event.getEventTime());
599                mTracking = false;
600                break;
601        }
602
603        return true;
604    }
605}
606