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