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
15package android.support.v17.leanback.widget;
16
17import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
18import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
19import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
20import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
21
22import static android.support.v7.widget.RecyclerView.HORIZONTAL;
23
24import android.view.View;
25
26/**
27 * Maintains Window Alignment information of two axis.
28 */
29class 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
74        public Axis(String name) {
75            reset();
76            mName = name;
77        }
78
79        final public int getWindowAlignment() {
80            return mWindowAlignment;
81        }
82
83        final public void setWindowAlignment(int windowAlignment) {
84            mWindowAlignment = windowAlignment;
85        }
86
87        final public int getWindowAlignmentOffset() {
88            return mWindowAlignmentOffset;
89        }
90
91        final public void setWindowAlignmentOffset(int offset) {
92            mWindowAlignmentOffset = offset;
93        }
94
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
103        final public float getWindowAlignmentOffsetPercent() {
104            return mWindowAlignmentOffsetPercent;
105        }
106
107        final public int getScrollCenter() {
108            return (int) mScrollCenter;
109        }
110
111        /** set minEdge,  Integer.MIN_VALUE means unknown*/
112        final public void setMinEdge(int minEdge) {
113            mMinEdge = minEdge;
114        }
115
116        final public int getMinEdge() {
117            return mMinEdge;
118        }
119
120        /** set minScroll,  Integer.MIN_VALUE means unknown*/
121        final public void setMinScroll(int minScroll) {
122            mMinScroll = minScroll;
123        }
124
125        final public int getMinScroll() {
126            return mMinScroll;
127        }
128
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*/
135        final public void setMaxEdge(int maxEdge) {
136            mMaxEdge = maxEdge;
137        }
138
139        final public int getMaxEdge() {
140            return mMaxEdge;
141        }
142
143        /** update max scroll,  Integer.MAX_VALUE means unknown*/
144        final public void setMaxScroll(int maxScroll) {
145            mMaxScroll = maxScroll;
146        }
147
148        final public int getMaxScroll() {
149            return mMaxScroll;
150        }
151
152        final public void invalidateScrollMax() {
153            mMaxEdge = Integer.MAX_VALUE;
154            mMaxScroll = Integer.MAX_VALUE;
155        }
156
157        final public float updateScrollCenter(float scrollTarget) {
158            mScrollCenter = scrollTarget;
159            return scrollTarget;
160        }
161
162        private void reset() {
163            mScrollCenter = Integer.MIN_VALUE;
164            mMinEdge = Integer.MIN_VALUE;
165            mMaxEdge = Integer.MAX_VALUE;
166        }
167
168        final public boolean isMinUnknown() {
169            return mMinEdge == Integer.MIN_VALUE;
170        }
171
172        final public boolean isMaxUnknown() {
173            return mMaxEdge == Integer.MAX_VALUE;
174        }
175
176        final public void setSize(int size) {
177            mSize = size;
178        }
179
180        final public int getSize() {
181            return mSize;
182        }
183
184        final public void setPadding(int paddingLow, int paddingHigh) {
185            mPaddingLow = paddingLow;
186            mPaddingHigh = paddingHigh;
187        }
188
189        final public int getPaddingLow() {
190            return mPaddingLow;
191        }
192
193        final public int getPaddingHigh() {
194            return mPaddingHigh;
195        }
196
197        final public int getClientSize() {
198            return mSize - mPaddingLow - mPaddingHigh;
199        }
200
201        final public int getSystemScrollPos(boolean isAtMin, boolean isAtMax) {
202            return getSystemScrollPos((int) mScrollCenter, isAtMin, isAtMax);
203        }
204
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
261        final public void setReversedFlow(boolean reversedFlow) {
262            mReversedFlow = reversedFlow;
263        }
264
265        @Override
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
283    final public Axis mainAxis() {
284        return mMainAxis;
285    }
286
287    final public Axis secondAxis() {
288        return mSecondAxis;
289    }
290
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
302    final public int getOrientation() {
303        return mOrientation;
304    }
305
306    final public void reset() {
307        mainAxis().reset();
308    }
309
310    @Override
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