blob: 5e5eefe6022073cf9858221eda9674d234c7e07a [file] [log] [blame]
/*
* 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;
}
}