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