1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.celllayout
17 
18 import android.graphics.Rect
19 import android.view.View
20 import com.android.launcher3.CellLayout
21 import java.util.Collections
22 
23 /**
24  * This helper class defines a cluster of views. It helps with defining complex edges of the cluster
25  * and determining how those edges interact with other views. The edges essentially define a
26  * fine-grained boundary around the cluster of views -- like a more precise version of a bounding
27  * box.
28  */
29 class ViewCluster(
30     private val mCellLayout: CellLayout,
31     views: ArrayList<View>,
32     val config: ItemConfiguration
33 ) {
34 
35     @JvmField val views = ArrayList<View>(views)
36     private val boundingRect = Rect()
37 
38     private val leftEdge = IntArray(mCellLayout.countY)
39     private val rightEdge = IntArray(mCellLayout.countY)
40     private val topEdge = IntArray(mCellLayout.countX)
41     private val bottomEdge = IntArray(mCellLayout.countX)
42 
43     private var dirtyEdges = 0
44     private var boundingRectDirty = false
45 
46     val comparator: PositionComparator = PositionComparator()
47 
48     init {
49         resetEdges()
50     }
51     private fun resetEdges() {
52         for (i in 0 until mCellLayout.countX) {
53             topEdge[i] = -1
54             bottomEdge[i] = -1
55         }
56         for (i in 0 until mCellLayout.countY) {
57             leftEdge[i] = -1
58             rightEdge[i] = -1
59         }
60         dirtyEdges = LEFT or TOP or RIGHT or BOTTOM
61         boundingRectDirty = true
62     }
63 
64     private fun computeEdge(which: Int) =
65         views
66             .mapNotNull { v -> config.map[v] }
67             .forEach { cs ->
68                 val left = cs.cellX
69                 val right = cs.cellX + cs.spanX
70                 val top = cs.cellY
71                 val bottom = cs.cellY + cs.spanY
72                 when (which) {
73                     LEFT ->
74                         for (j in top until bottom) {
75                             if (left < leftEdge[j] || leftEdge[j] < 0) {
76                                 leftEdge[j] = left
77                             }
78                         }
79                     RIGHT ->
80                         for (j in top until bottom) {
81                             if (right > rightEdge[j]) {
82                                 rightEdge[j] = right
83                             }
84                         }
85                     TOP ->
86                         for (j in left until right) {
87                             if (top < topEdge[j] || topEdge[j] < 0) {
88                                 topEdge[j] = top
89                             }
90                         }
91                     BOTTOM ->
92                         for (j in left until right) {
93                             if (bottom > bottomEdge[j]) {
94                                 bottomEdge[j] = bottom
95                             }
96                         }
97                 }
98             }
99 
100     fun isViewTouchingEdge(v: View?, whichEdge: Int): Boolean {
101         val cs = config.map[v] ?: return false
102         val left = cs.cellX
103         val right = cs.cellX + cs.spanX
104         val top = cs.cellY
105         val bottom = cs.cellY + cs.spanY
106         if ((dirtyEdges and whichEdge) == whichEdge) {
107             computeEdge(whichEdge)
108             dirtyEdges = dirtyEdges and whichEdge.inv()
109         }
110         return when (whichEdge) {
111             // In this case if any of the values of leftEdge is equal to right, which is the
112             // rightmost x value of the view, it means that the cluster is touching the view from
113             // the left the same logic applies for the other sides.
114             LEFT -> edgeContainsValue(top, bottom, leftEdge, right)
115             RIGHT -> edgeContainsValue(top, bottom, rightEdge, left)
116             TOP -> edgeContainsValue(left, right, topEdge, bottom)
117             BOTTOM -> edgeContainsValue(left, right, bottomEdge, top)
118             else -> false
119         }
120     }
121 
122     private fun edgeContainsValue(start: Int, end: Int, edge: IntArray, value: Int): Boolean {
123         for (i in start until end) {
124             if (edge[i] == value) {
125                 return true
126             }
127         }
128         return false
129     }
130 
131     fun shift(whichEdge: Int, delta: Int) {
132         views
133             .mapNotNull { v -> config.map[v] }
134             .forEach { c ->
135                 when (whichEdge) {
136                     LEFT -> c.cellX -= delta
137                     RIGHT -> c.cellX += delta
138                     TOP -> c.cellY -= delta
139                     BOTTOM -> c.cellY += delta
140                     else -> c.cellY += delta
141                 }
142             }
143         resetEdges()
144     }
145 
146     fun addView(v: View) {
147         views.add(v)
148         resetEdges()
149     }
150 
151     fun getBoundingRect(): Rect {
152         if (boundingRectDirty) {
153             config.getBoundingRectForViews(views, boundingRect)
154         }
155         return boundingRect
156     }
157 
158     inner class PositionComparator : Comparator<View?> {
159         var whichEdge = 0
160         override fun compare(left: View?, right: View?): Int {
161             val l = config.map[left]
162             val r = config.map[right]
163             if (l == null || r == null) throw NullPointerException()
164             return when (whichEdge) {
165                 LEFT -> r.cellX + r.spanX - (l.cellX + l.spanX)
166                 RIGHT -> l.cellX - r.cellX
167                 TOP -> r.cellY + r.spanY - (l.cellY + l.spanY)
168                 BOTTOM -> l.cellY - r.cellY
169                 else -> l.cellY - r.cellY
170             }
171         }
172     }
173 
174     fun sortConfigurationForEdgePush(edge: Int) {
175         comparator.whichEdge = edge
176         Collections.sort(config.sortedViews, comparator)
177     }
178 
179     companion object {
180         const val LEFT = 1 shl 0
181         const val TOP = 1 shl 1
182         const val RIGHT = 1 shl 2
183         const val BOTTOM = 1 shl 3
184     }
185 }
186