• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package com.android.launcher3.celllayout;
17 
18 import android.view.View;
19 
20 import com.android.launcher3.CellLayout;
21 
22 /**
23  * Contains the logic of a reorder.
24  *
25  * The content of this class was extracted from {@link CellLayout} and should mimic the exact
26  * same behaviour.
27  */
28 public class ReorderAlgorithm {
29 
30     CellLayout mCellLayout;
31 
ReorderAlgorithm(CellLayout cellLayout)32     public ReorderAlgorithm(CellLayout cellLayout) {
33         mCellLayout = cellLayout;
34     }
35 
36     /**
37      * This method differs from closestEmptySpaceReorder and dropInPlaceSolution because this method
38      * will move items around and will change the shape of the item if possible to try to find a
39      * solution.
40      *
41      * When changing the size of the widget this method will try first subtracting -1 in the x
42      * dimension and then subtracting -1 in the y dimension until finding a possible solution or
43      * until it no longer can reduce the span.
44      *
45      * @param pixelX    X coordinate in pixels in the screen
46      * @param pixelY    Y coordinate in pixels in the screen
47      * @param minSpanX  minimum possible horizontal span it will try to find a solution for.
48      * @param minSpanY  minimum possible vertical span it will try to find a solution for.
49      * @param spanX     horizontal cell span
50      * @param spanY     vertical cell span
51      * @param direction direction in which it will try to push the items intersecting the desired
52      *                  view
53      * @param dragView  view being dragged in reorder
54      * @param decX      whether it will decrease the horizontal or vertical span if it can't find a
55      *                  solution for the current span.
56      * @param solution  variable to store the solution
57      * @return the same solution variable
58      */
findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, CellLayout.ItemConfiguration solution)59     public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
60             int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
61             CellLayout.ItemConfiguration solution) {
62         // Copy the current state into the solution. This solution will be manipulated as necessary.
63         mCellLayout.copyCurrentStateToSolution(solution, false);
64         // Copy the current occupied array into the temporary occupied array. This array will be
65         // manipulated as necessary to find a solution.
66         mCellLayout.getOccupied().copyTo(mCellLayout.mTmpOccupied);
67 
68         // We find the nearest cell into which we would place the dragged item, assuming there's
69         // nothing in its way.
70         int[] result = new int[2];
71         result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
72 
73         boolean success;
74         // First we try the exact nearest position of the item being dragged,
75         // we will then want to try to move this around to other neighbouring positions
76         success = mCellLayout.rearrangementExists(result[0], result[1], spanX, spanY, direction,
77                 dragView, solution);
78 
79         if (!success) {
80             // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
81             // x, then 1 in y etc.
82             if (spanX > minSpanX && (minSpanY == spanY || decX)) {
83                 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
84                         direction, dragView, false, solution);
85             } else if (spanY > minSpanY) {
86                 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
87                         direction, dragView, true, solution);
88             }
89             solution.isSolution = false;
90         } else {
91             solution.isSolution = true;
92             solution.cellX = result[0];
93             solution.cellY = result[1];
94             solution.spanX = spanX;
95             solution.spanY = spanY;
96         }
97         return solution;
98     }
99 
100     /**
101      * Returns a "reorder" if there is empty space without rearranging anything.
102      *
103      * @param pixelX   X coordinate in pixels in the screen
104      * @param pixelY   Y coordinate in pixels in the screen
105      * @param spanX    horizontal cell span
106      * @param spanY    vertical cell span
107      * @param dragView view being dragged in reorder
108      * @return the configuration that represents the found reorder
109      */
dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY, View dragView)110     public CellLayout.ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
111             int spanY, View dragView) {
112         int[] result = new int[2];
113         if (mCellLayout.isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView,
114                 result)) {
115             result[0] = result[1] = -1;
116         }
117         CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
118         mCellLayout.copyCurrentStateToSolution(solution, false);
119         solution.isSolution = result[0] != -1;
120         if (!solution.isSolution) {
121             return solution;
122         }
123         solution.cellX = result[0];
124         solution.cellY = result[1];
125         solution.spanX = spanX;
126         solution.spanY = spanY;
127         return solution;
128     }
129 
130     /**
131      * Returns a "reorder" where we simply drop the item in the closest empty space, without moving
132      * any other item in the way.
133      *
134      * @param pixelX X coordinate in pixels in the screen
135      * @param pixelY Y coordinate in pixels in the screen
136      * @param spanX  horizontal cell span
137      * @param spanY  vertical cell span
138      * @return the configuration that represents the found reorder
139      */
closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY)140     public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
141             int minSpanX, int minSpanY, int spanX, int spanY) {
142         CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
143         int[] result = new int[2];
144         int[] resultSpan = new int[2];
145         mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
146                 resultSpan);
147         if (result[0] >= 0 && result[1] >= 0) {
148             mCellLayout.copyCurrentStateToSolution(solution, false);
149             solution.cellX = result[0];
150             solution.cellY = result[1];
151             solution.spanX = resultSpan[0];
152             solution.spanY = resultSpan[1];
153             solution.isSolution = true;
154         } else {
155             solution.isSolution = false;
156         }
157         return solution;
158     }
159 
160     /**
161      * When the user drags an Item in the workspace sometimes we need to move the items already in
162      * the workspace to make space for the new item, this function return a solution for that
163      * reorder.
164      *
165      * @param pixelX   X coordinate in the screen of the dragView in pixels
166      * @param pixelY   Y coordinate in the screen of the dragView in pixels
167      * @param minSpanX minimum horizontal span the item can be shrunk to
168      * @param minSpanY minimum vertical span the item can be shrunk to
169      * @param spanX    occupied horizontal span
170      * @param spanY    occupied vertical span
171      * @param dragView the view of the item being draged
172      * @return returns a solution for the given parameters, the solution contains all the icons and
173      * the locations they should be in the given solution.
174      */
calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView)175     public CellLayout.ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX,
176             int minSpanY, int spanX, int spanY, View dragView) {
177         mCellLayout.getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView,
178                 mCellLayout.mDirectionVector);
179 
180         CellLayout.ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY,
181                 spanX, spanY,
182                 dragView);
183 
184         // Find a solution involving pushing / displacing any items in the way
185         CellLayout.ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX,
186                 minSpanY, spanX, spanY, mCellLayout.mDirectionVector, dragView, true,
187                 new CellLayout.ItemConfiguration());
188 
189         // We attempt the approach which doesn't shuffle views at all
190         CellLayout.ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(
191                 pixelX, pixelY, minSpanX, minSpanY, spanX, spanY);
192 
193         // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
194         // favor a solution in which the item is not resized, but
195         if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) {
196             return swapSolution;
197         } else if (closestSpaceSolution.isSolution) {
198             return closestSpaceSolution;
199         } else if (dropInPlaceSolution.isSolution) {
200             return dropInPlaceSolution;
201         }
202         return null;
203     }
204 }
205