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_BOTH_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_LOW_EDGE;
20 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
21 import static android.support.v7.widget.RecyclerView.HORIZONTAL;
22 
23 /**
24  * Maintains Window Alignment information of two axis.
25  */
26 class WindowAlignment {
27 
28     /**
29      * Maintains alignment information in one direction.
30      */
31     public static class Axis {
32         /**
33          * Right or bottom edge of last child.
34          */
35         private int mMaxEdge;
36         /**
37          * Left or top edge of first child
38          */
39         private int mMinEdge;
40         /**
41          * Scroll distance to align last child, it defines limit of scroll.
42          */
43         private int mMaxScroll;
44         /**
45          * Scroll distance to align first child, it defines limit of scroll.
46          */
47         private int mMinScroll;
48 
49         static final int PF_KEYLINE_OVER_LOW_EDGE = 1;
50         static final int PF_KEYLINE_OVER_HIGH_EDGE = 1 << 1;
51 
52         /**
53          * By default we prefer low edge over keyline, prefer keyline over high edge.
54          */
55         private int mPreferredKeyLine = PF_KEYLINE_OVER_HIGH_EDGE;
56 
57         private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;
58 
59         private int mWindowAlignmentOffset = 0;
60 
61         private float mWindowAlignmentOffsetPercent = 50f;
62 
63         private int mSize;
64 
65         /**
66          * Padding at the min edge, it is the left or top padding.
67          */
68         private int mPaddingMin;
69 
70         /**
71          * Padding at the max edge, it is the right or bottom padding.
72          */
73         private int mPaddingMax;
74 
75         private boolean mReversedFlow;
76 
77         private String mName; // for debugging
78 
Axis(String name)79         public Axis(String name) {
80             reset();
81             mName = name;
82         }
83 
getWindowAlignment()84         public final int getWindowAlignment() {
85             return mWindowAlignment;
86         }
87 
setWindowAlignment(int windowAlignment)88         public final void setWindowAlignment(int windowAlignment) {
89             mWindowAlignment = windowAlignment;
90         }
91 
setPreferKeylineOverLowEdge(boolean keylineOverLowEdge)92         final void setPreferKeylineOverLowEdge(boolean keylineOverLowEdge) {
93             mPreferredKeyLine = keylineOverLowEdge
94                     ? mPreferredKeyLine | PF_KEYLINE_OVER_LOW_EDGE
95                     : mPreferredKeyLine & ~PF_KEYLINE_OVER_LOW_EDGE;
96         }
97 
setPreferKeylineOverHighEdge(boolean keylineOverHighEdge)98         final void setPreferKeylineOverHighEdge(boolean keylineOverHighEdge) {
99             mPreferredKeyLine = keylineOverHighEdge
100                     ? mPreferredKeyLine | PF_KEYLINE_OVER_HIGH_EDGE
101                     : mPreferredKeyLine & ~PF_KEYLINE_OVER_HIGH_EDGE;
102         }
103 
isPreferKeylineOverHighEdge()104         final boolean isPreferKeylineOverHighEdge() {
105             return (mPreferredKeyLine & PF_KEYLINE_OVER_HIGH_EDGE) != 0;
106         }
107 
isPreferKeylineOverLowEdge()108         final boolean isPreferKeylineOverLowEdge() {
109             return (mPreferredKeyLine & PF_KEYLINE_OVER_LOW_EDGE) != 0;
110         }
111 
getWindowAlignmentOffset()112         public final int getWindowAlignmentOffset() {
113             return mWindowAlignmentOffset;
114         }
115 
setWindowAlignmentOffset(int offset)116         public final void setWindowAlignmentOffset(int offset) {
117             mWindowAlignmentOffset = offset;
118         }
119 
setWindowAlignmentOffsetPercent(float percent)120         public final void setWindowAlignmentOffsetPercent(float percent) {
121             if ((percent < 0 || percent > 100)
122                     && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
123                 throw new IllegalArgumentException();
124             }
125             mWindowAlignmentOffsetPercent = percent;
126         }
127 
getWindowAlignmentOffsetPercent()128         public final float getWindowAlignmentOffsetPercent() {
129             return mWindowAlignmentOffsetPercent;
130         }
131 
132         /**
133          * Returns scroll distance to align min child.
134          */
getMinScroll()135         public final int getMinScroll() {
136             return mMinScroll;
137         }
138 
invalidateScrollMin()139         public final void invalidateScrollMin() {
140             mMinEdge = Integer.MIN_VALUE;
141             mMinScroll = Integer.MIN_VALUE;
142         }
143 
144         /**
145          * Returns scroll distance to align max child.
146          */
getMaxScroll()147         public final int getMaxScroll() {
148             return mMaxScroll;
149         }
150 
invalidateScrollMax()151         public final void invalidateScrollMax() {
152             mMaxEdge = Integer.MAX_VALUE;
153             mMaxScroll = Integer.MAX_VALUE;
154         }
155 
reset()156         void reset() {
157             mMinEdge = Integer.MIN_VALUE;
158             mMaxEdge = Integer.MAX_VALUE;
159         }
160 
isMinUnknown()161         public final boolean isMinUnknown() {
162             return mMinEdge == Integer.MIN_VALUE;
163         }
164 
isMaxUnknown()165         public final boolean isMaxUnknown() {
166             return mMaxEdge == Integer.MAX_VALUE;
167         }
168 
setSize(int size)169         public final void setSize(int size) {
170             mSize = size;
171         }
172 
getSize()173         public final int getSize() {
174             return mSize;
175         }
176 
setPadding(int paddingMin, int paddingMax)177         public final void setPadding(int paddingMin, int paddingMax) {
178             mPaddingMin = paddingMin;
179             mPaddingMax = paddingMax;
180         }
181 
getPaddingMin()182         public final int getPaddingMin() {
183             return mPaddingMin;
184         }
185 
getPaddingMax()186         public final int getPaddingMax() {
187             return mPaddingMax;
188         }
189 
getClientSize()190         public final int getClientSize() {
191             return mSize - mPaddingMin - mPaddingMax;
192         }
193 
calculateKeyline()194         final int calculateKeyline() {
195             int keyLine;
196             if (!mReversedFlow) {
197                 if (mWindowAlignmentOffset >= 0) {
198                     keyLine = mWindowAlignmentOffset;
199                 } else {
200                     keyLine = mSize + mWindowAlignmentOffset;
201                 }
202                 if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
203                     keyLine += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
204                 }
205             } else {
206                 if (mWindowAlignmentOffset >= 0) {
207                     keyLine = mSize - mWindowAlignmentOffset;
208                 } else {
209                     keyLine = -mWindowAlignmentOffset;
210                 }
211                 if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
212                     keyLine -= (int) (mSize * mWindowAlignmentOffsetPercent / 100);
213                 }
214             }
215             return keyLine;
216         }
217 
218         /**
219          * Returns scroll distance to move viewCenterPosition to keyLine.
220          */
calculateScrollToKeyLine(int viewCenterPosition, int keyLine)221         final int calculateScrollToKeyLine(int viewCenterPosition, int keyLine) {
222             return viewCenterPosition - keyLine;
223         }
224 
225         /**
226          * Update {@link #getMinScroll()} and {@link #getMaxScroll()}
227          */
updateMinMax(int minEdge, int maxEdge, int minChildViewCenter, int maxChildViewCenter)228         public final void updateMinMax(int minEdge, int maxEdge,
229                 int minChildViewCenter, int maxChildViewCenter) {
230             mMinEdge = minEdge;
231             mMaxEdge = maxEdge;
232             final int clientSize = getClientSize();
233             final int keyLine = calculateKeyline();
234             final boolean isMinUnknown = isMinUnknown();
235             final boolean isMaxUnknown = isMaxUnknown();
236             if (!isMinUnknown) {
237                 if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
238                         : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
239                     // calculate scroll distance to move current mMinEdge to padding at min edge
240                     mMinScroll = mMinEdge - mPaddingMin;
241                 } else  {
242                     // calculate scroll distance to move min child center to key line
243                     mMinScroll = calculateScrollToKeyLine(minChildViewCenter, keyLine);
244                 }
245             }
246             if (!isMaxUnknown) {
247                 if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
248                         : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
249                     // calculate scroll distance to move current mMaxEdge to padding at max edge
250                     mMaxScroll = mMaxEdge - mPaddingMin - clientSize;
251                 } else  {
252                     // calculate scroll distance to move max child center to key line
253                     mMaxScroll = calculateScrollToKeyLine(maxChildViewCenter, keyLine);
254                 }
255             }
256             if (!isMaxUnknown && !isMinUnknown) {
257                 if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
258                         : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
259                     if (!mReversedFlow ? isPreferKeylineOverLowEdge()
260                             : isPreferKeylineOverHighEdge()) {
261                         // if we prefer key line, might align max child to key line for minScroll
262                         mMinScroll = Math.min(mMinScroll,
263                                 calculateScrollToKeyLine(maxChildViewCenter, keyLine));
264                     } else {
265                         // don't over scroll max
266                         mMaxScroll = Math.max(mMinScroll, mMaxScroll);
267                     }
268                 } else if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
269                         : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
270                     if (!mReversedFlow ? isPreferKeylineOverHighEdge()
271                             : isPreferKeylineOverLowEdge()) {
272                         // if we prefer key line, might align min child to key line for maxScroll
273                         mMaxScroll = Math.max(mMaxScroll,
274                                 calculateScrollToKeyLine(minChildViewCenter, keyLine));
275                     } else {
276                         // don't over scroll min
277                         mMinScroll = Math.min(mMinScroll, mMaxScroll);
278                     }
279                 }
280             }
281         }
282 
283         /**
284          * Get scroll distance of align an item (depends on ALIGN_LOW_EDGE, ALIGN_HIGH_EDGE or the
285          * item should be aligned to key line). The scroll distance will be capped by
286          * {@link #getMinScroll()} and {@link #getMaxScroll()}.
287          */
getScroll(int viewCenter)288         public final int getScroll(int viewCenter) {
289             final int size = getSize();
290             final int keyLine = calculateKeyline();
291             final boolean isMinUnknown = isMinUnknown();
292             final boolean isMaxUnknown = isMaxUnknown();
293             if (!isMinUnknown) {
294                 final int keyLineToMinEdge = keyLine - mPaddingMin;
295                 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
296                      : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0)
297                         && (viewCenter - mMinEdge <= keyLineToMinEdge)) {
298                     // view center is before key line: align the min edge (first child) to padding.
299                     int alignToMin = mMinEdge - mPaddingMin;
300                     // Also we need make sure don't over scroll
301                     if (!isMaxUnknown && alignToMin > mMaxScroll) {
302                         alignToMin = mMaxScroll;
303                     }
304                     return alignToMin;
305                 }
306             }
307             if (!isMaxUnknown) {
308                 final int keyLineToMaxEdge = size - keyLine - mPaddingMax;
309                 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
310                         : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0)
311                         && (mMaxEdge - viewCenter <= keyLineToMaxEdge)) {
312                     // view center is after key line: align the max edge (last child) to padding.
313                     int alignToMax = mMaxEdge - (size - mPaddingMax);
314                     // Also we need make sure don't over scroll
315                     if (!isMinUnknown && alignToMax < mMinScroll) {
316                         alignToMax = mMinScroll;
317                     }
318                     return alignToMax;
319                 }
320             }
321             // else put view center at key line.
322             return calculateScrollToKeyLine(viewCenter, keyLine);
323         }
324 
setReversedFlow(boolean reversedFlow)325         public final void setReversedFlow(boolean reversedFlow) {
326             mReversedFlow = reversedFlow;
327         }
328 
329         @Override
toString()330         public String toString() {
331             return " min:" + mMinEdge + " " + mMinScroll + " max:" + mMaxEdge + " " + mMaxScroll;
332         }
333 
334     }
335 
336     private int mOrientation = HORIZONTAL;
337 
338     public final Axis vertical = new Axis("vertical");
339 
340     public final Axis horizontal = new Axis("horizontal");
341 
342     private Axis mMainAxis = horizontal;
343 
344     private Axis mSecondAxis = vertical;
345 
mainAxis()346     public final Axis mainAxis() {
347         return mMainAxis;
348     }
349 
secondAxis()350     public final Axis secondAxis() {
351         return mSecondAxis;
352     }
353 
setOrientation(int orientation)354     public final void setOrientation(int orientation) {
355         mOrientation = orientation;
356         if (mOrientation == HORIZONTAL) {
357             mMainAxis = horizontal;
358             mSecondAxis = vertical;
359         } else {
360             mMainAxis = vertical;
361             mSecondAxis = horizontal;
362         }
363     }
364 
getOrientation()365     public final int getOrientation() {
366         return mOrientation;
367     }
368 
reset()369     public final void reset() {
370         mainAxis().reset();
371     }
372 
373     @Override
toString()374     public String toString() {
375         return new StringBuffer().append("horizontal=")
376                 .append(horizontal.toString())
377                 .append("; vertical=")
378                 .append(vertical.toString())
379                 .toString();
380     }
381 
382 }
383