1 /* <lambda>null2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.ui.layout 18 19 import androidx.activity.ComponentActivity 20 import androidx.compose.foundation.layout.Box 21 import androidx.compose.foundation.layout.offset 22 import androidx.compose.foundation.layout.requiredSize 23 import androidx.compose.foundation.layout.size 24 import androidx.compose.runtime.Composable 25 import androidx.compose.runtime.getValue 26 import androidx.compose.runtime.mutableStateMapOf 27 import androidx.compose.runtime.mutableStateOf 28 import androidx.compose.runtime.setValue 29 import androidx.compose.ui.Modifier 30 import androidx.compose.ui.test.junit4.createAndroidComposeRule 31 import androidx.compose.ui.unit.Constraints 32 import androidx.compose.ui.unit.IntOffset 33 import androidx.compose.ui.unit.dp 34 import androidx.test.ext.junit.runners.AndroidJUnit4 35 import androidx.test.filters.MediumTest 36 import kotlin.math.min 37 import org.junit.Assert.assertEquals 38 import org.junit.Rule 39 import org.junit.Test 40 import org.junit.runner.RunWith 41 42 @MediumTest 43 @RunWith(AndroidJUnit4::class) 44 class AlignmentLineTest { 45 @get:Rule val rule = createAndroidComposeRule<ComponentActivity>() 46 47 @Test 48 fun queryingLinesOfUnmeasuredChild() { 49 val root = root { 50 queryAlignmentLineDuringMeasure() 51 add( 52 node { 53 doNotMeasure() 54 add(node()) 55 } 56 ) 57 } 58 59 createDelegate(root) 60 assertMeasuredAndLaidOut(root) 61 } 62 63 @Test 64 fun alignmentLinesPositionInCooperation_whenModifierDisobeys() { 65 val hLine = HorizontalAlignmentLine(::min) 66 val vLine = VerticalAlignmentLine(::min) 67 val hLinePosition = 50 68 val vLinePosition = 150 69 val constrainedSize = 100 70 val actualSize = 200 71 rule.setContent { 72 val contentWithAlignmentLines = 73 @Composable { 74 Box(Modifier.requiredSize(with(rule.density) { actualSize.toDp() })) { 75 Layout({}, Modifier) { _, _ -> 76 layout(0, 0, mapOf(hLine to hLinePosition, vLine to vLinePosition)) {} 77 } 78 } 79 } 80 Layout(contentWithAlignmentLines) { measurables, _ -> 81 val placeable = 82 measurables 83 .first() 84 .measure( 85 Constraints(maxWidth = constrainedSize, maxHeight = constrainedSize) 86 ) 87 val obtainedHLinePosition = placeable[hLine] 88 val obtainedVLinePosition = placeable[vLine] 89 assertEquals( 90 hLinePosition - (actualSize - constrainedSize) / 2, 91 obtainedHLinePosition 92 ) 93 assertEquals( 94 vLinePosition - (actualSize - constrainedSize) / 2, 95 obtainedVLinePosition 96 ) 97 layout(0, 0) {} 98 } 99 } 100 rule.waitForIdle() 101 } 102 103 @Test 104 fun alignmentLinesPositionInCooperation_whenLayoutDisobeys() { 105 val hLine = HorizontalAlignmentLine(::min) 106 val vLine = VerticalAlignmentLine(::min) 107 val hLinePosition = 50 108 val vLinePosition = 150 109 val constrainedSize = 100 110 val actualSize = 200 111 rule.setContent { 112 val contentWithAlignmentLines = 113 @Composable { 114 Layout({}, Modifier) { _, _ -> 115 layout( 116 actualSize, 117 actualSize, 118 mapOf(hLine to hLinePosition, vLine to vLinePosition) 119 ) {} 120 } 121 } 122 Layout(contentWithAlignmentLines) { measurables, _ -> 123 val placeable = 124 measurables 125 .first() 126 .measure( 127 Constraints(maxWidth = constrainedSize, maxHeight = constrainedSize) 128 ) 129 val obtainedHLinePosition = placeable[hLine] 130 val obtainedVLinePosition = placeable[vLine] 131 assertEquals( 132 hLinePosition - (actualSize - constrainedSize) / 2, 133 obtainedHLinePosition 134 ) 135 assertEquals( 136 vLinePosition - (actualSize - constrainedSize) / 2, 137 obtainedVLinePosition 138 ) 139 layout(0, 0) {} 140 } 141 } 142 rule.waitForIdle() 143 } 144 145 @Test 146 fun alignmentLinesArePropagated_whenSuppliedViaModifier() { 147 val size = 50 148 val sizeDp = with(rule.density) { size.toDp() } 149 val linePosition = 25 150 val hLine = HorizontalAlignmentLine(::min) 151 val vLine = VerticalAlignmentLine(::min) 152 rule.setContent { 153 val content = 154 @Composable { 155 Box(Modifier.size(sizeDp)) { 156 Box( 157 Modifier.supplyAlignmentLines { 158 mapOf(hLine to linePosition, vLine to linePosition) 159 } 160 .size(sizeDp) 161 ) 162 } 163 } 164 165 Layout(content) { measurables, constraints -> 166 val placeable = measurables.first().measure(constraints) 167 assertEquals(linePosition, placeable[hLine]) 168 assertEquals(linePosition, placeable[vLine]) 169 layout(0, 0) {} 170 } 171 } 172 } 173 174 @Test 175 fun alignmentLinesArePropagated_whenSuppliedViaModifier_withCorrectPosition() { 176 val size = 50 177 val sizeDp = with(rule.density) { size.toDp() } 178 val offset = 10 179 val offsetDp = with(rule.density) { offset.toDp() } 180 val linePosition = 25 181 val hLine = HorizontalAlignmentLine(::min) 182 val vLine = VerticalAlignmentLine(::min) 183 rule.setContent { 184 val content = 185 @Composable { 186 Box(Modifier.size(sizeDp)) { 187 Box( 188 Modifier.offset(offsetDp, offsetDp) 189 .supplyAlignmentLines { 190 mapOf(hLine to linePosition, vLine to linePosition) 191 } 192 .size(sizeDp) 193 .offset(offsetDp, offsetDp) 194 ) 195 } 196 } 197 198 Layout(content) { measurables, constraints -> 199 val placeable = measurables.first().measure(constraints) 200 assertEquals(linePosition + offset, placeable[hLine]) 201 assertEquals(linePosition + offset, placeable[vLine]) 202 layout(0, 0) {} 203 } 204 } 205 } 206 207 @Test 208 fun alignmentLinesChangeCausesRemeasure_whenSuppliedViaModifier() { 209 val size = 50 210 val sizeDp = with(rule.density) { size.toDp() } 211 val offset = 10 212 val linePosition = 25 213 val hLine = HorizontalAlignmentLine(::min) 214 val vLine = VerticalAlignmentLine(::min) 215 val alignmentLines = mutableStateMapOf(hLine to linePosition, vLine to linePosition) 216 var obtainedHLinePosition = -1 217 var obtainedVLinePosition = -1 218 rule.setContent { 219 val content = 220 @Composable { 221 Box(Modifier.size(sizeDp)) { 222 Box(Modifier.supplyAlignmentLines { alignmentLines.toMap() }.size(sizeDp)) 223 } 224 } 225 226 Layout(content) { measurables, constraints -> 227 val placeable = measurables.first().measure(constraints) 228 obtainedHLinePosition = placeable[hLine] 229 obtainedVLinePosition = placeable[vLine] 230 layout(0, 0) {} 231 } 232 } 233 234 rule.runOnIdle { 235 assertEquals(linePosition, obtainedHLinePosition) 236 assertEquals(linePosition, obtainedVLinePosition) 237 alignmentLines[hLine] = linePosition + offset 238 alignmentLines[vLine] = linePosition + offset 239 } 240 241 rule.runOnIdle { 242 assertEquals(linePosition + offset, obtainedHLinePosition) 243 assertEquals(linePosition + offset, obtainedVLinePosition) 244 } 245 } 246 247 @Test 248 fun alignmentLinesChangeCausesRemeasure_whenSuppliedViaLayout() { 249 val size = 50 250 val sizeDp = with(rule.density) { size.toDp() } 251 val offset = 10 252 val linePosition = 25 253 val hLine = HorizontalAlignmentLine(::min) 254 val vLine = VerticalAlignmentLine(::min) 255 val alignmentLines = mutableStateMapOf(hLine to linePosition, vLine to linePosition) 256 var obtainedHLinePosition = -1 257 var obtainedVLinePosition = -1 258 rule.setContent { 259 val content = 260 @Composable { 261 val innerContent = 262 @Composable { Layout({}) { _, _ -> layout(size, size, alignmentLines) {} } } 263 Layout(content = innerContent, Modifier.size(sizeDp)) { measurables, constraints 264 -> 265 val placeable = measurables.first().measure(constraints) 266 layout(constraints.maxWidth, constraints.maxHeight) { 267 placeable.place(0, 0) 268 } 269 } 270 } 271 272 Layout(content) { measurables, constraints -> 273 val placeable = measurables.first().measure(constraints) 274 obtainedHLinePosition = placeable[hLine] 275 obtainedVLinePosition = placeable[vLine] 276 layout(0, 0) {} 277 } 278 } 279 280 rule.runOnIdle { 281 assertEquals(linePosition, obtainedHLinePosition) 282 assertEquals(linePosition, obtainedVLinePosition) 283 alignmentLines[hLine] = linePosition + offset 284 alignmentLines[vLine] = linePosition + offset 285 } 286 287 rule.runOnIdle { 288 assertEquals(linePosition + offset, obtainedHLinePosition) 289 assertEquals(linePosition + offset, obtainedVLinePosition) 290 } 291 } 292 293 @Test 294 fun scenario1() { 295 var parentMeasures = 0 296 var measures = 0 297 rule.setContent { 298 Parent(onMeasure = { ++parentMeasures }) { 299 Parent(onMeasure = { ++measures }, readDuringMeasure = true) { 300 Parent { Provider() } 301 } 302 } 303 } 304 rule.runOnIdle { 305 assertEquals(1, parentMeasures) 306 assertEquals(1, measures) 307 changeLinePosition() 308 } 309 310 rule.runOnIdle { 311 assertEquals(1, parentMeasures) 312 assertEquals(2, measures) 313 } 314 } 315 316 @Test 317 fun scenario2() { 318 var parentLayouts = 0 319 var measures = 0 320 var layouts = 0 321 rule.setContent { 322 Parent(onLayout = { ++parentLayouts }) { 323 Parent( 324 onMeasure = { ++measures }, 325 onLayout = { ++layouts }, 326 readDuringLayoutBeforePlacing = true 327 ) { 328 Parent { Provider() } 329 } 330 } 331 } 332 rule.runOnIdle { 333 assertEquals(1, parentLayouts) 334 assertEquals(1, measures) 335 assertEquals(1, layouts) 336 changeLinePosition() 337 } 338 339 rule.runOnIdle { 340 assertEquals(1, parentLayouts) 341 assertEquals(1, measures) 342 assertEquals(2, layouts) 343 } 344 } 345 346 @Test 347 fun scenario3() { 348 var parentLayouts = 0 349 var measures = 0 350 var layouts = 0 351 rule.setContent { 352 Parent(onLayout = { ++parentLayouts }) { 353 Parent( 354 onMeasure = { ++measures }, 355 onLayout = { ++layouts }, 356 readDuringLayoutAfterPlacing = true 357 ) { 358 Parent { Provider() } 359 } 360 } 361 } 362 rule.runOnIdle { 363 assertEquals(1, parentLayouts) 364 assertEquals(1, measures) 365 assertEquals(1, layouts) 366 changeLinePosition() 367 } 368 369 rule.runOnIdle { 370 assertEquals(1, parentLayouts) 371 assertEquals(1, measures) 372 assertEquals(2, layouts) 373 } 374 } 375 376 @Test 377 fun scenario4() { 378 var parentMeasures = 0 379 var parentLayouts = 0 380 var measures = 0 381 var layouts = 0 382 rule.setContent { 383 Parent( 384 onMeasure = { ++parentMeasures }, 385 onLayout = { ++parentLayouts }, 386 readDuringMeasure = true 387 ) { 388 Parent( 389 onMeasure = { ++measures }, 390 onLayout = { ++layouts }, 391 readDuringLayoutBeforePlacing = true 392 ) { 393 Parent { Provider() } 394 } 395 } 396 } 397 rule.runOnIdle { 398 assertEquals(1, parentMeasures) 399 assertEquals(1, parentLayouts) 400 assertEquals(1, measures) 401 assertEquals(1, layouts) 402 changeLinePosition() 403 } 404 405 rule.runOnIdle { 406 assertEquals(2, parentMeasures) 407 assertEquals(2, parentLayouts) 408 assertEquals(1, measures) 409 assertEquals(2, layouts) 410 } 411 } 412 413 @Test 414 fun scenario5() { 415 var parentMeasures = 0 416 var parentLayouts = 0 417 var measures = 0 418 var layouts = 0 419 rule.setContent { 420 Parent( 421 onMeasure = { ++parentMeasures }, 422 onLayout = { ++parentLayouts }, 423 readDuringMeasure = true 424 ) { 425 Parent( 426 onMeasure = { ++measures }, 427 onLayout = { ++layouts }, 428 readDuringLayoutAfterPlacing = true 429 ) { 430 Parent { Provider() } 431 } 432 } 433 } 434 rule.runOnIdle { 435 assertEquals(1, parentMeasures) 436 assertEquals(1, parentLayouts) 437 assertEquals(1, measures) 438 assertEquals(1, layouts) 439 changeLinePosition() 440 } 441 442 rule.runOnIdle { 443 assertEquals(2, parentMeasures) 444 assertEquals(2, parentLayouts) 445 assertEquals(1, measures) 446 assertEquals(2, layouts) 447 } 448 } 449 450 @Test 451 fun scenario6() { 452 var parentMeasures = 0 453 var parentLayouts = 0 454 var measures = 0 455 var layouts = 0 456 rule.setContent { 457 Parent( 458 onMeasure = { ++parentMeasures }, 459 onLayout = { ++parentLayouts }, 460 readDuringLayoutAfterPlacing = true 461 ) { 462 Parent( 463 onMeasure = { ++measures }, 464 onLayout = { ++layouts }, 465 readDuringMeasure = true 466 ) { 467 Parent { Provider() } 468 } 469 } 470 } 471 rule.runOnIdle { 472 assertEquals(1, parentMeasures) 473 assertEquals(1, parentLayouts) 474 assertEquals(1, measures) 475 assertEquals(1, layouts) 476 changeLinePosition() 477 } 478 479 rule.runOnIdle { 480 assertEquals(1, parentMeasures) 481 assertEquals(2, parentLayouts) 482 assertEquals(2, measures) 483 assertEquals(2, layouts) 484 } 485 } 486 487 @Test 488 fun scenario7() { 489 var parentMeasures = 0 490 var measures = 0 491 var layouts = 0 492 var childMeasures = 0 493 rule.setContent { 494 Parent(onMeasure = { ++parentMeasures }) { 495 Parent( 496 onMeasure = { ++measures }, 497 onLayout = { ++layouts }, 498 readDuringMeasure = true 499 ) { 500 Parent(modifier = Modifier.provider(), onMeasure = { ++childMeasures }) { 501 Parent() 502 } 503 } 504 } 505 } 506 rule.runOnIdle { 507 assertEquals(1, parentMeasures) 508 assertEquals(1, measures) 509 assertEquals(1, layouts) 510 assertEquals(1, childMeasures) 511 changeLinePosition() 512 } 513 514 rule.runOnIdle { 515 assertEquals(1, parentMeasures) 516 assertEquals(2, measures) 517 assertEquals(2, layouts) 518 assertEquals(2, childMeasures) 519 } 520 } 521 522 @Test 523 fun scenario8() { 524 var parentMeasures = 0 525 var measures = 0 526 var layouts = 0 527 var childMeasures = 0 528 rule.setContent { 529 Parent(onMeasure = { ++parentMeasures }) { 530 Parent( 531 onMeasure = { ++measures }, 532 onLayout = { ++layouts }, 533 readDuringLayoutBeforePlacing = true 534 ) { 535 Parent(modifier = Modifier.provider(), onMeasure = { ++childMeasures }) { 536 Parent() 537 } 538 } 539 } 540 } 541 rule.runOnIdle { 542 assertEquals(1, parentMeasures) 543 assertEquals(1, measures) 544 assertEquals(1, layouts) 545 assertEquals(1, childMeasures) 546 changeLinePosition() 547 } 548 549 rule.runOnIdle { 550 assertEquals(1, parentMeasures) 551 assertEquals(1, measures) 552 assertEquals(2, layouts) 553 assertEquals(2, childMeasures) 554 } 555 } 556 557 @Test 558 fun scenario9() { 559 var parentMeasures = 0 560 var measures = 0 561 var layouts = 0 562 var childMeasures = 0 563 rule.setContent { 564 Parent(onMeasure = { ++parentMeasures }) { 565 Parent( 566 onMeasure = { ++measures }, 567 onLayout = { ++layouts }, 568 readDuringLayoutAfterPlacing = true 569 ) { 570 Parent(modifier = Modifier.provider(), onMeasure = { ++childMeasures }) { 571 Parent() 572 } 573 } 574 } 575 } 576 rule.runOnIdle { 577 assertEquals(1, parentMeasures) 578 assertEquals(1, measures) 579 assertEquals(1, layouts) 580 assertEquals(1, childMeasures) 581 changeLinePosition() 582 } 583 584 rule.runOnIdle { 585 assertEquals(1, parentMeasures) 586 assertEquals(1, measures) 587 assertEquals(2, layouts) 588 assertEquals(2, childMeasures) 589 } 590 } 591 592 @Test 593 fun scenario10() { 594 var parentMeasures = 0 595 var measures = 0 596 var layouts = 0 597 var childMeasures = 0 598 rule.setContent { 599 Parent(onMeasure = { ++parentMeasures }, readDuringMeasure = true) { 600 Parent( 601 onMeasure = { ++measures }, 602 onLayout = { ++layouts }, 603 readDuringLayoutAfterPlacing = true 604 ) { 605 Parent(modifier = Modifier.provider(), onMeasure = { ++childMeasures }) { 606 Parent() 607 } 608 } 609 } 610 } 611 rule.runOnIdle { 612 assertEquals(1, parentMeasures) 613 assertEquals(1, measures) 614 assertEquals(1, layouts) 615 assertEquals(1, childMeasures) 616 changeLinePosition() 617 } 618 619 rule.runOnIdle { 620 assertEquals(2, parentMeasures) 621 assertEquals(1, measures) 622 assertEquals(2, layouts) 623 assertEquals(2, childMeasures) 624 } 625 } 626 627 @Test 628 fun scenario11() { 629 var measures = 0 630 var layouts = 0 631 var childMeasures = 0 632 rule.setContent { 633 Parent { 634 Parent( 635 onMeasure = { ++measures }, 636 onLayout = { ++layouts }, 637 readDuringLayoutAfterPlacing = true 638 ) { 639 Parent( 640 modifier = Modifier.reader(readDuringMeasure = true), 641 onMeasure = { ++childMeasures } 642 ) { 643 Provider() 644 } 645 } 646 } 647 } 648 rule.runOnIdle { 649 assertEquals(1, measures) 650 assertEquals(1, layouts) 651 assertEquals(1, childMeasures) 652 changeLinePosition() 653 } 654 655 rule.runOnIdle { 656 assertEquals(1, measures) 657 assertEquals(2, layouts) 658 assertEquals(2, childMeasures) 659 } 660 } 661 662 @Test 663 fun scenario12() { 664 var childMeasures = 0 665 rule.setContent { 666 Parent { 667 Provider( 668 modifier = Modifier.reader(readDuringMeasure = true), 669 onMeasure = { ++childMeasures } 670 ) 671 } 672 } 673 rule.runOnIdle { 674 assertEquals(1, childMeasures) 675 changeLinePosition() 676 } 677 678 rule.runOnIdle { assertEquals(2, childMeasures) } 679 } 680 681 @Test 682 fun scenario13() { 683 var measures = 0 684 var childMeasures = 0 685 var childLayouts = 0 686 rule.setContent { 687 Parent(onMeasure = { ++measures }) { 688 Provider( 689 modifier = Modifier.reader(readDuringLayoutBeforePlacing = true), 690 onMeasure = { ++childMeasures }, 691 onLayout = { ++childLayouts } 692 ) 693 } 694 } 695 rule.runOnIdle { 696 assertEquals(1, measures) 697 assertEquals(1, childMeasures) 698 assertEquals(1, childLayouts) 699 changeLinePosition() 700 } 701 702 rule.runOnIdle { 703 assertEquals(1, measures) 704 assertEquals(2, childMeasures) 705 assertEquals(2, childLayouts) 706 } 707 } 708 709 @Test 710 fun scenario14() { 711 var measures = 0 712 var childMeasures = 0 713 var childLayouts = 0 714 rule.setContent { 715 Parent(onMeasure = { ++measures }) { 716 Provider( 717 modifier = Modifier.reader(readDuringLayoutAfterPlacing = true), 718 onMeasure = { ++childMeasures }, 719 onLayout = { ++childLayouts } 720 ) 721 } 722 } 723 rule.runOnIdle { 724 assertEquals(1, measures) 725 assertEquals(1, childMeasures) 726 assertEquals(1, childLayouts) 727 changeLinePosition() 728 } 729 730 rule.runOnIdle { 731 assertEquals(1, measures) 732 assertEquals(2, childMeasures) 733 assertEquals(2, childLayouts) 734 } 735 } 736 737 @Test 738 fun scenario15() { 739 var parentMeasures = 0 740 var measures = 0 741 var childMeasures = 0 742 rule.setContent { 743 Parent(onMeasure = { ++parentMeasures }) { 744 Parent( 745 modifier = Modifier.reader(readDuringMeasure = true).provider(), 746 onMeasure = { ++measures } 747 ) { 748 Parent(onMeasure = { ++childMeasures }) 749 } 750 } 751 } 752 rule.runOnIdle { 753 assertEquals(1, parentMeasures) 754 assertEquals(1, measures) 755 assertEquals(1, childMeasures) 756 changeLinePosition() 757 } 758 759 rule.runOnIdle { 760 assertEquals(1, parentMeasures) 761 assertEquals(2, measures) 762 assertEquals(1, childMeasures) 763 } 764 } 765 766 @Test 767 fun scenario16() { 768 var parentMeasures = 0 769 var measures = 0 770 var childMeasures = 0 771 rule.setContent { 772 Parent(onMeasure = { ++parentMeasures }, readDuringMeasure = true) { 773 Parent( 774 modifier = Modifier.reader(readDuringMeasure = true).provider(), 775 onMeasure = { ++measures } 776 ) { 777 Parent(onMeasure = { ++childMeasures }) 778 } 779 } 780 } 781 rule.runOnIdle { 782 assertEquals(1, parentMeasures) 783 assertEquals(1, measures) 784 assertEquals(1, childMeasures) 785 changeLinePosition() 786 } 787 788 rule.runOnIdle { 789 assertEquals(2, parentMeasures) 790 assertEquals(2, measures) 791 assertEquals(1, childMeasures) 792 } 793 } 794 795 @Test 796 fun scenario17() { 797 var parentMeasures = 0 798 var measures = 0 799 var childMeasures = 0 800 var read by mutableStateOf(true) 801 rule.setContent { 802 Parent(onMeasure = { ++parentMeasures }, readDuringMeasure = true) { 803 ChangingParent(onMeasure = { ++measures }, readDuringMeasure = { read }) { 804 Parent(onMeasure = { ++childMeasures }) { Provider() } 805 } 806 } 807 } 808 rule.runOnIdle { 809 assertEquals(1, parentMeasures) 810 assertEquals(1, measures) 811 assertEquals(1, childMeasures) 812 changeLinePosition() 813 read = false 814 } 815 816 rule.runOnIdle { 817 assertEquals(2, parentMeasures) 818 assertEquals(2, measures) 819 assertEquals(1, childMeasures) 820 changeLinePosition() 821 } 822 823 rule.runOnIdle { 824 assertEquals(3, parentMeasures) 825 assertEquals(2, measures) 826 assertEquals(1, childMeasures) 827 } 828 } 829 830 @Test 831 fun scenario18() { 832 var parentLayouts = 0 833 var parentMeasures = 0 834 var measures = 0 835 var childMeasures = 0 836 var read by mutableStateOf(true) 837 rule.setContent { 838 Parent( 839 onMeasure = { ++parentMeasures }, 840 onLayout = { ++parentLayouts }, 841 readDuringLayoutAfterPlacing = true 842 ) { 843 ChangingParent(onMeasure = { ++measures }, readDuringMeasure = { read }) { 844 Parent(onMeasure = { ++childMeasures }) { Provider() } 845 } 846 } 847 } 848 rule.runOnIdle { 849 assertEquals(1, parentMeasures) 850 assertEquals(1, parentLayouts) 851 assertEquals(1, measures) 852 assertEquals(1, childMeasures) 853 changeLinePosition() 854 read = false 855 } 856 857 rule.runOnIdle { 858 assertEquals(1, parentMeasures) 859 assertEquals(2, parentLayouts) 860 assertEquals(2, measures) 861 assertEquals(1, childMeasures) 862 changeLinePosition() 863 } 864 865 rule.runOnIdle { 866 assertEquals(1, parentMeasures) 867 assertEquals(3, parentLayouts) 868 assertEquals(2, measures) 869 assertEquals(1, childMeasures) 870 } 871 } 872 873 @Test 874 fun scenario19() { 875 var offset by mutableStateOf(IntOffset.Zero) 876 rule.setContent { 877 Parent(readDuringLayoutBeforePlacing = true) { 878 Provider(modifier = Modifier.offset { offset }) 879 } 880 } 881 rule.runOnIdle { 882 offset = IntOffset(10, 10) 883 linePosition += 10 884 } 885 886 rule.waitForIdle() 887 } 888 889 @Test 890 fun scenario20() { 891 var parentLayouts = 0 892 var offset by mutableStateOf(IntOffset.Zero) 893 rule.setContent { 894 Parent(readDuringLayoutBeforePlacing = true, onLayout = { ++parentLayouts }) { 895 Parent { Provider(modifier = Modifier.offset { offset }) } 896 } 897 } 898 rule.runOnIdle { 899 offset = IntOffset(10, 10) 900 linePosition += 10 901 } 902 903 rule.runOnIdle { assertEquals(2, parentLayouts) } 904 905 rule.waitForIdle() 906 } 907 908 @Test 909 fun scenario21() { 910 var parentMeasures = 0 911 var read by mutableStateOf(false) 912 rule.setContent { 913 ChangingParent(readDuringMeasure = { read }, onMeasure = { ++parentMeasures }) { 914 Parent { Provider() } 915 } 916 } 917 rule.runOnIdle { read = true } 918 919 rule.runOnIdle { assertEquals(2, parentMeasures) } 920 } 921 922 @Test 923 fun scenario22() { 924 var parentLayouts = 0 925 var read by mutableStateOf(false) 926 rule.setContent { 927 ChangingParent( 928 readDuringLayoutBeforePlacing = { read }, 929 onLayout = { ++parentLayouts } 930 ) { 931 Parent { Provider() } 932 } 933 } 934 rule.runOnIdle { read = true } 935 936 rule.runOnIdle { assertEquals(2, parentLayouts) } 937 } 938 939 @Test 940 fun scenario23() { 941 var obtainedPosition = 0 942 var changingState by mutableStateOf(false) 943 rule.setContent { 944 Layout(content = { Parent { Provider() } }) { measurables, constraints -> 945 val placeable = measurables.first().measure(constraints) 946 layout(constraints.maxWidth, constraints.maxHeight) { 947 if (changingState) require(true) 948 obtainedPosition = placeable[TestLine] 949 placeable.place(0, 0) 950 } 951 } 952 } 953 rule.runOnIdle { 954 assertEquals(linePosition, obtainedPosition) 955 changeLinePosition() 956 changingState = true 957 } 958 959 rule.runOnIdle { assertEquals(linePosition, obtainedPosition) } 960 } 961 962 @Test 963 fun scenario24() { 964 var obtainedPosition = 0 965 var changingState by mutableStateOf(false) 966 rule.setContent { 967 Layout(content = { Parent { Provider() } }) { measurables, constraints -> 968 val placeable = measurables.first().measure(constraints) 969 layout(constraints.maxWidth, constraints.maxHeight) { 970 if (changingState) require(true) 971 placeable.place(0, 0) 972 obtainedPosition = placeable[TestLine] 973 } 974 } 975 } 976 rule.runOnIdle { 977 assertEquals(linePosition, obtainedPosition) 978 changeLinePosition() 979 changingState = true 980 } 981 982 rule.runOnIdle { assertEquals(linePosition, obtainedPosition) } 983 } 984 985 @Test 986 fun scenario25() { 987 var obtainedPosition = 0 988 rule.setContent { 989 Parent(modifier = Modifier.onGloballyPositioned { obtainedPosition = it[TestLine] }) { 990 Parent { Provider() } 991 } 992 } 993 rule.runOnIdle { 994 assertEquals(linePosition, obtainedPosition) 995 changeLinePosition() 996 } 997 998 rule.runOnIdle { assertEquals(linePosition, obtainedPosition) } 999 } 1000 1001 @Test 1002 fun scenario26() { 1003 var measures = 0 1004 var layouts = 0 1005 rule.setContent { 1006 Parent( 1007 modifier = 1008 Modifier.reader( 1009 readDuringMeasure = true, 1010 readDuringLayoutBeforePlacing = true, 1011 onMeasure = { ++measures }, 1012 onLayout = { ++layouts } 1013 ) 1014 ) { 1015 Parent { Provider() } 1016 } 1017 } 1018 rule.runOnIdle { 1019 assertEquals(1, measures) 1020 assertEquals(1, layouts) 1021 changeLinePosition() 1022 } 1023 1024 rule.runOnIdle { 1025 assertEquals(2, measures) 1026 assertEquals(2, layouts) 1027 } 1028 } 1029 1030 @Test 1031 fun scenario27() { 1032 var measures = 0 1033 var layouts = 0 1034 rule.setContent { 1035 Parent( 1036 modifier = 1037 Modifier.reader( 1038 readDuringLayoutBeforePlacing = true, 1039 onMeasure = { ++measures }, 1040 onLayout = { ++layouts } 1041 ) 1042 ) { 1043 Parent { Provider() } 1044 } 1045 } 1046 rule.runOnIdle { 1047 assertEquals(1, measures) 1048 assertEquals(1, layouts) 1049 changeLinePosition() 1050 } 1051 1052 rule.runOnIdle { 1053 assertEquals(1, measures) 1054 assertEquals(2, layouts) 1055 } 1056 } 1057 1058 @Test 1059 fun scenario28() { 1060 var measures = 0 1061 var layouts = 0 1062 rule.setContent { 1063 Parent( 1064 modifier = 1065 Modifier.reader( 1066 readDuringLayoutAfterPlacing = true, 1067 onMeasure = { ++measures }, 1068 onLayout = { ++layouts } 1069 ) 1070 ) { 1071 Parent { Provider() } 1072 } 1073 } 1074 rule.runOnIdle { 1075 assertEquals(1, measures) 1076 assertEquals(1, layouts) 1077 changeLinePosition() 1078 } 1079 1080 rule.runOnIdle { 1081 assertEquals(1, measures) 1082 assertEquals(2, layouts) 1083 } 1084 } 1085 1086 @Test 1087 fun notMeasuredChildIsNotCrashingWhenGrandParentQueriesAlignments() { 1088 var emit by mutableStateOf(false) 1089 1090 rule.setContent { 1091 Layout( 1092 content = { 1093 Layout( 1094 content = { 1095 if (emit) { 1096 Box(Modifier.size(10.dp)) 1097 } 1098 } 1099 ) { _, constraints -> 1100 layout(constraints.maxWidth, constraints.maxHeight) {} 1101 } 1102 } 1103 ) { measurables, constraints -> 1104 val placeable = measurables.first().measure(constraints) 1105 placeable[FirstBaseline] 1106 layout(placeable.width, placeable.height) { placeable.place(0, 0) } 1107 } 1108 } 1109 1110 rule.runOnIdle { emit = true } 1111 1112 rule.runOnIdle {} 1113 } 1114 1115 private var linePosition = 10 1116 private var linePositionState by mutableStateOf(10) 1117 1118 private fun changeLinePosition() { 1119 linePosition += 10 1120 linePositionState += 10 1121 } 1122 1123 private val TestLine = HorizontalAlignmentLine(::min) 1124 1125 @Composable 1126 private fun Parent( 1127 modifier: Modifier = Modifier, 1128 onMeasure: () -> Unit = {}, 1129 onLayout: () -> Unit = {}, 1130 readDuringMeasure: Boolean = false, 1131 readDuringLayoutBeforePlacing: Boolean = false, 1132 readDuringLayoutAfterPlacing: Boolean = false, 1133 content: @Composable () -> Unit = {} 1134 ) { 1135 ChangingParent( 1136 modifier, 1137 onMeasure, 1138 onLayout, 1139 { readDuringMeasure }, 1140 { readDuringLayoutBeforePlacing }, 1141 { readDuringLayoutAfterPlacing }, 1142 content 1143 ) 1144 } 1145 1146 @Composable 1147 private fun ChangingParent( 1148 modifier: Modifier = Modifier, 1149 onMeasure: () -> Unit = {}, 1150 onLayout: () -> Unit = {}, 1151 readDuringMeasure: () -> Boolean = { false }, 1152 readDuringLayoutBeforePlacing: () -> Boolean = { false }, 1153 readDuringLayoutAfterPlacing: () -> Boolean = { false }, 1154 content: @Composable () -> Unit = {} 1155 ) { 1156 Layout(content, modifier) { measurables, constraints -> 1157 onMeasure() 1158 val placeables = 1159 measurables.map { 1160 it.measure(constraints).also { 1161 if (readDuringMeasure()) assertEquals(linePosition, it[TestLine]) 1162 } 1163 } 1164 layout(constraints.maxWidth, constraints.maxHeight) { 1165 onLayout() 1166 placeables.forEach { placeable -> 1167 if (readDuringLayoutBeforePlacing()) { 1168 // placeable[TestLine] 1169 assertEquals(linePosition, placeable[TestLine]) 1170 } 1171 placeable.place(0, 0) 1172 if (readDuringLayoutAfterPlacing()) { 1173 // placeable[TestLine] 1174 assertEquals(linePosition, placeable[TestLine]) 1175 } 1176 } 1177 } 1178 } 1179 } 1180 1181 @Composable 1182 private fun Provider( 1183 modifier: Modifier = Modifier, 1184 onMeasure: () -> Unit = {}, 1185 onLayout: () -> Unit = {}, 1186 content: @Composable () -> Unit = {} 1187 ) { 1188 Layout(content, modifier) { _, constraints -> 1189 onMeasure() 1190 layout( 1191 constraints.maxWidth, 1192 constraints.maxHeight, 1193 mapOf(TestLine to linePositionState) 1194 ) { 1195 onLayout() 1196 } 1197 } 1198 } 1199 1200 private fun Modifier.reader( 1201 onMeasure: () -> Unit = {}, 1202 onLayout: () -> Unit = {}, 1203 readDuringMeasure: Boolean = false, 1204 readDuringLayoutBeforePlacing: Boolean = false, 1205 readDuringLayoutAfterPlacing: Boolean = false 1206 ) = 1207 this.then( 1208 ReaderModifier( 1209 onMeasure, 1210 onLayout, 1211 readDuringMeasure, 1212 readDuringLayoutBeforePlacing, 1213 readDuringLayoutAfterPlacing 1214 ) 1215 ) 1216 1217 private inner class ReaderModifier( 1218 val onMeasure: () -> Unit, 1219 val onLayout: () -> Unit, 1220 val readDuringMeasure: Boolean, 1221 val readDuringLayoutBeforePlacing: Boolean, 1222 val readDuringLayoutAfterPlacing: Boolean 1223 ) : LayoutModifier { 1224 override fun MeasureScope.measure( 1225 measurable: Measurable, 1226 constraints: Constraints 1227 ): MeasureResult { 1228 onMeasure() 1229 val placeable = measurable.measure(constraints) 1230 if (readDuringMeasure) assertEquals(linePosition, placeable[TestLine]) 1231 return layout(constraints.maxWidth, constraints.maxHeight) { 1232 onLayout() 1233 if (readDuringLayoutBeforePlacing) assertEquals(linePosition, placeable[TestLine]) 1234 placeable.place(0, 0) 1235 if (readDuringLayoutAfterPlacing) assertEquals(linePosition, placeable[TestLine]) 1236 } 1237 } 1238 } 1239 1240 private fun Modifier.provider() = this.then(ProviderModifier()) 1241 1242 private inner class ProviderModifier : LayoutModifier { 1243 override fun MeasureScope.measure( 1244 measurable: Measurable, 1245 constraints: Constraints 1246 ): MeasureResult { 1247 val placeable = measurable.measure(constraints) 1248 return layout( 1249 constraints.maxWidth, 1250 constraints.maxHeight, 1251 mapOf(TestLine to linePositionState) 1252 ) { 1253 placeable.place(0, 0) 1254 } 1255 } 1256 1257 override fun hashCode(): Int { 1258 return 0 1259 } 1260 1261 override fun equals(other: Any?): Boolean { 1262 return other is ProviderModifier 1263 } 1264 } 1265 1266 private fun Modifier.supplyAlignmentLines(alignmentLines: () -> Map<AlignmentLine, Int>) = 1267 layout { measurable, constraints -> 1268 val placeable = measurable.measure(constraints) 1269 layout(placeable.width, placeable.height, alignmentLines()) { placeable.place(0, 0) } 1270 } 1271 } 1272