TaskStackViewLayoutAlgorithm.java revision 012ef36a6c5e9745d112c734aed916cab052558c
1/*
2 * Copyright (C) 2014 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 com.android.systemui.recents.views;
18
19import android.graphics.Rect;
20import com.android.systemui.recents.RecentsConfiguration;
21import com.android.systemui.recents.misc.Console;
22import com.android.systemui.recents.misc.Utilities;
23import com.android.systemui.recents.model.Task;
24
25import java.util.ArrayList;
26import java.util.HashMap;
27
28/* The layout logic for a TaskStackView.
29 *
30 * We are using a curve that defines the curve of the tasks as that go back in the recents list.
31 * The curve is defined such that at curve progress p = 0 is the end of the curve (the top of the
32 * stack rect), and p = 1 at the start of the curve and the bottom of the stack rect.
33 */
34public class TaskStackViewLayoutAlgorithm {
35
36    // These are all going to change
37    static final float StackPeekMinScale = 0.825f; // The min scale of the last card in the peek area
38
39    RecentsConfiguration mConfig;
40
41    // The various rects that define the stack view
42    Rect mViewRect = new Rect();
43    Rect mStackVisibleRect = new Rect();
44    Rect mStackRect = new Rect();
45    Rect mTaskRect = new Rect();
46
47    // The min/max scroll progress
48    float mMinScrollP;
49    float mMaxScrollP;
50    float mInitialScrollP;
51    int mWithinAffiliationOffset;
52    int mBetweenAffiliationOffset;
53    HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<Task.TaskKey, Float>();
54
55    // Log function
56    static final float XScale = 1.75f;  // The large the XScale, the longer the flat area of the curve
57    static final float LogBase = 300;
58    static final int PrecisionSteps = 250;
59    static float[] xp;
60    static float[] px;
61
62    public TaskStackViewLayoutAlgorithm(RecentsConfiguration config) {
63        mConfig = config;
64        mWithinAffiliationOffset = mConfig.taskBarHeight;
65        mBetweenAffiliationOffset = 4 * mConfig.taskBarHeight;
66
67        // Precompute the path
68        initializeCurve();
69    }
70
71    /** Computes the stack and task rects */
72    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
73        // Compute the stack rects
74        mViewRect.set(0, 0, windowWidth, windowHeight);
75        mStackRect.set(taskStackBounds);
76        mStackVisibleRect.set(taskStackBounds);
77        mStackVisibleRect.bottom = mViewRect.bottom;
78
79        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
80        int heightPadding = mConfig.taskStackTopPaddingPx;
81        mStackRect.inset(widthPadding, heightPadding);
82
83        // Compute the task rect
84        int size = mStackRect.width();
85        int left = mStackRect.left + (mStackRect.width() - size) / 2;
86        mTaskRect.set(left, mStackRect.top,
87                left + size, mStackRect.top + size);
88    }
89
90    /** Computes the minimum and maximum scroll progress values */
91    void computeMinMaxScroll(ArrayList<Task> tasks) {
92        // Clear the progress map
93        mTaskProgressMap.clear();
94
95        // Return early if we have no tasks
96        if (tasks.isEmpty()) {
97            mMinScrollP = mMaxScrollP = 0;
98            return;
99        }
100
101        int taskHeight = mTaskRect.height();
102        float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);
103        float pWithinAffiliateOffset = pAtBottomOfStackRect -
104                screenYToCurveProgress(mStackVisibleRect.bottom - mWithinAffiliationOffset);
105        float pBetweenAffiliateOffset = pAtBottomOfStackRect -
106                screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);
107        float pTaskHeightOffset = pAtBottomOfStackRect -
108                screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
109        float pNavBarOffset = pAtBottomOfStackRect -
110                screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom - mStackRect.bottom));
111
112        // Update the task offsets
113        float pAtBackMostCardTop = screenYToCurveProgress(mStackVisibleRect.top +
114                (mStackVisibleRect.height() - taskHeight) / 2);
115        float pAtFrontMostCardTop = pAtBackMostCardTop;
116        float pAtSecondFrontMostCardTop = pAtBackMostCardTop;
117        int taskCount = tasks.size();
118        for (int i = 0; i < taskCount; i++) {
119            Task task = tasks.get(i);
120            mTaskProgressMap.put(task.key, pAtFrontMostCardTop);
121
122            if (i < (taskCount - 1)) {
123                // Increment the peek height
124                float pPeek = task.group.isFrontMostTask(task) ? pBetweenAffiliateOffset :
125                    pWithinAffiliateOffset;
126                pAtSecondFrontMostCardTop = pAtFrontMostCardTop;
127                pAtFrontMostCardTop += pPeek;
128            }
129        }
130
131        mMinScrollP = 0f;
132        mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
133        mInitialScrollP = pAtSecondFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
134    }
135
136    /** Update/get the transform */
137    public TaskViewTransform getStackTransform(Task task, float stackScroll, TaskViewTransform transformOut,
138            TaskViewTransform prevTransform) {
139        // Return early if we have an invalid index
140        if (task == null) {
141            transformOut.reset();
142            return transformOut;
143        }
144        return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut, prevTransform);
145    }
146
147    /** Update/get the transform */
148    public TaskViewTransform getStackTransform(float taskProgress, float stackScroll, TaskViewTransform transformOut, TaskViewTransform prevTransform) {
149        float pTaskRelative = taskProgress - stackScroll;
150        float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
151        // If the task top is outside of the bounds below the screen, then immediately reset it
152        if (pTaskRelative > 1f) {
153            transformOut.reset();
154            return transformOut;
155        }
156        // The check for the top is trickier, since we want to show the next task if it is at all
157        // visible, even if p < 0.
158        if (pTaskRelative < 0f) {
159            if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
160                transformOut.reset();
161                return transformOut;
162            }
163        }
164        float scale = curveProgressToScale(pBounded);
165        int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
166        int minZ = mConfig.taskViewTranslationZMinPx;
167        int maxZ = mConfig.taskViewTranslationZMaxPx;
168        transformOut.scale = scale;
169        transformOut.translationY = curveProgressToScreenY(pBounded) - mStackVisibleRect.top -
170                scaleYOffset;
171        transformOut.translationZ = Math.max(minZ, minZ + (pBounded * (maxZ - minZ)));
172        transformOut.rect.set(mTaskRect);
173        transformOut.rect.offset(0, transformOut.translationY);
174        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
175        transformOut.visible = true;
176        transformOut.p = pTaskRelative;
177        return transformOut;
178    }
179
180    /**
181     * Returns the scroll to such task top = 1f;
182     */
183    float getStackScrollForTaskIndex(Task t) {
184        return mTaskProgressMap.get(t.key);
185    }
186
187    /** Initializes the curve. */
188    public static void initializeCurve() {
189        if (xp != null && px != null) return;
190        xp = new float[PrecisionSteps + 1];
191        px = new float[PrecisionSteps + 1];
192
193        // Approximate f(x)
194        float[] fx = new float[PrecisionSteps + 1];
195        float step = 1f / PrecisionSteps;
196        float x = 0;
197        for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
198            fx[xStep] = logFunc(x);
199            x += step;
200        }
201        // Calculate the arc length for x:1->0
202        float pLength = 0;
203        float[] dx = new float[PrecisionSteps + 1];
204        dx[0] = 0;
205        for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
206            dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2));
207            pLength += dx[xStep];
208        }
209        // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
210        float p = 0;
211        px[0] = 0f;
212        px[PrecisionSteps] = 1f;
213        for (int xStep = 1; xStep <= PrecisionSteps; xStep++) {
214            p += Math.abs(dx[xStep] / pLength);
215            px[xStep] = p;
216        }
217        // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
218        // function.
219        int xStep = 0;
220        p = 0;
221        xp[0] = 0f;
222        xp[PrecisionSteps] = 1f;
223        for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
224            // Walk forward in px and find the x where px <= p && p < px+1
225            while (xStep < PrecisionSteps) {
226                if (px[xStep] > p) break;
227                xStep++;
228            }
229            // Now, px[xStep-1] <= p < px[xStep]
230            if (xStep == 0) {
231                xp[pStep] = 0;
232            } else {
233                // Find x such that proportionally, x is correct
234                float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
235                x = (xStep - 1 + fraction) * step;
236                xp[pStep] = x;
237            }
238            p += step;
239        }
240    }
241
242    /** Reverses and scales out x. */
243    static float reverse(float x) {
244        return (-x * XScale) + 1;
245    }
246    /** The log function describing the curve. */
247    static float logFunc(float x) {
248        return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
249    }
250    /** The inverse of the log function describing the curve. */
251    float invLogFunc(float y) {
252        return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase));
253    }
254
255    /** Converts from the progress along the curve to a screen coordinate. */
256    int curveProgressToScreenY(float p) {
257        if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height());
258        float pIndex = p * PrecisionSteps;
259        int pFloorIndex = (int) Math.floor(pIndex);
260        int pCeilIndex = (int) Math.ceil(pIndex);
261        float xFraction = 0;
262        if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
263            float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex);
264            xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction;
265        }
266        float x = xp[pFloorIndex] + xFraction;
267        return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height());
268    }
269
270    /** Converts from the progress along the curve to a scale. */
271    float curveProgressToScale(float p) {
272        if (p < 0) return StackPeekMinScale;
273        if (p > 1) return 1f;
274        float scaleRange = (1f - StackPeekMinScale);
275        float scale = StackPeekMinScale + (p * scaleRange);
276        return scale;
277    }
278
279    /** Converts from a screen coordinate to the progress along the curve. */
280    float screenYToCurveProgress(int screenY) {
281        float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height();
282        if (x < 0 || x > 1) return x;
283        float xIndex = x * PrecisionSteps;
284        int xFloorIndex = (int) Math.floor(xIndex);
285        int xCeilIndex = (int) Math.ceil(xIndex);
286        float pFraction = 0;
287        if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
288            float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex);
289            pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction;
290        }
291        return px[xFloorIndex] + pFraction;
292    }
293}