| /* |
| * 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 |
| } |
| } |