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