diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 17e4a5b2dd..11b263decd 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -70,7 +70,6 @@ import com.android.launcher3.celllayout.CellPosMapper.CellPos; import com.android.launcher3.celllayout.DelegatedCellDrawing; import com.android.launcher3.celllayout.ItemConfiguration; import com.android.launcher3.celllayout.ReorderAlgorithm; -import com.android.launcher3.celllayout.ViewCluster; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.folder.PreviewBackground; @@ -1761,7 +1760,7 @@ public class CellLayout extends ViewGroup { * @return The X, Y cell of a vacant area that can contain this object, * nearest the requested location. */ - private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction, + public int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction, boolean[][] occupied, boolean blockOccupied[][], int[] result) { // Keep track of best-scoring drop area final int[] bestXY = result != null ? result : new int[2]; @@ -1809,217 +1808,6 @@ public class CellLayout extends ViewGroup { return bestXY; } - public boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, - int[] direction, ItemConfiguration currentState) { - CellAndSpan c = currentState.map.get(v); - boolean success = false; - mTmpOccupied.markCells(c, false); - mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true); - - findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction, - mTmpOccupied.cells, null, mTempLocation); - - if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { - c.cellX = mTempLocation[0]; - c.cellY = mTempLocation[1]; - success = true; - } - mTmpOccupied.markCells(c, true); - return success; - } - - public boolean pushViewsToTempLocation(ArrayList views, Rect rectOccupiedByPotentialDrop, - int[] direction, View dragView, ItemConfiguration currentState) { - - ViewCluster cluster = new ViewCluster(this, views, currentState); - Rect clusterRect = cluster.getBoundingRect(); - int whichEdge; - int pushDistance; - boolean fail = false; - - // Determine the edge of the cluster that will be leading the push and how far - // the cluster must be shifted. - if (direction[0] < 0) { - whichEdge = ViewCluster.LEFT; - pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left; - } else if (direction[0] > 0) { - whichEdge = ViewCluster.RIGHT; - pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left; - } else if (direction[1] < 0) { - whichEdge = ViewCluster.TOP; - pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top; - } else { - whichEdge = ViewCluster.BOTTOM; - pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top; - } - - // Break early for invalid push distance. - if (pushDistance <= 0) { - return false; - } - - // Mark the occupied state as false for the group of views we want to move. - for (View v: views) { - CellAndSpan c = currentState.map.get(v); - mTmpOccupied.markCells(c, false); - } - - // We save the current configuration -- if we fail to find a solution we will revert - // to the initial state. The process of finding a solution modifies the configuration - // in place, hence the need for revert in the failure case. - currentState.save(); - - // The pushing algorithm is simplified by considering the views in the order in which - // they would be pushed by the cluster. For example, if the cluster is leading with its - // left edge, we consider sort the views by their right edge, from right to left. - cluster.sortConfigurationForEdgePush(whichEdge); - - while (pushDistance > 0 && !fail) { - for (View v: currentState.sortedViews) { - // For each view that isn't in the cluster, we see if the leading edge of the - // cluster is contacting the edge of that view. If so, we add that view to the - // cluster. - if (!cluster.views.contains(v) && v != dragView) { - if (cluster.isViewTouchingEdge(v, whichEdge)) { - CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams(); - if (!lp.canReorder) { - // The push solution includes the all apps button, this is not viable. - fail = true; - break; - } - cluster.addView(v); - CellAndSpan c = currentState.map.get(v); - - // Adding view to cluster, mark it as not occupied. - mTmpOccupied.markCells(c, false); - } - } - } - pushDistance--; - - // The cluster has been completed, now we move the whole thing over in the appropriate - // direction. - cluster.shift(whichEdge, 1); - } - - boolean foundSolution = false; - clusterRect = cluster.getBoundingRect(); - - // Due to the nature of the algorithm, the only check required to verify a valid solution - // is to ensure that completed shifted cluster lies completely within the cell layout. - if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 && - clusterRect.bottom <= mCountY) { - foundSolution = true; - } else { - currentState.restore(); - } - - // In either case, we set the occupied array as marked for the location of the views - for (View v: cluster.views) { - CellAndSpan c = currentState.map.get(v); - mTmpOccupied.markCells(c, true); - } - - return foundSolution; - } - - // This method tries to find a reordering solution which satisfies the push mechanic by trying - // to push items in each of the cardinal directions, in an order based on the direction vector - // passed. - public boolean attemptPushInDirection(ArrayList intersectingViews, Rect occupied, - int[] direction, View ignoreView, ItemConfiguration solution) { - if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) { - // If the direction vector has two non-zero components, we try pushing - // separately in each of the components. - int temp = direction[1]; - direction[1] = 0; - - if (pushViewsToTempLocation(intersectingViews, occupied, direction, - ignoreView, solution)) { - return true; - } - direction[1] = temp; - temp = direction[0]; - direction[0] = 0; - - if (pushViewsToTempLocation(intersectingViews, occupied, direction, - ignoreView, solution)) { - return true; - } - // Revert the direction - direction[0] = temp; - - // Now we try pushing in each component of the opposite direction - direction[0] *= -1; - direction[1] *= -1; - temp = direction[1]; - direction[1] = 0; - if (pushViewsToTempLocation(intersectingViews, occupied, direction, - ignoreView, solution)) { - return true; - } - - direction[1] = temp; - temp = direction[0]; - direction[0] = 0; - if (pushViewsToTempLocation(intersectingViews, occupied, direction, - ignoreView, solution)) { - return true; - } - // revert the direction - direction[0] = temp; - direction[0] *= -1; - direction[1] *= -1; - - } else { - // If the direction vector has a single non-zero component, we push first in the - // direction of the vector - if (pushViewsToTempLocation(intersectingViews, occupied, direction, - ignoreView, solution)) { - return true; - } - // Then we try the opposite direction - direction[0] *= -1; - direction[1] *= -1; - if (pushViewsToTempLocation(intersectingViews, occupied, direction, - ignoreView, solution)) { - return true; - } - // Switch the direction back - direction[0] *= -1; - direction[1] *= -1; - - // If we have failed to find a push solution with the above, then we try - // to find a solution by pushing along the perpendicular axis. - - // Swap the components - int temp = direction[1]; - direction[1] = direction[0]; - direction[0] = temp; - if (pushViewsToTempLocation(intersectingViews, occupied, direction, - ignoreView, solution)) { - return true; - } - - // Then we try the opposite direction - direction[0] *= -1; - direction[1] *= -1; - if (pushViewsToTempLocation(intersectingViews, occupied, direction, - ignoreView, solution)) { - return true; - } - // Switch the direction back - direction[0] *= -1; - direction[1] *= -1; - - // Swap the components back - temp = direction[1]; - direction[1] = direction[0]; - direction[0] = temp; - } - return false; - } - /* * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between * the provided point and the provided cell @@ -2087,57 +1875,6 @@ public class CellLayout extends ViewGroup { } } - public boolean addViewsToTempLocation(ArrayList views, Rect rectOccupiedByPotentialDrop, - int[] direction, View dragView, ItemConfiguration currentState) { - if (views.size() == 0) return true; - - boolean success = false; - Rect boundingRect = new Rect(); - // We construct a rect which represents the entire group of views passed in - currentState.getBoundingRectForViews(views, boundingRect); - - // Mark the occupied state as false for the group of views we want to move. - for (View v: views) { - CellAndSpan c = currentState.map.get(v); - mTmpOccupied.markCells(c, false); - } - - GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height()); - int top = boundingRect.top; - int left = boundingRect.left; - // We mark more precisely which parts of the bounding rect are truly occupied, allowing - // for interlocking. - for (View v: views) { - CellAndSpan c = currentState.map.get(v); - blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true); - } - - mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true); - - findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(), - boundingRect.height(), direction, - mTmpOccupied.cells, blockOccupied.cells, mTempLocation); - - // If we successfully found a location by pushing the block of views, we commit it - if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { - int deltaX = mTempLocation[0] - boundingRect.left; - int deltaY = mTempLocation[1] - boundingRect.top; - for (View v: views) { - CellAndSpan c = currentState.map.get(v); - c.cellX += deltaX; - c.cellY += deltaY; - } - success = true; - } - - // In either case, we set the occupied array as marked for the location of the views - for (View v: views) { - CellAndSpan c = currentState.map.get(v); - mTmpOccupied.markCells(c, true); - } - return success; - } - public ReorderAlgorithm createReorderAlgorithm() { return new ReorderAlgorithm(this); } diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java index d098ebae80..6682f32873 100644 --- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java +++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java @@ -20,6 +20,7 @@ import android.view.View; import com.android.launcher3.CellLayout; import com.android.launcher3.util.CellAndSpan; +import com.android.launcher3.util.GridOccupancy; import java.util.ArrayList; import java.util.Comparator; @@ -157,14 +158,14 @@ public class ReorderAlgorithm { // First we try to find a solution which respects the push mechanic. That is, // we try to find a solution such that no displaced item travels through another item // without also displacing that item. - if (mCellLayout.attemptPushInDirection(intersectingViews, occupiedRect, direction, + if (attemptPushInDirection(intersectingViews, occupiedRect, direction, ignoreView, solution)) { return true; } // Next we try moving the views as a block, but without requiring the push mechanic. - if (mCellLayout.addViewsToTempLocation(intersectingViews, occupiedRect, direction, + if (addViewsToTempLocation(intersectingViews, occupiedRect, direction, ignoreView, solution)) { return true; @@ -172,13 +173,276 @@ public class ReorderAlgorithm { // Ok, they couldn't move as a block, let's move them individually for (View v : intersectingViews) { - if (!mCellLayout.addViewToTempLocation(v, occupiedRect, direction, solution)) { + if (!addViewToTempLocation(v, occupiedRect, direction, solution)) { return false; } } return true; } + private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, + int[] direction, ItemConfiguration currentState) { + CellAndSpan c = currentState.map.get(v); + boolean success = false; + mCellLayout.mTmpOccupied.markCells(c, false); + mCellLayout.mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true); + + int[] tmpLocation = mCellLayout.findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, + direction, mCellLayout.mTmpOccupied.cells, null, new int[2]); + + if (tmpLocation[0] >= 0 && tmpLocation[1] >= 0) { + c.cellX = tmpLocation[0]; + c.cellY = tmpLocation[1]; + success = true; + } + mCellLayout.mTmpOccupied.markCells(c, true); + return success; + } + + private boolean pushViewsToTempLocation(ArrayList views, Rect rectOccupiedByPotentialDrop, + int[] direction, View dragView, ItemConfiguration currentState) { + + ViewCluster cluster = new ViewCluster(mCellLayout, views, currentState); + Rect clusterRect = cluster.getBoundingRect(); + int whichEdge; + int pushDistance; + boolean fail = false; + + // Determine the edge of the cluster that will be leading the push and how far + // the cluster must be shifted. + if (direction[0] < 0) { + whichEdge = ViewCluster.LEFT; + pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left; + } else if (direction[0] > 0) { + whichEdge = ViewCluster.RIGHT; + pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left; + } else if (direction[1] < 0) { + whichEdge = ViewCluster.TOP; + pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top; + } else { + whichEdge = ViewCluster.BOTTOM; + pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top; + } + + // Break early for invalid push distance. + if (pushDistance <= 0) { + return false; + } + + // Mark the occupied state as false for the group of views we want to move. + for (View v : views) { + CellAndSpan c = currentState.map.get(v); + mCellLayout.mTmpOccupied.markCells(c, false); + } + + // We save the current configuration -- if we fail to find a solution we will revert + // to the initial state. The process of finding a solution modifies the configuration + // in place, hence the need for revert in the failure case. + currentState.save(); + + // The pushing algorithm is simplified by considering the views in the order in which + // they would be pushed by the cluster. For example, if the cluster is leading with its + // left edge, we consider sort the views by their right edge, from right to left. + cluster.sortConfigurationForEdgePush(whichEdge); + + while (pushDistance > 0 && !fail) { + for (View v : currentState.sortedViews) { + // For each view that isn't in the cluster, we see if the leading edge of the + // cluster is contacting the edge of that view. If so, we add that view to the + // cluster. + if (!cluster.views.contains(v) && v != dragView) { + if (cluster.isViewTouchingEdge(v, whichEdge)) { + CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams(); + if (!lp.canReorder) { + // The push solution includes the all apps button, this is not viable. + fail = true; + break; + } + cluster.addView(v); + CellAndSpan c = currentState.map.get(v); + + // Adding view to cluster, mark it as not occupied. + mCellLayout.mTmpOccupied.markCells(c, false); + } + } + } + pushDistance--; + + // The cluster has been completed, now we move the whole thing over in the appropriate + // direction. + cluster.shift(whichEdge, 1); + } + + boolean foundSolution = false; + clusterRect = cluster.getBoundingRect(); + + // Due to the nature of the algorithm, the only check required to verify a valid solution + // is to ensure that completed shifted cluster lies completely within the cell layout. + if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCellLayout.getCountX() + && clusterRect.top >= 0 && clusterRect.bottom <= mCellLayout.getCountY()) { + foundSolution = true; + } else { + currentState.restore(); + } + + // In either case, we set the occupied array as marked for the location of the views + for (View v : cluster.views) { + CellAndSpan c = currentState.map.get(v); + mCellLayout.mTmpOccupied.markCells(c, true); + } + + return foundSolution; + } + + // This method tries to find a reordering solution which satisfies the push mechanic by trying + // to push items in each of the cardinal directions, in an order based on the direction vector + // passed. + private boolean attemptPushInDirection(ArrayList intersectingViews, Rect occupied, + int[] direction, View ignoreView, ItemConfiguration solution) { + if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) { + // If the direction vector has two non-zero components, we try pushing + // separately in each of the components. + int temp = direction[1]; + direction[1] = 0; + + if (pushViewsToTempLocation(intersectingViews, occupied, direction, + ignoreView, solution)) { + return true; + } + direction[1] = temp; + temp = direction[0]; + direction[0] = 0; + + if (pushViewsToTempLocation(intersectingViews, occupied, direction, + ignoreView, solution)) { + return true; + } + // Revert the direction + direction[0] = temp; + + // Now we try pushing in each component of the opposite direction + direction[0] *= -1; + direction[1] *= -1; + temp = direction[1]; + direction[1] = 0; + if (pushViewsToTempLocation(intersectingViews, occupied, direction, + ignoreView, solution)) { + return true; + } + + direction[1] = temp; + temp = direction[0]; + direction[0] = 0; + if (pushViewsToTempLocation(intersectingViews, occupied, direction, + ignoreView, solution)) { + return true; + } + // revert the direction + direction[0] = temp; + direction[0] *= -1; + direction[1] *= -1; + + } else { + // If the direction vector has a single non-zero component, we push first in the + // direction of the vector + if (pushViewsToTempLocation(intersectingViews, occupied, direction, + ignoreView, solution)) { + return true; + } + // Then we try the opposite direction + direction[0] *= -1; + direction[1] *= -1; + if (pushViewsToTempLocation(intersectingViews, occupied, direction, + ignoreView, solution)) { + return true; + } + // Switch the direction back + direction[0] *= -1; + direction[1] *= -1; + + // If we have failed to find a push solution with the above, then we try + // to find a solution by pushing along the perpendicular axis. + + // Swap the components + int temp = direction[1]; + direction[1] = direction[0]; + direction[0] = temp; + if (pushViewsToTempLocation(intersectingViews, occupied, direction, + ignoreView, solution)) { + return true; + } + + // Then we try the opposite direction + direction[0] *= -1; + direction[1] *= -1; + if (pushViewsToTempLocation(intersectingViews, occupied, direction, + ignoreView, solution)) { + return true; + } + // Switch the direction back + direction[0] *= -1; + direction[1] *= -1; + + // Swap the components back + temp = direction[1]; + direction[1] = direction[0]; + direction[0] = temp; + } + return false; + } + + private boolean addViewsToTempLocation(ArrayList views, Rect rectOccupiedByPotentialDrop, + int[] direction, View dragView, ItemConfiguration currentState) { + if (views.isEmpty()) return true; + + boolean success = false; + Rect boundingRect = new Rect(); + // We construct a rect which represents the entire group of views passed in + currentState.getBoundingRectForViews(views, boundingRect); + + // Mark the occupied state as false for the group of views we want to move. + for (View v : views) { + CellAndSpan c = currentState.map.get(v); + mCellLayout.mTmpOccupied.markCells(c, false); + } + + GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), + boundingRect.height()); + int top = boundingRect.top; + int left = boundingRect.left; + // We mark more precisely which parts of the bounding rect are truly occupied, allowing + // for interlocking. + for (View v : views) { + CellAndSpan c = currentState.map.get(v); + blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true); + } + + mCellLayout.mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true); + + int[] tmpLocation = mCellLayout.findNearestArea(boundingRect.left, boundingRect.top, + boundingRect.width(), boundingRect.height(), direction, + mCellLayout.mTmpOccupied.cells, blockOccupied.cells, new int[2]); + + // If we successfully found a location by pushing the block of views, we commit it + if (tmpLocation[0] >= 0 && tmpLocation[1] >= 0) { + int deltaX = tmpLocation[0] - boundingRect.left; + int deltaY = tmpLocation[1] - boundingRect.top; + for (View v : views) { + CellAndSpan c = currentState.map.get(v); + c.cellX += deltaX; + c.cellY += deltaY; + } + success = true; + } + + // In either case, we set the occupied array as marked for the location of the views + for (View v : views) { + CellAndSpan c = currentState.map.get(v); + mCellLayout.mTmpOccupied.markCells(c, true); + } + return success; + } + /** * Returns a "reorder" if there is empty space without rearranging anything. *