1 /*
2  * 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.board
17 
18 import android.graphics.Point
19 import android.graphics.Rect
20 
21 /**
22  * Compares two [CellLayoutBoard] and returns 0 if they are identical, meaning they have the same
23  * widget and icons in the same place, they can be different letters tough.
24  */
25 class IdenticalBoardComparator : Comparator<CellLayoutBoard> {
26 
27     /** Converts a list of WidgetRect into a map of the count of different widget.bounds */
widgetsToBoundsMapnull28     private fun widgetsToBoundsMap(widgets: List<WidgetRect>) =
29         widgets.groupingBy { it.mBounds }.eachCount()
30 
31     /** Converts a list of IconPoint into a map of the count of different icon.coord */
iconsToPosCountMapnull32     private fun iconsToPosCountMap(widgets: List<IconPoint>) =
33         widgets.groupingBy { it.getCoord() }.eachCount()
34 
comparenull35     override fun compare(
36         cellLayoutBoard: CellLayoutBoard,
37         otherCellLayoutBoard: CellLayoutBoard
38     ): Int {
39         // to be equal they need to have the same number of widgets and the same dimensions
40         // their order can be different
41         val widgetsMap: Map<Rect, Int> =
42             widgetsToBoundsMap(cellLayoutBoard.widgets.filter { !it.shouldIgnore() })
43         val ignoredRectangles: Map<Rect, Int> =
44             widgetsToBoundsMap(cellLayoutBoard.widgets.filter { it.shouldIgnore() })
45 
46         val otherWidgetMap: Map<Rect, Int> =
47             widgetsToBoundsMap(
48                 otherCellLayoutBoard.widgets
49                     .filter { !it.shouldIgnore() }
50                     .filter { !overlapsWithIgnored(ignoredRectangles, it.mBounds) }
51             )
52 
53         if (widgetsMap != otherWidgetMap) {
54             return -1
55         }
56 
57         // to be equal they need to have the same number of icons their order can be different
58         return if (
59             iconsToPosCountMap(cellLayoutBoard.icons) ==
60                 iconsToPosCountMap(otherCellLayoutBoard.icons)
61         ) {
62             0
63         } else {
64             1
65         }
66     }
67 
overlapsWithIgnorednull68     private fun overlapsWithIgnored(ignoredRectangles: Map<Rect, Int>, rect: Rect): Boolean {
69         for (ignoredRect in ignoredRectangles.keys) {
70             // Using the built in intersects doesn't work because it doesn't account for area 0
71             if (touches(ignoredRect, rect)) {
72                 return true
73             }
74         }
75         return false
76     }
77 
78     companion object {
79         /**
80          * Similar function to {@link Rect#intersects} but this one returns true if the rectangles
81          * are intersecting or touching whereas {@link Rect#intersects} doesn't return true when
82          * they are touching.
83          */
touchesnull84         fun touches(r1: Rect, r2: Rect): Boolean {
85             // If one rectangle is on left side of other
86             return if (r1.left > r2.right || r2.left > r1.right) {
87                 false
88             } else r1.bottom <= r2.top && r2.bottom <= r1.top
89 
90             // If one rectangle is above other
91         }
92 
93         /**
94          * Similar function to {@link Rect#contains} but this one returns true if {link @Point} is
95          * intersecting or touching the {@link Rect}. Similar to {@link touches}.
96          */
touchesPointnull97         fun touchesPoint(r1: Rect, p: Point): Boolean {
98             return r1.left <= p.x && p.x <= r1.right && r1.bottom <= p.y && p.y <= r1.top
99         }
100     }
101 }
102