1 /*
<lambda>null2  * Copyright 2021 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 package androidx.glance.layout
17 
18 import android.content.res.Resources
19 import androidx.annotation.DimenRes
20 import androidx.annotation.RestrictTo
21 import androidx.compose.ui.unit.Dp
22 import androidx.compose.ui.unit.dp
23 import androidx.glance.GlanceModifier
24 
25 /**
26  * Apply additional space along each edge of the content in [Dp]: [start], [top], [end] and
27  * [bottom]. The start and end edges will be determined by layout direction of the current locale.
28  * Padding is applied before content measurement and takes precedence; content may only be as large
29  * as the remaining space.
30  *
31  * If any value is not defined, it will be [0.dp] or whatever value was defined by an earlier
32  * modifier.
33  */
34 fun GlanceModifier.padding(
35     start: Dp = 0.dp,
36     top: Dp = 0.dp,
37     end: Dp = 0.dp,
38     bottom: Dp = 0.dp,
39 ): GlanceModifier =
40     this.then(
41         PaddingModifier(
42             start = start.toPadding(),
43             top = top.toPadding(),
44             end = end.toPadding(),
45             bottom = bottom.toPadding(),
46         )
47     )
48 
49 /**
50  * Apply additional space along each edge of the content in [Dp]: [start], [top], [end] and
51  * [bottom]. The start and end edges will be determined by layout direction of the current locale.
52  * Padding is applied before content measurement and takes precedence; content may only be as large
53  * as the remaining space.
54  *
55  * If any value is not defined, it will be [0.dp] or whatever value was defined by an earlier
56  * modifier.
57  */
58 fun GlanceModifier.padding(
59     @DimenRes start: Int = 0,
60     @DimenRes top: Int = 0,
61     @DimenRes end: Int = 0,
62     @DimenRes bottom: Int = 0
63 ): GlanceModifier =
64     this.then(
65         PaddingModifier(
66             start = start.toPadding(),
67             top = top.toPadding(),
68             end = end.toPadding(),
69             bottom = bottom.toPadding(),
70         )
71     )
72 
73 /**
74  * Apply [horizontal] dp space along the left and right edges of the content, and [vertical] dp
75  * space along the top and bottom edges.
76  *
77  * If any value is not defined, it will be [0.dp] or whatever value was defined by an earlier
78  * modifier.
79  */
80 fun GlanceModifier.padding(
81     horizontal: Dp = 0.dp,
82     vertical: Dp = 0.dp,
83 ): GlanceModifier =
84     this.then(
85         PaddingModifier(
86             start = horizontal.toPadding(),
87             top = vertical.toPadding(),
88             end = horizontal.toPadding(),
89             bottom = vertical.toPadding(),
90         )
91     )
92 
93 /**
94  * Apply [horizontal] dp space along the left and right edges of the content, and [vertical] dp
95  * space along the top and bottom edges.
96  *
97  * If any value is not defined, it will be [0.dp] or whatever value was defined by an earlier
98  * modifier.
99  */
100 fun GlanceModifier.padding(
101     @DimenRes horizontal: Int = 0,
102     @DimenRes vertical: Int = 0
103 ): GlanceModifier =
104     this.then(
105         PaddingModifier(
106             start = horizontal.toPadding(),
107             top = vertical.toPadding(),
108             end = horizontal.toPadding(),
109             bottom = vertical.toPadding(),
110         )
111     )
112 
113 /**
114  * Apply [all] dp of additional space along each edge of the content, left, top, right and bottom.
115  */
116 fun GlanceModifier.padding(all: Dp): GlanceModifier {
117     val allDp = all.toPadding()
118     return this.then(
119         PaddingModifier(
120             start = allDp,
121             top = allDp,
122             end = allDp,
123             bottom = allDp,
124         )
125     )
126 }
127 
128 /**
129  * Apply [all] dp of additional space along each edge of the content, left, top, right and bottom.
130  */
paddingnull131 fun GlanceModifier.padding(@DimenRes all: Int): GlanceModifier {
132     val allDp = all.toPadding()
133     return this.then(
134         PaddingModifier(
135             start = allDp,
136             top = allDp,
137             end = allDp,
138             bottom = allDp,
139         )
140     )
141 }
142 
143 /**
144  * Apply additional space along each edge of the content in [Dp]: [left], [top], [right] and
145  * [bottom], ignoring the current locale's layout direction.
146  */
absolutePaddingnull147 fun GlanceModifier.absolutePadding(
148     left: Dp = 0.dp,
149     top: Dp = 0.dp,
150     right: Dp = 0.dp,
151     bottom: Dp = 0.dp,
152 ): GlanceModifier =
153     this.then(
154         PaddingModifier(
155             left = left.toPadding(),
156             top = top.toPadding(),
157             right = right.toPadding(),
158             bottom = bottom.toPadding(),
159         )
160     )
161 
162 /**
163  * Apply additional space along each edge of the content in [Dp]: [left], [top], [right] and
164  * [bottom], ignoring the current locale's layout direction.
165  */
166 fun GlanceModifier.absolutePadding(
167     @DimenRes left: Int = 0,
168     @DimenRes top: Int = 0,
169     @DimenRes right: Int = 0,
170     @DimenRes bottom: Int = 0
171 ): GlanceModifier =
172     this.then(
173         PaddingModifier(
174             left = left.toPadding(),
175             top = top.toPadding(),
176             right = right.toPadding(),
177             bottom = bottom.toPadding(),
178         )
179     )
180 
181 private fun Dp.toPadding() = PaddingDimension(dp = this)
182 
183 private fun Int.toPadding() = if (this == 0) PaddingDimension() else PaddingDimension(this)
184 
185 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
186 fun GlanceModifier.collectPadding(): PaddingModifier? =
187     foldIn<PaddingModifier?>(null) { acc, modifier ->
188         if (modifier is PaddingModifier) {
189             (acc ?: PaddingModifier()) + modifier
190         } else {
191             acc
192         }
193     }
194 
195 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
GlanceModifiernull196 fun GlanceModifier.collectPaddingInDp(resources: Resources) = collectPadding()?.toDp(resources)
197 
198 private fun List<Int>.toDp(resources: Resources) =
199     fold(0.dp) { acc, res ->
200         acc + (resources.getDimension(res) / resources.displayMetrics.density).dp
201     }
202 
203 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
204 data class PaddingModifier(
205     val left: PaddingDimension = PaddingDimension(),
206     val start: PaddingDimension = PaddingDimension(),
207     val top: PaddingDimension = PaddingDimension(),
208     val right: PaddingDimension = PaddingDimension(),
209     val end: PaddingDimension = PaddingDimension(),
210     val bottom: PaddingDimension = PaddingDimension(),
211 ) : GlanceModifier.Element {
212 
plusnull213     operator fun plus(other: PaddingModifier) =
214         PaddingModifier(
215             left = left + other.left,
216             start = start + other.start,
217             top = top + other.top,
218             right = right + other.right,
219             end = end + other.end,
220             bottom = bottom + other.bottom,
221         )
222 
223     fun toDp(resources: Resources): PaddingInDp =
224         PaddingInDp(
225             left = left.dp + left.resourceIds.toDp(resources),
226             start = start.dp + start.resourceIds.toDp(resources),
227             top = top.dp + top.resourceIds.toDp(resources),
228             right = right.dp + right.resourceIds.toDp(resources),
229             end = end.dp + end.resourceIds.toDp(resources),
230             bottom = bottom.dp + bottom.resourceIds.toDp(resources),
231         )
232 }
233 
234 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
235 data class PaddingDimension(
236     val dp: Dp = 0.dp,
237     val resourceIds: List<Int> = emptyList(),
238 ) {
239     constructor(@DimenRes resource: Int) : this(resourceIds = listOf(resource))
240 
241     operator fun plus(other: PaddingDimension) =
242         PaddingDimension(
243             dp = dp + other.dp,
244             resourceIds = resourceIds + other.resourceIds,
245         )
246 
247     companion object {
248         val Zero = PaddingDimension()
249     }
250 }
251 
252 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
253 data class PaddingInDp(
254     val left: Dp = 0.dp,
255     val start: Dp = 0.dp,
256     val top: Dp = 0.dp,
257     val right: Dp = 0.dp,
258     val end: Dp = 0.dp,
259     val bottom: Dp = 0.dp,
260 ) {
261     /** Transfer [start] / [end] to [left] / [right] depending on [isRtl]. */
toAbsolutenull262     fun toAbsolute(isRtl: Boolean) =
263         PaddingInDp(
264             left = left + if (isRtl) end else start,
265             top = top,
266             right = right + if (isRtl) start else end,
267             bottom = bottom,
268         )
269 
270     /** Transfer [left] / [right] to [start] / [end] depending on [isRtl]. */
271     fun toRelative(isRtl: Boolean) =
272         PaddingInDp(
273             start = start + if (isRtl) right else left,
274             top = top,
275             end = end + if (isRtl) left else right,
276             bottom = bottom
277         )
278 }
279