1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package android.support.v17.leanback.widget;
16 
17 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
18 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
19 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
20 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
21 
22 import static android.support.v7.widget.RecyclerView.HORIZONTAL;
23 
24 import android.view.View;
25 
26 /**
27  * Maintains Window Alignment information of two axis.
28  */
29 class WindowAlignment {
30 
31     /**
32      * Maintains alignment information in one direction.
33      */
34     public static class Axis {
35         /**
36          * mScrollCenter is used to calculate dynamic transformation based on how far a view
37          * is from the mScrollCenter. For example, the views with center close to mScrollCenter
38          * will be scaled up.
39          */
40         private float mScrollCenter;
41         /**
42          * Right or bottom edge of last child.
43          */
44         private int mMaxEdge;
45         /**
46          * Left or top edge of first child, typically should be zero.
47          */
48         private int mMinEdge;
49         /**
50          * Max Scroll value
51          */
52         private int mMaxScroll;
53         /**
54          * Min Scroll value
55          */
56         private int mMinScroll;
57 
58         private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;
59 
60         private int mWindowAlignmentOffset = 0;
61 
62         private float mWindowAlignmentOffsetPercent = 50f;
63 
64         private int mSize;
65 
66         private int mPaddingLow;
67 
68         private int mPaddingHigh;
69 
70         private boolean mReversedFlow;
71 
72         private String mName; // for debugging
73 
Axis(String name)74         public Axis(String name) {
75             reset();
76             mName = name;
77         }
78 
getWindowAlignment()79         final public int getWindowAlignment() {
80             return mWindowAlignment;
81         }
82 
setWindowAlignment(int windowAlignment)83         final public void setWindowAlignment(int windowAlignment) {
84             mWindowAlignment = windowAlignment;
85         }
86 
getWindowAlignmentOffset()87         final public int getWindowAlignmentOffset() {
88             return mWindowAlignmentOffset;
89         }
90 
setWindowAlignmentOffset(int offset)91         final public void setWindowAlignmentOffset(int offset) {
92             mWindowAlignmentOffset = offset;
93         }
94 
setWindowAlignmentOffsetPercent(float percent)95         final public void setWindowAlignmentOffsetPercent(float percent) {
96             if ((percent < 0 || percent > 100)
97                     && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
98                 throw new IllegalArgumentException();
99             }
100             mWindowAlignmentOffsetPercent = percent;
101         }
102 
getWindowAlignmentOffsetPercent()103         final public float getWindowAlignmentOffsetPercent() {
104             return mWindowAlignmentOffsetPercent;
105         }
106 
getScrollCenter()107         final public int getScrollCenter() {
108             return (int) mScrollCenter;
109         }
110 
111         /** set minEdge,  Integer.MIN_VALUE means unknown*/
setMinEdge(int minEdge)112         final public void setMinEdge(int minEdge) {
113             mMinEdge = minEdge;
114         }
115 
getMinEdge()116         final public int getMinEdge() {
117             return mMinEdge;
118         }
119 
120         /** set minScroll,  Integer.MIN_VALUE means unknown*/
setMinScroll(int minScroll)121         final public void setMinScroll(int minScroll) {
122             mMinScroll = minScroll;
123         }
124 
getMinScroll()125         final public int getMinScroll() {
126             return mMinScroll;
127         }
128 
invalidateScrollMin()129         final public void invalidateScrollMin() {
130             mMinEdge = Integer.MIN_VALUE;
131             mMinScroll = Integer.MIN_VALUE;
132         }
133 
134         /** update max edge,  Integer.MAX_VALUE means unknown*/
setMaxEdge(int maxEdge)135         final public void setMaxEdge(int maxEdge) {
136             mMaxEdge = maxEdge;
137         }
138 
getMaxEdge()139         final public int getMaxEdge() {
140             return mMaxEdge;
141         }
142 
143         /** update max scroll,  Integer.MAX_VALUE means unknown*/
setMaxScroll(int maxScroll)144         final public void setMaxScroll(int maxScroll) {
145             mMaxScroll = maxScroll;
146         }
147 
getMaxScroll()148         final public int getMaxScroll() {
149             return mMaxScroll;
150         }
151 
invalidateScrollMax()152         final public void invalidateScrollMax() {
153             mMaxEdge = Integer.MAX_VALUE;
154             mMaxScroll = Integer.MAX_VALUE;
155         }
156 
updateScrollCenter(float scrollTarget)157         final public float updateScrollCenter(float scrollTarget) {
158             mScrollCenter = scrollTarget;
159             return scrollTarget;
160         }
161 
reset()162         private void reset() {
163             mScrollCenter = Integer.MIN_VALUE;
164             mMinEdge = Integer.MIN_VALUE;
165             mMaxEdge = Integer.MAX_VALUE;
166         }
167 
isMinUnknown()168         final public boolean isMinUnknown() {
169             return mMinEdge == Integer.MIN_VALUE;
170         }
171 
isMaxUnknown()172         final public boolean isMaxUnknown() {
173             return mMaxEdge == Integer.MAX_VALUE;
174         }
175 
setSize(int size)176         final public void setSize(int size) {
177             mSize = size;
178         }
179 
getSize()180         final public int getSize() {
181             return mSize;
182         }
183 
setPadding(int paddingLow, int paddingHigh)184         final public void setPadding(int paddingLow, int paddingHigh) {
185             mPaddingLow = paddingLow;
186             mPaddingHigh = paddingHigh;
187         }
188 
getPaddingLow()189         final public int getPaddingLow() {
190             return mPaddingLow;
191         }
192 
getPaddingHigh()193         final public int getPaddingHigh() {
194             return mPaddingHigh;
195         }
196 
getClientSize()197         final public int getClientSize() {
198             return mSize - mPaddingLow - mPaddingHigh;
199         }
200 
getSystemScrollPos(boolean isAtMin, boolean isAtMax)201         final public int getSystemScrollPos(boolean isAtMin, boolean isAtMax) {
202             return getSystemScrollPos((int) mScrollCenter, isAtMin, isAtMax);
203         }
204 
getSystemScrollPos(int scrollCenter, boolean isAtMin, boolean isAtMax)205         final public int getSystemScrollPos(int scrollCenter, boolean isAtMin, boolean isAtMax) {
206             int middlePosition;
207             if (!mReversedFlow) {
208                 if (mWindowAlignmentOffset >= 0) {
209                     middlePosition = mWindowAlignmentOffset - mPaddingLow;
210                 } else {
211                     middlePosition = mSize + mWindowAlignmentOffset - mPaddingLow;
212                 }
213                 if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
214                     middlePosition += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
215                 }
216             } else {
217                 if (mWindowAlignmentOffset >= 0) {
218                     middlePosition = mSize - mWindowAlignmentOffset - mPaddingLow;
219                 } else {
220                     middlePosition = - mWindowAlignmentOffset - mPaddingLow;
221                 }
222                 if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
223                     middlePosition -= (int) (mSize * mWindowAlignmentOffsetPercent / 100);
224                 }
225             }
226             int clientSize = getClientSize();
227             int afterMiddlePosition = clientSize - middlePosition;
228             boolean isMinUnknown = isMinUnknown();
229             boolean isMaxUnknown = isMaxUnknown();
230             if (!isMinUnknown && !isMaxUnknown &&
231                     (mWindowAlignment & WINDOW_ALIGN_BOTH_EDGE) == WINDOW_ALIGN_BOTH_EDGE) {
232                 if (mMaxEdge - mMinEdge <= clientSize) {
233                     // total children size is less than view port and we want to align
234                     // both edge:  align first child to start edge of view port
235                     return mReversedFlow ? mMaxEdge - mPaddingLow - clientSize
236                             : mMinEdge - mPaddingLow;
237                 }
238             }
239             if (!isMinUnknown) {
240                 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
241                      : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0)
242                         && (isAtMin || scrollCenter - mMinEdge <= middlePosition)) {
243                     // scroll center is within half of view port size: align the start edge
244                     // of first child to the start edge of view port
245                     return mMinEdge - mPaddingLow;
246                 }
247             }
248             if (!isMaxUnknown) {
249                 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
250                         : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0)
251                         && (isAtMax || mMaxEdge - scrollCenter <= afterMiddlePosition)) {
252                     // scroll center is very close to the end edge of view port : align the
253                     // end edge of last children (plus expanded size) to view port's end
254                     return mMaxEdge - mPaddingLow - clientSize;
255                 }
256             }
257             // else put scroll center in middle of view port
258             return scrollCenter - middlePosition - mPaddingLow;
259         }
260 
setReversedFlow(boolean reversedFlow)261         final public void setReversedFlow(boolean reversedFlow) {
262             mReversedFlow = reversedFlow;
263         }
264 
265         @Override
toString()266         public String toString() {
267             return "center: " + mScrollCenter + " min:" + mMinEdge +
268                     " max:" + mMaxEdge;
269         }
270 
271     }
272 
273     private int mOrientation = HORIZONTAL;
274 
275     final public Axis vertical = new Axis("vertical");
276 
277     final public Axis horizontal = new Axis("horizontal");
278 
279     private Axis mMainAxis = horizontal;
280 
281     private Axis mSecondAxis = vertical;
282 
mainAxis()283     final public Axis mainAxis() {
284         return mMainAxis;
285     }
286 
secondAxis()287     final public Axis secondAxis() {
288         return mSecondAxis;
289     }
290 
setOrientation(int orientation)291     final public void setOrientation(int orientation) {
292         mOrientation = orientation;
293         if (mOrientation == HORIZONTAL) {
294             mMainAxis = horizontal;
295             mSecondAxis = vertical;
296         } else {
297             mMainAxis = vertical;
298             mSecondAxis = horizontal;
299         }
300     }
301 
getOrientation()302     final public int getOrientation() {
303         return mOrientation;
304     }
305 
reset()306     final public void reset() {
307         mainAxis().reset();
308     }
309 
310     @Override
toString()311     public String toString() {
312         return new StringBuffer().append("horizontal=")
313                 .append(horizontal.toString())
314                 .append("; vertical=")
315                 .append(vertical.toString())
316                 .toString();
317     }
318 
319 }
320