diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 731ad7444a..38b0e0818c 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -65,6 +65,7 @@ import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.celllayout.CellLayoutLayoutParams; import com.android.launcher3.celllayout.CellPosMapper.CellPos; +import com.android.launcher3.celllayout.ReorderAlgorithm; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.folder.PreviewBackground; @@ -115,7 +116,7 @@ public class CellLayout extends ViewGroup { final PointF mTmpPointF = new PointF(); protected GridOccupancy mOccupied; - protected GridOccupancy mTmpOccupied; + public GridOccupancy mTmpOccupied; private OnTouchListener mInterceptTouchListener; @@ -194,7 +195,7 @@ public class CellLayout extends ViewGroup { private final ArrayList mIntersectingViews = new ArrayList<>(); private final Rect mOccupiedRect = new Rect(); - private final int[] mDirectionVector = new int[2]; + public final int[] mDirectionVector = new int[2]; ItemConfiguration mPreviousSolution = null; private static final int INVALID_DIRECTION = -100; @@ -1246,8 +1247,8 @@ public class CellLayout extends ViewGroup { * @return The X, Y cell of a vacant area that can contain this object, * nearest the requested location. */ - int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, - int spanY, int[] result, int[] resultSpan) { + public int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, + int spanX, int spanY, int[] result, int[] resultSpan) { return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, false, result, resultSpan); } @@ -1380,6 +1381,10 @@ public class CellLayout extends ViewGroup { return bestXY; } + public GridOccupancy getOccupied() { + return mOccupied; + } + private void copySolutionToTempState(ItemConfiguration solution, View dragView) { mTmpOccupied.clear(); @@ -1655,38 +1660,8 @@ public class CellLayout extends ViewGroup { } } - /** - * 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 - */ - ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, - int minSpanY, int spanX, int spanY) { - ItemConfiguration solution = new ItemConfiguration(); - int[] result = new int[2]; - int[] resultSpan = new int[2]; - findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result, - resultSpan); - if (result[0] >= 0 && result[1] >= 0) { - 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; - } - // For a given cell and span, fetch the set of views intersecting the region. - private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY, + public void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY, View dragView, Rect boundingRect, ArrayList intersectingViews) { if (boundingRect != null) { boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY); @@ -1711,7 +1686,7 @@ public class CellLayout extends ViewGroup { } } - boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY, + public boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY, View dragView, int[] result) { result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result); getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null, @@ -2257,7 +2232,7 @@ public class CellLayout extends ViewGroup { those cells. Instead we use some heuristics to often lock the vector to up, down, left or right, which helps make pushing feel right. */ - private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX, + public void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX, int spanY, View dragView, int[] resultDirection) { //TODO(adamcohen) b/151776141 use the items visual center for the direction vector @@ -2349,7 +2324,7 @@ public class CellLayout extends ViewGroup { return success; } - private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction, + public boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction, View ignoreView, ItemConfiguration solution) { // Return early if get invalid cell positions if (cellX < 0 || cellY < 0) return false; @@ -2405,55 +2380,18 @@ public class CellLayout extends ViewGroup { return true; } + public ReorderAlgorithm createReorderAlgorithm() { + return new ReorderAlgorithm(this); + } + protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) { - return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, - direction, dragView, decX, solution); + return createReorderAlgorithm().findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, + spanX, spanY, direction, dragView, decX, solution); } - protected ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY, int minSpanX, - int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, - ItemConfiguration solution) { - // Copy the current state into the solution. This solution will be manipulated as necessary. - copyCurrentStateToSolution(solution, false); - // Copy the current occupied array into the temporary occupied array. This array will be - // manipulated as necessary to find a solution. - mOccupied.copyTo(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 = 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 = 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 findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, - spanY, direction, dragView, false, solution); - } else if (spanY > minSpanY) { - return findReorderSolutionRecursive(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; - } - - protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) { + public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) { int childCount = mShortcutsAndWidgets.getChildCount(); for (int i = 0; i < childCount; i++) { View child = mShortcutsAndWidgets.getChildAt(i); @@ -2468,35 +2406,6 @@ public class CellLayout extends ViewGroup { } } - /** - * 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 ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, - int spanY, View dragView) { - int[] result = new int[2]; - if (isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView, result)) { - result[0] = result[1] = -1; - } - ItemConfiguration solution = new ItemConfiguration(); - 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; - } - /** * 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 @@ -2514,29 +2423,8 @@ public class CellLayout extends ViewGroup { */ public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView) { - getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector); - - ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY, spanX, spanY, - dragView); - - // Find a solution involving pushing / displacing any items in the way - ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, - spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration()); - - // We attempt the approach which doesn't shuffle views at all - 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; + return createReorderAlgorithm().calculateReorder(pixelX, pixelY, minSpanX, minSpanY, + spanX, spanY, dragView); } int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, @@ -2588,7 +2476,7 @@ public class CellLayout extends ViewGroup { * {@link MODE_ON_DROP}, {@link MODE_ON_DROP_EXTERNAL}, {@link MODE_ACCEPT_DROP} * defined in {@link CellLayout}. */ - void performReorder(ItemConfiguration solution, View dragView, int mode) { + public void performReorder(ItemConfiguration solution, View dragView, int mode) { if (mode == MODE_SHOW_REORDER_HINT) { beginOrAdjustReorderPreviewAnimations(solution, dragView, ReorderPreviewAnimation.MODE_HINT); @@ -2634,38 +2522,41 @@ public class CellLayout extends ViewGroup { return mItemPlacementDirty; } - static class ItemConfiguration extends CellAndSpan { - final ArrayMap map = new ArrayMap<>(); + /** + * Represents the solution to a reorder of items in the Workspace. + */ + public static class ItemConfiguration extends CellAndSpan { + public final ArrayMap map = new ArrayMap<>(); private final ArrayMap savedMap = new ArrayMap<>(); - final ArrayList sortedViews = new ArrayList<>(); - ArrayList intersectingViews; - boolean isSolution = false; + public final ArrayList sortedViews = new ArrayList<>(); + public ArrayList intersectingViews; + public boolean isSolution = false; - void save() { + public void save() { // Copy current state into savedMap for (View v: map.keySet()) { savedMap.get(v).copyFrom(map.get(v)); } } - void restore() { + public void restore() { // Restore current state from savedMap for (View v: savedMap.keySet()) { map.get(v).copyFrom(savedMap.get(v)); } } - void add(View v, CellAndSpan cs) { + public void add(View v, CellAndSpan cs) { map.put(v, cs); savedMap.put(v, new CellAndSpan()); sortedViews.add(v); } - int area() { + public int area() { return spanX * spanY; } - void getBoundingRectForViews(ArrayList views, Rect outRect) { + public void getBoundingRectForViews(ArrayList views, Rect outRect) { boolean first = true; for (View v: views) { CellAndSpan c = map.get(v); diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java index 6a518a79e7..12cb35d18d 100644 --- a/src/com/android/launcher3/MultipageCellLayout.java +++ b/src/com/android/launcher3/MultipageCellLayout.java @@ -23,11 +23,11 @@ import android.util.AttributeSet; import android.view.View; import com.android.launcher3.celllayout.CellLayoutLayoutParams; +import com.android.launcher3.celllayout.MulticellReorderAlgorithm; +import com.android.launcher3.celllayout.ReorderAlgorithm; import com.android.launcher3.util.CellAndSpan; import com.android.launcher3.util.GridOccupancy; -import java.util.function.Supplier; - /** * CellLayout that simulates a split in the middle for use in foldable devices. */ @@ -36,8 +36,6 @@ public class MultipageCellLayout extends CellLayout { private final Drawable mLeftBackground; private final Drawable mRightBackground; - private View mSeam; - private boolean mSeamWasAdded = false; public MultipageCellLayout(Context context) { @@ -62,7 +60,6 @@ public class MultipageCellLayout extends CellLayout { mCountX = deviceProfile.inv.numColumns * 2; mCountY = deviceProfile.inv.numRows; - mSeam = new View(getContext()); setGridSize(mCountX, mCountY); } @@ -74,90 +71,18 @@ public class MultipageCellLayout extends CellLayout { cellX++; } int finalCellX = cellX; - return simulateSeam( + return ((MulticellReorderAlgorithm) createReorderAlgorithm()).simulateSeam( () -> super.createAreaForResize(finalCellX, cellY, spanX, spanY, dragView, direction, commit)); } @Override - ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, - int spanX, int spanY) { - return removeSeamFromSolution(simulateSeam( - () -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX, - spanY))); + public ReorderAlgorithm createReorderAlgorithm() { + return new MulticellReorderAlgorithm(this); } @Override - protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, - int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, - ItemConfiguration solution) { - return removeSeamFromSolution(simulateSeam( - () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, - direction, dragView, decX, solution))); - } - - @Override - public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY, - View dragView) { - return removeSeamFromSolution(simulateSeam( - () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView))); - } - - void addSeam() { - CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mCountX / 2, 0, 1, mCountY); - mSeamWasAdded = true; - lp.canReorder = false; - mCountX++; - mShortcutsAndWidgets.addViewInLayout(mSeam, lp); - mOccupied = createGridOccupancyWithSeam(mOccupied); - mTmpOccupied = new GridOccupancy(mCountX, mCountY); - } - - void removeSeam() { - mCountX--; - mShortcutsAndWidgets.removeViewInLayout(mSeam); - mTmpOccupied = new GridOccupancy(mCountX, mCountY); - mSeamWasAdded = false; - } - - protected T simulateSeam(Supplier f) { - if (mSeamWasAdded) { - return f.get(); - } - GridOccupancy auxGrid = mOccupied; - addSeam(); - T res = f.get(); - removeSeam(); - mOccupied = auxGrid; - return res; - } - - private ItemConfiguration removeSeamFromSolution(ItemConfiguration solution) { - solution.map.forEach((view, cell) -> cell.cellX = cell.cellX > mCountX / 2 - ? cell.cellX - 1 : cell.cellX); - solution.cellX = solution.cellX > mCountX / 2 ? solution.cellX - 1 : solution.cellX; - return solution; - } - - - - GridOccupancy createGridOccupancyWithSeam(GridOccupancy gridOccupancy) { - GridOccupancy grid = new GridOccupancy(getCountX(), getCountY()); - for (int x = 0; x < getCountX(); x++) { - for (int y = 0; y < getCountY(); y++) { - int offset = x >= getCountX() / 2 ? 1 : 0; - if (x == getCountX() / 2) { - grid.cells[x][y] = true; - } else { - grid.cells[x][y] = gridOccupancy.cells[x - offset][y]; - } - } - } - return grid; - } - - @Override - protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) { + public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) { int childCount = mShortcutsAndWidgets.getChildCount(); for (int i = 0; i < childCount; i++) { View child = mShortcutsAndWidgets.getChildAt(i); @@ -196,4 +121,24 @@ public class MultipageCellLayout extends CellLayout { mLeftBackground.setBounds(rect.left, rect.top, rect.right / 2 - 20, rect.bottom); mRightBackground.setBounds(rect.right / 2 + 20, rect.top, rect.right, rect.bottom); } + + public void setCountX(int countX) { + mCountX = countX; + } + + public void setCountY(int countY) { + mCountY = countY; + } + + public void setOccupied(GridOccupancy occupied) { + mOccupied = occupied; + } + + public boolean isSeamWasAdded() { + return mSeamWasAdded; + } + + public void setSeamWasAdded(boolean seamWasAdded) { + mSeamWasAdded = seamWasAdded; + } } diff --git a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java new file mode 100644 index 0000000000..cb1216109a --- /dev/null +++ b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java @@ -0,0 +1,127 @@ +/* + * 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; +import com.android.launcher3.MultipageCellLayout; +import com.android.launcher3.util.GridOccupancy; + +import java.util.function.Supplier; + +/** + * Variant of ReorderAlgorithm which simulates a foldable screen and adds a seam in the middle + * to prevent items to be placed in the middle. + */ +public class MulticellReorderAlgorithm extends ReorderAlgorithm { + + private final View mSeam; + + public MulticellReorderAlgorithm(CellLayout cellLayout) { + super(cellLayout); + mSeam = new View(cellLayout.getContext()); + } + + private CellLayout.ItemConfiguration removeSeamFromSolution( + CellLayout.ItemConfiguration solution) { + solution.map.forEach((view, cell) -> cell.cellX = + cell.cellX > mCellLayout.getCountX() / 2 ? cell.cellX - 1 : cell.cellX); + solution.cellX = + solution.cellX > mCellLayout.getCountX() / 2 ? solution.cellX - 1 : solution.cellX; + return solution; + } + + @Override + public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, + int minSpanX, int minSpanY, + int spanX, int spanY) { + return removeSeamFromSolution(simulateSeam( + () -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX, + spanY))); + } + + @Override + 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) { + return removeSeamFromSolution(simulateSeam( + () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, + direction, dragView, decX, solution))); + } + + @Override + public CellLayout.ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, + int spanY, + View dragView) { + return removeSeamFromSolution(simulateSeam( + () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView))); + } + + void addSeam() { + MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout; + mcl.setSeamWasAdded(true); + CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mcl.getCountX() / 2, 0, 1, + mcl.getCountY()); + lp.canReorder = false; + mcl.setCountX(mcl.getCountX() + 1); + mcl.getShortcutsAndWidgets().addViewInLayout(mSeam, lp); + mcl.setOccupied(createGridOccupancyWithSeam(mcl.getOccupied())); + mcl.mTmpOccupied = new GridOccupancy(mcl.getCountX(), mcl.getCountY()); + } + + void removeSeam() { + MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout; + mcl.setCountX(mcl.getCountX() - 1); + mcl.getShortcutsAndWidgets().removeViewInLayout(mSeam); + mcl.mTmpOccupied = new GridOccupancy(mcl.getCountX(), mcl.getCountY()); + mcl.setSeamWasAdded(false); + } + + /** + * The function supplied here will execute while the CellLayout has a simulated seam added. + * @param f function to run under simulation + * @param return value of the supplied function + * @return Value of supplied function + */ + public T simulateSeam(Supplier f) { + MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout; + if (mcl.isSeamWasAdded()) { + return f.get(); + } + GridOccupancy auxGrid = mcl.getOccupied(); + addSeam(); + T res = f.get(); + removeSeam(); + mcl.setOccupied(auxGrid); + return res; + } + + GridOccupancy createGridOccupancyWithSeam(GridOccupancy gridOccupancy) { + GridOccupancy grid = new GridOccupancy(mCellLayout.getCountX(), mCellLayout.getCountY()); + for (int x = 0; x < mCellLayout.getCountX(); x++) { + for (int y = 0; y < mCellLayout.getCountY(); y++) { + int offset = x >= mCellLayout.getCountX() / 2 ? 1 : 0; + if (x == mCellLayout.getCountX() / 2) { + grid.cells[x][y] = true; + } else { + grid.cells[x][y] = gridOccupancy.cells[x - offset][y]; + } + } + } + return grid; + } +} diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java new file mode 100644 index 0000000000..5e5eefe602 --- /dev/null +++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java @@ -0,0 +1,204 @@ +/* + * 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; + } +}