1 /*
2  * Copyright (C) 2024 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 
17 package com.android.launcher3.widget.picker.util
18 
19 import com.android.launcher3.DeviceProfile
20 import com.android.launcher3.model.WidgetItem
21 import kotlin.math.abs
22 
23 /** Size of a preview container in terms of the grid spans. */
24 data class WidgetPreviewContainerSize(@JvmField val spanX: Int, @JvmField val spanY: Int) {
25     companion object {
26         /**
27          * Returns the size of the preview container in which the given widget's preview should be
28          * displayed (by scaling it if necessary).
29          */
forItemnull30         fun forItem(item: WidgetItem, dp: DeviceProfile): WidgetPreviewContainerSize {
31             val sizes =
32                 if (dp.isTablet && !dp.isTwoPanels) {
33                     TABLET_WIDGET_PREVIEW_SIZES
34                 } else {
35                     HANDHELD_WIDGET_PREVIEW_SIZES
36                 }
37 
38             for ((index, containerSize) in sizes.withIndex()) {
39                 if (containerSize.spanX == item.spanX && containerSize.spanY == item.spanY) {
40                     return containerSize // Exact match!
41                 }
42                 if (containerSize.spanX <= item.spanX && containerSize.spanY <= item.spanY) {
43                     return findClosestFittingContainer(
44                         containerSizes = sizes.toList(),
45                         startIndex = index,
46                         item = item
47                     )
48                 }
49             }
50             // Use largest container if no match found
51             return sizes.elementAt(0)
52         }
53 
findClosestFittingContainernull54         private fun findClosestFittingContainer(
55             containerSizes: List<WidgetPreviewContainerSize>,
56             startIndex: Int,
57             item: WidgetItem
58         ): WidgetPreviewContainerSize {
59             // Checks if it's a smaller container, but close enough to keep the down-scale minimal.
60             fun hasAcceptableSize(currentIndex: Int): Boolean {
61                 val container = containerSizes[currentIndex]
62                 val isSmallerThanItem =
63                     container.spanX <= item.spanX && container.spanY <= item.spanY
64                 val isCloseToItemSize =
65                     (item.spanY - container.spanY <= 1) && (item.spanX - container.spanX <= 1)
66 
67                 return isSmallerThanItem && isCloseToItemSize
68             }
69 
70             var currentIndex = startIndex
71             var match = containerSizes[currentIndex]
72             val itemCellSizeRatio = item.spanX.toFloat() / item.spanY
73             var lastCellSizeRatioDiff = Float.MAX_VALUE
74 
75             // Look for a smaller container (up to an acceptable extent) with closest cell size
76             // ratio.
77             while (currentIndex <= containerSizes.lastIndex && hasAcceptableSize(currentIndex)) {
78                 val current = containerSizes[currentIndex]
79                 val currentCellSizeRatio = current.spanX.toFloat() / current.spanY
80                 val currentCellSizeRatioDiff = abs(itemCellSizeRatio - currentCellSizeRatio)
81 
82                 if (currentCellSizeRatioDiff < lastCellSizeRatioDiff) {
83                     lastCellSizeRatioDiff = currentCellSizeRatioDiff
84                     match = containerSizes[currentIndex]
85                 }
86                 currentIndex++
87             }
88             return match
89         }
90     }
91 }
92