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 17 package com.android.launcher3.responsive 18 19 import android.content.res.TypedArray 20 import android.util.AttributeSet 21 import android.util.Log 22 import android.util.TypedValue 23 import com.android.launcher3.R 24 import com.android.launcher3.util.ResourceHelper 25 import kotlin.math.roundToInt 26 27 /** 28 * [SizeSpec] is an attribute used to represent a property in the responsive grid specs. 29 * 30 * @param fixedSize a fixed size in dp to be used 31 * @param ofAvailableSpace a percentage of the available space 32 * @param ofRemainderSpace a percentage of the remaining space (available space minus used space) 33 * @param matchWorkspace indicates whether the workspace value will be used or not. 34 * @param maxSize restricts the maximum value allowed for the [SizeSpec]. 35 */ 36 data class SizeSpec( 37 val fixedSize: Float = 0f, 38 val ofAvailableSpace: Float = 0f, 39 val ofRemainderSpace: Float = 0f, 40 val matchWorkspace: Boolean = false, 41 val maxSize: Int = Int.MAX_VALUE 42 ) { 43 44 /** Retrieves the correct value for [SizeSpec]. */ getCalculatedValuenull45 fun getCalculatedValue(availableSpace: Int, workspaceValue: Int = 0): Int { 46 val calculatedValue = 47 when { 48 fixedSize > 0 -> fixedSize.roundToInt() 49 matchWorkspace -> workspaceValue 50 ofAvailableSpace > 0 -> (ofAvailableSpace * availableSpace).roundToInt() 51 else -> 0 52 } 53 54 return calculatedValue.coerceAtMost(maxSize) 55 } 56 57 /** 58 * Calculates the [SizeSpec] value when remainder space value is defined. If no remainderSpace 59 * is 0, returns a default value. 60 * 61 * @param remainderSpace The remainder space to be used for the calculation 62 * @param defaultValue The default value to be returned when no ofRemainderSpace is defined 63 * @param factor A number to divide the remainder space. The default value is 1. This property 64 * is used to split equally the remainder space by the number of cells and gutters. 65 */ getRemainderSpaceValuenull66 fun getRemainderSpaceValue(remainderSpace: Int, defaultValue: Int, factor: Int = 1): Int { 67 val remainderSpaceValue = 68 if (ofRemainderSpace > 0) { 69 (ofRemainderSpace * remainderSpace / factor).roundToInt() 70 } else { 71 defaultValue 72 } 73 74 return remainderSpaceValue.coerceAtMost(maxSize) 75 } 76 isValidnull77 fun isValid(): Boolean { 78 // All attributes are empty 79 if (fixedSize < 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f && !matchWorkspace) { 80 Log.e(TAG, "SizeSpec#isValid - all attributes are empty") 81 return false 82 } 83 84 // More than one attribute is filled 85 val attrCount = 86 (if (fixedSize > 0) 1 else 0) + 87 (if (ofAvailableSpace > 0) 1 else 0) + 88 (if (ofRemainderSpace > 0) 1 else 0) + 89 (if (matchWorkspace) 1 else 0) 90 if (attrCount > 1) { 91 Log.e(TAG, "SizeSpec#isValid - more than one attribute is filled") 92 return false 93 } 94 95 // Values should be between 0 and 1 96 if (ofAvailableSpace !in 0f..1f || ofRemainderSpace !in 0f..1f) { 97 Log.e(TAG, "SizeSpec#isValid - values should be between 0 and 1") 98 return false 99 } 100 101 // Invalid fixed or max size 102 if (fixedSize < 0f || maxSize < 0f) { 103 Log.e(TAG, "SizeSpec#isValid - values should be bigger or equal to zero.") 104 return false 105 } 106 107 if (fixedSize > 0f && fixedSize > maxSize) { 108 Log.e(TAG, "SizeSpec#isValid - fixed size should be smaller than the max size.") 109 return false 110 } 111 112 return true 113 } 114 onlyFixedSizenull115 fun onlyFixedSize(): Boolean { 116 if (ofAvailableSpace > 0 || ofRemainderSpace > 0 || matchWorkspace) { 117 Log.e(TAG, "SizeSpec#onlyFixedSize - only fixed size allowed for this tag") 118 return false 119 } 120 return true 121 } 122 123 object XmlTags { 124 const val START_PADDING = "startPadding" 125 const val END_PADDING = "endPadding" 126 const val GUTTER = "gutter" 127 const val CELL_SIZE = "cellSize" 128 const val HOTSEAT_QSB_SPACE = "hotseatQsbSpace" 129 const val EDGE_PADDING = "edgePadding" 130 const val ICON_SIZE = "iconSize" 131 const val ICON_TEXT_SIZE = "iconTextSize" 132 const val ICON_DRAWABLE_PADDING = "iconDrawablePadding" 133 } 134 135 companion object { 136 private const val TAG = "SizeSpec" 137 getValuenull138 private fun getValue(a: TypedArray, index: Int): Float { 139 return when (a.getType(index)) { 140 TypedValue.TYPE_DIMENSION -> a.getDimensionPixelSize(index, 0).toFloat() 141 TypedValue.TYPE_FLOAT -> a.getFloat(index, 0f) 142 else -> 0f 143 } 144 } 145 createnull146 fun create(resourceHelper: ResourceHelper, attrs: AttributeSet): SizeSpec { 147 val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SizeSpec) 148 149 val fixedSize = getValue(styledAttrs, R.styleable.SizeSpec_fixedSize) 150 val ofAvailableSpace = getValue(styledAttrs, R.styleable.SizeSpec_ofAvailableSpace) 151 val ofRemainderSpace = getValue(styledAttrs, R.styleable.SizeSpec_ofRemainderSpace) 152 val matchWorkspace = styledAttrs.getBoolean(R.styleable.SizeSpec_matchWorkspace, false) 153 val maxSize = 154 styledAttrs.getDimensionPixelSize(R.styleable.SizeSpec_maxSize, Int.MAX_VALUE) 155 156 styledAttrs.recycle() 157 158 return SizeSpec(fixedSize, ofAvailableSpace, ofRemainderSpace, matchWorkspace, maxSize) 159 } 160 } 161 } 162