1 /*
<lambda>null2  * Copyright (C) 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 
17 package androidx.constraintlayout.compose
18 
19 import android.annotation.SuppressLint
20 import androidx.annotation.IntRange
21 import androidx.compose.runtime.Immutable
22 import androidx.compose.runtime.Stable
23 import androidx.compose.ui.layout.FirstBaseline
24 import androidx.compose.ui.unit.Dp
25 import androidx.compose.ui.unit.dp
26 import androidx.constraintlayout.core.parser.CLArray
27 import androidx.constraintlayout.core.parser.CLElement
28 import androidx.constraintlayout.core.parser.CLNumber
29 import androidx.constraintlayout.core.parser.CLObject
30 import androidx.constraintlayout.core.parser.CLString
31 import androidx.constraintlayout.core.state.ConstraintSetParser
32 import androidx.constraintlayout.core.utils.GridCore
33 import org.jetbrains.annotations.TestOnly
34 
35 /**
36  * Common scope for [ConstraintLayoutScope] and [ConstraintSetScope], the content being shared
37  * between the inline DSL API and the ConstraintSet-based API.
38  */
39 abstract class ConstraintLayoutBaseScope internal constructor(extendFrom: CLObject?) {
40     @Suppress("unused") // Needed to maintain binary compatibility
41     constructor() : this(null)
42 
43     @Deprecated("Tasks is unused, it breaks the immutability promise.")
44     protected val tasks = mutableListOf<(State) -> Unit>()
45 
46     @PublishedApi
47     internal val containerObject: CLObject = extendFrom?.clone() ?: CLObject(charArrayOf())
48 
49     fun applyTo(state: State) {
50         ConstraintSetParser.populateState(
51             containerObject,
52             state,
53             ConstraintSetParser.LayoutVariables()
54         )
55     }
56 
57     open fun reset() {
58         containerObject.clear()
59         helperId = HelpersStartId
60         helpersHashCode = 0
61     }
62 
63     @PublishedApi internal var helpersHashCode: Int = 0
64 
65     private fun updateHelpersHashCode(value: Int) {
66         helpersHashCode = (helpersHashCode * 1009 + value) % 1000000007
67     }
68 
69     private val HelpersStartId = 1000
70     private var helperId = HelpersStartId
71 
72     private fun createHelperId() = helperId++
73 
74     /**
75      * Represents a vertical anchor (e.g. start/end of a layout, guideline) that layouts can link to
76      * in their `Modifier.constrainAs` or `constrain` blocks.
77      *
78      * @param reference The [LayoutReference] that this anchor belongs to.
79      */
80     @Stable
81     @Suppress("DataClassDefinition", "DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING")
82     data class VerticalAnchor
83     internal constructor(
84         internal val id: Any,
85         internal val index: Int,
86         val reference: LayoutReference
87     )
88 
89     /**
90      * Represents a horizontal anchor (e.g. top/bottom of a layout, guideline) that layouts can link
91      * to in their `Modifier.constrainAs` or `constrain` blocks.
92      *
93      * @param reference The [LayoutReference] that this anchor belongs to.
94      */
95     @Stable
96     @Suppress("DataClassDefinition", "DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING")
97     data class HorizontalAnchor
98     internal constructor(
99         internal val id: Any,
100         internal val index: Int,
101         val reference: LayoutReference
102     )
103 
104     /**
105      * Represents a horizontal anchor corresponding to the [FirstBaseline] of a layout that other
106      * layouts can link to in their `Modifier.constrainAs` or `constrain` blocks.
107      *
108      * @param reference The [LayoutReference] that this anchor belongs to.
109      */
110     // TODO(popam): investigate if this can be just a HorizontalAnchor
111     @Stable
112     @Suppress("DataClassDefinition", "DATA_CLASS_COPY_VISIBILITY_WILL_BE_CHANGED_WARNING")
113     data class BaselineAnchor
114     internal constructor(internal val id: Any, val reference: LayoutReference)
115 
116     /**
117      * Specifies additional constraints associated to the horizontal chain identified with [ref].
118      */
119     fun constrain(
120         ref: HorizontalChainReference,
121         constrainBlock: HorizontalChainScope.() -> Unit
122     ): HorizontalChainScope =
123         HorizontalChainScope(ref.id, ref.asCLContainer()).apply(constrainBlock)
124 
125     /** Specifies additional constraints associated to the vertical chain identified with [ref]. */
126     fun constrain(
127         ref: VerticalChainReference,
128         constrainBlock: VerticalChainScope.() -> Unit
129     ): VerticalChainScope = VerticalChainScope(ref.id, ref.asCLContainer()).apply(constrainBlock)
130 
131     /** Specifies the constraints associated to the layout identified with [ref]. */
132     fun constrain(
133         ref: ConstrainedLayoutReference,
134         constrainBlock: ConstrainScope.() -> Unit
135     ): ConstrainScope = ConstrainScope(ref.id, ref.asCLContainer()).apply(constrainBlock)
136 
137     /** Convenient way to apply the same constraints to multiple [ConstrainedLayoutReference]s. */
138     fun constrain(
139         vararg refs: ConstrainedLayoutReference,
140         constrainBlock: ConstrainScope.() -> Unit
141     ) {
142         refs.forEach { ref -> constrain(ref, constrainBlock) }
143     }
144 
145     /** Creates a guideline at a specific offset from the start of the [ConstraintLayout]. */
146     fun createGuidelineFromStart(offset: Dp): VerticalAnchor {
147         val ref = LayoutReferenceImpl(createHelperId())
148 
149         ref.asCLContainer().apply {
150             putString("type", "vGuideline")
151             putNumber("start", offset.value)
152         }
153 
154         updateHelpersHashCode(1)
155         updateHelpersHashCode(offset.hashCode())
156         return VerticalAnchor(ref.id, 0, ref)
157     }
158 
159     /** Creates a guideline at a specific offset from the left of the [ConstraintLayout]. */
160     fun createGuidelineFromAbsoluteLeft(offset: Dp): VerticalAnchor {
161         val ref = LayoutReferenceImpl(createHelperId())
162 
163         ref.asCLContainer().apply {
164             putString("type", "vGuideline")
165             putNumber("left", offset.value)
166         }
167 
168         updateHelpersHashCode(2)
169         updateHelpersHashCode(offset.hashCode())
170         return VerticalAnchor(ref.id, 0, ref)
171     }
172 
173     /**
174      * Creates a guideline at a specific offset from the start of the [ConstraintLayout]. A
175      * [fraction] of 0f will correspond to the start of the [ConstraintLayout], while 1f will
176      * correspond to the end.
177      */
178     fun createGuidelineFromStart(fraction: Float): VerticalAnchor {
179         val ref = LayoutReferenceImpl(createHelperId())
180 
181         val percentParams =
182             CLArray(charArrayOf()).apply {
183                 add(CLString.from("start"))
184                 add(CLNumber(fraction))
185             }
186 
187         ref.asCLContainer().apply {
188             putString("type", "vGuideline")
189             put("percent", percentParams)
190         }
191 
192         updateHelpersHashCode(3)
193         updateHelpersHashCode(fraction.hashCode())
194         return VerticalAnchor(ref.id, 0, ref)
195     }
196 
197     /**
198      * Creates a guideline at a width fraction from the left of the [ConstraintLayout]. A [fraction]
199      * of 0f will correspond to the left of the [ConstraintLayout], while 1f will correspond to the
200      * right.
201      */
202     fun createGuidelineFromAbsoluteLeft(fraction: Float): VerticalAnchor {
203         val ref = LayoutReferenceImpl(createHelperId())
204 
205         ref.asCLContainer().apply {
206             putString("type", "vGuideline")
207             putNumber("percent", fraction)
208         }
209 
210         updateHelpersHashCode(4)
211         updateHelpersHashCode(fraction.hashCode())
212         return VerticalAnchor(ref.id, 0, ref)
213     }
214 
215     /** Creates a guideline at a specific offset from the end of the [ConstraintLayout]. */
216     fun createGuidelineFromEnd(offset: Dp): VerticalAnchor {
217         val ref = LayoutReferenceImpl(createHelperId())
218 
219         ref.asCLContainer().apply {
220             putString("type", "vGuideline")
221             putNumber("end", offset.value)
222         }
223 
224         updateHelpersHashCode(5)
225         updateHelpersHashCode(offset.hashCode())
226         return VerticalAnchor(ref.id, 0, ref)
227     }
228 
229     /** Creates a guideline at a specific offset from the right of the [ConstraintLayout]. */
230     fun createGuidelineFromAbsoluteRight(offset: Dp): VerticalAnchor {
231         val ref = LayoutReferenceImpl(createHelperId())
232 
233         ref.asCLContainer().apply {
234             putString("type", "vGuideline")
235             putNumber("right", offset.value)
236         }
237 
238         updateHelpersHashCode(6)
239         updateHelpersHashCode(offset.hashCode())
240         return VerticalAnchor(ref.id, 0, ref)
241     }
242 
243     /**
244      * Creates a guideline at a width fraction from the end of the [ConstraintLayout]. A [fraction]
245      * of 0f will correspond to the end of the [ConstraintLayout], while 1f will correspond to the
246      * start.
247      */
248     fun createGuidelineFromEnd(fraction: Float): VerticalAnchor {
249         val ref = LayoutReferenceImpl(createHelperId())
250 
251         val percentParams =
252             CLArray(charArrayOf()).apply {
253                 add(CLString.from("end"))
254                 add(CLNumber(fraction))
255             }
256 
257         ref.asCLContainer().apply {
258             putString("type", "vGuideline")
259             put("percent", percentParams)
260         }
261 
262         updateHelpersHashCode(3)
263         updateHelpersHashCode(fraction.hashCode())
264         return VerticalAnchor(ref.id, 0, ref)
265     }
266 
267     /**
268      * Creates a guideline at a width fraction from the right of the [ConstraintLayout]. A
269      * [fraction] of 0f will correspond to the right of the [ConstraintLayout], while 1f will
270      * correspond to the left.
271      */
272     fun createGuidelineFromAbsoluteRight(fraction: Float): VerticalAnchor {
273         return createGuidelineFromAbsoluteLeft(1f - fraction)
274     }
275 
276     /** Creates a guideline at a specific offset from the top of the [ConstraintLayout]. */
277     fun createGuidelineFromTop(offset: Dp): HorizontalAnchor {
278         val ref = LayoutReferenceImpl(createHelperId())
279 
280         ref.asCLContainer().apply {
281             putString("type", "hGuideline")
282             putNumber("start", offset.value)
283         }
284 
285         updateHelpersHashCode(7)
286         updateHelpersHashCode(offset.hashCode())
287         return HorizontalAnchor(ref.id, 0, ref)
288     }
289 
290     /**
291      * Creates a guideline at a height fraction from the top of the [ConstraintLayout]. A [fraction]
292      * of 0f will correspond to the top of the [ConstraintLayout], while 1f will correspond to the
293      * bottom.
294      */
295     fun createGuidelineFromTop(fraction: Float): HorizontalAnchor {
296         val ref = LayoutReferenceImpl(createHelperId())
297 
298         ref.asCLContainer().apply {
299             putString("type", "hGuideline")
300             putNumber("percent", fraction)
301         }
302 
303         updateHelpersHashCode(8)
304         updateHelpersHashCode(fraction.hashCode())
305         return HorizontalAnchor(ref.id, 0, ref)
306     }
307 
308     /** Creates a guideline at a specific offset from the bottom of the [ConstraintLayout]. */
309     fun createGuidelineFromBottom(offset: Dp): HorizontalAnchor {
310         val ref = LayoutReferenceImpl(createHelperId())
311 
312         ref.asCLContainer().apply {
313             putString("type", "hGuideline")
314             putNumber("end", offset.value)
315         }
316 
317         updateHelpersHashCode(9)
318         updateHelpersHashCode(offset.hashCode())
319         return HorizontalAnchor(ref.id, 0, ref)
320     }
321 
322     /**
323      * Creates a guideline at a height percentage from the bottom of the [ConstraintLayout]. A
324      * [fraction] of 0f will correspond to the bottom of the [ConstraintLayout], while 1f will
325      * correspond to the top.
326      */
327     fun createGuidelineFromBottom(fraction: Float): HorizontalAnchor {
328         return createGuidelineFromTop(1f - fraction)
329     }
330 
331     /** Creates and returns a start barrier, containing the specified elements. */
332     fun createStartBarrier(vararg elements: LayoutReference, margin: Dp = 0.dp): VerticalAnchor {
333         val ref = LayoutReferenceImpl(createHelperId())
334 
335         val elementArray = CLArray(charArrayOf())
336         elements.forEach { elementArray.add(CLString.from(it.id.toString())) }
337 
338         ref.asCLContainer().apply {
339             putString("type", "barrier")
340             putString("direction", "start")
341             putNumber("margin", margin.value)
342             put("contains", elementArray)
343         }
344 
345         updateHelpersHashCode(10)
346         elements.forEach { updateHelpersHashCode(it.hashCode()) }
347         updateHelpersHashCode(margin.hashCode())
348         return VerticalAnchor(ref.id, 0, ref)
349     }
350 
351     /** Creates and returns a left barrier, containing the specified elements. */
352     fun createAbsoluteLeftBarrier(
353         vararg elements: LayoutReference,
354         margin: Dp = 0.dp
355     ): VerticalAnchor {
356         val ref = LayoutReferenceImpl(createHelperId())
357 
358         val elementArray = CLArray(charArrayOf())
359         elements.forEach { elementArray.add(CLString.from(it.id.toString())) }
360 
361         ref.asCLContainer().apply {
362             putString("type", "barrier")
363             putString("direction", "left")
364             putNumber("margin", margin.value)
365             put("contains", elementArray)
366         }
367 
368         updateHelpersHashCode(11)
369         elements.forEach { updateHelpersHashCode(it.hashCode()) }
370         updateHelpersHashCode(margin.hashCode())
371         return VerticalAnchor(ref.id, 0, ref)
372     }
373 
374     /** Creates and returns a top barrier, containing the specified elements. */
375     fun createTopBarrier(vararg elements: LayoutReference, margin: Dp = 0.dp): HorizontalAnchor {
376         val ref = LayoutReferenceImpl(createHelperId())
377 
378         val elementArray = CLArray(charArrayOf())
379         elements.forEach { elementArray.add(CLString.from(it.id.toString())) }
380 
381         ref.asCLContainer().apply {
382             putString("type", "barrier")
383             putString("direction", "top")
384             putNumber("margin", margin.value)
385             put("contains", elementArray)
386         }
387 
388         updateHelpersHashCode(12)
389         elements.forEach { updateHelpersHashCode(it.hashCode()) }
390         updateHelpersHashCode(margin.hashCode())
391         return HorizontalAnchor(ref.id, 0, ref)
392     }
393 
394     /** Creates and returns an end barrier, containing the specified elements. */
395     fun createEndBarrier(vararg elements: LayoutReference, margin: Dp = 0.dp): VerticalAnchor {
396         val ref = LayoutReferenceImpl(createHelperId())
397 
398         val elementArray = CLArray(charArrayOf())
399         elements.forEach { elementArray.add(CLString.from(it.id.toString())) }
400 
401         ref.asCLContainer().apply {
402             putString("type", "barrier")
403             putString("direction", "end")
404             putNumber("margin", margin.value)
405             put("contains", elementArray)
406         }
407 
408         updateHelpersHashCode(13)
409         elements.forEach { updateHelpersHashCode(it.hashCode()) }
410         updateHelpersHashCode(margin.hashCode())
411         return VerticalAnchor(ref.id, 0, ref)
412     }
413 
414     /** Creates and returns a right barrier, containing the specified elements. */
415     fun createAbsoluteRightBarrier(
416         vararg elements: LayoutReference,
417         margin: Dp = 0.dp
418     ): VerticalAnchor {
419         val ref = LayoutReferenceImpl(createHelperId())
420 
421         val elementArray = CLArray(charArrayOf())
422         elements.forEach { elementArray.add(CLString.from(it.id.toString())) }
423 
424         ref.asCLContainer().apply {
425             putString("type", "barrier")
426             putString("direction", "right")
427             putNumber("margin", margin.value)
428             put("contains", elementArray)
429         }
430 
431         updateHelpersHashCode(14)
432         elements.forEach { updateHelpersHashCode(it.hashCode()) }
433         updateHelpersHashCode(margin.hashCode())
434         return VerticalAnchor(ref.id, 0, ref)
435     }
436 
437     /** Creates and returns a bottom barrier, containing the specified elements. */
438     fun createBottomBarrier(vararg elements: LayoutReference, margin: Dp = 0.dp): HorizontalAnchor {
439         val ref = LayoutReferenceImpl(createHelperId())
440 
441         val elementArray = CLArray(charArrayOf())
442         elements.forEach { elementArray.add(CLString.from(it.id.toString())) }
443 
444         ref.asCLContainer().apply {
445             putString("type", "barrier")
446             putString("direction", "bottom")
447             putNumber("margin", margin.value)
448             put("contains", elementArray)
449         }
450 
451         updateHelpersHashCode(15)
452         elements.forEach { updateHelpersHashCode(it.hashCode()) }
453         updateHelpersHashCode(margin.hashCode())
454         return HorizontalAnchor(ref.id, 0, ref)
455     }
456 
457     /**
458      * Flow helpers allows a long sequence of Composable widgets to wrap onto multiple rows or
459      * columns.
460      *
461      * @param elements [LayoutReference]s to be laid out by the Flow helper
462      * @param flowVertically if set to true arranges the Composables from top to bottom. Normally
463      *   they are arranged from left to right.
464      * @param verticalGap defines the gap between views in the y axis
465      * @param horizontalGap defines the gap between views in the x axis
466      * @param maxElement defines the maximum element on a row before it if the
467      * @param padding sets padding around the content
468      * @param wrapMode sets the way reach maxElements is handled [Wrap.None] (default) -- no wrap
469      *   behavior, [Wrap.Chain] - create additional chains
470      * @param verticalAlign set the way elements are aligned vertically. Center is default
471      * @param horizontalAlign set the way elements are aligned horizontally. Center is default
472      * @param horizontalFlowBias set the way elements are aligned vertically Center is default
473      * @param verticalFlowBias sets the top bottom bias of the vertical chain
474      * @param verticalStyle sets the style of a vertical chain (Spread,Packed, or SpreadInside)
475      * @param horizontalStyle set the style of the horizontal chain (Spread, Packed, or
476      *   SpreadInside)
477      */
478     fun createFlow(
479         vararg elements: LayoutReference?,
480         flowVertically: Boolean = false,
481         verticalGap: Dp = 0.dp,
482         horizontalGap: Dp = 0.dp,
483         maxElement: Int = 0, // TODO: shouldn't this be -1? (aka: UNKNOWN)?
484         padding: Dp = 0.dp,
485         wrapMode: Wrap = Wrap.None,
486         verticalAlign: VerticalAlign = VerticalAlign.Center,
487         horizontalAlign: HorizontalAlign = HorizontalAlign.Center,
488         horizontalFlowBias: Float = 0.0f,
489         verticalFlowBias: Float = 0.0f,
490         verticalStyle: FlowStyle = FlowStyle.Packed,
491         horizontalStyle: FlowStyle = FlowStyle.Packed,
492     ): ConstrainedLayoutReference {
493         return createFlow(
494             elements = elements,
495             flowVertically = flowVertically,
496             verticalGap = verticalGap,
497             horizontalGap = horizontalGap,
498             maxElement = maxElement,
499             paddingLeft = padding,
500             paddingTop = padding,
501             paddingRight = padding,
502             paddingBottom = padding,
503             wrapMode = wrapMode,
504             verticalAlign = verticalAlign,
505             horizontalAlign = horizontalAlign,
506             horizontalFlowBias = horizontalFlowBias,
507             verticalFlowBias = verticalFlowBias,
508             verticalStyle = verticalStyle,
509             horizontalStyle = horizontalStyle
510         )
511     }
512 
513     /**
514      * Flow helpers allows a long sequence of Composable widgets to wrap onto multiple rows or
515      * columns.
516      *
517      * @param elements [LayoutReference]s to be laid out by the Flow helper
518      * @param flowVertically if set to true aranges the Composables from top to bottom. Normally
519      *   they are arranged from left to right.
520      * @param verticalGap defines the gap between views in the y axis
521      * @param horizontalGap defines the gap between views in the x axis
522      * @param maxElement defines the maximum element on a row before it if the
523      * @param paddingHorizontal sets paddingLeft and paddingRight of the content
524      * @param paddingVertical sets paddingTop and paddingBottom of the content
525      * @param wrapMode sets the way reach maxElements is handled [Wrap.None] (default) -- no wrap
526      *   behavior, [Wrap.Chain] - create additional chains
527      * @param verticalAlign set the way elements are aligned vertically. Center is default
528      * @param horizontalAlign set the way elements are aligned horizontally. Center is default
529      * @param horizontalFlowBias set the way elements are aligned vertically Center is default
530      * @param verticalFlowBias sets the top bottom bias of the vertical chain
531      * @param verticalStyle sets the style of a vertical chain (Spread,Packed, or SpreadInside)
532      * @param horizontalStyle set the style of the horizontal chain (Spread, Packed, or
533      *   SpreadInside)
534      */
535     fun createFlow(
536         vararg elements: LayoutReference?,
537         flowVertically: Boolean = false,
538         verticalGap: Dp = 0.dp,
539         horizontalGap: Dp = 0.dp,
540         maxElement: Int = 0,
541         paddingHorizontal: Dp = 0.dp,
542         paddingVertical: Dp = 0.dp,
543         wrapMode: Wrap = Wrap.None,
544         verticalAlign: VerticalAlign = VerticalAlign.Center,
545         horizontalAlign: HorizontalAlign = HorizontalAlign.Center,
546         horizontalFlowBias: Float = 0.0f,
547         verticalFlowBias: Float = 0.0f,
548         verticalStyle: FlowStyle = FlowStyle.Packed,
549         horizontalStyle: FlowStyle = FlowStyle.Packed,
550     ): ConstrainedLayoutReference {
551         return createFlow(
552             elements = elements,
553             flowVertically = flowVertically,
554             verticalGap = verticalGap,
555             horizontalGap = horizontalGap,
556             maxElement = maxElement,
557             paddingLeft = paddingHorizontal,
558             paddingTop = paddingVertical,
559             paddingRight = paddingHorizontal,
560             paddingBottom = paddingVertical,
561             wrapMode = wrapMode,
562             verticalAlign = verticalAlign,
563             horizontalAlign = horizontalAlign,
564             horizontalFlowBias = horizontalFlowBias,
565             verticalFlowBias = verticalFlowBias,
566             verticalStyle = verticalStyle,
567             horizontalStyle = horizontalStyle
568         )
569     }
570 
571     /**
572      * Flow helpers allows a long sequence of Composable widgets to wrap onto multiple rows or
573      * columns.
574      *
575      * @param elements [LayoutReference]s to be laid out by the Flow helper
576      * @param flowVertically if set to true aranges the Composables from top to bottom. Normally
577      *   they are arranged from left to right.
578      * @param verticalGap defines the gap between views in the y axis
579      * @param horizontalGap defines the gap between views in the x axis
580      * @param maxElement defines the maximum element on a row before it if the
581      * @param paddingLeft sets paddingLeft of the content
582      * @param paddingTop sets paddingTop of the content
583      * @param paddingRight sets paddingRight of the content
584      * @param paddingBottom sets paddingBottom of the content
585      * @param wrapMode sets the way reach maxElements is handled [Wrap.None] (default) -- no wrap
586      *   behavior, [Wrap.Chain] - create additional chains
587      * @param verticalAlign set the way elements are aligned vertically. Center is default
588      * @param horizontalAlign set the way elements are aligned horizontally. Center is default
589      * @param horizontalFlowBias set the way elements are aligned vertically Center is default
590      * @param verticalFlowBias sets the top bottom bias of the vertical chain
591      * @param verticalStyle sets the style of a vertical chain (Spread,Packed, or SpreadInside)
592      * @param horizontalStyle set the style of the horizontal chain (Spread, Packed, or
593      *   SpreadInside)
594      */
595     fun createFlow(
596         vararg elements: LayoutReference?,
597         flowVertically: Boolean = false,
598         verticalGap: Dp = 0.dp,
599         horizontalGap: Dp = 0.dp,
600         maxElement: Int = 0,
601         paddingLeft: Dp = 0.dp,
602         paddingTop: Dp = 0.dp,
603         paddingRight: Dp = 0.dp,
604         paddingBottom: Dp = 0.dp,
605         wrapMode: Wrap = Wrap.None,
606         verticalAlign: VerticalAlign = VerticalAlign.Center,
607         horizontalAlign: HorizontalAlign = HorizontalAlign.Center,
608         horizontalFlowBias: Float = 0.0f,
609         verticalFlowBias: Float = 0.0f,
610         verticalStyle: FlowStyle = FlowStyle.Packed,
611         horizontalStyle: FlowStyle = FlowStyle.Packed,
612     ): ConstrainedLayoutReference {
613         val ref = ConstrainedLayoutReference(createHelperId())
614         val elementArray = CLArray(charArrayOf())
615         elements.forEach {
616             if (it != null) {
617                 elementArray.add(CLString.from(it.id.toString()))
618             }
619         }
620         val paddingArray =
621             CLArray(charArrayOf()).apply {
622                 add(CLNumber(paddingLeft.value))
623                 add(CLNumber(paddingTop.value))
624                 add(CLNumber(paddingRight.value))
625                 add(CLNumber(paddingBottom.value))
626             }
627         ref.asCLContainer().apply {
628             put("contains", elementArray)
629             putString("type", if (flowVertically) "vFlow" else "hFlow")
630             putNumber("vGap", verticalGap.value)
631             putNumber("hGap", horizontalGap.value)
632             putNumber("maxElement", maxElement.toFloat())
633             put("padding", paddingArray)
634             putString("wrap", wrapMode.name)
635             putString("vAlign", verticalAlign.name)
636             putString("hAlign", horizontalAlign.name)
637             putNumber("hFlowBias", horizontalFlowBias)
638             putNumber("vFlowBias", verticalFlowBias)
639             putString("vStyle", verticalStyle.name)
640             putString("hStyle", horizontalStyle.name)
641         }
642         updateHelpersHashCode(16)
643         elements.forEach { updateHelpersHashCode(it.hashCode()) }
644 
645         return ref
646     }
647 
648     /**
649      * Creates a Grid based helper that lays out its elements in a single Row.
650      *
651      * Example:
652      *
653      * @sample androidx.constraintlayout.compose.samples.Row_sample
654      * @param elements [LayoutReference]s to be laid out by the Grid-based Row helper.
655      * @param spacing Defines the horizontal spacing between each item in the Row.
656      * @param weights Defines the weight for each element in the Row. Note that the number of
657      *   weights provided are expected to match the number of [elements] given.
658      * @throws IllegalArgumentException When non empty [weights] don't match the number of elements.
659      * @see createGrid
660      */
661     @SuppressLint("Range") // Enables internal grid mode for row and column
662     fun createRow(
663         vararg elements: LayoutReference,
664         spacing: Dp = 0.dp,
665         weights: FloatArray = floatArrayOf(),
666     ): ConstrainedLayoutReference {
667         if (weights.isNotEmpty() && elements.size != weights.size) {
668             throw IllegalArgumentException(
669                 "Number of weights (${weights.size}) should match number of elements (${elements.size})."
670             )
671         }
672         return createGrid(
673             elements = elements,
674             rows = 1,
675             columns = 0,
676             horizontalSpacing = spacing,
677             columnWeights = weights,
678         )
679     }
680 
681     /**
682      * Creates a Grid based helper that lays out its elements in a single Column.
683      *
684      * Example:
685      *
686      * @sample androidx.constraintlayout.compose.samples.Column_sample
687      * @param elements [LayoutReference]s to be laid out by the Grid-based Column helper
688      * @param spacing Defines the vertical spacing between each item in the Column.
689      * @param weights Defines the weight for each element in the Column. Note that the number of
690      *   weights provided are expected to match the number of [elements] given.
691      * @throws IllegalArgumentException When non empty [weights] don't match the number of elements.
692      * @see createGrid
693      */
694     @SuppressLint("Range") // Enables internal grid mode for row and column
695     fun createColumn(
696         vararg elements: LayoutReference,
697         spacing: Dp = 0.dp,
698         weights: FloatArray = floatArrayOf(),
699     ): ConstrainedLayoutReference {
700         if (weights.isNotEmpty() && elements.size != weights.size) {
701             throw IllegalArgumentException(
702                 "Number of weights (${weights.size}) should match number of elements (${elements.size})."
703             )
704         }
705         return createGrid(
706             elements = elements,
707             rows = 0,
708             columns = 1,
709             verticalSpacing = spacing,
710             rowWeights = weights,
711         )
712     }
713 
714     /**
715      * Helper that creates a fixed Grid layout.
716      *
717      * A grid is defined by a set number of rows and columns. By default the given [elements] are
718      * arranged horizontally first (left to right, then top to bottom).
719      *
720      * Either [rowWeights] or [columnWeights] may be provided to modify their size in the grid
721      * relative to each other.
722      *
723      * You may provide [Span]s to define size of each cell within the grid.
724      *
725      * To avoid placing Layouts in specific cells (or an area within the grid), you may provide
726      * [skips]. Note that if the provided [Span]s and [Skip]s overlap, the [Skip]s will take
727      * priority, ignoring the overlapping [Span] definition.
728      *
729      * Here's an example showing how to build a calculator layout using a couple of [Span]s:
730      *
731      * @sample androidx.constraintlayout.compose.samples.Grid_calculator_sample
732      *
733      * Here's another example using [Skip]s to easily lay out the typical Keyboard navigation pad:
734      *
735      * @sample androidx.constraintlayout.compose.samples.Grid_navigationPad_sample
736      * @param elements [LayoutReference]s to be laid out by the Grid helper. By default, they are
737      *   positioned in the given order based on the arrangement. Horizontal arrangement by default.
738      * @param rows Sets the number of rows in the Grid
739      * @param columns Sets the number of columns in the Grid
740      * @param isHorizontalArrangement Whether to place the given [elements] horizontally, filling
741      *   the cells from left to right and top to bottom. Otherwise, the [elements] are placed
742      *   vertically, filling each cell from top to bottom and left to right. `true` by default.
743      * @param verticalSpacing Defines the gap between each row.
744      * @param horizontalSpacing Defines the gap between each column.
745      * @param rowWeights Defines the weight for each row. The weight specifies how much space each
746      *   row takes relative to each other. Should be either an empty array (all rows are the same
747      *   size), or have a value corresponding for each row.
748      * @param columnWeights Defines the weight for each column. The weight specifies how much space
749      *   each column takes relative to each other. Should be either an empty array (all columns are
750      *   the same size), or have a value corresponding for each column.
751      * @param skips A [Skip] defines an area within the Grid where Layouts may **not** be placed.
752      *   So, as the [elements] are being placed, they will skip any cell covered by the given skips.
753      * @param spans A [Span] defines how much area should each cell occupy when placing an item on
754      *   it. Keep in mind that when laying out, the Grid won't place any overlapping items over the
755      *   spanned area. In that sense, a [Span] works similarly to a [Skip], except that an item will
756      *   be placed at the original spanned cell position. Also note, [skips] take priority over
757      *   spans, meaning that defining a [Span] that overlaps a [Skip] is a no-op.
758      * @param flags A [GridFlag] definition that may change certain behaviors of the Grid helper.
759      *   [GridFlag.None] by default.
760      * @throws IllegalArgumentException When non empty weights don't match the number of columns or
761      *   rows respectively.
762      * @see createColumn
763      * @see createRow
764      */
765     fun createGrid(
766         vararg elements: LayoutReference,
767         @IntRange(from = 1) rows: Int,
768         @IntRange(from = 1) columns: Int,
769         isHorizontalArrangement: Boolean = true,
770         verticalSpacing: Dp = 0.dp,
771         horizontalSpacing: Dp = 0.dp,
772         rowWeights: FloatArray = floatArrayOf(),
773         columnWeights: FloatArray = floatArrayOf(),
774         skips: Array<Skip> = arrayOf(),
775         spans: Array<Span> = arrayOf(),
776         flags: GridFlag = GridFlag.None,
777     ): ConstrainedLayoutReference {
778         if (rowWeights.isNotEmpty() && rows > 0 && rows != rowWeights.size) {
779             throw IllegalArgumentException(
780                 "Number of weights (${rowWeights.size}) should match number of rows ($rows)."
781             )
782         }
783         if (columnWeights.isNotEmpty() && columns > 0 && columns != columnWeights.size) {
784             throw IllegalArgumentException(
785                 "Number of weights (${columnWeights.size}) should match number of columns ($columns)."
786             )
787         }
788 
789         val ref = ConstrainedLayoutReference(createHelperId())
790         val elementArray = CLArray(charArrayOf())
791         elements.forEach { elementArray.add(CLString.from(it.id.toString())) }
792         var strRowWeights = ""
793         var strColumnWeights = ""
794         if (rowWeights.size > 1) {
795             strRowWeights = rowWeights.joinToString(",")
796         }
797         if (columnWeights.size > 1) {
798             strColumnWeights = columnWeights.joinToString(",")
799         }
800 
801         var strSkips = ""
802         var strSpans = ""
803         if (skips.isNotEmpty()) {
804             strSkips = skips.joinToString(",") { it.description }
805         }
806         if (spans.isNotEmpty()) {
807             strSpans = spans.joinToString(",") { it.description }
808         }
809 
810         ref.asCLContainer().apply {
811             put("contains", elementArray)
812             putString("type", "grid")
813             putNumber("orientation", if (isHorizontalArrangement) 0f else 1f)
814             putNumber("rows", rows.toFloat())
815             putNumber("columns", columns.toFloat())
816             putNumber("vGap", verticalSpacing.value)
817             putNumber("hGap", horizontalSpacing.value)
818             putString("rowWeights", strRowWeights)
819             putString("columnWeights", strColumnWeights)
820             putString("skips", strSkips)
821             putString("spans", strSpans)
822             putNumber("flags", flags.value.toFloat())
823         }
824 
825         return ref
826     }
827 
828     /**
829      * Creates a horizontal chain including the referenced layouts.
830      *
831      * Use [constrain] with the resulting [HorizontalChainReference] to modify the start/left and
832      * end/right constraints of this chain.
833      */
834     fun createHorizontalChain(
835         vararg elements: LayoutReference,
836         chainStyle: ChainStyle = ChainStyle.Spread
837     ): HorizontalChainReference {
838         val ref = HorizontalChainReference(createHelperId())
839         val elementArray = CLArray(charArrayOf())
840         elements.forEach {
841             val chainParams = it.getHelperParams<ChainParams>()
842             val elementContent: CLElement =
843                 if (chainParams != null) {
844                     CLArray(charArrayOf()).apply {
845                         add(CLString.from(it.id.toString()))
846                         add(CLNumber(chainParams.weight))
847                         add(CLNumber(chainParams.startMargin.value))
848                         add(CLNumber(chainParams.endMargin.value))
849                         add(CLNumber(chainParams.startGoneMargin.value))
850                         add(CLNumber(chainParams.endGoneMargin.value))
851                     }
852                 } else {
853                     CLString.from(it.id.toString())
854                 }
855             elementArray.add(elementContent)
856         }
857         val styleArray = CLArray(charArrayOf())
858         styleArray.add(CLString.from(chainStyle.name))
859         styleArray.add(CLNumber(chainStyle.bias ?: 0.5f))
860 
861         ref.asCLContainer().apply {
862             putString("type", "hChain")
863             put("contains", elementArray)
864             put("style", styleArray)
865         }
866 
867         updateHelpersHashCode(16)
868         elements.forEach { updateHelpersHashCode(it.hashCode()) }
869         updateHelpersHashCode(chainStyle.hashCode())
870         return ref
871     }
872 
873     /**
874      * Creates a vertical chain including the referenced layouts.
875      *
876      * Use [constrain] with the resulting [VerticalChainReference] to modify the top and bottom
877      * constraints of this chain.
878      */
879     fun createVerticalChain(
880         vararg elements: LayoutReference,
881         chainStyle: ChainStyle = ChainStyle.Spread
882     ): VerticalChainReference {
883         val ref = VerticalChainReference(createHelperId())
884         val elementArray = CLArray(charArrayOf())
885         elements.forEach {
886             val chainParams = it.getHelperParams<ChainParams>()
887             val elementContent: CLElement =
888                 if (chainParams != null) {
889                     CLArray(charArrayOf()).apply {
890                         add(CLString.from(it.id.toString()))
891                         add(CLNumber(chainParams.weight))
892                         add(CLNumber(chainParams.topMargin.value))
893                         add(CLNumber(chainParams.bottomMargin.value))
894                         add(CLNumber(chainParams.topGoneMargin.value))
895                         add(CLNumber(chainParams.bottomGoneMargin.value))
896                     }
897                 } else {
898                     CLString.from(it.id.toString())
899                 }
900             elementArray.add(elementContent)
901         }
902         val styleArray = CLArray(charArrayOf())
903         styleArray.add(CLString.from(chainStyle.name))
904         styleArray.add(CLNumber(chainStyle.bias ?: 0.5f))
905 
906         ref.asCLContainer().apply {
907             putString("type", "vChain")
908             put("contains", elementArray)
909             put("style", styleArray)
910         }
911 
912         updateHelpersHashCode(17)
913         elements.forEach { updateHelpersHashCode(it.hashCode()) }
914         updateHelpersHashCode(chainStyle.hashCode())
915         return ref
916     }
917 
918     /**
919      * Sets the parameters that are used by chains to customize the resulting layout.
920      *
921      * Use margins to customize the space between widgets in the chain.
922      *
923      * Use weight to distribute available space to each widget when their dimensions are not fixed.
924      *
925      * Similarly named parameters available from [ConstrainScope.linkTo] are ignored in Chains.
926      *
927      * Since margins are only for widgets within the chain: Top, Start and End, Bottom margins are
928      * ignored when the widget is the first or the last element in the chain, respectively.
929      *
930      * @param startMargin Added space from the start of this widget to the previous widget
931      * @param topMargin Added space from the top of this widget to the previous widget
932      * @param endMargin Added space from the end of this widget to the next widget
933      * @param bottomMargin Added space from the bottom of this widget to the next widget
934      * @param startGoneMargin Added space from the start of this widget when the previous widget has
935      *   [Visibility.Gone]
936      * @param topGoneMargin Added space from the top of this widget when the previous widget has
937      *   [Visibility.Gone]
938      * @param endGoneMargin Added space from the end of this widget when the next widget has
939      *   [Visibility.Gone]
940      * @param bottomGoneMargin Added space from the bottom of this widget when the next widget has
941      *   [Visibility.Gone]
942      * @param weight Defines the proportion of space (relative to the total weight) occupied by this
943      *   layout when the corresponding dimension is not a fixed value.
944      * @return The same [LayoutReference] instance with the applied values
945      */
946     fun LayoutReference.withChainParams(
947         startMargin: Dp = 0.dp,
948         topMargin: Dp = 0.dp,
949         endMargin: Dp = 0.dp,
950         bottomMargin: Dp = 0.dp,
951         startGoneMargin: Dp = 0.dp,
952         topGoneMargin: Dp = 0.dp,
953         endGoneMargin: Dp = 0.dp,
954         bottomGoneMargin: Dp = 0.dp,
955         weight: Float = Float.NaN,
956     ): LayoutReference =
957         this.apply {
958             setHelperParams(
959                 ChainParams(
960                     startMargin = startMargin,
961                     topMargin = topMargin,
962                     endMargin = endMargin,
963                     bottomMargin = bottomMargin,
964                     startGoneMargin = startGoneMargin,
965                     topGoneMargin = topGoneMargin,
966                     endGoneMargin = endGoneMargin,
967                     bottomGoneMargin = bottomGoneMargin,
968                     weight = weight
969                 )
970             )
971         }
972 
973     /**
974      * Sets the parameters that are used by horizontal chains to customize the resulting layout.
975      *
976      * Use margins to customize the space between widgets in the chain.
977      *
978      * Use weight to distribute available space to each widget when their horizontal dimension is
979      * not fixed.
980      *
981      * Similarly named parameters available from [ConstrainScope.linkTo] are ignored in Chains.
982      *
983      * Since margins are only for widgets within the chain: Start and End margins are ignored when
984      * the widget is the first or the last element in the chain, respectively.
985      *
986      * @param startMargin Added space from the start of this widget to the previous widget
987      * @param endMargin Added space from the end of this widget to the next widget
988      * @param startGoneMargin Added space from the start of this widget when the previous widget has
989      *   [Visibility.Gone]
990      * @param endGoneMargin Added space from the end of this widget when the next widget has
991      *   [Visibility.Gone]
992      * @param weight Defines the proportion of space (relative to the total weight) occupied by this
993      *   layout when the width is not a fixed dimension.
994      * @return The same [LayoutReference] instance with the applied values
995      */
996     fun LayoutReference.withHorizontalChainParams(
997         startMargin: Dp = 0.dp,
998         endMargin: Dp = 0.dp,
999         startGoneMargin: Dp = 0.dp,
1000         endGoneMargin: Dp = 0.dp,
1001         weight: Float = Float.NaN
1002     ): LayoutReference =
1003         withChainParams(
1004             startMargin = startMargin,
1005             topMargin = 0.dp,
1006             endMargin = endMargin,
1007             bottomMargin = 0.dp,
1008             startGoneMargin = startGoneMargin,
1009             topGoneMargin = 0.dp,
1010             endGoneMargin = endGoneMargin,
1011             bottomGoneMargin = 0.dp,
1012             weight = weight
1013         )
1014 
1015     /**
1016      * Sets the parameters that are used by vertical chains to customize the resulting layout.
1017      *
1018      * Use margins to customize the space between widgets in the chain.
1019      *
1020      * Use weight to distribute available space to each widget when their vertical dimension is not
1021      * fixed.
1022      *
1023      * Similarly named parameters available from [ConstrainScope.linkTo] are ignored in Chains.
1024      *
1025      * Since margins are only for widgets within the chain: Top and Bottom margins are ignored when
1026      * the widget is the first or the last element in the chain, respectively.
1027      *
1028      * @param topMargin Added space from the top of this widget to the previous widget
1029      * @param bottomMargin Added space from the bottom of this widget to the next widget
1030      * @param topGoneMargin Added space from the top of this widget when the previous widget has
1031      *   [Visibility.Gone]
1032      * @param bottomGoneMargin Added space from the bottom of this widget when the next widget has
1033      *   [Visibility.Gone]
1034      * @param weight Defines the proportion of space (relative to the total weight) occupied by this
1035      *   layout when the height is not a fixed dimension.
1036      * @return The same [LayoutReference] instance with the applied values
1037      */
1038     fun LayoutReference.withVerticalChainParams(
1039         topMargin: Dp = 0.dp,
1040         bottomMargin: Dp = 0.dp,
1041         topGoneMargin: Dp = 0.dp,
1042         bottomGoneMargin: Dp = 0.dp,
1043         weight: Float = Float.NaN
1044     ): LayoutReference =
1045         withChainParams(
1046             startMargin = 0.dp,
1047             topMargin = topMargin,
1048             endMargin = 0.dp,
1049             bottomMargin = bottomMargin,
1050             startGoneMargin = 0.dp,
1051             topGoneMargin = topGoneMargin,
1052             endGoneMargin = 0.dp,
1053             bottomGoneMargin = bottomGoneMargin,
1054             weight = weight
1055         )
1056 
1057     internal fun LayoutReference.asCLContainer(): CLObject {
1058         val idString = id.toString()
1059         if (containerObject.getObjectOrNull(idString) == null) {
1060             containerObject.put(idString, CLObject(charArrayOf()))
1061         }
1062         return containerObject.getObject(idString)
1063     }
1064 
1065     override fun equals(other: Any?): Boolean {
1066         if (this === other) {
1067             return true
1068         }
1069         if (other is ConstraintLayoutBaseScope) {
1070             return containerObject == other.containerObject
1071         }
1072         return false
1073     }
1074 
1075     override fun hashCode(): Int {
1076         return containerObject.hashCode()
1077     }
1078 }
1079 
1080 /**
1081  * Represents a [ConstraintLayout] item that requires a unique identifier. Typically a layout or a
1082  * helper such as barriers, guidelines or chains.
1083  */
1084 @Stable
1085 abstract class LayoutReference internal constructor(internal open val id: Any) {
1086     /**
1087      * This map should be used to store one instance of different implementations of [HelperParams].
1088      */
1089     private val helperParamsMap: MutableMap<String, HelperParams> = mutableMapOf()
1090 
equalsnull1091     override fun equals(other: Any?): Boolean {
1092         if (this === other) return true
1093         if (other !is LayoutReference) return false
1094         if (id != other.id) return false
1095 
1096         return true
1097     }
1098 
hashCodenull1099     override fun hashCode(): Int {
1100         return id.hashCode()
1101     }
1102 
setHelperParamsnull1103     internal fun setHelperParams(helperParams: HelperParams) {
1104         // Use the class name to force one instance per implementation
1105         helperParams::class.simpleName?.let { helperParamsMap[it] = helperParams }
1106     }
1107 
1108     /**
1109      * Returns the [HelperParams] that corresponds to the class type [T]. Null if no instance of
1110      * type [T] has been set.
1111      */
getHelperParamsnull1112     internal inline fun <reified T> getHelperParams(): T? where T : HelperParams {
1113         return helperParamsMap[T::class.simpleName] as? T
1114     }
1115 }
1116 
1117 /**
1118  * Helpers that need parameters on a per-widget basis may implement this interface to store custom
1119  * parameters within [LayoutReference].
1120  *
1121  * @see [LayoutReference.getHelperParams]
1122  * @see [LayoutReference.setHelperParams]
1123  */
1124 internal interface HelperParams
1125 
1126 /**
1127  * Parameters that may be defined for each widget within a chain.
1128  *
1129  * These will always be used instead of similarly named parameters defined with other calls such as
1130  * [ConstrainScope.linkTo].
1131  */
1132 internal class ChainParams(
1133     val startMargin: Dp,
1134     val topMargin: Dp,
1135     val endMargin: Dp,
1136     val bottomMargin: Dp,
1137     val startGoneMargin: Dp,
1138     val topGoneMargin: Dp,
1139     val endGoneMargin: Dp,
1140     val bottomGoneMargin: Dp,
1141     val weight: Float,
1142 ) : HelperParams {
1143     companion object {
1144         internal val Default =
1145             ChainParams(
1146                 startMargin = 0.dp,
1147                 topMargin = 0.dp,
1148                 endMargin = 0.dp,
1149                 bottomMargin = 0.dp,
1150                 startGoneMargin = 0.dp,
1151                 topGoneMargin = 0.dp,
1152                 endGoneMargin = 0.dp,
1153                 bottomGoneMargin = 0.dp,
1154                 weight = Float.NaN
1155             )
1156     }
1157 }
1158 
1159 /**
1160  * Basic implementation of [LayoutReference], used as fallback for items that don't fit other
1161  * implementations of [LayoutReference], such as [ConstrainedLayoutReference].
1162  */
1163 @Stable internal class LayoutReferenceImpl internal constructor(id: Any) : LayoutReference(id)
1164 
1165 /**
1166  * Represents a layout within a [ConstraintLayout].
1167  *
1168  * This is a [LayoutReference] that may be constrained to other elements.
1169  */
1170 @Stable
1171 class ConstrainedLayoutReference(override val id: Any) : LayoutReference(id) {
1172     /**
1173      * The start anchor of this layout. Represents left in LTR layout direction, or right in RTL.
1174      */
1175     @Stable val start = ConstraintLayoutBaseScope.VerticalAnchor(id, -2, this)
1176 
1177     /** The left anchor of this layout. */
1178     @Stable val absoluteLeft = ConstraintLayoutBaseScope.VerticalAnchor(id, 0, this)
1179 
1180     /** The top anchor of this layout. */
1181     @Stable val top = ConstraintLayoutBaseScope.HorizontalAnchor(id, 0, this)
1182 
1183     /** The end anchor of this layout. Represents right in LTR layout direction, or left in RTL. */
1184     @Stable val end = ConstraintLayoutBaseScope.VerticalAnchor(id, -1, this)
1185 
1186     /** The right anchor of this layout. */
1187     @Stable val absoluteRight = ConstraintLayoutBaseScope.VerticalAnchor(id, 1, this)
1188 
1189     /** The bottom anchor of this layout. */
1190     @Stable val bottom = ConstraintLayoutBaseScope.HorizontalAnchor(id, 1, this)
1191 
1192     /** The baseline anchor of this layout. */
1193     @Stable val baseline = ConstraintLayoutBaseScope.BaselineAnchor(id, this)
1194 }
1195 
1196 /**
1197  * Represents a horizontal chain within a [ConstraintLayout].
1198  *
1199  * The anchors correspond to the first and last elements in the chain.
1200  */
1201 @Stable
1202 class HorizontalChainReference internal constructor(id: Any) : LayoutReference(id) {
1203     /**
1204      * The start anchor of the first element in the chain.
1205      *
1206      * Represents left in LTR layout direction, or right in RTL.
1207      */
1208     @Stable val start = ConstraintLayoutBaseScope.VerticalAnchor(id, -2, this)
1209 
1210     /** The left anchor of the first element in the chain. */
1211     @Stable val absoluteLeft = ConstraintLayoutBaseScope.VerticalAnchor(id, 0, this)
1212 
1213     /**
1214      * The end anchor of the last element in the chain.
1215      *
1216      * Represents right in LTR layout direction, or left in RTL.
1217      */
1218     @Stable val end = ConstraintLayoutBaseScope.VerticalAnchor(id, -1, this)
1219 
1220     /** The right anchor of the last element in the chain. */
1221     @Stable val absoluteRight = ConstraintLayoutBaseScope.VerticalAnchor(id, 1, this)
1222 }
1223 
1224 /**
1225  * Represents a vertical chain within a [ConstraintLayout].
1226  *
1227  * The anchors correspond to the first and last elements in the chain.
1228  */
1229 @Stable
1230 class VerticalChainReference internal constructor(id: Any) : LayoutReference(id) {
1231     /** The top anchor of the first element in the chain. */
1232     @Stable val top = ConstraintLayoutBaseScope.HorizontalAnchor(id, 0, this)
1233 
1234     /** The bottom anchor of the last element in the chain. */
1235     @Stable val bottom = ConstraintLayoutBaseScope.HorizontalAnchor(id, 1, this)
1236 }
1237 
1238 /** The style of a horizontal or vertical chain. */
1239 @Immutable
1240 class ChainStyle internal constructor(internal val name: String, internal val bias: Float? = null) {
1241     companion object {
1242         /** A chain style that evenly distributes the contained layouts. */
1243         @Stable val Spread = ChainStyle("spread")
1244 
1245         /**
1246          * A chain style where the first and last layouts are affixed to the constraints on each end
1247          * of the chain and the rest are evenly distributed.
1248          */
1249         @Stable val SpreadInside = ChainStyle("spread_inside")
1250 
1251         /**
1252          * A chain style where the contained layouts are packed together and placed to the center of
1253          * the available space.
1254          */
1255         @Stable val Packed = Packed(0.5f)
1256 
1257         /**
1258          * A chain style where the contained layouts are packed together and placed in the available
1259          * space according to a given [bias].
1260          */
Packednull1261         @Stable fun Packed(bias: Float) = ChainStyle("packed", bias)
1262     }
1263 }
1264 
1265 /** The overall visibility of a widget in a [ConstraintLayout]. */
1266 @Immutable
1267 class Visibility internal constructor(internal val name: String) {
1268     companion object {
1269         /**
1270          * Indicates that the widget will be painted in the [ConstraintLayout]. All render-time
1271          * transforms will apply normally.
1272          */
1273         @Stable val Visible = Visibility("visible")
1274 
1275         /**
1276          * The widget will not be painted in the [ConstraintLayout] but its dimensions and
1277          * constraints will still apply.
1278          *
1279          * Equivalent to forcing the alpha to 0.0.
1280          */
1281         @Stable val Invisible = Visibility("invisible")
1282 
1283         /**
1284          * Like [Invisible], but the dimensions of the widget will collapse to (0,0), the
1285          * constraints will still apply.
1286          */
1287         @Stable val Gone = Visibility("gone")
1288     }
1289 }
1290 
1291 /**
1292  * Set of individual options that may change the Grid helper behavior, each flag can be combined
1293  * with the [GridFlag.or] operator.
1294  *
1295  * By default, the Grid helper places its [LayoutReference]s as given in the `elements` parameter.
1296  * Following arrangement rules (skips, spans and orientation).
1297  *
1298  * However, when [isPlaceLayoutsOnSpansFirst] is `true`. The given [LayoutReference]s will be first
1299  * placed on the cells occupied by the given `spans` array. Then, the remaining layouts are placed
1300  * on the remaining cells following typical arrangement rules.
1301  *
1302  * For example, on a grid layout with 1 row and 3 columns, placing two widgets: w0, w1, with a span
1303  * defined as `Span(position = 1, rows = 1, columns = 2)`. The grid layout by default would place
1304  * them as `[w0 w1 w1]`. Whereas when [isPlaceLayoutsOnSpansFirst] is `true`, they'd be placed as
1305  * `[w1 w0 w0]`.
1306  *
1307  * In some situations, [isPlaceLayoutsOnSpansFirst] can make it easier to match the desired layouts
1308  * with the given spans on the Grid.
1309  *
1310  * @see ConstraintLayoutBaseScope.createGrid
1311  */
1312 @JvmInline
1313 value class GridFlag private constructor(internal val value: Int) {
1314 
1315     /**
1316      * Handles the conversion of compose flags to :constraintlayout-core flags, handled like this
1317      * since we invert the meaning of one the flags for API ergonomics in Compose.
1318      */
1319     private constructor(
1320         isPlaceLayoutsOnSpansFirst: Boolean = false,
1321         // isSubGridByColRow is only expected to be used on tests
1322         isSubGridByColRow: Boolean = false
1323     ) : this(
1324         (if (isPlaceLayoutsOnSpansFirst) 0 else GridCore.SPANS_RESPECT_WIDGET_ORDER) or
1325             (if (isSubGridByColRow) GridCore.SUB_GRID_BY_COL_ROW else 0)
1326     )
1327 
1328     /** `or` operator override to allow combining flags */
ornull1329     infix fun or(other: GridFlag): GridFlag =
1330         // Again, implemented like this as the flag handling is non-standard. It differs from the
1331         // :constraintlayout-core flag behaviors.
1332         GridFlag(
1333             isPlaceLayoutsOnSpansFirst or other.isPlaceLayoutsOnSpansFirst,
1334             isSubGridByColRow or other.isSubGridByColRow
1335         )
1336 
1337     /**
1338      * When true, the Grid helper will first place Layouts on cells occupied by spans, then fill the
1339      * remaining cells following the typical arrangement rules.
1340      */
1341     val isPlaceLayoutsOnSpansFirst: Boolean
1342         get() = value and GridCore.SPANS_RESPECT_WIDGET_ORDER == 0
1343 
1344     /**
1345      * Whether area definitions in Spans and Skips are treated as "columns by rows".
1346      *
1347      * Note that this property is only relevant for testing.
1348      */
1349     internal val isSubGridByColRow: Boolean
1350         get() = value and GridCore.SUB_GRID_BY_COL_ROW > 0
1351 
1352     override fun toString(): String =
1353         "GridFlag(isPlaceLayoutsOnSpansFirst = $isPlaceLayoutsOnSpansFirst)"
1354 
1355     companion object {
1356         /** All default behaviors apply. */
1357         val None = GridFlag()
1358 
1359         /**
1360          * Creates a [GridFlag] instance with `isPlaceLayoutsOnSpansFirst` as `true`.
1361          *
1362          * Making it so that when placing the layouts, they are first placed on cells occupied by
1363          * spans, then, any remaining layouts are placed on the remaining cells following the
1364          * typical arrangement rules.
1365          */
1366         val PlaceLayoutsOnSpansFirst = GridFlag(isPlaceLayoutsOnSpansFirst = true)
1367 
1368         /** Not relevant for the public API, only used now to test "internal" features. */
1369         @TestOnly internal val SubGridByColRow = GridFlag(isSubGridByColRow = true)
1370     }
1371 }
1372 
1373 /** Wrap defines the type of chain */
1374 @Immutable
1375 class Wrap internal constructor(internal val name: String) {
1376     companion object {
1377         val None = Wrap("none")
1378         val Chain = Wrap("chain")
1379         val Aligned = Wrap("aligned")
1380     }
1381 }
1382 
1383 /** Defines how objects align vertically within the chain */
1384 @Immutable
1385 class VerticalAlign internal constructor(internal val name: String) {
1386     companion object {
1387         val Top = VerticalAlign("top")
1388         val Bottom = VerticalAlign("bottom")
1389         val Center = VerticalAlign("center")
1390         val Baseline = VerticalAlign("baseline")
1391     }
1392 }
1393 
1394 /** Defines how objects align horizontally in the chain */
1395 @Immutable
1396 class HorizontalAlign internal constructor(internal val name: String) {
1397     companion object {
1398         val Start = HorizontalAlign("start")
1399         val End = HorizontalAlign("end")
1400         val Center = HorizontalAlign("center")
1401     }
1402 }
1403 
1404 /** Defines how widgets are spaced in a chain */
1405 @Immutable
1406 class FlowStyle internal constructor(internal val name: String) {
1407     companion object {
1408         val Spread = FlowStyle("spread")
1409         val SpreadInside = FlowStyle("spread_inside")
1410         val Packed = FlowStyle("packed")
1411     }
1412 }
1413 
1414 /**
1415  * Defines how many rows and/or columns to skip, starting from the given position. For Grid, specify
1416  * the Skip with Skip(position, rows, columns) For Row/Column, specify the Skip with Skip(position,
1417  * size)
1418  *
1419  * @param description string to specify span. For Grid: "position:rowsxcolumns"; For Row/Columns:
1420  *   "position:size"
1421  * @constructor create a new Skip containing the position and size information of the skipped area
1422  */
1423 @JvmInline
1424 value class Skip private constructor(val description: String) {
1425     constructor(
1426         @IntRange(from = 0) position: Int,
1427         @IntRange(from = 1) rows: Int,
1428         @IntRange(from = 1) columns: Int
1429     ) : this("$position:${rows}x$columns")
1430 
1431     constructor(
1432         @IntRange(from = 0) position: Int,
1433         @IntRange(from = 1) size: Int
1434     ) : this("$position:$size")
1435 }
1436 
1437 /**
1438  * Defines the spanned area (that crosses multiple columns and/or rows) that a widget will take when
1439  * placed at the given position. For Grid, specify the Span with Span(position, rows, columns) For
1440  * Row/Column, specify the Span with Span(position, size)
1441  *
1442  * @param description string to specify skip. For Grid: "position:rowsxcolumns"; For Row/Columns:
1443  *   "position:size"
1444  * @constructor create a new Span containing the position and size information of the spanned area
1445  */
1446 @JvmInline
1447 value class Span(val description: String) {
1448     constructor(
1449         @IntRange(from = 0) position: Int,
1450         @IntRange(from = 1) rows: Int,
1451         @IntRange(from = 1) columns: Int
1452     ) : this("$position:${rows}x$columns")
1453 
1454     constructor(
1455         @IntRange(from = 0) position: Int,
1456         @IntRange(from = 1) size: Int
1457     ) : this("$position:$size")
1458 }
1459