1 /*
<lambda>null2 * Copyright 2019 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 androidx.compose.foundation.layout
18
19 import androidx.annotation.FloatRange
20 import androidx.compose.runtime.Stable
21 import androidx.compose.ui.Alignment
22 import androidx.compose.ui.Modifier
23 import androidx.compose.ui.layout.IntrinsicMeasurable
24 import androidx.compose.ui.layout.IntrinsicMeasureScope
25 import androidx.compose.ui.layout.Measurable
26 import androidx.compose.ui.layout.MeasureResult
27 import androidx.compose.ui.layout.MeasureScope
28 import androidx.compose.ui.node.LayoutModifierNode
29 import androidx.compose.ui.node.ModifierNodeElement
30 import androidx.compose.ui.platform.InspectorInfo
31 import androidx.compose.ui.platform.debugInspectorInfo
32 import androidx.compose.ui.unit.Constraints
33 import androidx.compose.ui.unit.Density
34 import androidx.compose.ui.unit.Dp
35 import androidx.compose.ui.unit.DpSize
36 import androidx.compose.ui.unit.IntOffset
37 import androidx.compose.ui.unit.IntSize
38 import androidx.compose.ui.unit.LayoutDirection
39 import androidx.compose.ui.unit.constrain
40 import androidx.compose.ui.unit.constrainHeight
41 import androidx.compose.ui.unit.constrainWidth
42 import androidx.compose.ui.unit.isSpecified
43 import androidx.compose.ui.util.fastCoerceAtLeast
44 import androidx.compose.ui.util.fastCoerceAtMost
45 import androidx.compose.ui.util.fastCoerceIn
46 import androidx.compose.ui.util.fastRoundToInt
47
48 /**
49 * Declare the preferred width of the content to be exactly [width]dp. The incoming measurement
50 * [Constraints] may override this value, forcing the content to be either smaller or larger.
51 *
52 * For a modifier that sets the width of the content regardless of the incoming constraints see
53 * [Modifier.requiredWidth]. See [height] or [size] to set other preferred dimensions. See
54 * [widthIn], [heightIn] or [sizeIn] to set a preferred size range.
55 *
56 * Example usage:
57 *
58 * @sample androidx.compose.foundation.layout.samples.SimpleWidthModifier
59 */
60 @Stable
61 fun Modifier.width(width: Dp) =
62 this.then(
63 SizeElement(
64 minWidth = width,
65 maxWidth = width,
66 enforceIncoming = true,
67 inspectorInfo =
68 debugInspectorInfo {
69 name = "width"
70 value = width
71 }
72 )
73 )
74
75 /**
76 * Declare the preferred height of the content to be exactly [height]dp. The incoming measurement
77 * [Constraints] may override this value, forcing the content to be either smaller or larger.
78 *
79 * For a modifier that sets the height of the content regardless of the incoming constraints see
80 * [Modifier.requiredHeight]. See [width] or [size] to set other preferred dimensions. See
81 * [widthIn], [heightIn] or [sizeIn] to set a preferred size range.
82 *
83 * Example usage:
84 *
85 * @sample androidx.compose.foundation.layout.samples.SimpleHeightModifier
86 */
87 @Stable
Modifiernull88 fun Modifier.height(height: Dp) =
89 this.then(
90 SizeElement(
91 minHeight = height,
92 maxHeight = height,
93 enforceIncoming = true,
94 inspectorInfo =
95 debugInspectorInfo {
96 name = "height"
97 value = height
98 }
99 )
100 )
101
102 /**
103 * Declare the preferred size of the content to be exactly [size]dp square. The incoming measurement
104 * [Constraints] may override this value, forcing the content to be either smaller or larger.
105 *
106 * For a modifier that sets the size of the content regardless of the incoming constraints, see
107 * [Modifier.requiredSize]. See [width] or [height] to set width or height alone. See [widthIn],
108 * [heightIn] or [sizeIn] to set a preferred size range.
109 *
110 * Example usage:
111 *
112 * @sample androidx.compose.foundation.layout.samples.SimpleSizeModifier
113 */
114 @Stable
sizenull115 fun Modifier.size(size: Dp) =
116 this.then(
117 SizeElement(
118 minWidth = size,
119 maxWidth = size,
120 minHeight = size,
121 maxHeight = size,
122 enforceIncoming = true,
123 inspectorInfo =
124 debugInspectorInfo {
125 name = "size"
126 value = size
127 }
128 )
129 )
130
131 /**
132 * Declare the preferred size of the content to be exactly [width]dp by [height]dp. The incoming
133 * measurement [Constraints] may override this value, forcing the content to be either smaller or
134 * larger.
135 *
136 * For a modifier that sets the size of the content regardless of the incoming constraints, see
137 * [Modifier.requiredSize]. See [width] or [height] to set width or height alone. See [widthIn],
138 * [heightIn] or [sizeIn] to set a preferred size range.
139 *
140 * Example usage:
141 *
142 * @sample androidx.compose.foundation.layout.samples.SimpleSizeModifier
143 */
144 @Stable
sizenull145 fun Modifier.size(width: Dp, height: Dp) =
146 this.then(
147 SizeElement(
148 minWidth = width,
149 maxWidth = width,
150 minHeight = height,
151 maxHeight = height,
152 enforceIncoming = true,
153 inspectorInfo =
154 debugInspectorInfo {
155 name = "size"
156 properties["width"] = width
157 properties["height"] = height
158 }
159 )
160 )
161
162 /**
163 * Declare the preferred size of the content to be exactly [size]. The incoming measurement
164 * [Constraints] may override this value, forcing the content to be either smaller or larger.
165 *
166 * For a modifier that sets the size of the content regardless of the incoming constraints, see
167 * [Modifier.requiredSize]. See [width] or [height] to set width or height alone. See [widthIn],
168 * [heightIn] or [sizeIn] to set a preferred size range.
169 *
170 * Example usage:
171 *
172 * @sample androidx.compose.foundation.layout.samples.SimpleSizeModifierWithDpSize
173 */
sizenull174 @Stable fun Modifier.size(size: DpSize) = size(size.width, size.height)
175
176 /**
177 * Constrain the width of the content to be between [min]dp and [max]dp as permitted by the incoming
178 * measurement [Constraints]. If the incoming constraints are more restrictive the requested size
179 * will obey the incoming constraints and attempt to be as close as possible to the preferred size.
180 */
181 @Stable
182 fun Modifier.widthIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified) =
183 this.then(
184 SizeElement(
185 minWidth = min,
186 maxWidth = max,
187 enforceIncoming = true,
188 inspectorInfo =
189 debugInspectorInfo {
190 name = "widthIn"
191 properties["min"] = min
192 properties["max"] = max
193 }
194 )
195 )
196
197 /**
198 * Constrain the height of the content to be between [min]dp and [max]dp as permitted by the
199 * incoming measurement [Constraints]. If the incoming constraints are more restrictive the
200 * requested size will obey the incoming constraints and attempt to be as close as possible to the
201 * preferred size.
202 */
203 @Stable
heightInnull204 fun Modifier.heightIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified) =
205 this.then(
206 SizeElement(
207 minHeight = min,
208 maxHeight = max,
209 enforceIncoming = true,
210 inspectorInfo =
211 debugInspectorInfo {
212 name = "heightIn"
213 properties["min"] = min
214 properties["max"] = max
215 }
216 )
217 )
218
219 /**
220 * Constrain the width of the content to be between [minWidth]dp and [maxWidth]dp and the height of
221 * the content to be between [minHeight]dp and [maxHeight]dp as permitted by the incoming
222 * measurement [Constraints]. If the incoming constraints are more restrictive the requested size
223 * will obey the incoming constraints and attempt to be as close as possible to the preferred size.
224 */
225 @Stable
sizeInnull226 fun Modifier.sizeIn(
227 minWidth: Dp = Dp.Unspecified,
228 minHeight: Dp = Dp.Unspecified,
229 maxWidth: Dp = Dp.Unspecified,
230 maxHeight: Dp = Dp.Unspecified
231 ) =
232 this.then(
233 SizeElement(
234 minWidth = minWidth,
235 minHeight = minHeight,
236 maxWidth = maxWidth,
237 maxHeight = maxHeight,
238 enforceIncoming = true,
239 inspectorInfo =
240 debugInspectorInfo {
241 name = "sizeIn"
242 properties["minWidth"] = minWidth
243 properties["minHeight"] = minHeight
244 properties["maxWidth"] = maxWidth
245 properties["maxHeight"] = maxHeight
246 }
247 )
248 )
249
250 /**
251 * Declare the width of the content to be exactly [width]dp. The incoming measurement [Constraints]
252 * will not override this value. If the content chooses a size that does not satisfy the incoming
253 * [Constraints], the parent layout will be reported a size coerced in the [Constraints], and the
254 * position of the content will be automatically offset to be centered on the space assigned to the
255 * child by the parent layout under the assumption that [Constraints] were respected.
256 *
257 * See [requiredWidthIn] and [requiredSizeIn] to set a size range. See [width] to set a preferred
258 * width, which is only respected when the incoming constraints allow it.
259 *
260 * Example usage:
261 *
262 * @sample androidx.compose.foundation.layout.samples.SimpleRequiredWidthModifier
263 */
264 @Stable
requiredWidthnull265 fun Modifier.requiredWidth(width: Dp) =
266 this.then(
267 SizeElement(
268 minWidth = width,
269 maxWidth = width,
270 enforceIncoming = false,
271 inspectorInfo =
272 debugInspectorInfo {
273 name = "requiredWidth"
274 value = width
275 }
276 )
277 )
278
279 /**
280 * Declare the height of the content to be exactly [height]dp. The incoming measurement
281 * [Constraints] will not override this value. If the content chooses a size that does not satisfy
282 * the incoming [Constraints], the parent layout will be reported a size coerced in the
283 * [Constraints], and the position of the content will be automatically offset to be centered on the
284 * space assigned to the child by the parent layout under the assumption that [Constraints] were
285 * respected.
286 *
287 * See [requiredHeightIn] and [requiredSizeIn] to set a size range. See [height] to set a preferred
288 * height, which is only respected when the incoming constraints allow it.
289 *
290 * Example usage:
291 *
292 * @sample androidx.compose.foundation.layout.samples.SimpleRequiredHeightModifier
293 */
294 @Stable
requiredHeightnull295 fun Modifier.requiredHeight(height: Dp) =
296 this.then(
297 SizeElement(
298 minHeight = height,
299 maxHeight = height,
300 enforceIncoming = false,
301 inspectorInfo =
302 debugInspectorInfo {
303 name = "requiredHeight"
304 value = height
305 }
306 )
307 )
308
309 /**
310 * Declare the size of the content to be exactly [size]dp width and height. The incoming measurement
311 * [Constraints] will not override this value. If the content chooses a size that does not satisfy
312 * the incoming [Constraints], the parent layout will be reported a size coerced in the
313 * [Constraints], and the position of the content will be automatically offset to be centered on the
314 * space assigned to the child by the parent layout under the assumption that [Constraints] were
315 * respected.
316 *
317 * See [requiredSizeIn] to set a size range. See [size] to set a preferred size, which is only
318 * respected when the incoming constraints allow it.
319 *
320 * Example usage:
321 *
322 * @sample androidx.compose.foundation.layout.samples.SimpleRequiredSizeModifier
323 */
324 @Stable
requiredSizenull325 fun Modifier.requiredSize(size: Dp) =
326 this.then(
327 SizeElement(
328 minWidth = size,
329 maxWidth = size,
330 minHeight = size,
331 maxHeight = size,
332 enforceIncoming = false,
333 inspectorInfo =
334 debugInspectorInfo {
335 name = "requiredSize"
336 value = size
337 }
338 )
339 )
340
341 /**
342 * Declare the size of the content to be exactly [width]dp and [height]dp. The incoming measurement
343 * [Constraints] will not override this value. If the content chooses a size that does not satisfy
344 * the incoming [Constraints], the parent layout will be reported a size coerced in the
345 * [Constraints], and the position of the content will be automatically offset to be centered on the
346 * space assigned to the child by the parent layout under the assumption that [Constraints] were
347 * respected.
348 *
349 * See [requiredSizeIn] to set a size range. See [size] to set a preferred size, which is only
350 * respected when the incoming constraints allow it.
351 */
352 @Stable
requiredSizenull353 fun Modifier.requiredSize(width: Dp, height: Dp) =
354 this.then(
355 SizeElement(
356 minWidth = width,
357 maxWidth = width,
358 minHeight = height,
359 maxHeight = height,
360 enforceIncoming = false,
361 inspectorInfo =
362 debugInspectorInfo {
363 name = "requiredSize"
364 properties["width"] = width
365 properties["height"] = height
366 }
367 )
368 )
369
370 /**
371 * Declare the size of the content to be exactly [size]. The incoming measurement [Constraints] will
372 * not override this value. If the content chooses a size that does not satisfy the incoming
373 * [Constraints], the parent layout will be reported a size coerced in the [Constraints], and the
374 * position of the content will be automatically offset to be centered on the space assigned to the
375 * child by the parent layout under the assumption that [Constraints] were respected.
376 *
377 * See [requiredSizeIn] to set a size range. See [size] to set a preferred size, which is only
378 * respected when the incoming constraints allow it.
379 */
requiredSizenull380 @Stable fun Modifier.requiredSize(size: DpSize) = requiredSize(size.width, size.height)
381
382 /**
383 * Constrain the width of the content to be between [min]dp and [max]dp. If the content chooses a
384 * size that does not satisfy the incoming [Constraints], the parent layout will be reported a size
385 * coerced in the [Constraints], and the position of the content will be automatically offset to be
386 * centered on the space assigned to the child by the parent layout under the assumption that
387 * [Constraints] were respected.
388 */
389 @Stable
390 fun Modifier.requiredWidthIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified) =
391 this.then(
392 SizeElement(
393 minWidth = min,
394 maxWidth = max,
395 enforceIncoming = false,
396 inspectorInfo =
397 debugInspectorInfo {
398 name = "requiredWidthIn"
399 properties["min"] = min
400 properties["max"] = max
401 }
402 )
403 )
404
405 /**
406 * Constrain the height of the content to be between [min]dp and [max]dp. If the content chooses a
407 * size that does not satisfy the incoming [Constraints], the parent layout will be reported a size
408 * coerced in the [Constraints], and the position of the content will be automatically offset to be
409 * centered on the space assigned to the child by the parent layout under the assumption that
410 * [Constraints] were respected.
411 */
412 @Stable
requiredHeightInnull413 fun Modifier.requiredHeightIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified) =
414 this.then(
415 SizeElement(
416 minHeight = min,
417 maxHeight = max,
418 enforceIncoming = false,
419 inspectorInfo =
420 debugInspectorInfo {
421 name = "requiredHeightIn"
422 properties["min"] = min
423 properties["max"] = max
424 }
425 )
426 )
427
428 /**
429 * Constrain the width of the content to be between [minWidth]dp and [maxWidth]dp, and the height of
430 * the content to be between [minHeight]dp and [maxHeight]dp. If the content chooses a size that
431 * does not satisfy the incoming [Constraints], the parent layout will be reported a size coerced in
432 * the [Constraints], and the position of the content will be automatically offset to be centered on
433 * the space assigned to the child by the parent layout under the assumption that [Constraints] were
434 * respected.
435 */
436 @Stable
requiredSizeInnull437 fun Modifier.requiredSizeIn(
438 minWidth: Dp = Dp.Unspecified,
439 minHeight: Dp = Dp.Unspecified,
440 maxWidth: Dp = Dp.Unspecified,
441 maxHeight: Dp = Dp.Unspecified
442 ) =
443 this.then(
444 SizeElement(
445 minWidth = minWidth,
446 minHeight = minHeight,
447 maxWidth = maxWidth,
448 maxHeight = maxHeight,
449 enforceIncoming = false,
450 inspectorInfo =
451 debugInspectorInfo {
452 name = "requiredSizeIn"
453 properties["minWidth"] = minWidth
454 properties["minHeight"] = minHeight
455 properties["maxWidth"] = maxWidth
456 properties["maxHeight"] = maxHeight
457 }
458 )
459 )
460
461 /**
462 * Have the content fill (possibly only partially) the [Constraints.maxWidth] of the incoming
463 * measurement constraints, by setting the [minimum width][Constraints.minWidth] and the
464 * [maximum width][Constraints.maxWidth] to be equal to the [maximum width][Constraints.maxWidth]
465 * multiplied by [fraction]. Note that, by default, the [fraction] is 1, so the modifier will make
466 * the content fill the whole available width. If the incoming maximum width is
467 * [Constraints.Infinity] this modifier will have no effect.
468 *
469 * @param fraction The fraction of the maximum width to use, between `0` and `1`, inclusive.
470 *
471 * Example usage:
472 *
473 * @sample androidx.compose.foundation.layout.samples.SimpleFillWidthModifier
474 * @sample androidx.compose.foundation.layout.samples.FillHalfWidthModifier
475 */
476 @Stable
fillMaxWidthnull477 fun Modifier.fillMaxWidth(@FloatRange(from = 0.0, to = 1.0) fraction: Float = 1f) =
478 this.then(if (fraction == 1f) FillWholeMaxWidth else FillElement.width(fraction))
479
480 private val FillWholeMaxWidth = FillElement.width(1f)
481
482 /**
483 * Have the content fill (possibly only partially) the [Constraints.maxHeight] of the incoming
484 * measurement constraints, by setting the [minimum height][Constraints.minHeight] and the
485 * [maximum height][Constraints.maxHeight] to be equal to the
486 * [maximum height][Constraints.maxHeight] multiplied by [fraction]. Note that, by default, the
487 * [fraction] is 1, so the modifier will make the content fill the whole available height. If the
488 * incoming maximum height is [Constraints.Infinity] this modifier will have no effect.
489 *
490 * @param fraction The fraction of the maximum height to use, between `0` and `1`, inclusive.
491 *
492 * Example usage:
493 *
494 * @sample androidx.compose.foundation.layout.samples.SimpleFillHeightModifier
495 * @sample androidx.compose.foundation.layout.samples.FillHalfHeightModifier
496 */
497 @Stable
498 fun Modifier.fillMaxHeight(@FloatRange(from = 0.0, to = 1.0) fraction: Float = 1f) =
499 this.then(if (fraction == 1f) FillWholeMaxHeight else FillElement.height(fraction))
500
501 private val FillWholeMaxHeight = FillElement.height(1f)
502
503 /**
504 * Have the content fill (possibly only partially) the [Constraints.maxWidth] and
505 * [Constraints.maxHeight] of the incoming measurement constraints, by setting the
506 * [minimum width][Constraints.minWidth] and the [maximum width][Constraints.maxWidth] to be equal
507 * to the [maximum width][Constraints.maxWidth] multiplied by [fraction], as well as the
508 * [minimum height][Constraints.minHeight] and the [maximum height][Constraints.minHeight] to be
509 * equal to the [maximum height][Constraints.maxHeight] multiplied by [fraction]. Note that, by
510 * default, the [fraction] is 1, so the modifier will make the content fill the whole available
511 * space. If the incoming maximum width or height is [Constraints.Infinity] this modifier will have
512 * no effect in that dimension.
513 *
514 * @param fraction The fraction of the maximum size to use, between `0` and `1`, inclusive.
515 *
516 * Example usage:
517 *
518 * @sample androidx.compose.foundation.layout.samples.SimpleFillModifier
519 * @sample androidx.compose.foundation.layout.samples.FillHalfSizeModifier
520 */
521 @Stable
522 fun Modifier.fillMaxSize(@FloatRange(from = 0.0, to = 1.0) fraction: Float = 1f) =
523 this.then(if (fraction == 1f) FillWholeMaxSize else FillElement.size(fraction))
524
525 private val FillWholeMaxSize = FillElement.size(1f)
526
527 /**
528 * Allow the content to measure at its desired width without regard for the incoming measurement
529 * [minimum width constraint][Constraints.minWidth], and, if [unbounded] is true, also without
530 * regard for the incoming measurement [maximum width constraint][Constraints.maxWidth]. If the
531 * content's measured size is smaller than the minimum width constraint, [align] it within that
532 * minimum width space. If the content's measured size is larger than the maximum width constraint
533 * (only possible when [unbounded] is true), [align] over the maximum width space.
534 *
535 * Example usage:
536 *
537 * @sample androidx.compose.foundation.layout.samples.SimpleWrapContentHorizontallyAlignedModifier
538 */
539 @Stable
540 fun Modifier.wrapContentWidth(
541 align: Alignment.Horizontal = Alignment.CenterHorizontally,
542 unbounded: Boolean = false
543 ) =
544 this.then(
545 if (align == Alignment.CenterHorizontally && !unbounded) {
546 WrapContentWidthCenter
547 } else if (align == Alignment.Start && !unbounded) {
548 WrapContentWidthStart
549 } else {
550 WrapContentElement.width(align, unbounded)
551 }
552 )
553
554 private val WrapContentWidthCenter = WrapContentElement.width(Alignment.CenterHorizontally, false)
555 private val WrapContentWidthStart = WrapContentElement.width(Alignment.Start, false)
556
557 /**
558 * Allow the content to measure at its desired height without regard for the incoming measurement
559 * [minimum height constraint][Constraints.minHeight], and, if [unbounded] is true, also without
560 * regard for the incoming measurement [maximum height constraint][Constraints.maxHeight]. If the
561 * content's measured size is smaller than the minimum height constraint, [align] it within that
562 * minimum height space. If the content's measured size is larger than the maximum height constraint
563 * (only possible when [unbounded] is true), [align] over the maximum height space.
564 *
565 * Example usage:
566 *
567 * @sample androidx.compose.foundation.layout.samples.SimpleWrapContentVerticallyAlignedModifier
568 */
569 @Stable
wrapContentHeightnull570 fun Modifier.wrapContentHeight(
571 align: Alignment.Vertical = Alignment.CenterVertically,
572 unbounded: Boolean = false
573 ) =
574 this.then(
575 if (align == Alignment.CenterVertically && !unbounded) {
576 WrapContentHeightCenter
577 } else if (align == Alignment.Top && !unbounded) {
578 WrapContentHeightTop
579 } else {
580 WrapContentElement.height(align, unbounded)
581 }
582 )
583
584 private val WrapContentHeightCenter = WrapContentElement.height(Alignment.CenterVertically, false)
585 private val WrapContentHeightTop = WrapContentElement.height(Alignment.Top, false)
586
587 /**
588 * Allow the content to measure at its desired size without regard for the incoming measurement
589 * [minimum width][Constraints.minWidth] or [minimum height][Constraints.minHeight] constraints,
590 * and, if [unbounded] is true, also without regard for the incoming maximum constraints. If the
591 * content's measured size is smaller than the minimum size constraint, [align] it within that
592 * minimum sized space. If the content's measured size is larger than the maximum size constraint
593 * (only possible when [unbounded] is true), [align] within the maximum space.
594 *
595 * Example usage:
596 *
597 * @sample androidx.compose.foundation.layout.samples.SimpleWrapContentAlignedModifier
598 */
599 @Stable
wrapContentSizenull600 fun Modifier.wrapContentSize(align: Alignment = Alignment.Center, unbounded: Boolean = false) =
601 this.then(
602 if (align == Alignment.Center && !unbounded) {
603 WrapContentSizeCenter
604 } else if (align == Alignment.TopStart && !unbounded) {
605 WrapContentSizeTopStart
606 } else {
607 WrapContentElement.size(align, unbounded)
608 }
609 )
610
611 private val WrapContentSizeCenter = WrapContentElement.size(Alignment.Center, false)
612 private val WrapContentSizeTopStart = WrapContentElement.size(Alignment.TopStart, false)
613
614 /**
615 * Constrain the size of the wrapped layout only when it would be otherwise unconstrained: the
616 * [minWidth] and [minHeight] constraints are only applied when the incoming corresponding
617 * constraint is `0`. The modifier can be used, for example, to define a default min size of a
618 * component, while still allowing it to be overidden with smaller min sizes across usages.
619 *
620 * Example usage:
621 *
622 * @sample androidx.compose.foundation.layout.samples.DefaultMinSizeSample
623 */
624 @Stable
Modifiernull625 fun Modifier.defaultMinSize(minWidth: Dp = Dp.Unspecified, minHeight: Dp = Dp.Unspecified) =
626 this.then(UnspecifiedConstraintsElement(minWidth = minWidth, minHeight = minHeight))
627
628 private class FillElement(
629 private val direction: Direction,
630 private val fraction: Float,
631 private val inspectorName: String
632 ) : ModifierNodeElement<FillNode>() {
633 override fun create(): FillNode = FillNode(direction = direction, fraction = fraction)
634
635 override fun update(node: FillNode) {
636 node.direction = direction
637 node.fraction = fraction
638 }
639
640 override fun InspectorInfo.inspectableProperties() {
641 name = inspectorName
642 properties["fraction"] = fraction
643 }
644
645 override fun equals(other: Any?): Boolean {
646 if (this === other) return true
647 if (other !is FillElement) return false
648
649 if (direction != other.direction) return false
650 if (fraction != other.fraction) return false
651
652 return true
653 }
654
655 override fun hashCode(): Int {
656 var result = direction.hashCode()
657 result = 31 * result + fraction.hashCode()
658 return result
659 }
660
661 @Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
662 companion object {
663 @Stable
664 fun width(fraction: Float) =
665 FillElement(
666 direction = Direction.Horizontal,
667 fraction = fraction,
668 inspectorName = "fillMaxWidth"
669 )
670
671 @Stable
672 fun height(fraction: Float) =
673 FillElement(
674 direction = Direction.Vertical,
675 fraction = fraction,
676 inspectorName = "fillMaxHeight"
677 )
678
679 @Stable
680 fun size(fraction: Float) =
681 FillElement(
682 direction = Direction.Both,
683 fraction = fraction,
684 inspectorName = "fillMaxSize"
685 )
686 }
687 }
688
689 private class FillNode(var direction: Direction, var fraction: Float) :
690 LayoutModifierNode, Modifier.Node() {
measurenull691 override fun MeasureScope.measure(
692 measurable: Measurable,
693 constraints: Constraints
694 ): MeasureResult {
695 val minWidth: Int
696 val maxWidth: Int
697 if (constraints.hasBoundedWidth && direction != Direction.Vertical) {
698 val width =
699 (constraints.maxWidth * fraction)
700 .fastRoundToInt()
701 .fastCoerceIn(constraints.minWidth, constraints.maxWidth)
702 minWidth = width
703 maxWidth = width
704 } else {
705 minWidth = constraints.minWidth
706 maxWidth = constraints.maxWidth
707 }
708 val minHeight: Int
709 val maxHeight: Int
710 if (constraints.hasBoundedHeight && direction != Direction.Horizontal) {
711 val height =
712 (constraints.maxHeight * fraction)
713 .fastRoundToInt()
714 .fastCoerceIn(constraints.minHeight, constraints.maxHeight)
715 minHeight = height
716 maxHeight = height
717 } else {
718 minHeight = constraints.minHeight
719 maxHeight = constraints.maxHeight
720 }
721 val placeable = measurable.measure(Constraints(minWidth, maxWidth, minHeight, maxHeight))
722
723 return layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
724 }
725 }
726
727 private class SizeElement(
728 private val minWidth: Dp = Dp.Unspecified,
729 private val minHeight: Dp = Dp.Unspecified,
730 private val maxWidth: Dp = Dp.Unspecified,
731 private val maxHeight: Dp = Dp.Unspecified,
732 private val enforceIncoming: Boolean,
733 private val inspectorInfo: InspectorInfo.() -> Unit
734 ) : ModifierNodeElement<SizeNode>() {
createnull735 override fun create(): SizeNode =
736 SizeNode(
737 minWidth = minWidth,
738 minHeight = minHeight,
739 maxWidth = maxWidth,
740 maxHeight = maxHeight,
741 enforceIncoming = enforceIncoming
742 )
743
744 override fun update(node: SizeNode) {
745 node.minWidth = minWidth
746 node.minHeight = minHeight
747 node.maxWidth = maxWidth
748 node.maxHeight = maxHeight
749 node.enforceIncoming = enforceIncoming
750 }
751
inspectablePropertiesnull752 override fun InspectorInfo.inspectableProperties() {
753 inspectorInfo()
754 }
755
equalsnull756 override fun equals(other: Any?): Boolean {
757 if (this === other) return true
758 if (other !is SizeElement) return false
759
760 if (minWidth != other.minWidth) return false
761 if (minHeight != other.minHeight) return false
762 if (maxWidth != other.maxWidth) return false
763 if (maxHeight != other.maxHeight) return false
764 if (enforceIncoming != other.enforceIncoming) return false
765
766 return true
767 }
768
hashCodenull769 override fun hashCode(): Int {
770 var result = minWidth.hashCode()
771 result = 31 * result + minHeight.hashCode()
772 result = 31 * result + maxWidth.hashCode()
773 result = 31 * result + maxHeight.hashCode()
774 result = 31 * result + enforceIncoming.hashCode()
775 return result
776 }
777 }
778
779 private class SizeNode(
780 var minWidth: Dp = Dp.Unspecified,
781 var minHeight: Dp = Dp.Unspecified,
782 var maxWidth: Dp = Dp.Unspecified,
783 var maxHeight: Dp = Dp.Unspecified,
784 var enforceIncoming: Boolean
785 ) : LayoutModifierNode, Modifier.Node() {
786 private val Density.targetConstraints: Constraints
787 get() {
788 val maxWidth =
789 if (maxWidth.isSpecified) {
790 maxWidth.roundToPx().fastCoerceAtLeast(0)
791 } else {
792 Constraints.Infinity
793 }
794 val maxHeight =
795 if (maxHeight.isSpecified) {
796 maxHeight.roundToPx().fastCoerceAtLeast(0)
797 } else {
798 Constraints.Infinity
799 }
800 val minWidth =
801 if (minWidth.isSpecified) {
<lambda>null802 minWidth.roundToPx().fastCoerceIn(0, maxWidth).let {
803 if (it != Constraints.Infinity) it else 0
804 }
805 } else {
806 0
807 }
808 val minHeight =
809 if (minHeight.isSpecified) {
<lambda>null810 minHeight.roundToPx().fastCoerceIn(0, maxHeight).let {
811 if (it != Constraints.Infinity) it else 0
812 }
813 } else {
814 0
815 }
816 return Constraints(
817 minWidth = minWidth,
818 minHeight = minHeight,
819 maxWidth = maxWidth,
820 maxHeight = maxHeight
821 )
822 }
823
measurenull824 override fun MeasureScope.measure(
825 measurable: Measurable,
826 constraints: Constraints
827 ): MeasureResult {
828 val wrappedConstraints =
829 targetConstraints.let { targetConstraints ->
830 if (enforceIncoming) {
831 constraints.constrain(targetConstraints)
832 } else {
833 val resolvedMinWidth =
834 if (minWidth.isSpecified) {
835 targetConstraints.minWidth
836 } else {
837 constraints.minWidth.fastCoerceAtMost(targetConstraints.maxWidth)
838 }
839 val resolvedMaxWidth =
840 if (maxWidth.isSpecified) {
841 targetConstraints.maxWidth
842 } else {
843 constraints.maxWidth.fastCoerceAtLeast(targetConstraints.minWidth)
844 }
845 val resolvedMinHeight =
846 if (minHeight.isSpecified) {
847 targetConstraints.minHeight
848 } else {
849 constraints.minHeight.fastCoerceAtMost(targetConstraints.maxHeight)
850 }
851 val resolvedMaxHeight =
852 if (maxHeight.isSpecified) {
853 targetConstraints.maxHeight
854 } else {
855 constraints.maxHeight.fastCoerceAtLeast(targetConstraints.minHeight)
856 }
857 Constraints(
858 resolvedMinWidth,
859 resolvedMaxWidth,
860 resolvedMinHeight,
861 resolvedMaxHeight
862 )
863 }
864 }
865 val placeable = measurable.measure(wrappedConstraints)
866 return layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
867 }
868
minIntrinsicWidthnull869 override fun IntrinsicMeasureScope.minIntrinsicWidth(
870 measurable: IntrinsicMeasurable,
871 height: Int
872 ): Int {
873 val constraints = targetConstraints
874 return if (constraints.hasFixedWidth) {
875 constraints.maxWidth
876 } else {
877 val childHeight = if (enforceIncoming) height else constraints.constrainHeight(height)
878 constraints.constrainWidth(measurable.minIntrinsicWidth(childHeight))
879 }
880 }
881
minIntrinsicHeightnull882 override fun IntrinsicMeasureScope.minIntrinsicHeight(
883 measurable: IntrinsicMeasurable,
884 width: Int
885 ): Int {
886 val constraints = targetConstraints
887 return if (constraints.hasFixedHeight) {
888 constraints.maxHeight
889 } else {
890 val childWidth = if (enforceIncoming) width else constraints.constrainWidth(width)
891 constraints.constrainHeight(measurable.minIntrinsicHeight(childWidth))
892 }
893 }
894
maxIntrinsicWidthnull895 override fun IntrinsicMeasureScope.maxIntrinsicWidth(
896 measurable: IntrinsicMeasurable,
897 height: Int
898 ): Int {
899 val constraints = targetConstraints
900 return if (constraints.hasFixedWidth) {
901 constraints.maxWidth
902 } else {
903 val childHeight = if (enforceIncoming) height else constraints.constrainHeight(height)
904 constraints.constrainWidth(measurable.maxIntrinsicWidth(childHeight))
905 }
906 }
907
maxIntrinsicHeightnull908 override fun IntrinsicMeasureScope.maxIntrinsicHeight(
909 measurable: IntrinsicMeasurable,
910 width: Int
911 ): Int {
912 val constraints = targetConstraints
913 return if (constraints.hasFixedHeight) {
914 constraints.maxHeight
915 } else {
916 val childWidth = if (enforceIncoming) width else constraints.constrainWidth(width)
917 constraints.constrainHeight(measurable.maxIntrinsicHeight(childWidth))
918 }
919 }
920 }
921
922 private class WrapContentElement(
923 private val direction: Direction,
924 private val unbounded: Boolean,
925 private val alignmentCallback: (IntSize, LayoutDirection) -> IntOffset,
926 private val align: Any, // only used for equals and hashcode
927 private val inspectorName: String
928 ) : ModifierNodeElement<WrapContentNode>() {
createnull929 override fun create(): WrapContentNode =
930 WrapContentNode(direction, unbounded, alignmentCallback)
931
932 override fun update(node: WrapContentNode) {
933 node.direction = direction
934 node.unbounded = unbounded
935 node.alignmentCallback = alignmentCallback
936 }
937
inspectablePropertiesnull938 override fun InspectorInfo.inspectableProperties() {
939 name = inspectorName
940 properties["align"] = align
941 properties["unbounded"] = unbounded
942 }
943
equalsnull944 override fun equals(other: Any?): Boolean {
945 if (this === other) return true
946 if (other === null) return false
947 if (this::class != other::class) return false
948
949 other as WrapContentElement
950
951 if (direction != other.direction) return false
952 if (unbounded != other.unbounded) return false
953 if (align != other.align) return false
954
955 return true
956 }
957
hashCodenull958 override fun hashCode(): Int {
959 var result = direction.hashCode()
960 result = 31 * result + unbounded.hashCode()
961 result = 31 * result + align.hashCode()
962 return result
963 }
964
965 @Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
966 companion object {
967 @Stable
widthnull968 fun width(align: Alignment.Horizontal, unbounded: Boolean) =
969 WrapContentElement(
970 direction = Direction.Horizontal,
971 unbounded = unbounded,
972 alignmentCallback = { size, layoutDirection ->
973 IntOffset(align.align(0, size.width, layoutDirection), 0)
974 },
975 align,
976 inspectorName = "wrapContentWidth"
977 )
978
979 @Stable
heightnull980 fun height(align: Alignment.Vertical, unbounded: Boolean) =
981 WrapContentElement(
982 direction = Direction.Vertical,
983 unbounded = unbounded,
984 alignmentCallback = { size, _ -> IntOffset(0, align.align(0, size.height)) },
985 align,
986 inspectorName = "wrapContentHeight"
987 )
988
989 @Stable
sizenull990 fun size(align: Alignment, unbounded: Boolean) =
991 WrapContentElement(
992 direction = Direction.Both,
993 unbounded = unbounded,
994 alignmentCallback = { size, layoutDirection ->
995 align.align(IntSize.Zero, size, layoutDirection)
996 },
997 align,
998 inspectorName = "wrapContentSize"
999 )
1000 }
1001 }
1002
1003 private class WrapContentNode(
1004 var direction: Direction,
1005 var unbounded: Boolean,
1006 var alignmentCallback: (IntSize, LayoutDirection) -> IntOffset,
1007 ) : LayoutModifierNode, Modifier.Node() {
measurenull1008 override fun MeasureScope.measure(
1009 measurable: Measurable,
1010 constraints: Constraints
1011 ): MeasureResult {
1012 val wrappedConstraints =
1013 Constraints(
1014 minWidth = if (direction != Direction.Vertical) 0 else constraints.minWidth,
1015 minHeight = if (direction != Direction.Horizontal) 0 else constraints.minHeight,
1016 maxWidth =
1017 if (direction != Direction.Vertical && unbounded) {
1018 Constraints.Infinity
1019 } else {
1020 constraints.maxWidth
1021 },
1022 maxHeight =
1023 if (direction != Direction.Horizontal && unbounded) {
1024 Constraints.Infinity
1025 } else {
1026 constraints.maxHeight
1027 }
1028 )
1029 val placeable = measurable.measure(wrappedConstraints)
1030 val wrapperWidth = placeable.width.coerceIn(constraints.minWidth, constraints.maxWidth)
1031 val wrapperHeight = placeable.height.coerceIn(constraints.minHeight, constraints.maxHeight)
1032 return layout(wrapperWidth, wrapperHeight) {
1033 val position =
1034 alignmentCallback(
1035 IntSize(wrapperWidth - placeable.width, wrapperHeight - placeable.height),
1036 layoutDirection
1037 )
1038 placeable.place(position)
1039 }
1040 }
1041 }
1042
1043 private class UnspecifiedConstraintsElement(
1044 val minWidth: Dp = Dp.Unspecified,
1045 val minHeight: Dp = Dp.Unspecified,
1046 ) : ModifierNodeElement<UnspecifiedConstraintsNode>() {
createnull1047 override fun create(): UnspecifiedConstraintsNode =
1048 UnspecifiedConstraintsNode(minWidth = minWidth, minHeight = minHeight)
1049
1050 override fun update(node: UnspecifiedConstraintsNode) {
1051 node.minWidth = minWidth
1052 node.minHeight = minHeight
1053 }
1054
inspectablePropertiesnull1055 override fun InspectorInfo.inspectableProperties() {
1056 name = "defaultMinSize"
1057 properties["minWidth"] = minWidth
1058 properties["minHeight"] = minHeight
1059 }
1060
equalsnull1061 override fun equals(other: Any?): Boolean {
1062 if (other !is UnspecifiedConstraintsElement) return false
1063 return minWidth == other.minWidth && minHeight == other.minHeight
1064 }
1065
hashCodenull1066 override fun hashCode() = minWidth.hashCode() * 31 + minHeight.hashCode()
1067 }
1068
1069 private class UnspecifiedConstraintsNode(
1070 var minWidth: Dp = Dp.Unspecified,
1071 var minHeight: Dp = Dp.Unspecified
1072 ) : LayoutModifierNode, Modifier.Node() {
1073 override fun MeasureScope.measure(
1074 measurable: Measurable,
1075 constraints: Constraints
1076 ): MeasureResult {
1077 val wrappedConstraints =
1078 Constraints(
1079 if (minWidth.isSpecified && constraints.minWidth == 0) {
1080 minWidth.roundToPx().fastCoerceIn(0, constraints.maxWidth)
1081 } else {
1082 constraints.minWidth
1083 },
1084 constraints.maxWidth,
1085 if (minHeight.isSpecified && constraints.minHeight == 0) {
1086 minHeight.roundToPx().fastCoerceIn(0, constraints.maxHeight)
1087 } else {
1088 constraints.minHeight
1089 },
1090 constraints.maxHeight
1091 )
1092 val placeable = measurable.measure(wrappedConstraints)
1093 return layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
1094 }
1095
1096 override fun IntrinsicMeasureScope.minIntrinsicWidth(
1097 measurable: IntrinsicMeasurable,
1098 height: Int
1099 ) =
1100 measurable
1101 .minIntrinsicWidth(height)
1102 .fastCoerceAtLeast(if (minWidth.isSpecified) minWidth.roundToPx() else 0)
1103
1104 override fun IntrinsicMeasureScope.maxIntrinsicWidth(
1105 measurable: IntrinsicMeasurable,
1106 height: Int
1107 ) =
1108 measurable
1109 .maxIntrinsicWidth(height)
1110 .fastCoerceAtLeast(if (minWidth.isSpecified) minWidth.roundToPx() else 0)
1111
1112 override fun IntrinsicMeasureScope.minIntrinsicHeight(
1113 measurable: IntrinsicMeasurable,
1114 width: Int
1115 ) =
1116 measurable
1117 .minIntrinsicHeight(width)
1118 .fastCoerceAtLeast(if (minHeight.isSpecified) minHeight.roundToPx() else 0)
1119
1120 override fun IntrinsicMeasureScope.maxIntrinsicHeight(
1121 measurable: IntrinsicMeasurable,
1122 width: Int
1123 ) =
1124 measurable
1125 .maxIntrinsicHeight(width)
1126 .fastCoerceAtLeast(if (minHeight.isSpecified) minHeight.roundToPx() else 0)
1127 }
1128
1129 internal enum class Direction {
1130 Vertical,
1131 Horizontal,
1132 Both
1133 }
1134