@@ -531,12 +531,74 @@ private struct RootTransform: Rule {
531
531
532
532
// MARK: - RootGeometry
533
533
534
+ /// Calculates the geometry of the root view within the proposed size.
535
+ ///
536
+ /// `RootGeometry` is responsible for:
537
+ /// - Computing the size that fits the content based on layout preferences
538
+ /// - Determining the origin of the content, applying centering if needed
539
+ /// - Handling right-to-left layout direction adjustments
540
+ /// - Applying safe area insets to the available space
541
+ ///
542
+ /// The diagram below illustrates how the geometry is calculated:
543
+ ///
544
+ /// ```
545
+ /// |←--------------------------proposedSize.value.width--------------------------→| (0, 0)
546
+ /// ┌──────────────────────────────────────────────────────────────────────────────┐ ┌─────────> x
547
+ /// │ (x: insets.leading, y: insets.top) | │
548
+ /// │ ↓ | |
549
+ /// | |←--------------------------proposal.width--------------------------→| | |
550
+ /// │ ┌────────────┬───────────────────────────────────────────────────────┐ | |
551
+ /// │ |████████████| | │ ↓ y
552
+ /// │ |████████████| | │
553
+ /// │ ├────────────┘ │ |
554
+ /// | |←----------→| | |
555
+ /// │ |fittingSize.width | |
556
+ /// │ | | │
557
+ /// | | | |
558
+ /// | | | |
559
+ /// | | | |
560
+ /// │ | centersRootView == true: │ |
561
+ /// │ | origin = insets + (proposal - fittingSize) * 0.5 │ |
562
+ /// │ | | │
563
+ /// │ | centersRootView == false: | │
564
+ /// │ | origin = (insets.leading, insets.top) | │
565
+ /// │ | | │
566
+ /// │ | If layoutDirection == .rightToLeft: | │
567
+ /// │ | origin.x is flipped to keep visual order consistent | │
568
+ /// │ | | │
569
+ /// │ └────────────────────────────────────────────────────────────────────┘ |
570
+ /// | |
571
+ /// └──────────────────────────────────────────────────────────────────────────────┘
572
+ /// ```
534
573
package struct RootGeometry : Rule , AsyncAttribute {
535
- @OptionalAttribute package var layoutDirection : LayoutDirection ?
536
- @Attribute package var proposedSize : ViewSize
537
- @OptionalAttribute package var safeAreaInsets : _SafeAreaInsetsModifier ?
538
- @OptionalAttribute package var childLayoutComputer : LayoutComputer ?
539
-
574
+ /// The layout direction to apply to the root view.
575
+ ///
576
+ /// When set to `.rightToLeft`, the x-coordinate will be adjusted to maintain
577
+ /// proper visual alignment from the right edge.
578
+ @OptionalAttribute
579
+ package var layoutDirection : LayoutDirection ?
580
+
581
+ /// The proposed size for the entire view, including any insets.
582
+ @Attribute
583
+ package var proposedSize : ViewSize
584
+
585
+ /// Safe area insets to apply to the view.
586
+ ///
587
+ /// These insets define padding from the edges of the proposed size.
588
+ @OptionalAttribute
589
+ package var safeAreaInsets : _SafeAreaInsetsModifier ?
590
+
591
+ /// The layout computer used to determine the size of the child view.
592
+ @OptionalAttribute
593
+ package var childLayoutComputer : LayoutComputer ?
594
+
595
+ /// Creates a new root geometry with the specified attributes.
596
+ ///
597
+ /// - Parameters:
598
+ /// - layoutDirection: The layout direction to apply.
599
+ /// - proposedSize: The proposed size for the entire view.
600
+ /// - safeAreaInsets: Safe area insets to apply to the view.
601
+ /// - childLayoutComputer: The layout computer for determining the child view size.
540
602
package init (
541
603
layoutDirection: OptionalAttribute < LayoutDirection > = . init( ) ,
542
604
proposedSize: Attribute < ViewSize > ,
@@ -549,67 +611,43 @@ package struct RootGeometry: Rule, AsyncAttribute {
549
611
_childLayoutComputer = childLayoutComputer
550
612
}
551
613
552
-
553
- // |←--------------------------proposedSize.value.width--------------------------→| (0, 0)
554
- // ┌──────────────────────────────────────────────────────────────────────────────┐ ┌─────────> x
555
- // │ (x: insets.leading, y: insets.top) | │
556
- // │ ↓ | |
557
- // | |←--------------------------proposal.width--------------------------→| | |
558
- // │ ┌────────────┬───────────────────────────────────────────────────────┐ | |
559
- // │ |████████████| | │ ↓ y
560
- // │ |████████████| | │ eg.
561
- // │ ├────────────┘ │ | proposedSize = (width: 80, height: 30)
562
- // | |←----------→| | | insets = (
563
- // │ |fittingSize.width | | top: 4,
564
- // │ | | │ leading: 6,
565
- // | | (x: insets.leading + (proposal.width - fittingSize.width ) * 0.5, | | bottom: 2,
566
- // | | y: insets.top + (proposal.height - fittingSize.height) * 0.5) | | trailing: 4
567
- // | | ↓ | | )
568
- // │ | ┌────────────┐ │ | proposal = (width: 70, height: 24)
569
- // │ | |████████████| │ | fitting = (width: 14, height: 4)
570
- // │ | |████████████| | │
571
- // │ | └────────────┘ | │ Result:
572
- // │ | | │ center: false + left
573
- // │ | | │ x: insets.leading = 6
574
- // │ | | │ y: insets.top = 4
575
- // │ | | │
576
- // │ | | │ center: false + right
577
- // │ | | │ x: insets.leading = proposedSize.width-(i.l+f.w)
578
- // │ | | │ y: insets.top = 4
579
- // │ | | │
580
- // │ | | │ center: true + left
581
- // │ └────────────────────────────────────────────────────────────────────┘ | x: i.l+(p.width-f.width)*0.5=34
582
- // | | y: i.t+(p.height-f.height)*0.5=14
583
- // └──────────────────────────────────────────────────────────────────────────────┘
584
614
package var value : ViewGeometry {
585
- . init( origin: . zero, dimensions: . zero)
586
- // preconditionFailure("TODO")
587
- // let layoutComputer = childLayoutComputer ?? .defaultValue
588
- // let insets = safeAreaInsets?.insets ?? EdgeInsets()
589
- // let proposal = proposedSize.value.inset(by: insets)
590
- // let fittingSize = layoutComputer.delegate.sizeThatFits(_ProposedSize(size: proposal))
591
- //
592
- // var x = insets.leading
593
- // var y = insets.top
594
- // if ViewGraph.current.centersRootView {
595
- // x += (proposal.width - fittingSize.width) * 0.5
596
- // y += (proposal.height - fittingSize.height) * 0.5
597
- // }
598
- //
599
- // let layoutDirection = layoutDirection ?? .leftToRight
600
- // switch layoutDirection {
601
- // case .leftToRight:
602
- // break
603
- // case .rightToLeft:
604
- // x = proposedSize.value.width - CGRect(origin: CGPoint(x: x, y: y), size: fittingSize).maxX
605
- // }
606
- // return ViewGeometry(
607
- // origin: ViewOrigin(value: CGPoint(x: x, y: y)),
608
- // dimensions: ViewDimensions(
609
- // guideComputer: layoutComputer,
610
- // size: ViewSize(value: fittingSize, _proposal: proposal)
611
- // )
612
- // )
615
+ let layoutComputer = childLayoutComputer ?? . defaultValue
616
+ let insets : EdgeInsets
617
+ if let safeAreaInsets {
618
+ var safeAreaInsets = safeAreaInsets. elements. reduce ( . zero, { $0 + $1. insets } )
619
+ // Apply flipping if needed to convert left/right insets to leading/trailing
620
+ if let layoutDirection {
621
+ safeAreaInsets. xFlipIfRightToLeft { layoutDirection }
622
+ }
623
+ insets = safeAreaInsets
624
+ } else {
625
+ insets = . zero
626
+ }
627
+ let proposal = proposedSize. value. inset ( by: insets)
628
+ let fittingSize = layoutComputer. sizeThatFits ( . init( proposal) )
629
+ // Start with origin at the safe area offset
630
+ var origin = CGPoint ( x: insets. leading, y: insets. top)
631
+
632
+ // Apply centering if needed
633
+ if ViewGraph . current. centersRootView {
634
+ origin += ( proposal- fittingSize) * 0.5
635
+ }
636
+
637
+ // For RTL layout, adjust the x position to maintain proper visual alignment
638
+ if let layoutDirection, layoutDirection == . rightToLeft {
639
+ // This flips the x-coordinate to maintain proper visual layout in RTL mode
640
+ origin. x = proposedSize. width - CGRect( origin: origin, size: fittingSize) . maxX
641
+ }
642
+
643
+ return ViewGeometry (
644
+ origin: ViewOrigin ( origin) ,
645
+ dimensions: ViewDimensions (
646
+ guideComputer: layoutComputer,
647
+ size: fittingSize,
648
+ proposal: . init( proposal)
649
+ )
650
+ )
613
651
}
614
652
}
615
653
0 commit comments