1 /*
<lambda>null2 * Copyright 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 @file:Suppress("DEPRECATION")
18
19 package androidx.compose.foundation.layout
20
21 import androidx.collection.IntIntPair
22 import androidx.compose.foundation.layout.ContextualFlowRowOverflow.Companion.Clip
23 import androidx.compose.foundation.layout.ContextualFlowRowOverflow.Companion.Visible
24 import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.Clip
25 import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.Visible
26 import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.expandIndicator
27 import androidx.compose.foundation.layout.FlowColumnOverflow.Companion.expandOrCollapseIndicator
28 import androidx.compose.foundation.layout.FlowRowOverflow.Companion.Clip
29 import androidx.compose.foundation.layout.FlowRowOverflow.Companion.expandIndicator
30 import androidx.compose.foundation.layout.FlowRowOverflow.Companion.expandOrCollapseIndicator
31 import androidx.compose.runtime.Composable
32 import androidx.compose.runtime.remember
33 import androidx.compose.ui.layout.IntrinsicMeasurable
34 import androidx.compose.ui.layout.Measurable
35 import androidx.compose.ui.layout.Placeable
36 import androidx.compose.ui.platform.LocalDensity
37 import androidx.compose.ui.unit.Constraints
38 import androidx.compose.ui.unit.Dp
39 import androidx.compose.ui.unit.dp
40
41 /**
42 * Overflow Handling Options
43 *
44 * This enumeration defines the available options for handling content that exceeds the boundaries
45 * of its container for [FlowRow].
46 *
47 * Options:
48 * - [Visible]: The overflowing content remains visible outside its container. This can lead to
49 * overlapping with other elements. Use this option when it's crucial to display all content,
50 * regardless of the container's size.
51 * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of its
52 * container. Ideal for maintaining a clean and uncluttered UI, where overlapping elements are
53 * undesirable.
54 * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button indicating
55 * that more items can be loaded.
56 * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality by adding a 'Collapse'
57 * option. After expanding the content, users can choose to collapse it back to the summary view.
58 */
59 @Deprecated("FlowLayout overflow is no longer maintained")
60 @ExperimentalLayoutApi
61 class FlowRowOverflow
62 private constructor(
63 type: OverflowType,
64 minLinesToShowCollapse: Int = 0,
65 minCrossAxisSizeToShowCollapse: Int = 0,
66 seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
67 collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
68 ) :
69 FlowLayoutOverflow(
70 type,
71 minLinesToShowCollapse,
72 minCrossAxisSizeToShowCollapse,
73 seeMoreGetter,
74 collapseGetter
75 ) {
76
77 companion object {
78 /** Display all content, even if there is not enough space in the specified bounds. */
79 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
80 @ExperimentalLayoutApi
81 @get:ExperimentalLayoutApi
82 val Visible = FlowRowOverflow(OverflowType.Visible)
83
84 /** Clip the overflowing content to fix its container. */
85 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
86 @ExperimentalLayoutApi
87 @get:ExperimentalLayoutApi
88 val Clip = FlowRowOverflow(OverflowType.Clip)
89
90 /**
91 * Registers an "expand indicator" composable for handling overflow in a [FlowRow].
92 *
93 * This function allows the creation of a custom composable that can be displayed when there
94 * are more items in the [FlowRow] than can be displayed in the available space. The
95 * "expandable indicator" composable can be tailored to show a summary, a button, or any
96 * other composable element that indicates the presence of additional items.
97 *
98 * @sample androidx.compose.foundation.layout.samples.SimpleFlowRowMaxLinesWithSeeMore
99 * @param content composable that visually indicates more items can be loaded.
100 */
101 @ExperimentalLayoutApi
102 fun expandIndicator(content: @Composable FlowRowOverflowScope.() -> Unit): FlowRowOverflow {
103 val seeMoreGetter = { state: FlowLayoutOverflowState ->
104 @Composable {
105 val scope = FlowRowOverflowScopeImpl(state)
106 scope.content()
107 }
108 }
109 return FlowRowOverflow(OverflowType.ExpandIndicator, seeMoreGetter = seeMoreGetter)
110 }
111
112 /**
113 * Registers an "expand or collapse indicator" composable for handling overflow in a
114 * [FlowRow].
115 *
116 * This function is designed to switch between two states: a "Expandable" state when there
117 * are more items to show, and a "Collapsable" state when all items are being displayed and
118 * can be collapsed.
119 *
120 * Prior to layout, the function evaluates both composables indicators to determine their
121 * maximum intrinsic size. Depending on the space available and the number of items, either
122 * the [expandIndicator] or the [collapseIndicator] is rendered.
123 *
124 * @sample androidx.compose.foundation.layout.samples.SimpleFlowRowMaxLinesDynamicSeeMore
125 * @param minRowsToShowCollapse Specifies the minimum number of rows that should be visible
126 * before showing the collapse option. This parameter is useful when the number of rows is
127 * too small to be reduced further.
128 * @param minHeightToShowCollapse Specifies the minimum height at which the collapse option
129 * should be displayed. This parameter is useful for preventing the collapse option from
130 * appearing when the height is too narrow.
131 * @param expandIndicator composable that visually indicates more items can be loaded.
132 * @param collapseIndicator composable that visually indicates less items can be loaded.
133 */
134 @ExperimentalLayoutApi
135 @Composable
136 fun expandOrCollapseIndicator(
137 expandIndicator: @Composable FlowRowOverflowScope.() -> Unit,
138 collapseIndicator: @Composable FlowRowOverflowScope.() -> Unit,
139 minRowsToShowCollapse: Int = 1,
140 minHeightToShowCollapse: Dp = 0.dp,
141 ): FlowRowOverflow {
142 val minHeightToShowCollapsePx =
143 with(LocalDensity.current) { minHeightToShowCollapse.roundToPx() }
144 return remember(
145 minRowsToShowCollapse,
146 minHeightToShowCollapsePx,
147 expandIndicator,
148 collapseIndicator
149 ) {
150 val seeMoreGetter = { state: FlowLayoutOverflowState ->
151 @Composable {
152 val scope = FlowRowOverflowScopeImpl(state)
153 scope.expandIndicator()
154 }
155 }
156
157 val collapseGetter = { state: FlowLayoutOverflowState ->
158 @Composable {
159 val scope = FlowRowOverflowScopeImpl(state)
160 scope.collapseIndicator()
161 }
162 }
163
164 FlowRowOverflow(
165 OverflowType.ExpandOrCollapseIndicator,
166 minLinesToShowCollapse = minRowsToShowCollapse,
167 minCrossAxisSizeToShowCollapse = minHeightToShowCollapsePx,
168 seeMoreGetter = seeMoreGetter,
169 collapseGetter = collapseGetter
170 )
171 }
172 }
173 }
174 }
175
176 /**
177 * Overflow Handling Options
178 *
179 * This enumeration defines the available options for handling content that exceeds the boundaries
180 * of its container for [FlowColumn] and [ContextualFlowColumn].
181 *
182 * Options:
183 * - [Visible]: The overflowing content remains visible outside its container. This can lead to
184 * overlapping with other elements. Use this option when it's crucial to display all content,
185 * regardless of the container's size.
186 * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of its
187 * container. Ideal for maintaining a clean and uncluttered UI, where overlapping elements are
188 * undesirable.
189 * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button indicating
190 * that more items can be loaded.
191 * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality by adding a 'Collapse'
192 * option. After expanding the content, users can choose to collapse it back to the summary view.
193 */
194 @Deprecated("FlowLayout overflow is no longer maintained")
195 @ExperimentalLayoutApi
196 class FlowColumnOverflow
197 private constructor(
198 type: OverflowType,
199 minLinesToShowCollapse: Int = 0,
200 minCrossAxisSizeToShowCollapse: Int = 0,
201 seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
202 collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
203 ) :
204 FlowLayoutOverflow(
205 type,
206 minLinesToShowCollapse,
207 minCrossAxisSizeToShowCollapse,
208 seeMoreGetter,
209 collapseGetter
210 ) {
211 @Deprecated("FlowLayout overflow is no longer maintained")
212 @ExperimentalLayoutApi
213 companion object {
214 /** Display all content, even if there is not enough space in the specified bounds. */
215 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
216 @ExperimentalLayoutApi
217 @get:ExperimentalLayoutApi
218 val Visible = FlowColumnOverflow(FlowLayoutOverflow.OverflowType.Visible)
219
220 /** Clip the overflowing content to fix its container. */
221 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
222 @ExperimentalLayoutApi
223 @get:ExperimentalLayoutApi
224 val Clip = FlowColumnOverflow(FlowLayoutOverflow.OverflowType.Clip)
225
226 /**
227 * Registers an "expand indicator" composable for handling overflow in a [FlowColumn].
228 *
229 * This function allows the creation of a custom composable that can be displayed when there
230 * are more items in the [FlowColumn] than can be displayed in the available space. The
231 * "expandable indicator" composable can be tailored to show a summary, a button, or any
232 * other composable element that indicates the presence of additional items.
233 *
234 * @sample androidx.compose.foundation.layout.samples.SimpleFlowColumnMaxLinesWithSeeMore
235 * @param content composable that visually indicates more items can be loaded.
236 */
237 @ExperimentalLayoutApi
expandIndicatornull238 fun expandIndicator(
239 content: @Composable FlowColumnOverflowScope.() -> Unit
240 ): FlowColumnOverflow {
241 val seeMoreGetter = { state: FlowLayoutOverflowState ->
242 @Composable {
243 val scope = FlowColumnOverflowScopeImpl(state)
244 scope.content()
245 }
246 }
247 return FlowColumnOverflow(OverflowType.ExpandIndicator, seeMoreGetter = seeMoreGetter)
248 }
249
250 /**
251 * Registers an "expand or collapse indicator" composable for handling overflow in a
252 * [FlowColumn].
253 *
254 * This function is designed to switch between two states: a "Expandable" state when there
255 * are more items to show, and a "Collapsable" state when all items are being displayed and
256 * can be collapsed.
257 *
258 * Prior to layout, the function evaluates both composables indicators to determine their
259 * maximum intrinsic size. Depending on the space available and the number of items, either
260 * the [expandIndicator] or the [collapseIndicator] is rendered.
261 *
262 * @sample androidx.compose.foundation.layout.samples.SimpleFlowColumnMaxLinesDynamicSeeMore
263 * @param minColumnsToShowCollapse Specifies the minimum number of columns that should be
264 * visible before showing the collapse option. This parameter is useful when the number of
265 * columns is too small to be reduced further.
266 * @param minWidthToShowCollapse Specifies the minimum width at which the collapse option
267 * should be displayed. This parameter is useful for preventing the collapse option from
268 * appearing when the width is too narrow.
269 * @param expandIndicator composable that visually indicates more items can be loaded.
270 * @param collapseIndicator composable that visually indicates less items can be loaded.
271 */
272 @ExperimentalLayoutApi
273 @Composable
expandOrCollapseIndicatornull274 fun expandOrCollapseIndicator(
275 expandIndicator: @Composable FlowColumnOverflowScope.() -> Unit,
276 collapseIndicator: @Composable FlowColumnOverflowScope.() -> Unit,
277 minColumnsToShowCollapse: Int = 1,
278 minWidthToShowCollapse: Dp = 0.dp,
279 ): FlowColumnOverflow {
280 val minWidthToShowCollapsePx =
281 with(LocalDensity.current) { minWidthToShowCollapse.roundToPx() }
282 return remember(
283 minColumnsToShowCollapse,
284 minWidthToShowCollapsePx,
285 expandIndicator,
286 collapseIndicator
287 ) {
288 val seeMoreGetter = { state: FlowLayoutOverflowState ->
289 @Composable {
290 val scope = FlowColumnOverflowScopeImpl(state)
291 scope.expandIndicator()
292 }
293 }
294
295 val collapseGetter = { state: FlowLayoutOverflowState ->
296 @Composable {
297 val scope = FlowColumnOverflowScopeImpl(state)
298 scope.collapseIndicator()
299 }
300 }
301
302 FlowColumnOverflow(
303 OverflowType.ExpandOrCollapseIndicator,
304 minLinesToShowCollapse = minColumnsToShowCollapse,
305 minCrossAxisSizeToShowCollapse = minWidthToShowCollapsePx,
306 seeMoreGetter = seeMoreGetter,
307 collapseGetter = collapseGetter
308 )
309 }
310 }
311 }
312 }
313
314 /**
315 * Overflow Handling Options
316 *
317 * This enumeration defines the available options for handling content that exceeds the boundaries
318 * of its container for [ContextualFlowRow].
319 *
320 * Options:
321 * - [Visible]: The overflowing content remains visible outside its container. This can lead to
322 * overlapping with other elements. Use this option when it's crucial to display all content,
323 * regardless of the container's size.
324 * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of its
325 * container. Ideal for maintaining a clean and uncluttered UI, where overlapping elements are
326 * undesirable.
327 * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button indicating
328 * that more items can be loaded.
329 * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality by adding a 'Collapse'
330 * option. After expanding the content, users can choose to collapse it back to the summary view.
331 */
332 @Deprecated("ContextualFlowLayouts are no longer maintained")
333 @ExperimentalLayoutApi
334 class ContextualFlowRowOverflow
335 private constructor(
336 type: OverflowType,
337 minLinesToShowCollapse: Int = 0,
338 minCrossAxisSizeToShowCollapse: Int = 0,
339 seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
340 collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
341 ) :
342 FlowLayoutOverflow(
343 type,
344 minLinesToShowCollapse,
345 minCrossAxisSizeToShowCollapse,
346 seeMoreGetter,
347 collapseGetter
348 ) {
349
350 @Deprecated("FlowLayout overflow is no longer maintained")
351 @ExperimentalLayoutApi
352 companion object {
353 /** Display all content, even if there is not enough space in the specified bounds. */
354 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
355 @ExperimentalLayoutApi
356 @get:ExperimentalLayoutApi
357 val Visible = ContextualFlowRowOverflow(FlowLayoutOverflow.OverflowType.Visible)
358
359 /** Clip the overflowing content to fix its container. */
360 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
361 @ExperimentalLayoutApi
362 @get:ExperimentalLayoutApi
363 val Clip = ContextualFlowRowOverflow(FlowLayoutOverflow.OverflowType.Clip)
364
365 /**
366 * Registers an "expand indicator" composable for handling overflow in a
367 * [ContextualFlowRow].
368 *
369 * This function allows the creation of a custom composable that can be displayed when there
370 * are more items in the [ContextualFlowRow] than can be displayed in the available space.
371 * The "expandable indicator" composable can be tailored to show a summary, a button, or any
372 * other composable element that indicates the presence of additional items.
373 *
374 * @param content composable that visually indicates more items can be loaded.
375 */
376 @ExperimentalLayoutApi
expandIndicatornull377 fun expandIndicator(
378 content: @Composable ContextualFlowRowOverflowScope.() -> Unit
379 ): ContextualFlowRowOverflow {
380 val seeMoreGetter = { state: FlowLayoutOverflowState ->
381 @Composable {
382 val scope = ContextualFlowRowOverflowScopeImpl(state)
383 scope.content()
384 }
385 }
386 return ContextualFlowRowOverflow(
387 OverflowType.ExpandIndicator,
388 seeMoreGetter = seeMoreGetter
389 )
390 }
391
392 /**
393 * Registers an "expand or collapse indicator" composable for handling overflow in a
394 * [ContextualFlowRow].
395 *
396 * This function is designed to switch between two states: a "Expandable" state when there
397 * are more items to show, and a "Collapsable" state when all items are being displayed and
398 * can be collapsed.
399 *
400 * Prior to layout, the function evaluates both composables indicators to determine their
401 * maximum intrinsic size. Depending on the space available and the number of items, either
402 * the [expandIndicator] or the [collapseIndicator] is rendered.
403 *
404 * @sample androidx.compose.foundation.layout.samples.ContextualFlowRowMaxLineDynamicSeeMore
405 * @param minRowsToShowCollapse Specifies the minimum number of rows that should be visible
406 * before showing the collapse option. This parameter is useful when the number of rows is
407 * too small to be reduced further.
408 * @param minHeightToShowCollapse Specifies the minimum height at which the collapse option
409 * should be displayed. This parameter is useful for preventing the collapse option from
410 * appearing when the height is too narrow.
411 * @param expandIndicator composable that visually indicates more items can be loaded.
412 * @param collapseIndicator composable that visually indicates less items can be loaded.
413 */
414 @ExperimentalLayoutApi
415 @Composable
expandOrCollapseIndicatornull416 fun expandOrCollapseIndicator(
417 expandIndicator: @Composable ContextualFlowRowOverflowScope.() -> Unit,
418 collapseIndicator: @Composable ContextualFlowRowOverflowScope.() -> Unit,
419 minRowsToShowCollapse: Int = 1,
420 minHeightToShowCollapse: Dp = 0.dp,
421 ): ContextualFlowRowOverflow {
422 val minHeightToShowCollapsePx =
423 with(LocalDensity.current) { minHeightToShowCollapse.roundToPx() }
424 return remember(
425 minRowsToShowCollapse,
426 minHeightToShowCollapsePx,
427 expandIndicator,
428 collapseIndicator
429 ) {
430 val seeMoreGetter = { state: FlowLayoutOverflowState ->
431 @Composable {
432 val scope = ContextualFlowRowOverflowScopeImpl(state)
433 scope.expandIndicator()
434 }
435 }
436
437 val collapseGetter = { state: FlowLayoutOverflowState ->
438 @Composable {
439 val scope = ContextualFlowRowOverflowScopeImpl(state)
440 scope.collapseIndicator()
441 }
442 }
443
444 ContextualFlowRowOverflow(
445 OverflowType.ExpandOrCollapseIndicator,
446 minLinesToShowCollapse = minRowsToShowCollapse,
447 minCrossAxisSizeToShowCollapse = minHeightToShowCollapsePx,
448 seeMoreGetter = seeMoreGetter,
449 collapseGetter = collapseGetter
450 )
451 }
452 }
453 }
454 }
455
456 /**
457 * Overflow Handling Options
458 *
459 * This enumeration defines the available options for handling content that exceeds the boundaries
460 * of its container for [ContextualFlowColumn].
461 *
462 * Options:
463 * - [Visible]: The overflowing content remains visible outside its container. This can lead to
464 * overlapping with other elements. Use this option when it's crucial to display all content,
465 * regardless of the container's size.
466 * - [Clip]: The overflowing content is clipped and not visible beyond the boundary of its
467 * container. Ideal for maintaining a clean and uncluttered UI, where overlapping elements are
468 * undesirable.
469 * - [expandIndicator]: Behaves similar to [Clip], however shows an indicator or button indicating
470 * that more items can be loaded.
471 * - [expandOrCollapseIndicator]: Extends the [expandIndicator] functionality by adding a 'Collapse'
472 * option. After expanding the content, users can choose to collapse it back to the summary view.
473 */
474 @Deprecated("ContextualFlowLayouts are no longer maintained")
475 @ExperimentalLayoutApi
476 class ContextualFlowColumnOverflow
477 private constructor(
478 type: OverflowType,
479 minLinesToShowCollapse: Int = 0,
480 minCrossAxisSizeToShowCollapse: Int = 0,
481 seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
482 collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
483 ) :
484 FlowLayoutOverflow(
485 type,
486 minLinesToShowCollapse,
487 minCrossAxisSizeToShowCollapse,
488 seeMoreGetter,
489 collapseGetter
490 ) {
491
492 @Deprecated("ContextualFlowLayouts are no longer maintained")
493 @ExperimentalLayoutApi
494 companion object {
495 /** Display all content, even if there is not enough space in the specified bounds. */
496 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
497 @ExperimentalLayoutApi
498 @get:ExperimentalLayoutApi
499 val Visible = ContextualFlowColumnOverflow(FlowLayoutOverflow.OverflowType.Visible)
500
501 /** Clip the overflowing content to fix its container. */
502 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
503 @ExperimentalLayoutApi
504 @get:ExperimentalLayoutApi
505 val Clip = ContextualFlowColumnOverflow(FlowLayoutOverflow.OverflowType.Clip)
506
507 /**
508 * Registers an "expand indicator" composable for handling overflow in a
509 * [ContextualFlowColumn].
510 *
511 * This function allows the creation of a custom composable that can be displayed when there
512 * are more items in the [ContextualFlowColumn] than can be displayed in the available
513 * space. The "expandable indicator" composable can be tailored to show a summary, a button,
514 * or any other composable element that indicates the presence of additional items.
515 *
516 * @param content composable that visually indicates more items can be loaded.
517 */
518 @ExperimentalLayoutApi
expandIndicatornull519 fun expandIndicator(
520 content: @Composable ContextualFlowColumnOverflowScope.() -> Unit
521 ): ContextualFlowColumnOverflow {
522 val seeMoreGetter = { state: FlowLayoutOverflowState ->
523 @Composable {
524 val scope = ContextualFlowColumnOverflowScopeImpl(state)
525 scope.content()
526 }
527 }
528 return ContextualFlowColumnOverflow(
529 OverflowType.ExpandIndicator,
530 seeMoreGetter = seeMoreGetter
531 )
532 }
533
534 /**
535 * Registers an "expand or collapse indicator" composable for handling overflow in a
536 * [ContextualFlowColumn].
537 *
538 * This function is designed to switch between two states: a "Expandable" state when there
539 * are more items to show, and a "Collapsable" state when all items are being displayed and
540 * can be collapsed.
541 *
542 * Prior to layout, the function evaluates both composables indicators to determine their
543 * maximum intrinsic size. Depending on the space available and the number of items, either
544 * the [expandIndicator] or the [collapseIndicator] is rendered.
545 *
546 * @sample androidx.compose.foundation.layout.samples.ContextualFlowColMaxLineDynamicSeeMore
547 * @param minColumnsToShowCollapse Specifies the minimum number of columns that should be
548 * visible before showing the collapse option. This parameter is useful when the number of
549 * columns is too small to be reduced further.
550 * @param minWidthToShowCollapse Specifies the minimum width at which the collapse option
551 * should be displayed. This parameter is useful for preventing the collapse option from
552 * appearing when the width is too narrow.
553 * @param expandIndicator composable that visually indicates more items can be loaded.
554 * @param collapseIndicator composable that visually indicates less items can be loaded.
555 */
556 @ExperimentalLayoutApi
557 @Composable
expandOrCollapseIndicatornull558 fun expandOrCollapseIndicator(
559 expandIndicator: @Composable ContextualFlowColumnOverflowScope.() -> Unit,
560 collapseIndicator: @Composable ContextualFlowColumnOverflowScope.() -> Unit,
561 minColumnsToShowCollapse: Int = 1,
562 minWidthToShowCollapse: Dp = 0.dp,
563 ): ContextualFlowColumnOverflow {
564 val minWidthToShowCollapsePx =
565 with(LocalDensity.current) { minWidthToShowCollapse.roundToPx() }
566 return remember(
567 minColumnsToShowCollapse,
568 minWidthToShowCollapsePx,
569 expandIndicator,
570 collapseIndicator
571 ) {
572 val seeMoreGetter = { state: FlowLayoutOverflowState ->
573 @Composable {
574 val scope = ContextualFlowColumnOverflowScopeImpl(state)
575 scope.expandIndicator()
576 }
577 }
578
579 val collapseGetter = { state: FlowLayoutOverflowState ->
580 @Composable {
581 val scope = ContextualFlowColumnOverflowScopeImpl(state)
582 scope.collapseIndicator()
583 }
584 }
585
586 ContextualFlowColumnOverflow(
587 OverflowType.ExpandOrCollapseIndicator,
588 minLinesToShowCollapse = minColumnsToShowCollapse,
589 minCrossAxisSizeToShowCollapse = minWidthToShowCollapsePx,
590 seeMoreGetter = seeMoreGetter,
591 collapseGetter = collapseGetter
592 )
593 }
594 }
595 }
596 }
597
598 /**
599 * Overflow Handling Options
600 *
601 * This enumeration defines the available options for handling content that exceeds the boundaries
602 * of its container.
603 *
604 * Please check out the children classes on ways to initialize a FlowLayout overflow
605 *
606 * @see [FlowRowOverflow]
607 * @see [FlowColumnOverflow]
608 * @see [ContextualFlowRowOverflow]
609 * @see [ContextualFlowColumnOverflow]
610 */
611 @Deprecated("FlowLayout overflow is no longer maintained")
612 @ExperimentalLayoutApi
613 sealed class FlowLayoutOverflow(
614 internal val type: OverflowType,
615 private val minLinesToShowCollapse: Int = 0,
616 private val minCrossAxisSizeToShowCollapse: Int = 0,
617 private val seeMoreGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null,
618 private val collapseGetter: ((state: FlowLayoutOverflowState) -> @Composable () -> Unit)? = null
619 ) {
createOverflowStatenull620 internal fun createOverflowState() =
621 FlowLayoutOverflowState(type, minLinesToShowCollapse, minCrossAxisSizeToShowCollapse)
622
623 internal fun addOverflowComposables(
624 state: FlowLayoutOverflowState,
625 list: MutableList<@Composable () -> Unit>
626 ) {
627 val expandIndicator = seeMoreGetter?.let { getter -> getter(state) }
628 val collapseIndicator = collapseGetter?.let { getter -> getter(state) }
629
630 when (type) {
631 OverflowType.ExpandIndicator -> expandIndicator?.let { list.add(expandIndicator) }
632 OverflowType.ExpandOrCollapseIndicator -> {
633 expandIndicator?.let { list.add(expandIndicator) }
634 collapseIndicator?.let { list.add(collapseIndicator) }
635 }
636 else -> {}
637 }
638 }
639
640 internal enum class OverflowType {
641 Visible,
642 Clip,
643 ExpandIndicator,
644 ExpandOrCollapseIndicator,
645 }
646 }
647
lazyIntnull648 internal fun lazyInt(
649 errorMessage: String = "Lazy item is not yet initialized",
650 initializer: () -> Int
651 ): Lazy<Int> = LazyImpl(initializer, errorMessage)
652
653 private class LazyImpl(val initializer: () -> Int, val errorMessage: String) : Lazy<Int> {
654 private var _value: Int = UNINITIALIZED_VALUE
655 override val value: Int
656 get() {
657 if (_value == UNINITIALIZED_VALUE) {
658 _value = initializer()
659 }
660 if (_value == UNINITIALIZED_VALUE) {
661 throw IllegalStateException(errorMessage)
662 }
663 return _value
664 }
665
666 override fun isInitialized(): Boolean = _value != UNINITIALIZED_VALUE
667
668 override fun toString(): String = if (isInitialized()) value.toString() else errorMessage
669
670 companion object {
671 internal const val UNINITIALIZED_VALUE: Int = -1
672 }
673 }
674
675 /** Overflow State for managing overflow state within FlowLayouts. */
676 @OptIn(ExperimentalLayoutApi::class)
677 @Suppress("DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING")
678 internal data class FlowLayoutOverflowState
679 internal constructor(
680 internal val type: FlowLayoutOverflow.OverflowType,
681 internal val minLinesToShowCollapse: Int,
682 internal val minCrossAxisSizeToShowCollapse: Int
683 ) {
684 internal val shownItemCount: Int
685 get() {
686 if (itemShown == -1) {
687 throw IllegalStateException(shownItemLazyErrorMessage)
688 }
689 return itemShown
690 }
691
692 internal val shownItemLazyErrorMessage =
693 "Accessing shownItemCount before it is set. " +
694 "Are you calling this in the Composition phase, " +
695 "rather than in the draw phase? " +
696 "Consider our samples on how to use it during the draw phase " +
697 "or consider using ContextualFlowRow/ContextualFlowColumn " +
698 "which initializes this method in the composition phase."
699
700 internal var itemShown: Int = -1
701 internal var itemCount = 0
702 private var seeMoreMeasurable: Measurable? = null
703 private var seeMorePlaceable: Placeable? = null
704 private var collapseMeasurable: Measurable? = null
705 private var collapsePlaceable: Placeable? = null
706 private var seeMoreSize: IntIntPair? = null
707 private var collapseSize: IntIntPair? = null
708 // for contextual flow row
709 private var getOverflowMeasurable:
710 ((isExpandable: Boolean, noOfItemsShown: Int) -> Measurable?)? =
711 null
712
ellipsisSizenull713 internal fun ellipsisSize(
714 hasNext: Boolean,
715 lineIndex: Int,
716 totalCrossAxisSize: Int
717 ): IntIntPair? {
718 return when (type) {
719 FlowLayoutOverflow.OverflowType.Visible -> null
720 FlowLayoutOverflow.OverflowType.Clip -> null
721 FlowLayoutOverflow.OverflowType.ExpandIndicator ->
722 if (hasNext) {
723 seeMoreSize
724 } else {
725 null
726 }
727 FlowLayoutOverflow.OverflowType.ExpandOrCollapseIndicator -> {
728 if (hasNext) {
729 seeMoreSize
730 } else if (
731 lineIndex + 1 >= minLinesToShowCollapse &&
732 totalCrossAxisSize >= minCrossAxisSizeToShowCollapse
733 ) {
734 collapseSize
735 } else {
736 null
737 }
738 }
739 }
740 }
741
ellipsisInfonull742 internal fun ellipsisInfo(
743 hasNext: Boolean,
744 lineIndex: Int,
745 totalCrossAxisSize: Int
746 ): FlowLayoutBuildingBlocks.WrapEllipsisInfo? {
747 return when (type) {
748 FlowLayoutOverflow.OverflowType.Visible -> null
749 FlowLayoutOverflow.OverflowType.Clip -> null
750 FlowLayoutOverflow.OverflowType.ExpandIndicator,
751 FlowLayoutOverflow.OverflowType.ExpandOrCollapseIndicator -> {
752 var measurable: Measurable? = null
753 var placeable: Placeable? = null
754 var ellipsisSize: IntIntPair?
755 if (hasNext) {
756 measurable =
757 getOverflowMeasurable?.invoke(/* isExpandable */ true, shownItemCount)
758 ?: seeMoreMeasurable
759 ellipsisSize = seeMoreSize
760 if (getOverflowMeasurable == null) {
761 placeable = seeMorePlaceable
762 }
763 } else {
764 if (
765 lineIndex >= (minLinesToShowCollapse - 1) &&
766 totalCrossAxisSize >= (minCrossAxisSizeToShowCollapse)
767 ) {
768 measurable =
769 getOverflowMeasurable?.invoke(/* isExpandable */ false, shownItemCount)
770 ?: collapseMeasurable
771 }
772 ellipsisSize = collapseSize
773 if (getOverflowMeasurable == null) {
774 placeable = collapsePlaceable
775 }
776 }
777 measurable ?: return null
778 FlowLayoutBuildingBlocks.WrapEllipsisInfo(measurable, placeable, ellipsisSize!!)
779 }
780 }
781 }
782
setOverflowMeasurablesnull783 internal fun setOverflowMeasurables(
784 seeMoreMeasurable: IntrinsicMeasurable?,
785 collapseMeasurable: IntrinsicMeasurable?,
786 isHorizontal: Boolean,
787 constraints: Constraints,
788 ) {
789 val orientation =
790 if (isHorizontal) LayoutOrientation.Horizontal else LayoutOrientation.Vertical
791 val orientationIndependentConstraints =
792 OrientationIndependentConstraints(constraints, orientation)
793 seeMoreMeasurable?.let { item ->
794 val mainAxisSize =
795 item.mainAxisMin(isHorizontal, orientationIndependentConstraints.crossAxisMax)
796 val crossAxisSize = item.crossAxisMin(isHorizontal, mainAxisSize)
797 this.seeMoreSize = IntIntPair(mainAxisSize, crossAxisSize)
798 this.seeMoreMeasurable = item as? Measurable
799 this.seeMorePlaceable = null
800 }
801 collapseMeasurable?.let { item ->
802 val mainAxisSize =
803 item.mainAxisMin(isHorizontal, orientationIndependentConstraints.crossAxisMax)
804 val crossAxisSize = item.crossAxisMin(isHorizontal, mainAxisSize)
805 this.collapseSize = IntIntPair(mainAxisSize, crossAxisSize)
806 this.collapseMeasurable = item as? Measurable
807 this.collapsePlaceable = null
808 }
809 }
810
setOverflowMeasurablesnull811 internal fun setOverflowMeasurables(
812 measurePolicy: FlowLineMeasurePolicy,
813 seeMoreMeasurable: Measurable?,
814 collapseMeasurable: Measurable?,
815 constraints: Constraints,
816 ) {
817 val isHorizontal = measurePolicy.isHorizontal
818 val orientation =
819 if (isHorizontal) LayoutOrientation.Horizontal else LayoutOrientation.Vertical
820 val orientationIndependentConstraints =
821 OrientationIndependentConstraints(constraints, orientation)
822 .copy(mainAxisMin = 0, crossAxisMin = 0)
823 val finalConstraints = orientationIndependentConstraints.toBoxConstraints(orientation)
824 seeMoreMeasurable?.let { item ->
825 item.measureAndCache(measurePolicy, finalConstraints) { placeable ->
826 var mainAxisSize = 0
827 var crossAxisSize = 0
828 placeable?.let {
829 with(measurePolicy) {
830 mainAxisSize = placeable.mainAxisSize()
831 crossAxisSize = placeable.crossAxisSize()
832 }
833 }
834 seeMoreSize = IntIntPair(mainAxisSize, crossAxisSize)
835 seeMorePlaceable = placeable
836 }
837 this.seeMoreMeasurable = item
838 }
839 collapseMeasurable?.let { item ->
840 item.measureAndCache(measurePolicy, finalConstraints) { placeable ->
841 var mainAxisSize = 0
842 var crossAxisSize = 0
843 placeable?.let {
844 with(measurePolicy) {
845 mainAxisSize = placeable.mainAxisSize()
846 crossAxisSize = placeable.crossAxisSize()
847 }
848 }
849 this.collapseSize = IntIntPair(mainAxisSize, crossAxisSize)
850 collapsePlaceable = placeable
851 }
852 this.collapseMeasurable = item
853 }
854 }
855
setOverflowMeasurablesnull856 internal fun setOverflowMeasurables(
857 measurePolicy: FlowLineMeasurePolicy,
858 constraints: Constraints,
859 getOverflowMeasurable: ((isExpandable: Boolean, numberOfItemsShown: Int) -> Measurable?)
860 ) {
861 this.itemShown = 0
862 this.getOverflowMeasurable = getOverflowMeasurable
863 val seeMoreMeasurable =
864 getOverflowMeasurable(/* isExpandable */ true, /* numberOfItemsShown */ 0)
865 val collapseMeasurable =
866 getOverflowMeasurable(/* isExpandable */ false, /* numberOfItemsShown */ 0)
867 setOverflowMeasurables(measurePolicy, seeMoreMeasurable, collapseMeasurable, constraints)
868 }
869 }
870