• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util.Log
20 
21 /**
22  * Base class for responsive specs that holds a list of width and height specs.
23  *
24  * @param widthSpecs List of width responsive specifications
25  * @param heightSpecs List of height responsive specifications
26  */
27 abstract class ResponsiveSpecs<T : ResponsiveSpec>(
28     val widthSpecs: List<T>,
29     val heightSpecs: List<T>
30 ) {
31 
32     init {
<lambda>null33         check(widthSpecs.isNotEmpty() && heightSpecs.isNotEmpty()) {
34             "${this::class.simpleName} is incomplete - " +
35                 "width list size = ${widthSpecs.size}; " +
36                 "height list size = ${heightSpecs.size}."
37         }
38     }
39 
40     /**
41      * Get a [ResponsiveSpec] for width within the breakpoint.
42      *
43      * @param availableWidth The width breakpoint for the spec
44      * @return A [ResponsiveSpec] for width.
45      */
getWidthSpecnull46     fun getWidthSpec(availableWidth: Int): T {
47         val spec = widthSpecs.firstOrNull { availableWidth <= it.maxAvailableSize }
48         check(spec != null) { "No available width spec found within $availableWidth." }
49         return spec
50     }
51 
52     /**
53      * Get a [ResponsiveSpec] for height within the breakpoint.
54      *
55      * @param availableHeight The height breakpoint for the spec
56      * @return A [ResponsiveSpec] for height.
57      */
getHeightSpecnull58     fun getHeightSpec(availableHeight: Int): T {
59         val spec = heightSpecs.firstOrNull { availableHeight <= it.maxAvailableSize }
60         check(spec != null) { "No available height spec found within $availableHeight." }
61         return spec
62     }
63 }
64 
65 /**
66  * Base class for a responsive specification that is used to calculate the paddings, gutter and cell
67  * size.
68  *
69  * @param maxAvailableSize indicates the breakpoint to use this specification.
70  * @param specType indicates whether the paddings and gutters will be applied vertically or
71  *   horizontally.
72  * @param startPadding padding used at the top or left (right in RTL) in the workspace folder.
73  * @param endPadding padding used at the bottom or right (left in RTL) in the workspace folder.
74  * @param gutter the space between the cells vertically or horizontally depending on the [specType].
75  * @param cellSize height or width of the cell depending on the [specType].
76  */
77 abstract class ResponsiveSpec(
78     open val maxAvailableSize: Int,
79     open val specType: SpecType,
80     open val startPadding: SizeSpec,
81     open val endPadding: SizeSpec,
82     open val gutter: SizeSpec,
83     open val cellSize: SizeSpec
84 ) {
isValidnull85     open fun isValid(): Boolean {
86         if (maxAvailableSize <= 0) {
87             Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
88             return false
89         }
90 
91         // All specs need to be individually valid
92         if (!allSpecsAreValid()) {
93             Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
94             return false
95         }
96 
97         return true
98     }
99 
allSpecsAreValidnull100     private fun allSpecsAreValid(): Boolean {
101         return startPadding.isValid() &&
102             endPadding.isValid() &&
103             gutter.isValid() &&
104             cellSize.isValid()
105     }
106 
107     enum class SpecType {
108         HEIGHT,
109         WIDTH
110     }
111 
112     companion object {
113         private const val LOG_TAG = "ResponsiveSpec"
114     }
115 }
116 
117 /**
118  * Calculated responsive specs contains the final paddings, gutter and cell size in pixels after
119  * they are calculated from the available space, cells and workspace specs.
120  */
121 sealed class CalculatedResponsiveSpec {
122     var availableSpace: Int = 0
123         private set
124 
125     var cells: Int = 0
126         private set
127 
128     var startPaddingPx: Int = 0
129         private set
130 
131     var endPaddingPx: Int = 0
132         private set
133 
134     var gutterPx: Int = 0
135         private set
136 
137     var cellSizePx: Int = 0
138         private set
139 
140     var spec: ResponsiveSpec
141         private set
142 
143     constructor(
144         availableSpace: Int,
145         cells: Int,
146         spec: ResponsiveSpec,
147         calculatedWorkspaceSpec: CalculatedWorkspaceSpec
148     ) {
149         this.availableSpace = availableSpace
150         this.cells = cells
151         this.spec = spec
152 
153         // Map if is fixedSize, ofAvailableSpace or matchWorkspace
154         startPaddingPx =
155             spec.startPadding.getCalculatedValue(
156                 availableSpace,
157                 calculatedWorkspaceSpec.startPaddingPx
158             )
159         endPaddingPx =
160             spec.endPadding.getCalculatedValue(availableSpace, calculatedWorkspaceSpec.endPaddingPx)
161         gutterPx = spec.gutter.getCalculatedValue(availableSpace, calculatedWorkspaceSpec.gutterPx)
162         cellSizePx =
163             spec.cellSize.getCalculatedValue(availableSpace, calculatedWorkspaceSpec.cellSizePx)
164 
165         updateRemainderSpaces(availableSpace, cells, spec)
166     }
167 
168     constructor(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
169         this.availableSpace = availableSpace
170         this.cells = cells
171         this.spec = spec
172 
173         // Map if is fixedSize or ofAvailableSpace
174         startPaddingPx = spec.startPadding.getCalculatedValue(availableSpace)
175         endPaddingPx = spec.endPadding.getCalculatedValue(availableSpace)
176         gutterPx = spec.gutter.getCalculatedValue(availableSpace)
177         cellSizePx = spec.cellSize.getCalculatedValue(availableSpace)
178 
179         updateRemainderSpaces(availableSpace, cells, spec)
180     }
181 
updateRemainderSpacesnull182     private fun updateRemainderSpaces(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
183         val gutters = cells - 1
184         val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
185         val remainderSpace = availableSpace - usedSpace
186 
187         startPaddingPx = spec.startPadding.getRemainderSpaceValue(remainderSpace, startPaddingPx)
188         endPaddingPx = spec.endPadding.getRemainderSpaceValue(remainderSpace, endPaddingPx)
189         gutterPx = spec.gutter.getRemainderSpaceValue(remainderSpace, gutterPx)
190         cellSizePx = spec.cellSize.getRemainderSpaceValue(remainderSpace, cellSizePx)
191     }
192 
hashCodenull193     override fun hashCode(): Int {
194         var result = availableSpace.hashCode()
195         result = 31 * result + cells.hashCode()
196         result = 31 * result + startPaddingPx.hashCode()
197         result = 31 * result + endPaddingPx.hashCode()
198         result = 31 * result + gutterPx.hashCode()
199         result = 31 * result + cellSizePx.hashCode()
200         result = 31 * result + spec.hashCode()
201         return result
202     }
203 
equalsnull204     override fun equals(other: Any?): Boolean {
205         return other is CalculatedResponsiveSpec &&
206             availableSpace == other.availableSpace &&
207             cells == other.cells &&
208             startPaddingPx == other.startPaddingPx &&
209             endPaddingPx == other.endPaddingPx &&
210             gutterPx == other.gutterPx &&
211             cellSizePx == other.cellSizePx &&
212             spec == other.spec
213     }
214 
toStringnull215     override fun toString(): String {
216         return "${this::class.simpleName}(" +
217             "availableSpace=$availableSpace, cells=$cells, startPaddingPx=$startPaddingPx, " +
218             "endPaddingPx=$endPaddingPx, gutterPx=$gutterPx, cellSizePx=$cellSizePx, " +
219             "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
220             ")"
221     }
222 }
223