Allow carousel apps to receive taps on detail textures

Change-Id: I909edf4f05e268a00a3727fa684f3d83360e2225
diff --git a/carousel/java/com/android/ex/carousel/CarouselRS.java b/carousel/java/com/android/ex/carousel/CarouselRS.java
index d09f13c..a4e7ef3 100644
--- a/carousel/java/com/android/ex/carousel/CarouselRS.java
+++ b/carousel/java/com/android/ex/carousel/CarouselRS.java
@@ -37,6 +37,7 @@
 
     // Client messages *** THIS LIST MUST MATCH THOSE IN carousel.rs ***
     public static final int CMD_CARD_SELECTED = 100;
+    public static final int CMD_DETAIL_SELECTED = 105;
     public static final int CMD_CARD_LONGPRESS = 110;
     public static final int CMD_REQUEST_TEXTURE = 200;
     public static final int CMD_INVALIDATE_TEXTURE = 210;
@@ -104,6 +105,14 @@
         void onCardSelected(int n);
 
         /**
+         * Called when the detail texture for a card is tapped
+         * @param n the id of the card
+         * @param x how far the user tapped from the left edge of the card, in pixels
+         * @param y how far the user tapped from the top edge of the card, in pixels
+         */
+        void onDetailSelected(int n, int x, int y);
+
+        /**
          * Called when a card is long-pressed
          * @param n the id of the card
          */
@@ -157,7 +166,8 @@
 
         /**
          * Called when card animation has stopped.
-         * @param startAngle the angle of rotation, in radians, at which the animation stopped.
+         * @param carouselRotationAngle the angle of rotation, in radians, at which the animation
+         * stopped.
          */
         void onAnimationFinished(float carouselRotationAngle);
     };
@@ -170,6 +180,10 @@
                     mCallback.onCardSelected(mData[0]);
                     break;
 
+                case CMD_DETAIL_SELECTED:
+                    mCallback.onDetailSelected(mData[0], mData[1], mData[2]);
+                    break;
+
                 case CMD_CARD_LONGPRESS:
                     mCallback.onCardLongPress(mData[0]);
                     break;
@@ -614,10 +628,6 @@
         mScript.invoke_doMotion(x, y, t);
     }
 
-    public void doSelection(float x, float y) {
-        mScript.invoke_doSelection(x, y);
-    }
-
     public void doStart(float x, float y, long t) {
         mScript.invoke_doStart(x, y, t);
     }
diff --git a/carousel/java/com/android/ex/carousel/CarouselViewHelper.java b/carousel/java/com/android/ex/carousel/CarouselViewHelper.java
index 3a97880..4acf99a 100644
--- a/carousel/java/com/android/ex/carousel/CarouselViewHelper.java
+++ b/carousel/java/com/android/ex/carousel/CarouselViewHelper.java
@@ -252,6 +252,10 @@
         if (DBG) Log.v(TAG, "onCardSelected(" + n + ")");
     }
 
+    public void onDetailSelected(int n, int x, int y) {
+        if (DBG) Log.v(TAG, "onDetailSelected(" + n + ", " + x + ", " + y + ")");
+    }
+
     public void onCardLongPress(int n) {
         if (DBG) Log.v(TAG, "onCardLongPress(" + n + ")");
     }
diff --git a/carousel/java/com/android/ex/carousel/carousel.rs b/carousel/java/com/android/ex/carousel/carousel.rs
index 73245ea..99da954 100644
--- a/carousel/java/com/android/ex/carousel/carousel.rs
+++ b/carousel/java/com/android/ex/carousel/carousel.rs
@@ -26,12 +26,14 @@
     rs_allocation detailTexture; // screen-aligned detail texture
     float2 detailTextureOffset; // offset to add, in screen coordinates
     float2 detailLineOffset; // offset to add to detail line, in screen coordinates
+    float2 detailTexturePosition[2]; // screen coordinates of detail texture, computed at draw time
     rs_mesh geometry;
     rs_matrix4x4 matrix; // custom transform for this card/geometry
     int textureState;  // whether or not the primary card texture is loaded.
     int detailTextureState; // whether or not the detail for the card is loaded.
     int geometryState; // whether or not geometry is loaded
-    int visible; // not bool because of packing bug?
+    int cardVisible; // not bool because of packing bug?
+    int detailVisible; // not bool because of packing bug?
     int64_t textureTimeStamp; // time when this texture was last updated, in seconds
     int64_t detailTextureTimeStamp; // time when this texture was last updated, in seconds
 } Card_t;
@@ -111,6 +113,7 @@
 
 // Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
 static const int CMD_CARD_SELECTED = 100;
+static const int CMD_DETAIL_SELECTED = 105;
 static const int CMD_CARD_LONGPRESS = 110;
 static const int CMD_REQUEST_TEXTURE = 200;
 static const int CMD_INVALIDATE_TEXTURE = 210;
@@ -186,7 +189,7 @@
 rs_sampler linearClamp;
 
 #pragma rs export_func(createCards, copyCards, lookAt, setRadius)
-#pragma rs export_func(doStart, doStop, doMotion, doLongPress, doSelection)
+#pragma rs export_func(doStart, doStop, doMotion, doLongPress)
 #pragma rs export_func(setTexture, setGeometry, setDetailTexture, debugCamera)
 #pragma rs export_func(setCarouselRotationAngle)
 
@@ -243,6 +246,7 @@
 
 // Forward references
 static int intersectGeometry(Ray* ray, float *bestTime);
+static int intersectDetailTexture(float x, float y, float2 *tapCoordinates);
 static bool __attribute__((overloadable))
         makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y);
 static bool __attribute__((overloadable))
@@ -296,7 +300,8 @@
     card->textureState = STATE_INVALID;
     card->detailTextureState = STATE_INVALID;
     card->geometryState = STATE_INVALID;
-    card->visible = false;
+    card->cardVisible = false;
+    card->detailVisible = false;
     card->textureTimeStamp = 0;
     card->detailTextureTimeStamp = 0;
 }
@@ -527,7 +532,7 @@
     const float endAngle = startAngle + visibleSlotCount * wedgeAngle;
     bool stillAnimating = false;
     for (int i = cardCount-1; i >= 0; i--) {
-        if (cards[i].visible) {
+        if (cards[i].cardVisible) {
             // If this card was recently loaded, this will be < 1.0f until the animation completes
             float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime);
             if (animatedAlpha < 1.0f) {
@@ -639,7 +644,7 @@
     const float endDetailFadeAngle = startDetailFadeAngle + detailFadeRate * wedgeAngle;
 
     for (int i = cardCount-1; i >= 0; --i) {
-        if (cards[i].visible) {
+        if (cards[i].cardVisible) {
             if (cards[i].detailTextureState == STATE_LOADED && cards[i].detailTexture.p != 0) {
                 const float lineWidth = rsAllocationGetDimX(detailLineTexture);
 
@@ -711,7 +716,12 @@
 
                 const float blendedAlpha = min(1.0f, animatedAlpha * positionAlpha);
 
-                if (blendedAlpha == 0.0f) continue; // nothing to draw
+                if (blendedAlpha == 0.0f) {
+                    cards[i].detailVisible = false;
+                    continue; // nothing to draw
+                } else {
+                    cards[i].detailVisible = true;
+                }
                 if (blendedAlpha == 1.0f) {
                     rsgBindProgramFragment(singleTextureFragmentProgram);
                 } else {
@@ -762,6 +772,10 @@
                 const float x1 = cards[i].detailLineOffset.x + screenCoord.x + offx + textureWidth;
                 const float y0 = textureTop + offy - textureHeight - cards[i].detailLineOffset.y;
                 const float y1 = textureTop + offy - cards[i].detailLineOffset.y;
+                cards[i].detailTexturePosition[0].x = x0;
+                cards[i].detailTexturePosition[0].y = height - y1;
+                cards[i].detailTexturePosition[1].x = x1;
+                cards[i].detailTexturePosition[1].y = height - y0;
 
                 if (blendedAlpha == 1.0f) {
                     rsgBindTexture(singleTextureFragmentProgram, 0, cards[i].detailTexture);
@@ -926,9 +940,18 @@
     updateAllocationVars(cards);
 
     if (enableSelection) {
-        int data[1];
-        int selection = doSelection(x, y);
-        if (selection != -1) {
+        int data[3];
+        int selection;
+        float2 point;
+
+        if ((selection = intersectDetailTexture(x, y, &point)) != -1) {
+            if (debugSelection) rsDebug("Selected detail texture on doStop():", selection);
+            data[0] = selection;
+            data[1] = point.x;
+            data[2] = point.y;
+            rsSendToClientBlocking(CMD_DETAIL_SELECTED, data, sizeof(data));
+        }
+        else if ((selection = doSelection(x, y))!= -1) {
             if (debugSelection) rsDebug("Selected item on doStop():", selection);
             data[0] = selection;
             rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data));
@@ -1150,11 +1173,29 @@
     return true;
 }
 
+static int intersectDetailTexture(float x, float y, float2 *tapCoordinates)
+{
+    for (int id = 0; id < cardCount; id++) {
+        if (cards[id].detailVisible) {
+            const int x0 = cards[id].detailTexturePosition[0].x;
+            const int y0 = cards[id].detailTexturePosition[0].y;
+            const int x1 = cards[id].detailTexturePosition[1].x;
+            const int y1 = cards[id].detailTexturePosition[1].y;
+            if (x >= x0 && x <= x1 && y >= y0 && y <= y1) {
+                float2 point = { x - x0, y - y0 };
+                *tapCoordinates = point;
+                return id;
+            }
+        }
+    }
+    return -1;
+}
+
 static int intersectGeometry(Ray* ray, float *bestTime)
 {
     int hit = -1;
     for (int id = 0; id < cardCount; id++) {
-        if (cards[id].visible) {
+        if (cards[id].cardVisible) {
             rs_matrix4x4 matrix;
             float3 p[4];
 
@@ -1292,15 +1333,18 @@
             // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
             float p = cardPosition(i);
             if (p >= thetaFirst && p < thetaLast) {
-                cards[i].visible = true;
+                cards[i].cardVisible = true;
+                // cards[i].detailVisible will be set at draw time
                 count++;
             } else {
-                cards[i].visible = false;
+                cards[i].cardVisible = false;
+                cards[i].detailVisible = false;
             }
         } else {
             // Cull the rest of the cards using bounding box of geometry.
             // TODO
-            cards[i].visible = true;
+            cards[i].cardVisible = true;
+            // cards[i].detailVisible will be set at draw time
             count++;
         }
     }
@@ -1313,7 +1357,7 @@
 {
     for (int i = cardCount-1; i >= 0; --i) {
         int data[1];
-        if (cards[i].visible) {
+        if (cards[i].cardVisible) {
             if (debugTextureLoading) rsDebug("*** Texture stamp: ", (int)cards[i].textureTimeStamp);
 
             // request texture from client if not loaded
diff --git a/carousel/test/src/com/android/carouseltest/CarouselTestActivity.java b/carousel/test/src/com/android/carouseltest/CarouselTestActivity.java
index abfff9c..b193ced 100644
--- a/carousel/test/src/com/android/carouseltest/CarouselTestActivity.java
+++ b/carousel/test/src/com/android/carouseltest/CarouselTestActivity.java
@@ -65,6 +65,11 @@
         }
 
         @Override
+        public void onDetailSelected(int id, int x, int y) {
+            Log.v(TAG, "Yay, detail " + id + " was selected at " + x + ", " + y);
+        }
+
+        @Override
         public DetailTextureParameters getDetailTextureParameters(int id) {
             return mDetailTextureParameters;
         }