diff options
author | 2023-11-21 10:45:45 -0600 | |
---|---|---|
committer | 2023-11-23 23:13:56 +0000 | |
commit | 5f0af4f6336ce220b601808f97d682000a96f2b0 (patch) | |
tree | 02340471d9df73a56fe24f85968cb389381a96de | |
parent | 5f14285de11888422418e6c26f1e7b96ab52430c (diff) |
Moving classes inside of CellLayout to their own file
This is a no-op change ensure this we have ReorderAlgorithmUnitTest.
Flag: NA
Bug: 229292911
Test: ReorderAlgorithmUnitTest
Change-Id: I6ffe2a1260f869a4686a9f1e652dd1ab6d406269
13 files changed, 362 insertions, 317 deletions
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java index 2064fe22f6..110ca167aa 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java +++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java @@ -44,13 +44,13 @@ import android.view.ViewGroup; import androidx.core.graphics.ColorUtils; -import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.celllayout.CellLayoutLayoutParams; +import com.android.launcher3.celllayout.DelegatedCellDrawing; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.icons.IconNormalizer; @@ -418,7 +418,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView { /** * Draws Predicted Icon outline on cell layout */ - public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing { + public static class PredictedIconOutlineDrawing extends DelegatedCellDrawing { private final PredictedAppIcon mIcon; private final Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 1a0f2cf36c..7257d86ddc 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -67,7 +67,10 @@ import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate; import com.android.launcher3.celllayout.CellLayoutLayoutParams; 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; @@ -86,7 +89,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Stack; @@ -1434,9 +1436,8 @@ public class CellLayout extends ViewGroup { View child = mShortcutsAndWidgets.getChildAt(i); if (child == dragView) continue; CellAndSpan c = solution.map.get(child); - boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews - != null && !solution.intersectingViews.contains(child); - + boolean skip = mode == ReorderPreviewAnimation.MODE_HINT + && !solution.intersectingViews.contains(child); CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams(); if (c != null && !skip && (child instanceof Reorderable)) { @@ -1832,7 +1833,7 @@ public class CellLayout extends ViewGroup { private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, int[] direction, View dragView, ItemConfiguration currentState) { - ViewCluster cluster = new ViewCluster(views, currentState); + ViewCluster cluster = new ViewCluster(this, views, currentState); Rect clusterRect = cluster.getBoundingRect(); int whichEdge; int pushDistance; @@ -1924,191 +1925,6 @@ public class CellLayout extends ViewGroup { return foundSolution; } - /** - * This helper class defines a cluster of views. It helps with defining complex edges - * of the cluster and determining how those edges interact with other views. The edges - * essentially define a fine-grained boundary around the cluster of views -- like a more - * precise version of a bounding box. - */ - private class ViewCluster { - final static int LEFT = 1 << 0; - final static int TOP = 1 << 1; - final static int RIGHT = 1 << 2; - final static int BOTTOM = 1 << 3; - - final ArrayList<View> views; - final ItemConfiguration config; - final Rect boundingRect = new Rect(); - - final int[] leftEdge = new int[mCountY]; - final int[] rightEdge = new int[mCountY]; - final int[] topEdge = new int[mCountX]; - final int[] bottomEdge = new int[mCountX]; - int dirtyEdges; - boolean boundingRectDirty; - - @SuppressWarnings("unchecked") - public ViewCluster(ArrayList<View> views, ItemConfiguration config) { - this.views = (ArrayList<View>) views.clone(); - this.config = config; - resetEdges(); - } - - void resetEdges() { - for (int i = 0; i < mCountX; i++) { - topEdge[i] = -1; - bottomEdge[i] = -1; - } - for (int i = 0; i < mCountY; i++) { - leftEdge[i] = -1; - rightEdge[i] = -1; - } - dirtyEdges = LEFT | TOP | RIGHT | BOTTOM; - boundingRectDirty = true; - } - - void computeEdge(int which) { - int count = views.size(); - for (int i = 0; i < count; i++) { - CellAndSpan cs = config.map.get(views.get(i)); - switch (which) { - case LEFT: - int left = cs.cellX; - for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) { - if (left < leftEdge[j] || leftEdge[j] < 0) { - leftEdge[j] = left; - } - } - break; - case RIGHT: - int right = cs.cellX + cs.spanX; - for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) { - if (right > rightEdge[j]) { - rightEdge[j] = right; - } - } - break; - case TOP: - int top = cs.cellY; - for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) { - if (top < topEdge[j] || topEdge[j] < 0) { - topEdge[j] = top; - } - } - break; - case BOTTOM: - int bottom = cs.cellY + cs.spanY; - for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) { - if (bottom > bottomEdge[j]) { - bottomEdge[j] = bottom; - } - } - break; - } - } - } - - boolean isViewTouchingEdge(View v, int whichEdge) { - CellAndSpan cs = config.map.get(v); - - if ((dirtyEdges & whichEdge) == whichEdge) { - computeEdge(whichEdge); - dirtyEdges &= ~whichEdge; - } - - switch (whichEdge) { - case LEFT: - for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) { - if (leftEdge[i] == cs.cellX + cs.spanX) { - return true; - } - } - break; - case RIGHT: - for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) { - if (rightEdge[i] == cs.cellX) { - return true; - } - } - break; - case TOP: - for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) { - if (topEdge[i] == cs.cellY + cs.spanY) { - return true; - } - } - break; - case BOTTOM: - for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) { - if (bottomEdge[i] == cs.cellY) { - return true; - } - } - break; - } - return false; - } - - void shift(int whichEdge, int delta) { - for (View v: views) { - CellAndSpan c = config.map.get(v); - switch (whichEdge) { - case LEFT: - c.cellX -= delta; - break; - case RIGHT: - c.cellX += delta; - break; - case TOP: - c.cellY -= delta; - break; - case BOTTOM: - default: - c.cellY += delta; - break; - } - } - resetEdges(); - } - - public void addView(View v) { - views.add(v); - resetEdges(); - } - - public Rect getBoundingRect() { - if (boundingRectDirty) { - config.getBoundingRectForViews(views, boundingRect); - } - return boundingRect; - } - - final PositionComparator comparator = new PositionComparator(); - class PositionComparator implements Comparator<View> { - int whichEdge = 0; - public int compare(View left, View right) { - CellAndSpan l = config.map.get(left); - CellAndSpan r = config.map.get(right); - switch (whichEdge) { - case LEFT: - return (r.cellX + r.spanX) - (l.cellX + l.spanX); - case RIGHT: - return l.cellX - r.cellX; - case TOP: - return (r.cellY + r.spanY) - (l.cellY + l.spanY); - case BOTTOM: - default: - return l.cellY - r.cellY; - } - } - } - - public void sortConfigurationForEdgePush(int edge) { - comparator.whichEdge = edge; - Collections.sort(config.sortedViews, comparator); - } - } - // 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. @@ -2532,54 +2348,6 @@ public class CellLayout extends ViewGroup { } /** - * 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<>(); - public final ArrayList<View> sortedViews = new ArrayList<>(); - public ArrayList<View> intersectingViews; - public boolean isSolution = false; - - public void save() { - // Copy current state into savedMap - for (View v: map.keySet()) { - savedMap.get(v).copyFrom(map.get(v)); - } - } - - public void restore() { - // Restore current state from savedMap - for (View v: savedMap.keySet()) { - map.get(v).copyFrom(savedMap.get(v)); - } - } - - public void add(View v, CellAndSpan cs) { - map.put(v, cs); - savedMap.put(v, new CellAndSpan()); - sortedViews.add(v); - } - - public int area() { - return spanX * spanY; - } - - public void getBoundingRectForViews(ArrayList<View> views, Rect outRect) { - boolean first = true; - for (View v: views) { - CellAndSpan c = map.get(v); - if (first) { - outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY); - first = false; - } else { - outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY); - } - } - } - } - - /** * Find a starting cell position that will fit the given bounds nearest the requested * cell location. Uses Euclidean distance to score multiple vacant areas. * @@ -2758,52 +2526,6 @@ public class CellLayout extends ViewGroup { return new CellLayoutLayoutParams(p); } - // This class stores info for two purposes: - // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY, - // its spanX, spanY, and the screen it is on - // 2. When long clicking on an empty cell in a CellLayout, we save information about the - // cellX and cellY coordinates and which page was clicked. We then set this as a tag on - // the CellLayout that was long clicked - public static final class CellInfo extends CellAndSpan { - public final View cell; - final int screenId; - final int container; - - public CellInfo(View v, ItemInfo info, CellPos cellPos) { - cellX = cellPos.cellX; - cellY = cellPos.cellY; - spanX = info.spanX; - spanY = info.spanY; - cell = v; - screenId = cellPos.screenId; - container = info.container; - } - - @Override - public String toString() { - return "Cell[view=" + (cell == null ? "null" : cell.getClass()) - + ", x=" + cellX + ", y=" + cellY + "]"; - } - } - - /** - * A Delegated cell Drawing for drawing on CellLayout - */ - public abstract static class DelegatedCellDrawing { - public int mDelegateCellX; - public int mDelegateCellY; - - /** - * Draw under CellLayout - */ - public abstract void drawUnderItem(Canvas canvas); - - /** - * Draw over CellLayout - */ - public abstract void drawOverItem(Canvas canvas); - } - /** * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing * if necessary). diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java index 4b5c9ef8e4..123e8ca1e3 100644 --- a/src/com/android/launcher3/MultipageCellLayout.java +++ b/src/com/android/launcher3/MultipageCellLayout.java @@ -23,6 +23,7 @@ import android.util.AttributeSet; import android.view.View; import com.android.launcher3.celllayout.CellLayoutLayoutParams; +import com.android.launcher3.celllayout.ItemConfiguration; import com.android.launcher3.celllayout.MulticellReorderAlgorithm; import com.android.launcher3.util.CellAndSpan; import com.android.launcher3.util.GridOccupancy; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 30f3f5fb3d..be4168db3c 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -74,6 +74,7 @@ import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.apppairs.AppPairIcon; +import com.android.launcher3.celllayout.CellInfo; import com.android.launcher3.celllayout.CellLayoutLayoutParams; import com.android.launcher3.celllayout.CellPosMapper; import com.android.launcher3.celllayout.CellPosMapper.CellPos; @@ -189,7 +190,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T> /** * CellInfo for the cell that is currently being dragged */ - protected CellLayout.CellInfo mDragInfo; + protected CellInfo mDragInfo; /** * Target drop area calculated during last acceptDrop call. @@ -1620,7 +1621,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T> page.setAccessibilityDelegate(null); } - public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) { + public void startDrag(CellInfo cellInfo, DragOptions options) { View child = cellInfo.cell; mDragInfo = cellInfo; @@ -1784,7 +1785,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T> int spanX; int spanY; if (mDragInfo != null) { - final CellLayout.CellInfo dragCellInfo = mDragInfo; + final CellInfo dragCellInfo = mDragInfo; spanX = dragCellInfo.spanX; spanY = dragCellInfo.spanY; } else { @@ -3078,7 +3079,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T> * so that Launcher can sync this object with the correct info when the activity is created/ * destroyed */ - public CellLayout.CellInfo getDragInfo() { + public CellInfo getDragInfo() { return mDragInfo; } diff --git a/src/com/android/launcher3/celllayout/CellInfo.kt b/src/com/android/launcher3/celllayout/CellInfo.kt new file mode 100644 index 0000000000..5a3b7f7685 --- /dev/null +++ b/src/com/android/launcher3/celllayout/CellInfo.kt @@ -0,0 +1,44 @@ +/* + * 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.CellPosMapper.CellPos +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.util.CellAndSpan + +// This class stores info for two purposes: +// 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY, +// its spanX, spanY, and the screen it is on +// 2. When long clicking on an empty cell in a CellLayout, we save information about the +// cellX and cellY coordinates and which page was clicked. We then set this as a tag on +// the CellLayout that was long clicked +class CellInfo(v: View?, info: ItemInfo, cellPos: CellPos) : + CellAndSpan(cellPos.cellX, cellPos.cellY, info.spanX, info.spanY) { + @JvmField val cell: View? + @JvmField val screenId: Int + @JvmField val container: Int + + init { + cell = v + screenId = cellPos.screenId + container = info.container + } + + override fun toString(): String { + return "CellInfo(cell=$cell, screenId=$screenId, container=$container)" + } +} diff --git a/src/com/android/launcher3/celllayout/DelegatedCellDrawing.kt b/src/com/android/launcher3/celllayout/DelegatedCellDrawing.kt new file mode 100644 index 0000000000..1703f9b6a7 --- /dev/null +++ b/src/com/android/launcher3/celllayout/DelegatedCellDrawing.kt @@ -0,0 +1,30 @@ +/* + * 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.graphics.Canvas + +/** A Delegated cell Drawing for drawing on CellLayout */ +abstract class DelegatedCellDrawing { + @JvmField var mDelegateCellX = 0 + @JvmField var mDelegateCellY = 0 + + /** Draw under CellLayout */ + abstract fun drawUnderItem(canvas: Canvas) + + /** Draw over CellLayout */ + abstract fun drawOverItem(canvas: Canvas) +} diff --git a/src/com/android/launcher3/celllayout/ItemConfiguration.kt b/src/com/android/launcher3/celllayout/ItemConfiguration.kt new file mode 100644 index 0000000000..e775145697 --- /dev/null +++ b/src/com/android/launcher3/celllayout/ItemConfiguration.kt @@ -0,0 +1,61 @@ +/* + * 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.graphics.Rect +import android.util.ArrayMap +import android.view.View +import com.android.launcher3.util.CellAndSpan + +/** Represents the solution to a reorder of items in the Workspace. */ +class ItemConfiguration : CellAndSpan() { + @JvmField val map = ArrayMap<View, CellAndSpan>() + private val savedMap = ArrayMap<View, CellAndSpan>() + + @JvmField val sortedViews = ArrayList<View>() + + @JvmField var intersectingViews: ArrayList<View> = ArrayList() + + @JvmField var isSolution = false + fun save() { + // Copy current state into savedMap + map.forEach { (k, v) -> savedMap[k]?.copyFrom(v) } + } + + fun restore() { + // Restore current state from savedMap + savedMap.forEach { (k, v) -> map[k]?.copyFrom(v) } + } + + fun add(v: View, cs: CellAndSpan) { + map[v] = cs + savedMap[v] = CellAndSpan() + sortedViews.add(v) + } + + fun area(): Int { + return spanX * spanY + } + + fun getBoundingRectForViews(views: ArrayList<View>, outRect: Rect) { + views + .mapNotNull { v -> map[v] } + .forEachIndexed { i, c -> + if (i == 0) outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY) + else outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY) + } + } +} diff --git a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java index a2e26b37f3..b7d8093055 100644 --- a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java +++ b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java @@ -38,8 +38,8 @@ public class MulticellReorderAlgorithm extends ReorderAlgorithm { mSeam = new View(cellLayout.getContext()); } - private CellLayout.ItemConfiguration removeSeamFromSolution( - CellLayout.ItemConfiguration solution) { + private ItemConfiguration removeSeamFromSolution( + ItemConfiguration solution) { solution.map.forEach((view, cell) -> cell.cellX = cell.cellX > mCellLayout.getCountX() / 2 ? cell.cellX - 1 : cell.cellX); solution.cellX = @@ -48,7 +48,7 @@ public class MulticellReorderAlgorithm extends ReorderAlgorithm { } @Override - public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, + public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY) { return removeSeamFromSolution(simulateSeam( @@ -57,16 +57,16 @@ public class MulticellReorderAlgorithm extends ReorderAlgorithm { } @Override - public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, + public ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, - CellLayout.ItemConfiguration solution) { + 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, + public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY, View dragView) { return removeSeamFromSolution(simulateSeam( diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java index 17786f24fb..05bd13da81 100644 --- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java +++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java @@ -59,9 +59,9 @@ public class ReorderAlgorithm { * @param solution variable to store the solution * @return the same solution variable */ - public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, + public ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, - CellLayout.ItemConfiguration solution) { + 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 @@ -110,11 +110,11 @@ public class ReorderAlgorithm { * @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, + public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY, View dragView) { int[] result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, new int[2]); - CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration(); + ItemConfiguration solution = new ItemConfiguration(); mCellLayout.copyCurrentStateToSolution(solution, false); solution.isSolution = !isConfigurationRegionOccupied( @@ -133,7 +133,7 @@ public class ReorderAlgorithm { } private boolean isConfigurationRegionOccupied(Rect region, - CellLayout.ItemConfiguration configuration, View ignoreView) { + ItemConfiguration configuration, View ignoreView) { return configuration.map.entrySet() .stream() .filter(entry -> entry.getKey() != ignoreView) @@ -153,9 +153,9 @@ public class ReorderAlgorithm { * @param spanY vertical cell span * @return the configuration that represents the found reorder */ - public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, + public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY) { - CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration(); + ItemConfiguration solution = new ItemConfiguration(); int[] result = new int[2]; int[] resultSpan = new int[2]; mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result, @@ -188,22 +188,22 @@ public class ReorderAlgorithm { * @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, + public 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, + 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, + ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, mCellLayout.mDirectionVector, dragView, true, - new CellLayout.ItemConfiguration()); + new ItemConfiguration()); // We attempt the approach which doesn't shuffle views at all - CellLayout.ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder( + ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder( pixelX, pixelY, minSpanX, minSpanY, spanX, spanY); // If the reorder solution requires resizing (shrinking) the item being dropped, we instead diff --git a/src/com/android/launcher3/celllayout/ViewCluster.kt b/src/com/android/launcher3/celllayout/ViewCluster.kt new file mode 100644 index 0000000000..49693e3a78 --- /dev/null +++ b/src/com/android/launcher3/celllayout/ViewCluster.kt @@ -0,0 +1,185 @@ +/* + * 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.graphics.Rect +import android.view.View +import com.android.launcher3.CellLayout +import java.util.Collections + +/** + * This helper class defines a cluster of views. It helps with defining complex edges of the cluster + * and determining how those edges interact with other views. The edges essentially define a + * fine-grained boundary around the cluster of views -- like a more precise version of a bounding + * box. + */ +class ViewCluster( + private val mCellLayout: CellLayout, + views: ArrayList<View>, + val config: ItemConfiguration +) { + + @JvmField val views = ArrayList<View>(views) + private val boundingRect = Rect() + + private val leftEdge = IntArray(mCellLayout.countY) + private val rightEdge = IntArray(mCellLayout.countY) + private val topEdge = IntArray(mCellLayout.countX) + private val bottomEdge = IntArray(mCellLayout.countX) + + private var dirtyEdges = 0 + private var boundingRectDirty = false + + val comparator: PositionComparator = PositionComparator() + + init { + resetEdges() + } + private fun resetEdges() { + for (i in 0 until mCellLayout.countX) { + topEdge[i] = -1 + bottomEdge[i] = -1 + } + for (i in 0 until mCellLayout.countY) { + leftEdge[i] = -1 + rightEdge[i] = -1 + } + dirtyEdges = LEFT or TOP or RIGHT or BOTTOM + boundingRectDirty = true + } + + private fun computeEdge(which: Int) = + views + .mapNotNull { v -> config.map[v] } + .forEach { cs -> + val left = cs.cellX + val right = cs.cellX + cs.spanX + val top = cs.cellY + val bottom = cs.cellY + cs.spanY + when (which) { + LEFT -> + for (j in top until bottom) { + if (left < leftEdge[j] || leftEdge[j] < 0) { + leftEdge[j] = left + } + } + RIGHT -> + for (j in top until bottom) { + if (right > rightEdge[j]) { + rightEdge[j] = right + } + } + TOP -> + for (j in left until right) { + if (top < topEdge[j] || topEdge[j] < 0) { + topEdge[j] = top + } + } + BOTTOM -> + for (j in left until right) { + if (bottom > bottomEdge[j]) { + bottomEdge[j] = bottom + } + } + } + } + + fun isViewTouchingEdge(v: View?, whichEdge: Int): Boolean { + val cs = config.map[v] ?: return false + val left = cs.cellX + val right = cs.cellX + cs.spanX + val top = cs.cellY + val bottom = cs.cellY + cs.spanY + if ((dirtyEdges and whichEdge) == whichEdge) { + computeEdge(whichEdge) + dirtyEdges = dirtyEdges and whichEdge.inv() + } + return when (whichEdge) { + // In this case if any of the values of leftEdge is equal to right, which is the + // rightmost x value of the view, it means that the cluster is touching the view from + // the left the same logic applies for the other sides. + LEFT -> edgeContainsValue(top, bottom, leftEdge, right) + RIGHT -> edgeContainsValue(top, bottom, rightEdge, left) + TOP -> edgeContainsValue(left, right, topEdge, bottom) + BOTTOM -> edgeContainsValue(left, right, bottomEdge, top) + else -> false + } + } + + private fun edgeContainsValue(start: Int, end: Int, edge: IntArray, value: Int): Boolean { + for (i in start until end) { + if (edge[i] == value) { + return true + } + } + return false + } + + fun shift(whichEdge: Int, delta: Int) { + views + .mapNotNull { v -> config.map[v] } + .forEach { c -> + when (whichEdge) { + LEFT -> c.cellX -= delta + RIGHT -> c.cellX += delta + TOP -> c.cellY -= delta + BOTTOM -> c.cellY += delta + else -> c.cellY += delta + } + } + resetEdges() + } + + fun addView(v: View) { + views.add(v) + resetEdges() + } + + fun getBoundingRect(): Rect { + if (boundingRectDirty) { + config.getBoundingRectForViews(views, boundingRect) + } + return boundingRect + } + + inner class PositionComparator : Comparator<View?> { + var whichEdge = 0 + override fun compare(left: View?, right: View?): Int { + val l = config.map[left] + val r = config.map[right] + if (l == null || r == null) throw NullPointerException() + return when (whichEdge) { + LEFT -> r.cellX + r.spanX - (l.cellX + l.spanX) + RIGHT -> l.cellX - r.cellX + TOP -> r.cellY + r.spanY - (l.cellY + l.spanY) + BOTTOM -> l.cellY - r.cellY + else -> l.cellY - r.cellY + } + } + } + + fun sortConfigurationForEdgePush(edge: Int) { + comparator.whichEdge = edge + Collections.sort(config.sortedViews, comparator) + } + + companion object { + const val LEFT = 1 shl 0 + const val TOP = 1 shl 1 + const val RIGHT = 1 shl 2 + const val BOTTOM = 1 shl 3 + } +} diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java index b320cebcfc..ec038038ad 100644 --- a/src/com/android/launcher3/folder/PreviewBackground.java +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -48,6 +48,7 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; +import com.android.launcher3.celllayout.DelegatedCellDrawing; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; @@ -55,7 +56,7 @@ import com.android.launcher3.views.ActivityContext; * This object represents a FolderIcon preview background. It stores drawing / measurement * information, handles drawing, and animation (accept state <--> rest state). */ -public class PreviewBackground extends CellLayout.DelegatedCellDrawing { +public class PreviewBackground extends DelegatedCellDrawing { private static final boolean DRAW_SHADOW = false; private static final boolean DRAW_STROKE = false; diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java index 0c322cc003..96cc412845 100644 --- a/src/com/android/launcher3/touch/ItemLongClickListener.java +++ b/src/com/android/launcher3/touch/ItemLongClickListener.java @@ -27,9 +27,9 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import android.view.View; import android.view.View.OnLongClickListener; -import com.android.launcher3.CellLayout; import com.android.launcher3.DropTarget; import com.android.launcher3.Launcher; +import com.android.launcher3.celllayout.CellInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; @@ -86,7 +86,7 @@ public class ItemLongClickListener { } } - CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info, + CellInfo longClickCellInfo = new CellInfo(v, info, launcher.getCellPosMapper().mapModelToPresenter(info)); launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions); } diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java index 91a0634f3d..15e2d70f9c 100644 --- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java +++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java @@ -105,7 +105,7 @@ public class ReorderAlgorithmUnitTest { }; } - public CellLayout.ItemConfiguration solve(CellLayoutBoard board, int x, int y, int spanX, + public ItemConfiguration solve(CellLayoutBoard board, int x, int y, int spanX, int spanY, int minSpanX, int minSpanY) { CellLayout cl = createCellLayout(board.getWidth(), board.getHeight()); @@ -123,17 +123,17 @@ public class ReorderAlgorithmUnitTest { int[] testCaseXYinPixels = new int[2]; cl.regionToCenterPoint(x, y, spanX, spanY, testCaseXYinPixels); - CellLayout.ItemConfiguration solution = cl.createReorderAlgorithm().calculateReorder( + ItemConfiguration solution = cl.createReorderAlgorithm().calculateReorder( testCaseXYinPixels[0], testCaseXYinPixels[1], minSpanX, minSpanY, spanX, spanY, null); if (solution == null) { - solution = new CellLayout.ItemConfiguration(); + solution = new ItemConfiguration(); solution.isSolution = false; } return solution; } - public CellLayoutBoard boardFromSolution(CellLayout.ItemConfiguration solution, int width, + public CellLayoutBoard boardFromSolution(ItemConfiguration solution, int width, int height) { // Update the views with solution value solution.map.forEach((key, val) -> key.setLayoutParams( @@ -146,7 +146,7 @@ public class ReorderAlgorithmUnitTest { } public void evaluateTestCase(ReorderAlgorithmUnitTestCase testCase) { - CellLayout.ItemConfiguration solution = solve(testCase.startBoard, testCase.x, + ItemConfiguration solution = solve(testCase.startBoard, testCase.x, testCase.y, testCase.spanX, testCase.spanY, testCase.minSpanX, testCase.minSpanY); assertEquals("should be a valid solution", solution.isSolution, @@ -197,7 +197,7 @@ public class ReorderAlgorithmUnitTest { CellLayoutBoard board = generateBoard(new CellLayoutBoard(width, height), new Rect(0, 0, width, height), targetWidth * targetHeight); - CellLayout.ItemConfiguration solution = solve(board, x, y, targetWidth, targetHeight, + ItemConfiguration solution = solve(board, x, y, targetWidth, targetHeight, minTargetWidth, minTargetHeight); CellLayoutBoard finishBoard = solution.isSolution ? boardFromSolution(solution, |