summaryrefslogtreecommitdiffstats
path: root/carousel/java/com/android/ex/carousel/CarouselView.java
blob: 25c736690108124930dcb2380170743d3bfaf6cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * 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.
 */

package com.android.ex.carousel;

import android.view.View;
import com.android.ex.carousel.CarouselRS.CarouselCallback;

import android.content.Context;
import android.graphics.Bitmap;
import android.renderscript.Float4;
import android.renderscript.Mesh;
import android.renderscript.RSSurfaceView;
import android.renderscript.RenderScriptGL;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

/**
 * <p>
 * This class represents the basic building block for using a 3D Carousel. The Carousel is
 * basically a scene of cards and slots.  The spacing between cards is dictated by the number
 * of slots and the radius. The number of visible cards dictates how far the Carousel can be moved.
 * If the number of cards exceeds the number of slots, then the Carousel will continue to go
 * around until the last card can be seen.
 */
public abstract class CarouselView extends RSSurfaceView {
    private static final boolean USE_DEPTH_BUFFER = true;
    private static final String TAG = "CarouselView";
    private CarouselRS mRenderScript;
    private RenderScriptGL mRS;
    private Context mContext;
    private boolean mTracking;

    CarouselController mController;

    // Drag relative to x coordinate of motion on screen
    public static final int DRAG_MODEL_SCREEN_DELTA = CarouselRS.DRAG_MODEL_SCREEN_DELTA;
    // Drag relative to projected point on plane of carousel
    public static final int DRAG_MODEL_PLANE = CarouselRS.DRAG_MODEL_PLANE;
    // Drag relative to projected point on inside (far point) of cylinder centered around carousel
    public static final int DRAG_MODEL_CYLINDER_INSIDE = CarouselRS.DRAG_MODEL_CYLINDER_INSIDE;
    // Drag relative to projected point on outside (near point) of cylinder centered around carousel
    public static final int DRAG_MODEL_CYLINDER_OUTSIDE = CarouselRS.DRAG_MODEL_CYLINDER_OUTSIDE;

    // Draw cards counterclockwise around the carousel
    public static final int FILL_DIRECTION_CCW = CarouselRS.FILL_DIRECTION_CCW;
    // Draw cards clockwise around the carousel
    public static final int FILL_DIRECTION_CW = CarouselRS.FILL_DIRECTION_CW;

    // Note: remember to update carousel.rs when changing the values below
    public static class InterpolationMode {
        /** y= x **/
        public static final int LINEAR = 0;
        /** The quadratic curve y= 1 - (1 - x)^2 moves quickly towards the target
         * while decelerating constantly. **/
        public static final int DECELERATE_QUADRATIC = 1;
        /** The cubic curve y= (3-2x)*x^2 gradually accelerates at the origin,
         * and decelerates near the target. **/
        public static final int ACCELERATE_DECELERATE_CUBIC = 2;
    }

    // Note: remember to update carousel.rs when changing the values below
    public static class DetailAlignment {
        /** Detail is centered vertically with respect to the card **/
        public static final int CENTER_VERTICAL = 1;
        /** Detail is aligned with the top edge of the carousel view **/
        public static final int VIEW_TOP = 1 << 1;
        /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/
        public static final int VIEW_BOTTOM = 1 << 2;
        /** Detail is positioned above the card (not yet implemented) **/
        public static final int ABOVE = 1 << 3;
        /** Detail is positioned below the card **/
        public static final int BELOW = 1 << 4;
        /** Mask that selects those bits that control vertical alignment **/
        public static final int VERTICAL_ALIGNMENT_MASK = 0xff;

        /**
         * Detail is centered horizontally with respect to either the top or bottom
         * extent of the card, depending on whether the detail is above or below the card.
         */
        public static final int CENTER_HORIZONTAL = 1 << 8;
        /**
         * Detail is aligned with the left edge of either the top or the bottom of
         * the card, depending on whether the detail is above or below the card.
         */
        public static final int LEFT = 1 << 9;
        /**
         * Detail is aligned with the right edge of either the top or the bottom of
         * the card, depending on whether the detail is above or below the card.
         * (not yet implemented)
         */
        public static final int RIGHT = 1 << 10;
        /** Mask that selects those bits that control horizontal alignment **/
        public static final int HORIZONTAL_ALIGNMENT_MASK = 0xff00;
    }

    public static class Info {
        public Info(int _resId) { resId = _resId; }
        public int resId; // resource for renderscript resource (e.g. R.raw.carousel)
    }

    public abstract Info getRenderScriptInfo();

    public CarouselView(Context context) {
        this(context, new CarouselController());
    }

    public CarouselView(Context context, CarouselController controller) {
        this(context, null, controller);
    }

    /**
     * Constructor used when this widget is created from a layout file.
     */
    public CarouselView(Context context, AttributeSet attrs) {
        this(context, attrs, new CarouselController());
    }

    public CarouselView(Context context, AttributeSet attrs, CarouselController controller) {
        super(context, attrs);
        mContext = context;
        mController = controller;
        boolean useDepthBuffer = true;
        ensureRenderScript();
        // TODO: add parameters to layout

        setOnLongClickListener(new View.OnLongClickListener() {
            public boolean onLongClick(View v) {
                if (interpretLongPressEvents()) {
                    mController.onLongPress();
                    return true;
                } else {
                    return false;
                }
            }
        });
    }

    private void ensureRenderScript() {
        if (mRS == null) {
            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
            if (USE_DEPTH_BUFFER) {
                sc.setDepth(16, 24);
            }
            mRS = createRenderScriptGL(sc);
        }
        if (mRenderScript == null) {
            mRenderScript = new CarouselRS(mRS, mContext.getResources(),
                    getRenderScriptInfo().resId);
            mRenderScript.resumeRendering();
        }
        mController.setRS(mRS, mRenderScript);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        super.surfaceChanged(holder, format, w, h);
        // setZOrderOnTop(true);
        mController.onSurfaceChanged();
    }

    public CarouselController getController() {
        return mController;
    }

    public void setController(CarouselController controller) {
        mController = controller;
        mController.setRS(mRS, mRenderScript);
    }

    /**
     * Do I want to interpret the long-press gesture? If so, long-presses will cancel the
     * current selection and call the appropriate callbacks. Otherwise, a long press will
     * not be handled any way other than as a continued drag.
     *
     * @return True if we interpret long-presses
     */
    public boolean interpretLongPressEvents() {
        return false;
    }

    /**
     * Loads geometry from a resource id.
     *
     * @param resId
     * @return the loaded mesh or null if it cannot be loaded
     */
    public Mesh loadGeometry(int resId) {
        return mController.loadGeometry(resId);
    }

    /**
     * Set the geometry for a given item.
     * @param n
     * @param mesh
     */
    public void setGeometryForItem(int n, Mesh mesh) {
        mController.setGeometryForItem(n, mesh);
    }

    /**
     * Set the matrix for a given item.
     * @param n
     * @param matrix the requested matrix; null to just use the default
     */
    public void setMatrixForItem(int n, float[] matrix) {
        mController.setMatrixForItem(n, matrix);
    }

    /**
     * Set the number of slots around the Carousel. Basically equivalent to the poles horses
     * might attach to on a real Carousel.
     *
     * @param n the number of slots
     */
    public void setSlotCount(int n) {
        mController.setSlotCount(n);
    }

    /**
     * Sets the number of visible slots around the Carousel.  This is primarily used as a cheap
     * form of clipping. The Carousel will never show more than this many cards.
     * @param n the number of visible slots
     */
    public void setVisibleSlots(int n) {
        mController.setVisibleSlots(n);
    }

    /**
     * Set the number of cards to pre-load that are outside of the visible region, as determined by
     * setVisibleSlots(). This number gets added to the number of visible slots and used to
     * determine when resources for cards should be loaded. This number should be small (n <= 4)
     * for systems with limited texture memory or views that show more than half dozen cards in the
     * view.
     *
     * @param n the number of cards; should be even, so the count is the same on each side
     */
    public void setPrefetchCardCount(int n) {
        mController.setPrefetchCardCount(n);
    }

    /**
     * Sets the number of rows of cards to show in each slot.
     */
    public void setRowCount(int n) {
        mController.setRowCount(n);
    }

    /**
     * Sets the spacing between each row of cards when rowCount > 1.
     */
    public void setRowSpacing(float s) {
        mController.setRowSpacing(s);
    }

    /**
     * Sets the position of the first card when rowCount > 1.
     */
    public void setFirstCardTop(boolean f) {
        mController.setFirstCardTop(f);
    }

    /**
     * Sets the amount of allowed overscroll (in slots)
     */
    public void setOverscrollSlots(float slots) {
        mController.setOverscrollSlots(slots);
    }

    /**
     * Set the number of detail textures that can be visible at one time.
     *
     * @param n the number of slots
     */
    public void setVisibleDetails(int n) {
        mController.setVisibleDetails(n);
    }

    /**
     * Sets how detail textures are aligned with respect to the card.
     *
     * @param alignment a bitmask of DetailAlignment flags.
     */
    public void setDetailTextureAlignment(int alignment) {
        mController.setDetailTextureAlignment(alignment);
    }

    /**
     * Set whether depth is enabled while blending. Generally, this is discouraged because
     * it causes bad artifacts. Careful attention to geometry and alpha transparency of
     * textures can mitigate much of this. For example, geometry for an item must be drawn
     * back-to-front if any edges overlap.
     *
     * @param enabled True to enable depth while blending, and false to disable it.
     */
    public void setForceBlendCardsWithZ(boolean enabled) {
        mController.setForceBlendCardsWithZ(enabled);
    }

    /**
     * Set whether to draw a ruler from the card to the detail texture
     *
     * @param drawRuler True to draw a ruler, false to draw nothing where the ruler would go.
     */
    public void setDrawRuler(boolean drawRuler) {
        mController.setDrawRuler(drawRuler);
    }

    /**
     * This dictates how many cards are in the deck.  If the number of cards is greater than the
     * number of slots, then the Carousel goes around n / slot_count times.
     *
     * Can be called again to increase or decrease the number of cards.
     *
     * @param n the number of cards to create.
     */
    public void createCards(int n) {
        mController.createCards(n);
    }

    public int getCardCount() {
        return mController.getCardCount();
    }

    /**
     * This sets the texture on card n.  It should only be called in response to
     * {@link CarouselCallback#onRequestTexture(int)}.  Since there's no guarantee
     * that a given texture is still on the screen, replacing this texture should be done
     * by first setting it to null and then waiting for the next
     * {@link CarouselCallback#onRequestTexture(int)} to swap it with the new one.
     *
     * @param n the card given by {@link CarouselCallback#onRequestTexture(int)}
     * @param bitmap the bitmap image to show
     */
    public void setTextureForItem(int n, Bitmap bitmap) {
        mController.setTextureForItem(n, bitmap);
    }

    /**
     * This sets the detail texture that floats above card n. It should only be called in response
     * to {@link CarouselCallback#onRequestDetailTexture(int)}.  Since there's no guarantee
     * that a given texture is still on the screen, replacing this texture should be done
     * by first setting it to null and then waiting for the next
     * {@link CarouselCallback#onRequestDetailTexture(int)} to swap it with the new one.
     *
     * @param n the card to set detail texture for
     * @param offx an optional offset to apply to the texture (in pixels) from top of detail line
     * @param offy an optional offset to apply to the texture (in pixels) from top of detail line
     * @param loffx an optional offset to apply to the line (in pixels) from left edge of card
     * @param loffy an optional offset to apply to the line (in pixels) from top of screen
     * @param bitmap the bitmap to show as the detail
     */
    public void setDetailTextureForItem(int n, float offx, float offy, float loffx, float loffy,
            Bitmap bitmap) {
        mController.setDetailTextureForItem(n, offx, offy, loffx, loffy, bitmap);
    }

    /**
     * Sets the bitmap to show on a card when the card draws the very first time.
     * Generally, this bitmap will only be seen during the first few frames of startup
     * or when the number of cards are changed.  It can be ignored in most cases,
     * as the cards will generally only be in the loading or loaded state.
     *
     * @param bitmap
     */
    public void setDefaultBitmap(Bitmap bitmap) {
        mController.setDefaultBitmap(bitmap);
    }

    /**
     * Sets the bitmap to show on the card while the texture is loading. It is set to this
     * value just before {@link CarouselCallback#onRequestTexture(int)} is called and changed
     * when {@link CarouselView#setTextureForItem(int, Bitmap)} is called. It is shared by all
     * cards.
     *
     * @param bitmap
     */
    public void setLoadingBitmap(Bitmap bitmap) {
        mController.setLoadingBitmap(bitmap);
    }

    /**
     * Sets background to specified color.  If a background texture is specified with
     * {@link CarouselView#setBackgroundBitmap(Bitmap)}, then this call has no effect.
     *
     * @param red the amount of red
     * @param green the amount of green
     * @param blue the amount of blue
     * @param alpha the amount of alpha
     */
    public void setBackgroundColor(float red, float green, float blue, float alpha) {
        mController.setBackgroundColor(red, green, blue, alpha);
    }

    /**
     * Can be used to optionally set the background to a bitmap. When set to something other than
     * null, this overrides {@link CarouselView#setBackgroundColor(Float4)}.
     *
     * @param bitmap
     */
    public void setBackgroundBitmap(Bitmap bitmap) {
        mController.setBackgroundBitmap(bitmap);
    }

    /**
     * Can be used to optionally set a "loading" detail bitmap. Typically, this is just a black
     * texture with alpha = 0 to allow details to slowly fade in.
     *
     * @param bitmap
     */
    public void setDetailLoadingBitmap(Bitmap bitmap) {
        mController.setDetailLoadingBitmap(bitmap);
    }

    /**
     * This texture is used to draw a line from the card alongside the texture detail. The line
     * will be as wide as the texture. It can be used to give the line glow effects as well as
     * allowing other blending effects. It is typically one dimensional, e.g. 3x1.
     *
     * @param bitmap
     */
    public void setDetailLineBitmap(Bitmap bitmap) {
        mController.setDetailLineBitmap(bitmap);
    }

    /**
     * This geometry will be shown when no geometry has been loaded for a given slot. If not set,
     * a quad will be drawn in its place. It is shared for all cards. If something other than
     * simple planar geometry is used, consider enabling depth test with
     * {@link CarouselView#setForceBlendCardsWithZ(boolean)}
     *
     * @param resId
     */
    public void setDefaultGeometry(int resId) {
        mController.setDefaultGeometry(resId);
    }

    /**
     * Sets the matrix used to transform card geometries.  By default, this
     * is the identity matrix, but you can specify a different matrix if you
     * want to scale, translate and / or rotate the card before drawing.
     *
     * @param matrix array of 9 or 16 floats representing a 3x3 or 4x4 matrix,
     * or null as a shortcut for an identity matrix.
     */
    public void setDefaultCardMatrix(float[] matrix) {
        mController.setDefaultCardMatrix(matrix);
    }

    /**
     * This is an intermediate version of the object to show while geometry is loading. If not set,
     * a quad will be drawn in its place.  It is shared for all cards. If something other than
     * simple planar geometry is used, consider enabling depth test with
     * {@link CarouselView#setForceBlendCardsWithZ(boolean)}
     *
     * @param resId
     */
    public void setLoadingGeometry(int resId) {
        mController.setLoadingGeometry(resId);
    }

    /**
     * Sets the callback for receiving events from RenderScript.
     *
     * @param callback
     */
    public void setCallback(CarouselCallback callback)
    {
        mController.setCallback(callback);
    }

    /**
     * Sets the startAngle for the Carousel. The start angle is the first position of the first
     * slot draw.  Cards will be drawn from this angle in a counter-clockwise manner around the
     * Carousel.
     *
     * @param angle the angle, in radians.
     */
    public void setStartAngle(float angle)
    {
        mController.setStartAngle(angle);
    }

    public void setRadius(float radius) {
        mController.setRadius(radius);
    }

    public void setCardRotation(float cardRotation) {
        mController.setCardRotation(cardRotation);
    }

    public void setCardsFaceTangent(boolean faceTangent) {
        mController.setCardsFaceTangent(faceTangent);
    }

    public void setSwaySensitivity(float swaySensitivity) {
        mController.setSwaySensitivity(swaySensitivity);
    }

    public void setFrictionCoefficient(float frictionCoefficient) {
        mController.setFrictionCoefficient(frictionCoefficient);
    }

    public void setDragFactor(float dragFactor) {
        mController.setDragFactor(dragFactor);
    }

    public void setDragModel(int model) {
        mController.setDragModel(model);
    }

    public void setLookAt(float[] eye, float[] at, float[] up) {
        mController.setLookAt(eye, at, up);
    }

    /**
     * This sets the number of cards in the distance that will be shown "rezzing in".
     * These alpha values will be faded in from the background to the foreground over
     * 'n' cards.  A floating point value is used to allow subtly changing the rezzing in
     * position.
     *
     * @param n the number of cards to rez in.
     */
    public void setRezInCardCount(float n) {
        mController.setRezInCardCount(n);
    }

    /**
     * This sets the duration (in ms) that a card takes to fade in when loaded via a call
     * to {@link CarouselView#setTextureForItem(int, Bitmap)}. The timer starts the
     * moment {@link CarouselView#setTextureForItem(int, Bitmap)} is called and continues
     * until all of the cards have faded in.  Note: using large values will extend the
     * animation until all cards have faded in.
     *
     * @param t
     */
    public void setFadeInDuration(long t) {
        mController.setFadeInDuration(t);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mRenderScript = null;
        if (mRS != null) {
            mRS = null;
            destroyRenderScriptGL();
        }
        mController.setRS(mRS, mRenderScript);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        ensureRenderScript();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        final int action = event.getAction();

        if (mRenderScript == null) {
            return true;
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mTracking = true;
                mController.onTouchStarted(event.getX(), event.getY(), event.getEventTime());
                break;

            case MotionEvent.ACTION_MOVE:
                if (mTracking) {
                    for (int i = 0; i < event.getHistorySize(); i++) {
                        mController.onTouchMoved(event.getHistoricalX(i), event.getHistoricalY(i),
                                event.getHistoricalEventTime(i));
                    }
                    mController.onTouchMoved(event.getX(), event.getY(), event.getEventTime());
                }
                break;

            case MotionEvent.ACTION_UP:
                mController.onTouchStopped(event.getX(), event.getY(), event.getEventTime());
                mTracking = false;
                break;
        }

        return true;
    }
}