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 */ getRemainderSpaceValuenull61 fun getRemainderSpaceValue(remainderSpace: Int, defaultValue: Int): Int { 62 val remainderSpaceValue = 63 if (ofRemainderSpace > 0) { 64 (ofRemainderSpace * remainderSpace).roundToInt() 65 } else { 66 defaultValue 67 } 68 69 return remainderSpaceValue.coerceAtMost(maxSize) 70 } 71 isValidnull72 fun isValid(): Boolean { 73 // All attributes are empty 74 if (fixedSize < 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f && !matchWorkspace) { 75 Log.e(TAG, "SizeSpec#isValid - all attributes are empty") 76 return false 77 } 78 79 // More than one attribute is filled 80 val attrCount = 81 (if (fixedSize > 0) 1 else 0) + 82 (if (ofAvailableSpace > 0) 1 else 0) + 83 (if (ofRemainderSpace > 0) 1 else 0) + 84 (if (matchWorkspace) 1 else 0) 85 if (attrCount > 1) { 86 Log.e(TAG, "SizeSpec#isValid - more than one attribute is filled") 87 return false 88 } 89 90 // Values should be between 0 and 1 91 if (ofAvailableSpace !in 0f..1f || ofRemainderSpace !in 0f..1f) { 92 Log.e(TAG, "SizeSpec#isValid - values should be between 0 and 1") 93 return false 94 } 95 96 // Invalid fixed or max size 97 if (fixedSize < 0f || maxSize < 0f) { 98 Log.e(TAG, "SizeSpec#isValid - values should be bigger or equal to zero.") 99 return false 100 } 101 102 if (fixedSize > 0f && fixedSize > maxSize) { 103 Log.e(TAG, "SizeSpec#isValid - fixed size should be smaller than the max size.") 104 return false 105 } 106 107 return true 108 } 109 onlyFixedSizenull110 fun onlyFixedSize(): Boolean { 111 if (ofAvailableSpace > 0 || ofRemainderSpace > 0 || matchWorkspace) { 112 Log.e(TAG, "SizeSpec#onlyFixedSize - only fixed size allowed for this tag") 113 return false 114 } 115 return true 116 } 117 118 object XmlTags { 119 const val START_PADDING = "startPadding" 120 const val END_PADDING = "endPadding" 121 const val GUTTER = "gutter" 122 const val CELL_SIZE = "cellSize" 123 const val HOTSEAT_QSB_SPACE = "hotseatQsbSpace" 124 } 125 126 companion object { 127 private const val TAG = "SizeSpec" 128 getValuenull129 private fun getValue(a: TypedArray, index: Int): Float { 130 return when (a.getType(index)) { 131 TypedValue.TYPE_DIMENSION -> a.getDimensionPixelSize(index, 0).toFloat() 132 TypedValue.TYPE_FLOAT -> a.getFloat(index, 0f) 133 else -> 0f 134 } 135 } 136 createnull137 fun create(resourceHelper: ResourceHelper, attrs: AttributeSet): SizeSpec { 138 val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SizeSpec) 139 140 val fixedSize = getValue(styledAttrs, R.styleable.SizeSpec_fixedSize) 141 val ofAvailableSpace = getValue(styledAttrs, R.styleable.SizeSpec_ofAvailableSpace) 142 val ofRemainderSpace = getValue(styledAttrs, R.styleable.SizeSpec_ofRemainderSpace) 143 val matchWorkspace = styledAttrs.getBoolean(R.styleable.SizeSpec_matchWorkspace, false) 144 val maxSize = 145 styledAttrs.getDimensionPixelSize(R.styleable.SizeSpec_maxSize, Int.MAX_VALUE) 146 147 styledAttrs.recycle() 148 149 return SizeSpec(fixedSize, ofAvailableSpace, ofRemainderSpace, matchWorkspace, maxSize) 150 } 151 } 152 } 153