Skip to content

Commit 284bbe9

Browse files
authored
Update RootGeometry (#296)
1 parent c57e109 commit 284bbe9

File tree

1 file changed

+103
-65
lines changed

1 file changed

+103
-65
lines changed

Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift

Lines changed: 103 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -531,12 +531,74 @@ private struct RootTransform: Rule {
531531

532532
// MARK: - RootGeometry
533533

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+
/// ```
534573
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.
540602
package init(
541603
layoutDirection: OptionalAttribute<LayoutDirection> = .init(),
542604
proposedSize: Attribute<ViewSize>,
@@ -549,67 +611,43 @@ package struct RootGeometry: Rule, AsyncAttribute {
549611
_childLayoutComputer = childLayoutComputer
550612
}
551613

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-
// └──────────────────────────────────────────────────────────────────────────────┘
584614
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+
)
613651
}
614652
}
615653

0 commit comments

Comments
 (0)