1/*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.recyclerview.widget;
18
19import android.view.View;
20
21import androidx.annotation.IntDef;
22
23import java.lang.annotation.Retention;
24import java.lang.annotation.RetentionPolicy;
25
26/**
27 * A utility class used to check the boundaries of a given view within its parent view based on
28 * a set of boundary flags.
29 */
30class ViewBoundsCheck {
31
32    static final int GT = 1 << 0;
33    static final int EQ = 1 << 1;
34    static final int LT = 1 << 2;
35
36
37    static final int CVS_PVS_POS = 0;
38    /**
39     * The child view's start should be strictly greater than parent view's start.
40     */
41    static final int FLAG_CVS_GT_PVS = GT << CVS_PVS_POS;
42
43    /**
44     * The child view's start can be equal to its parent view's start. This flag follows with GT
45     * or LT indicating greater (less) than or equal relation.
46     */
47    static final int FLAG_CVS_EQ_PVS = EQ << CVS_PVS_POS;
48
49    /**
50     * The child view's start should be strictly less than parent view's start.
51     */
52    static final int FLAG_CVS_LT_PVS = LT << CVS_PVS_POS;
53
54
55    static final int CVS_PVE_POS = 4;
56    /**
57     * The child view's start should be strictly greater than parent view's end.
58     */
59    static final int FLAG_CVS_GT_PVE = GT << CVS_PVE_POS;
60
61    /**
62     * The child view's start can be equal to its parent view's end. This flag follows with GT
63     * or LT indicating greater (less) than or equal relation.
64     */
65    static final int FLAG_CVS_EQ_PVE = EQ << CVS_PVE_POS;
66
67    /**
68     * The child view's start should be strictly less than parent view's end.
69     */
70    static final int FLAG_CVS_LT_PVE = LT << CVS_PVE_POS;
71
72
73    static final int CVE_PVS_POS = 8;
74    /**
75     * The child view's end should be strictly greater than parent view's start.
76     */
77    static final int FLAG_CVE_GT_PVS = GT << CVE_PVS_POS;
78
79    /**
80     * The child view's end can be equal to its parent view's start. This flag follows with GT
81     * or LT indicating greater (less) than or equal relation.
82     */
83    static final int FLAG_CVE_EQ_PVS = EQ << CVE_PVS_POS;
84
85    /**
86     * The child view's end should be strictly less than parent view's start.
87     */
88    static final int FLAG_CVE_LT_PVS = LT << CVE_PVS_POS;
89
90
91    static final int CVE_PVE_POS = 12;
92    /**
93     * The child view's end should be strictly greater than parent view's end.
94     */
95    static final int FLAG_CVE_GT_PVE = GT << CVE_PVE_POS;
96
97    /**
98     * The child view's end can be equal to its parent view's end. This flag follows with GT
99     * or LT indicating greater (less) than or equal relation.
100     */
101    static final int FLAG_CVE_EQ_PVE = EQ << CVE_PVE_POS;
102
103    /**
104     * The child view's end should be strictly less than parent view's end.
105     */
106    static final int FLAG_CVE_LT_PVE = LT << CVE_PVE_POS;
107
108    static final int MASK = GT | EQ | LT;
109
110    final Callback mCallback;
111    BoundFlags mBoundFlags;
112    /**
113     * The set of flags that can be passed for checking the view boundary conditions.
114     * CVS in the flag name indicates the child view, and PV indicates the parent view.\
115     * The following S, E indicate a view's start and end points, respectively.
116     * GT and LT indicate a strictly greater and less than relationship.
117     * Greater than or equal (or less than or equal) can be specified by setting both GT and EQ (or
118     * LT and EQ) flags.
119     * For instance, setting both {@link #FLAG_CVS_GT_PVS} and {@link #FLAG_CVS_EQ_PVS} indicate the
120     * child view's start should be greater than or equal to its parent start.
121     */
122    @IntDef(flag = true, value = {
123            FLAG_CVS_GT_PVS, FLAG_CVS_EQ_PVS, FLAG_CVS_LT_PVS,
124            FLAG_CVS_GT_PVE, FLAG_CVS_EQ_PVE, FLAG_CVS_LT_PVE,
125            FLAG_CVE_GT_PVS, FLAG_CVE_EQ_PVS, FLAG_CVE_LT_PVS,
126            FLAG_CVE_GT_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE
127    })
128    @Retention(RetentionPolicy.SOURCE)
129    public @interface ViewBounds {}
130
131    ViewBoundsCheck(Callback callback) {
132        mCallback = callback;
133        mBoundFlags = new BoundFlags();
134    }
135
136    static class BoundFlags {
137        int mBoundFlags = 0;
138        int mRvStart, mRvEnd, mChildStart, mChildEnd;
139
140        void setBounds(int rvStart, int rvEnd, int childStart, int childEnd) {
141            mRvStart = rvStart;
142            mRvEnd = rvEnd;
143            mChildStart = childStart;
144            mChildEnd = childEnd;
145        }
146
147        void setFlags(@ViewBounds int flags, int mask) {
148            mBoundFlags = (mBoundFlags & ~mask) | (flags & mask);
149        }
150
151        void addFlags(@ViewBounds int flags) {
152            mBoundFlags |= flags;
153        }
154
155        void resetFlags() {
156            mBoundFlags = 0;
157        }
158
159        int compare(int x, int y) {
160            if (x > y) {
161                return GT;
162            }
163            if (x == y) {
164                return EQ;
165            }
166            return LT;
167        }
168
169        boolean boundsMatch() {
170            if ((mBoundFlags & (MASK << CVS_PVS_POS)) != 0) {
171                if ((mBoundFlags & (compare(mChildStart, mRvStart) << CVS_PVS_POS)) == 0) {
172                    return false;
173                }
174            }
175
176            if ((mBoundFlags & (MASK << CVS_PVE_POS)) != 0) {
177                if ((mBoundFlags & (compare(mChildStart, mRvEnd) << CVS_PVE_POS)) == 0) {
178                    return false;
179                }
180            }
181
182            if ((mBoundFlags & (MASK << CVE_PVS_POS)) != 0) {
183                if ((mBoundFlags & (compare(mChildEnd, mRvStart) << CVE_PVS_POS)) == 0) {
184                    return false;
185                }
186            }
187
188            if ((mBoundFlags & (MASK << CVE_PVE_POS)) != 0) {
189                if ((mBoundFlags & (compare(mChildEnd, mRvEnd) << CVE_PVE_POS)) == 0) {
190                    return false;
191                }
192            }
193            return true;
194        }
195    };
196
197    /**
198     * Returns the first view starting from fromIndex to toIndex in views whose bounds lie within
199     * its parent bounds based on the provided preferredBoundFlags. If no match is found based on
200     * the preferred flags, and a nonzero acceptableBoundFlags is specified, the last view whose
201     * bounds lie within its parent view based on the acceptableBoundFlags is returned. If no such
202     * view is found based on either of these two flags, null is returned.
203     * @param fromIndex The view position index to start the search from.
204     * @param toIndex The view position index to end the search at.
205     * @param preferredBoundFlags The flags indicating the preferred match. Once a match is found
206     *                            based on this flag, that view is returned instantly.
207     * @param acceptableBoundFlags The flags indicating the acceptable match if no preferred match
208     *                             is found. If so, and if acceptableBoundFlags is non-zero, the
209     *                             last matching acceptable view is returned. Otherwise, null is
210     *                             returned.
211     * @return The first view that satisfies acceptableBoundFlags or the last view satisfying
212     * acceptableBoundFlags boundary conditions.
213     */
214    View findOneViewWithinBoundFlags(int fromIndex, int toIndex,
215            @ViewBounds int preferredBoundFlags,
216            @ViewBounds int acceptableBoundFlags) {
217        final int start = mCallback.getParentStart();
218        final int end = mCallback.getParentEnd();
219        final int next = toIndex > fromIndex ? 1 : -1;
220        View acceptableMatch = null;
221        for (int i = fromIndex; i != toIndex; i += next) {
222            final View child = mCallback.getChildAt(i);
223            final int childStart = mCallback.getChildStart(child);
224            final int childEnd = mCallback.getChildEnd(child);
225            mBoundFlags.setBounds(start, end, childStart, childEnd);
226            if (preferredBoundFlags != 0) {
227                mBoundFlags.resetFlags();
228                mBoundFlags.addFlags(preferredBoundFlags);
229                if (mBoundFlags.boundsMatch()) {
230                    // found a perfect match
231                    return child;
232                }
233            }
234            if (acceptableBoundFlags != 0) {
235                mBoundFlags.resetFlags();
236                mBoundFlags.addFlags(acceptableBoundFlags);
237                if (mBoundFlags.boundsMatch()) {
238                    acceptableMatch = child;
239                }
240            }
241        }
242        return acceptableMatch;
243    }
244
245    /**
246     * Returns whether the specified view lies within the boundary condition of its parent view.
247     * @param child The child view to be checked.
248     * @param boundsFlags The flag against which the child view and parent view are matched.
249     * @return True if the view meets the boundsFlag, false otherwise.
250     */
251    boolean isViewWithinBoundFlags(View child, @ViewBounds int boundsFlags) {
252        mBoundFlags.setBounds(mCallback.getParentStart(), mCallback.getParentEnd(),
253                mCallback.getChildStart(child), mCallback.getChildEnd(child));
254        if (boundsFlags != 0) {
255            mBoundFlags.resetFlags();
256            mBoundFlags.addFlags(boundsFlags);
257            return mBoundFlags.boundsMatch();
258        }
259        return false;
260    }
261
262    /**
263     * Callback provided by the user of this class in order to retrieve information about child and
264     * parent boundaries.
265     */
266    interface Callback {
267        int getChildCount();
268        View getParent();
269        View getChildAt(int index);
270        int getParentStart();
271        int getParentEnd();
272        int getChildStart(View view);
273        int getChildEnd(View view);
274    }
275}
276