Provides more control over detail alignment.

Replaces setDrawDetailsBelowCard and setDetailTexturesCentered
with setDetailTextureAlignment, and implements support for
vertical centering.

Change-Id: Idee5f28235e269667132920421cfe32f91456285
diff --git a/carousel/java/com/android/ex/carousel/CarouselController.java b/carousel/java/com/android/ex/carousel/CarouselController.java
index 139ba14..7631948 100644
--- a/carousel/java/com/android/ex/carousel/CarouselController.java
+++ b/carousel/java/com/android/ex/carousel/CarouselController.java
@@ -17,6 +17,7 @@
 package com.android.ex.carousel;
 
 import com.android.ex.carousel.CarouselRS.CarouselCallback;
+import com.android.ex.carousel.MVCCarouselView.DetailAlignment;
 
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -43,6 +44,8 @@
     private final float DEFAULT_SWAY_SENSITIVITY = 0.0f;
     private final float DEFAULT_FRICTION_COEFFICIENT = 10.0f;
     private final float DEFAULT_DRAG_FACTOR = 0.25f;
+    private final int DEFAULT_DETAIL_ALIGNMENT =
+            DetailAlignment.VIEW_TOP | DetailAlignment.LEFT;
     private CarouselRS mRenderScript;
     private RenderScriptGL mRS;
     private static final String TAG = "CarouselController";
@@ -62,8 +65,7 @@
     private int mVisibleSlots = 0;
     private int mVisibleDetails = DEFAULT_VISIBLE_DETAIL_COUNT;
     private int mPrefetchCardCount = DEFAULT_PREFETCH_CARD_COUNT;
-    private boolean mDrawDetailBelowCard = false;
-    private boolean mDetailTexturesCentered = false;
+    private int mDetailTextureAlignment = DEFAULT_DETAIL_ALIGNMENT;
     private boolean mDrawCardsWithBlending = true;
     private boolean mDrawRuler = true;
     private float mStartAngle;
@@ -100,7 +102,7 @@
         setVisibleSlots(mVisibleSlots);
         setVisibleDetails(mVisibleDetails);
         setPrefetchCardCount(mPrefetchCardCount);
-        setDrawDetailBelowCard(mDrawDetailBelowCard);
+        setDetailTextureAlignment(mDetailTextureAlignment);
         setDrawRuler(mDrawRuler);
         setCallback(mCarouselCallback);
         setDefaultBitmap(mDefaultBitmap);
@@ -205,27 +207,25 @@
     }
 
     /**
-     * Set whether to draw the detail texture above or below the card.
+     * Sets how detail textures are aligned with respect to the card.
      *
-     * @param below False for above, true for below.
+     * @param alignment a bitmask of DetailAlignment flags.
      */
-    public void setDrawDetailBelowCard(boolean below) {
-        mDrawDetailBelowCard = below;
-        if (mRenderScript != null) {
-            mRenderScript.setDrawDetailBelowCard(below);
+    public void setDetailTextureAlignment(int alignment) {
+        int xBits = alignment & DetailAlignment.HORIZONTAL_ALIGNMENT_MASK;
+        if (xBits == 0 || ((xBits & (xBits - 1)) != 0)) {
+            throw new IllegalArgumentException(
+                    "Must specify exactly one horizontal alignment flag");
         }
-    }
+        int yBits = alignment & DetailAlignment.VERTICAL_ALIGNMENT_MASK;
+        if (yBits == 0 || ((yBits & (yBits - 1)) != 0)) {
+            throw new IllegalArgumentException(
+                    "Must specify exactly one vertical alignment flag");
+        }
 
-    /**
-     * Set whether to align the detail texture center with the card center.
-     * If not, left edges will be aligned instead.
-     *
-     * @param centered True for center-aligned, false for left-aligned.
-     */
-    public void setDetailTexturesCentered(boolean centered) {
-        mDetailTexturesCentered = centered;
+        mDetailTextureAlignment = alignment;
         if (mRenderScript != null) {
-            mRenderScript.setDetailTexturesCentered(centered);
+            mRenderScript.setDetailTextureAlignment(alignment);
         }
     }
 
diff --git a/carousel/java/com/android/ex/carousel/CarouselRS.java b/carousel/java/com/android/ex/carousel/CarouselRS.java
index 6f62bb6..efa6ff6 100644
--- a/carousel/java/com/android/ex/carousel/CarouselRS.java
+++ b/carousel/java/com/android/ex/carousel/CarouselRS.java
@@ -367,13 +367,8 @@
         mScript.set_prefetchCardCount(count);
     }
 
-
-    public void setDrawDetailBelowCard(boolean below) {
-        mScript.set_drawDetailBelowCard(below);
-    }
-
-    public void setDetailTexturesCentered(boolean centered) {
-        mScript.set_detailTexturesCentered(centered);
+    public void setDetailTextureAlignment(int alignment) {
+        mScript.set_detailTextureAlignment(alignment);
     }
 
     public void setDrawCardsWithBlending(boolean enabled) {
diff --git a/carousel/java/com/android/ex/carousel/CarouselView.java b/carousel/java/com/android/ex/carousel/CarouselView.java
index 062f250..0b6bc3f 100644
--- a/carousel/java/com/android/ex/carousel/CarouselView.java
+++ b/carousel/java/com/android/ex/carousel/CarouselView.java
@@ -51,6 +51,8 @@
     private final float DEFAULT_SWAY_SENSITIVITY = 0.0f;
     private final float DEFAULT_FRICTION_COEFFICIENT = 10.0f;
     private final float DEFAULT_DRAG_FACTOR = 0.25f;
+    private final int DEFAULT_DETAIL_ALIGNMENT =
+            DetailAlignment.VIEW_TOP | DetailAlignment.LEFT;
     private static final String TAG = "CarouselView";
     private static final boolean DBG = false;
     private CarouselRS mRenderScript;
@@ -71,8 +73,7 @@
     private int mVisibleSlots = 0;
     private int mVisibleDetails = DEFAULT_VISIBLE_DETAIL_COUNT;
     private int mPrefetchCardCount = DEFAULT_PREFETCH_CARD_COUNT;
-    private boolean mDrawDetailBelowCard = false;
-    private boolean mDetailTexturesCentered = false;
+    private int mDetailTextureAlignment = DEFAULT_DETAIL_ALIGNMENT;
     private boolean mDrawCardsWithBlending = true;
     private boolean mDrawRuler = true;
     private float mStartAngle;
@@ -94,6 +95,41 @@
     private Bitmap mDetailLoadingBitmap = Bitmap.createBitmap(
             new int[] {0}, 0, 1, 1, 1, Bitmap.Config.ARGB_4444);
 
+    // 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)
@@ -149,7 +185,7 @@
         setVisibleSlots(mVisibleSlots);
         setVisibleDetails(mVisibleDetails);
         setPrefetchCardCount(mPrefetchCardCount);
-        setDrawDetailBelowCard(mDrawDetailBelowCard);
+        setDetailTextureAlignment(mDetailTextureAlignment);
         setDrawRuler(mDrawRuler);
         setCallback(mCarouselCallback);
         setDefaultBitmap(mDefaultBitmap);
@@ -267,27 +303,25 @@
     }
 
     /**
-     * Set whether to draw the detail texture above or below the card.
+     * Sets how detail textures are aligned with respect to the card.
      *
-     * @param below False for above, true for below.
+     * @param alignment a bitmask of DetailAlignment flags.
      */
-    public void setDrawDetailBelowCard(boolean below) {
-        mDrawDetailBelowCard = below;
-        if (mRenderScript != null) {
-            mRenderScript.setDrawDetailBelowCard(below);
+    public void setDetailTextureAlignment(int alignment) {
+        int xBits = alignment & DetailAlignment.HORIZONTAL_ALIGNMENT_MASK;
+        if (xBits == 0 || ((xBits & (xBits - 1)) != 0)) {
+            throw new IllegalArgumentException(
+                  "Must specify exactly one horizontal alignment flag");
         }
-    }
+        int yBits = alignment & DetailAlignment.VERTICAL_ALIGNMENT_MASK;
+        if (yBits == 0 || ((yBits & (yBits - 1)) != 0)) {
+          throw new IllegalArgumentException(
+                  "Must specify exactly one vertical alignment flag");
+        }
 
-    /**
-     * Set whether to align the detail texture center with the card center.
-     * If not, left edges will be aligned instead.
-     *
-     * @param centered True for center-aligned, false for left-aligned.
-     */
-    public void setDetailTexturesCentered(boolean centered) {
-        mDetailTexturesCentered = centered;
+        mDetailTextureAlignment = alignment;
         if (mRenderScript != null) {
-            mRenderScript.setDetailTexturesCentered(centered);
+            mRenderScript.setDetailTextureAlignment(alignment);
         }
     }
 
diff --git a/carousel/java/com/android/ex/carousel/MVCCarouselView.java b/carousel/java/com/android/ex/carousel/MVCCarouselView.java
index 5d7573d..abbf32a 100644
--- a/carousel/java/com/android/ex/carousel/MVCCarouselView.java
+++ b/carousel/java/com/android/ex/carousel/MVCCarouselView.java
@@ -53,6 +53,41 @@
 
     CarouselController mController;
 
+    // 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)
@@ -198,22 +233,12 @@
     }
 
     /**
-     * Set whether to draw the detail texture above or below the card.
-     *
-     * @param below False for above, true for below.
+     * Sets how detail textures are aligned with respect to the card.
+     * 
+     * @param alignment a bitmask of DetailAlignment flags.
      */
-    public void setDrawDetailBelowCard(boolean below) {
-        mController.setDrawDetailBelowCard(below);
-    }
-
-    /**
-     * Set whether to align the detail texture center with the card center.
-     * If not, left edges will be aligned instead.
-     *
-     * @param centered True for center-aligned, false for left-aligned.
-     */
-    public void setDetailTexturesCentered(boolean centered) {
-        mController.setDetailTexturesCentered(centered);
+    public void setDetailTextureAlignment(int alignment) {
+        mController.setDetailTextureAlignment(alignment);
     }
 
     /**
diff --git a/carousel/java/com/android/ex/carousel/carousel.rs b/carousel/java/com/android/ex/carousel/carousel.rs
index 27fd522..9b38a5e 100644
--- a/carousel/java/com/android/ex/carousel/carousel.rs
+++ b/carousel/java/com/android/ex/carousel/carousel.rs
@@ -74,6 +74,41 @@
     STATE_LOADED // item was delivered
 };
 
+// Detail texture alignments ** THIS LIST MUST MATCH THOSE IN CarouselView.java ***
+enum {
+    /** Detail is centered vertically with respect to the card **/
+    CENTER_VERTICAL = 1,
+    /** Detail is aligned with the top edge of the carousel view **/
+    VIEW_TOP = 1 << 1,
+    /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/
+    VIEW_BOTTOM = 1 << 2,
+    /** Detail is positioned above the card (not yet implemented) **/
+    ABOVE = 1 << 3,
+    /** Detail is positioned below the card **/
+    BELOW = 1 << 4,
+    /** Mask that selects those bits that control vertical alignment **/
+    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.
+     */
+    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.
+     */
+    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)
+     */
+    RIGHT = 1 << 10,
+    /** Mask that selects those bits that control horizontal alignment **/
+    HORIZONTAL_ALIGNMENT_MASK = 0xff00,
+};
+
 // Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
 static const int CMD_CARD_SELECTED = 100;
 static const int CMD_CARD_LONGPRESS = 110;
@@ -111,9 +146,7 @@
 int visibleSlotCount; // number of visible slots (for culling)
 int visibleDetailCount; // number of visible detail textures to show
 int prefetchCardCount; // how many cards to keep in memory
-bool drawDetailBelowCard; // whether detail goes above (false) or below (true) the card
-// TODO(jshuma): Replace detailTexturesCentered with a detailTextureAlignment mode enum
-bool detailTexturesCentered; // line up detail center and card center (instead of left edges)
+int detailTextureAlignment; // How to align detail texture with respect to card
 bool drawCardsWithBlending; // Enable blending while drawing cards (for translucent card textures)
 bool drawRuler; // whether to draw a ruler from the card to the detail texture
 float radius; // carousel radius. Cards will be centered on a circle with this radius
@@ -596,7 +629,7 @@
 
 /*
  * Draws a screen-aligned card with the exact dimensions from the detail texture.
- * This is used to display information about the object being displayed above the geomertry.
+ * This is used to display information about the object being displayed.
  * Returns true if we're still animating any property of the cards (e.g. fades).
  */
 static bool drawDetails(int64_t currentTime)
@@ -638,7 +671,7 @@
 
                 int indexLeft, indexRight;
                 float4 screenCoord;
-                if (drawDetailBelowCard) {
+                if (detailTextureAlignment & BELOW) {
                     indexLeft = 0;
                     indexRight = 1;
                 } else {
@@ -652,6 +685,20 @@
                     rsDebug("Bad transform: ", screenCoord);
                     continue;
                 }
+                if (detailTextureAlignment & CENTER_VERTICAL) {
+                    // If we're centering vertically, we'll need the other vertices too
+                    if (detailTextureAlignment & BELOW) {
+                        indexLeft = 3;
+                        indexRight = 2;
+                    } else {
+                        indexLeft = 0;
+                        indexRight = 1;
+                    }
+                    float4 otherScreenLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
+                    float4 otherScreenRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
+                    screenCoordRight.y = screenCoordLeft.y = (screenCoordLeft.y + screenCoordRight.y
+                        + otherScreenLeft.y + otherScreenRight.y) / 4.;
+                }
                 (void) convertNormalizedToPixelCoordinates(&screenCoordLeft, width, height);
                 (void) convertNormalizedToPixelCoordinates(&screenCoordRight, width, height);
                 if (debugDetails) {
@@ -659,10 +706,12 @@
                     RS_DEBUG(screenCoordRight);
                 }
                 screenCoord = screenCoordLeft;
-                if (drawDetailBelowCard) {
+                if (detailTextureAlignment & BELOW) {
                     screenCoord.y = min(screenCoordLeft.y, screenCoordRight.y);
+                } else if (detailTextureAlignment & CENTER_VERTICAL) {
+                    screenCoord.y -= rsAllocationGetDimY(cards[i].detailTexture) / 2.;
                 }
-                if (detailTexturesCentered) {
+                if (detailTextureAlignment & CENTER_HORIZONTAL) {
                     screenCoord.x += (screenCoordRight.x - screenCoordLeft.x) / 2. -
                         rsAllocationGetDimX(cards[i].detailTexture) / 2.;
                 }
@@ -693,11 +742,21 @@
                 shaderConstants->fadeAmount = blendedAlpha;
                 rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
 
-                // Draw line from upper left card corner to the top of the screen
+                // Draw line from the card to the detail texture.
+                // The line is drawn from the top or bottom left of the card
+                // to either the top of the screen or the top of the detail
+                // texture, depending on detailTextureAlignment.
                 if (drawRuler) {
+                    float rulerTop;
+                    float rulerBottom;
+                    if (detailTextureAlignment & BELOW) {
+                        rulerTop = screenCoord.y;
+                        rulerBottom = 0;
+                    } else {
+                        rulerTop = height;
+                        rulerBottom = screenCoord.y;
+                    }
                     const float halfWidth = lineWidth * 0.5f;
-                    const float rulerTop = drawDetailBelowCard ? screenCoord.y : height;
-                    const float rulerBottom = drawDetailBelowCard ? 0 : screenCoord.y;
                     const float x0 = cards[i].detailLineOffset.x + screenCoord.x - halfWidth;
                     const float x1 = cards[i].detailLineOffset.x + screenCoord.x + halfWidth;
                     const float y0 = rulerBottom + yPadding;
@@ -717,7 +776,8 @@
                 const float textureHeight = rsAllocationGetDimY(cards[i].detailTexture);
                 const float offx = cards[i].detailTextureOffset.x;
                 const float offy = -cards[i].detailTextureOffset.y;
-                const float textureTop = drawDetailBelowCard ? screenCoord.y : height;
+                const float textureTop = (detailTextureAlignment & VIEW_TOP)
+                        ? height : screenCoord.y;
                 const float x0 = cards[i].detailLineOffset.x + screenCoord.x + offx;
                 const float x1 = cards[i].detailLineOffset.x + screenCoord.x + offx + textureWidth;
                 const float y0 = textureTop + offy - textureHeight - cards[i].detailLineOffset.y;