Loading src/com/android/launcher3/CellLayout.java +35 −144 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -194,7 +195,7 @@ public class CellLayout extends ViewGroup { private final ArrayList<View> 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; Loading Loading @@ -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); } Loading Loading @@ -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(); Loading Loading @@ -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<View> intersectingViews) { if (boundingRect != null) { boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY); Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -2405,55 +2380,18 @@ public class CellLayout extends ViewGroup { return true; } 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); public ReorderAlgorithm createReorderAlgorithm() { return new ReorderAlgorithm(this); } protected ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY, int minSpanX, protected ItemConfiguration findReorderSolution(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; return createReorderAlgorithm().findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, direction, dragView, decX, 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); Loading @@ -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 Loading @@ -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, Loading Loading @@ -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); Loading Loading @@ -2634,38 +2522,41 @@ public class CellLayout extends ViewGroup { return mItemPlacementDirty; } static class ItemConfiguration extends CellAndSpan { final ArrayMap<View, CellAndSpan> map = new ArrayMap<>(); /** * Represents the solution to a reorder of items in the Workspace. */ public static class ItemConfiguration extends CellAndSpan { public final ArrayMap<View, CellAndSpan> map = new ArrayMap<>(); private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>(); final ArrayList<View> sortedViews = new ArrayList<>(); ArrayList<View> intersectingViews; boolean isSolution = false; public final ArrayList<View> sortedViews = new ArrayList<>(); public ArrayList<View> 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<View> views, Rect outRect) { public void getBoundingRectForViews(ArrayList<View> views, Rect outRect) { boolean first = true; for (View v: views) { CellAndSpan c = map.get(v); Loading src/com/android/launcher3/MultipageCellLayout.java +26 −81 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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) { Loading @@ -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); } Loading @@ -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))); } @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> T simulateSeam(Supplier<T> 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; public ReorderAlgorithm createReorderAlgorithm() { return new MulticellReorderAlgorithm(this); } @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); Loading Loading @@ -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; } } src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * 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 <T> return value of the supplied function * @return Value of supplied function */ public <T> T simulateSeam(Supplier<T> 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; } } src/com/android/launcher3/celllayout/ReorderAlgorithm.java 0 → 100644 +204 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
src/com/android/launcher3/CellLayout.java +35 −144 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -194,7 +195,7 @@ public class CellLayout extends ViewGroup { private final ArrayList<View> 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; Loading Loading @@ -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); } Loading Loading @@ -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(); Loading Loading @@ -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<View> intersectingViews) { if (boundingRect != null) { boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY); Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -2405,55 +2380,18 @@ public class CellLayout extends ViewGroup { return true; } 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); public ReorderAlgorithm createReorderAlgorithm() { return new ReorderAlgorithm(this); } protected ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY, int minSpanX, protected ItemConfiguration findReorderSolution(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; return createReorderAlgorithm().findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, direction, dragView, decX, 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); Loading @@ -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 Loading @@ -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, Loading Loading @@ -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); Loading Loading @@ -2634,38 +2522,41 @@ public class CellLayout extends ViewGroup { return mItemPlacementDirty; } static class ItemConfiguration extends CellAndSpan { final ArrayMap<View, CellAndSpan> map = new ArrayMap<>(); /** * Represents the solution to a reorder of items in the Workspace. */ public static class ItemConfiguration extends CellAndSpan { public final ArrayMap<View, CellAndSpan> map = new ArrayMap<>(); private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>(); final ArrayList<View> sortedViews = new ArrayList<>(); ArrayList<View> intersectingViews; boolean isSolution = false; public final ArrayList<View> sortedViews = new ArrayList<>(); public ArrayList<View> 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<View> views, Rect outRect) { public void getBoundingRectForViews(ArrayList<View> views, Rect outRect) { boolean first = true; for (View v: views) { CellAndSpan c = map.get(v); Loading
src/com/android/launcher3/MultipageCellLayout.java +26 −81 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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) { Loading @@ -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); } Loading @@ -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))); } @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> T simulateSeam(Supplier<T> 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; public ReorderAlgorithm createReorderAlgorithm() { return new MulticellReorderAlgorithm(this); } @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); Loading Loading @@ -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; } }
src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * 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 <T> return value of the supplied function * @return Value of supplied function */ public <T> T simulateSeam(Supplier<T> 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; } }
src/com/android/launcher3/celllayout/ReorderAlgorithm.java 0 → 100644 +204 −0 File added.Preview size limit exceeded, changes collapsed. Show changes