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