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