/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.celllayout; import android.view.View; import com.android.launcher3.CellLayout; /** * Contains the logic of a reorder. * * The content of this class was extracted from {@link CellLayout} and should mimic the exact * same behaviour. */ public class ReorderAlgorithm { CellLayout mCellLayout; public ReorderAlgorithm(CellLayout cellLayout) { mCellLayout = cellLayout; } /** * This method differs from closestEmptySpaceReorder and dropInPlaceSolution because this method * will move items around and will change the shape of the item if possible to try to find a * solution. * * When changing the size of the widget this method will try first subtracting -1 in the x * dimension and then subtracting -1 in the y dimension until finding a possible solution or * until it no longer can reduce the span. * * @param pixelX X coordinate in pixels in the screen * @param pixelY Y coordinate in pixels in the screen * @param minSpanX minimum possible horizontal span it will try to find a solution for. * @param minSpanY minimum possible vertical span it will try to find a solution for. * @param spanX horizontal cell span * @param spanY vertical cell span * @param direction direction in which it will try to push the items intersecting the desired * view * @param dragView view being dragged in reorder * @param decX whether it will decrease the horizontal or vertical span if it can't find a * solution for the current span. * @param solution variable to store the solution * @return the same solution variable */ public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, CellLayout.ItemConfiguration solution) { // Copy the current state into the solution. This solution will be manipulated as necessary. mCellLayout.copyCurrentStateToSolution(solution, false); // Copy the current occupied array into the temporary occupied array. This array will be // manipulated as necessary to find a solution. mCellLayout.getOccupied().copyTo(mCellLayout.mTmpOccupied); // We find the nearest cell into which we would place the dragged item, assuming there's // nothing in its way. int[] result = new int[2]; result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result); boolean success; // First we try the exact nearest position of the item being dragged, // we will then want to try to move this around to other neighbouring positions success = mCellLayout.rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView, solution); if (!success) { // We try shrinking the widget down to size in an alternating pattern, shrink 1 in // x, then 1 in y etc. if (spanX > minSpanX && (minSpanY == spanY || decX)) { return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction, dragView, false, solution); } else if (spanY > minSpanY) { return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction, dragView, true, solution); } solution.isSolution = false; } else { solution.isSolution = true; solution.cellX = result[0]; solution.cellY = result[1]; solution.spanX = spanX; solution.spanY = spanY; } return solution; } /** * Returns a "reorder" if there is empty space without rearranging anything. * * @param pixelX X coordinate in pixels in the screen * @param pixelY Y coordinate in pixels in the screen * @param spanX horizontal cell span * @param spanY vertical cell span * @param dragView view being dragged in reorder * @return the configuration that represents the found reorder */ public CellLayout.ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY, View dragView) { int[] result = new int[2]; if (mCellLayout.isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView, result)) { result[0] = result[1] = -1; } CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration(); mCellLayout.copyCurrentStateToSolution(solution, false); solution.isSolution = result[0] != -1; if (!solution.isSolution) { return solution; } solution.cellX = result[0]; solution.cellY = result[1]; solution.spanX = spanX; solution.spanY = spanY; return solution; } /** * Returns a "reorder" where we simply drop the item in the closest empty space, without moving * any other item in the way. * * @param pixelX X coordinate in pixels in the screen * @param pixelY Y coordinate in pixels in the screen * @param spanX horizontal cell span * @param spanY vertical cell span * @return the configuration that represents the found reorder */ public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY) { CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration(); int[] result = new int[2]; int[] resultSpan = new int[2]; mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result, resultSpan); if (result[0] >= 0 && result[1] >= 0) { mCellLayout.copyCurrentStateToSolution(solution, false); solution.cellX = result[0]; solution.cellY = result[1]; solution.spanX = resultSpan[0]; solution.spanY = resultSpan[1]; solution.isSolution = true; } else { solution.isSolution = false; } return solution; } /** * When the user drags an Item in the workspace sometimes we need to move the items already in * the workspace to make space for the new item, this function return a solution for that * reorder. * * @param pixelX X coordinate in the screen of the dragView in pixels * @param pixelY Y coordinate in the screen of the dragView in pixels * @param minSpanX minimum horizontal span the item can be shrunk to * @param minSpanY minimum vertical span the item can be shrunk to * @param spanX occupied horizontal span * @param spanY occupied vertical span * @param dragView the view of the item being draged * @return returns a solution for the given parameters, the solution contains all the icons and * the locations they should be in the given solution. */ public CellLayout.ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView) { mCellLayout.getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mCellLayout.mDirectionVector); CellLayout.ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView); // Find a solution involving pushing / displacing any items in the way CellLayout.ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, mCellLayout.mDirectionVector, dragView, true, new CellLayout.ItemConfiguration()); // We attempt the approach which doesn't shuffle views at all CellLayout.ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder( pixelX, pixelY, minSpanX, minSpanY, spanX, spanY); // If the reorder solution requires resizing (shrinking) the item being dropped, we instead // favor a solution in which the item is not resized, but if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) { return swapSolution; } else if (closestSpaceSolution.isSolution) { return closestSpaceSolution; } else if (dropInPlaceSolution.isSolution) { return dropInPlaceSolution; } return null; } }