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