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