diff --git a/CLAUDE.md b/CLAUDE.md index 308f73b44..cd04bfde3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -141,46 +141,6 @@ The Package.swift heavily uses environment variables for conditional compilation - Library evolution mode generates .swiftinterface files - Various private framework integrations can be toggled -## Git and GitHub Workflow - -**IMPORTANT: This project uses GitBulter for version control management** - -### Critical Git Rules -- **NEVER change branches** - The user manages branches through GitBulter -- **NEVER run git commit commands** - The user commits through GitBulter GUI -- **ALWAYS create pull requests** using `gh pr create` with the exact branch specified by the user -- **DO NOT switch branches** when creating pull requests - -### Commit Workflow -When asked to commit changes: -1. **Generate a commit message** and provide it to the user (formatted without extra spaces) -2. **Save the commit message** to `.claude/tmp/commit-message.md` for easy copying -3. **List the files to be committed** (specify if not all files) -4. **Let the user commit through GitBulter GUI** - do not run git commit commands -5. Wait for user confirmation before proceeding with any PR creation - -Example response when asked to commit: -``` -Here's the commit message for you to use in GitBulter: - -"Add feature X to improve Y - -- Implemented new functionality -- Updated tests -- Fixed related issues" - -Files to commit: -- src/feature.swift -- tests/feature_test.swift - -(Saved to .claude/tmp/commit-message.md) -``` - -### Pull Request Workflow -1. Only create PR after user confirms the commit is done -2. Use `gh pr create` from the specified branch -3. Do not change HEAD or switch branches during PR creation - ## Important Notes - This project uses private Apple APIs and frameworks - NOT for App Store distribution diff --git a/Example/SharedExample/Animation/Timeline/AnimatedColorTimelineView.swift b/Example/SharedExample/Animation/Timeline/AnimatedColorTimelineView.swift index 6625d0aee..a4e2793a6 100644 --- a/Example/SharedExample/Animation/Timeline/AnimatedColorTimelineView.swift +++ b/Example/SharedExample/Animation/Timeline/AnimatedColorTimelineView.swift @@ -26,14 +26,14 @@ struct AnimatedColorTimelineView: View { saturation: 0.8, brightness: 0.9 ) -// .ignoresSafeArea() - + .ignoresSafeArea() + VStack(spacing: 30) { // Text("Animated Colors") // .font(.largeTitle) // .fontWeight(.bold) // .foregroundColor(.white) - + // Pulsing circle that changes color // Circle() // .fill( @@ -47,7 +47,7 @@ struct AnimatedColorTimelineView: View { width: 100 + sin(time * 3) * 20, height: 100 + sin(time * 3) * 20 ) - + // Display current color values let currentHue = (sin(time * 0.5) + 1) / 2 let _ = print(currentHue) diff --git a/Sources/OpenSwiftUICore/Layout/LayoutComputer/UnaryLayoutComputer.swift b/Sources/OpenSwiftUICore/Layout/LayoutComputer/UnaryLayoutComputer.swift index 7f9048e60..84a8f31fb 100644 --- a/Sources/OpenSwiftUICore/Layout/LayoutComputer/UnaryLayoutComputer.swift +++ b/Sources/OpenSwiftUICore/Layout/LayoutComputer/UnaryLayoutComputer.swift @@ -2,26 +2,74 @@ // UnaryLayoutComputer.swift // OpenSwiftUICore // -// Status: Blocked by _PositionAwarePlacementContext +// Audited for 6.5.4 +// Status: Complete // ID: 1C3B77B617AD058A6802F719E38F5D79 (SwiftUICore?) import Foundation package import OpenAttributeGraphShims -// MARK: - UnaryLayout + _PositionAwarePlacementContext [6.4.41] [TODO] +// MARK: - UnaryLayout + _PositionAwarePlacementContext +/// Extension for UnaryLayout when using position-aware placement context. +/// This provides layout computation that includes position, transform, and safe area information. extension UnaryLayout where Self.PlacementContextType == _PositionAwarePlacementContext { package static func makeViewImpl( modifier: _GraphValue, inputs: _ViewInputs, body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs ) -> _ViewOutputs { - _openSwiftUIUnimplementedFailure() + guard inputs.requestsLayoutComputer || inputs.needsGeometry else { + return body(_Graph(), inputs) + } + let animatedModifier = makeAnimatable(value: modifier, inputs: inputs.base) + var newInputs = inputs + let geometry: Attribute! + if inputs.needsGeometry { + geometry = Attribute( + UnaryPositionAwareChildGeometry( + layout: animatedModifier, + layoutDirection: inputs.layoutDirection, + parentSize: inputs.size, + position: inputs.position, + transform: inputs.transform, + environment: inputs.environment, + childLayoutComputer: .init(), + safeAreaInsets: inputs.safeAreaInsets + ) + ) + newInputs.size = geometry.size() + newInputs.position = geometry.origin() + newInputs.requestsLayoutComputer = true + } else { + geometry = nil + } + var outputs = body(_Graph(), newInputs) + if inputs.needsGeometry { + geometry.mutateBody( + as: UnaryPositionAwareChildGeometry.self, + invalidating: true + ) { geometry in + geometry.$childLayoutComputer = outputs.layoutComputer + } + } + if inputs.requestsLayoutComputer { + outputs.layoutComputer = Attribute( + UnaryPositionAwareLayoutComputer( + layout: animatedModifier, + environment: inputs.environment, + childLayoutComputer: .init(outputs.layoutComputer) + ) + ) + } + return outputs } } -// MARK: - UnaryLayout + PlacementContext [6.4.41] +// MARK: - UnaryLayout + PlacementContext +/// Extension for UnaryLayout when using standard placement context. +/// This provides layout computation without position awareness (no safe area or transform information). extension UnaryLayout where PlacementContextType == PlacementContext { package static func makeViewImpl( modifier: _GraphValue, @@ -37,7 +85,7 @@ extension UnaryLayout where PlacementContextType == PlacementContext { environment: inputs.environment, childLayoutComputer: OptionalAttribute() )) - var inputs = inputs + var newInputs = inputs let geometry: Attribute! if inputs.needsGeometry { geometry = Attribute(UnaryChildGeometry( @@ -45,17 +93,17 @@ extension UnaryLayout where PlacementContextType == PlacementContext { layoutDirection: inputs.layoutDirection, parentLayoutComputer: computer )) - inputs.size = geometry.size() - inputs.position = Attribute(LayoutPositionQuery( + newInputs.size = geometry.size() + newInputs.position = Attribute(LayoutPositionQuery( parentPosition: inputs.position, localPosition: geometry.origin() )) - inputs.requestsLayoutComputer = true + newInputs.requestsLayoutComputer = true } else { geometry = nil } let requestsLayoutComputer = inputs.requestsLayoutComputer - var outputs = body(_Graph(), inputs) + var outputs = body(_Graph(), newInputs) if inputs.needsGeometry { computer.mutateBody(as: UnaryLayoutComputer.self, invalidating: true) { computer in computer.$childLayoutComputer = outputs.layoutComputer @@ -71,6 +119,109 @@ extension UnaryLayout where PlacementContextType == PlacementContext { } } +// MARK: - LayoutPositionQuery + +/// Computes the absolute position of a view by combining parent and local positions. +/// This is used to track the hierarchical positioning in the layout system. +package struct LayoutPositionQuery: Rule, AsyncAttribute { + @Attribute private var parentPosition: ViewOrigin + @Attribute private var localPosition: ViewOrigin + + package init( + parentPosition: Attribute, + localPosition: Attribute, + ) { + _parentPosition = parentPosition + _localPosition = localPosition + } + + package var value: ViewOrigin { + let localPosition = localPosition + let parentPosition = parentPosition + return ViewOrigin( + x: localPosition.x + parentPosition.x, + y: localPosition.y + parentPosition.y + ) + } +} + +// MARK: - UnaryPositionAwareLayoutComputer + +/// A layout computer for unary layouts that are aware of their position in the view hierarchy. +/// This computer handles layouts that need access to safe area insets and transformations. +private struct UnaryPositionAwareLayoutComputer: StatefulRule, AsyncAttribute, CustomStringConvertible + where L: UnaryLayout, L.PlacementContextType == _PositionAwarePlacementContext { + @Attribute var layout: L + @Attribute var environment: EnvironmentValues + @OptionalAttribute var childLayoutComputer: LayoutComputer? + + typealias Value = LayoutComputer + + mutating func updateValue() { + let context = AnyRuleContext(context) + let engine = UnaryPositionAwareLayoutEngine( + layout: layout, + layoutContext: SizeAndSpacingContext(context: context, environment: $environment), + child: LayoutProxy(context: context, layoutComputer: $childLayoutComputer) + ) + update(to: engine) + } + + var description: String { + "\(L.self) → LayoutComputer" + } +} + +// MARK: - UnaryPositionAwareChildGeometry + +/// Geometry calculator for child views in position-aware unary layouts. +/// This handles the geometric transformations and safe area calculations for child views. +private struct UnaryPositionAwareChildGeometry: Rule, AsyncAttribute, CustomStringConvertible + where L: UnaryLayout, L.PlacementContextType == _PositionAwarePlacementContext { + @Attribute var layout: L + @Attribute var layoutDirection: LayoutDirection + @Attribute var parentSize: ViewSize + @Attribute var position: ViewOrigin + @Attribute var transform: ViewTransform + @Attribute var environment: EnvironmentValues + @OptionalAttribute var childLayoutComputer: LayoutComputer? + @OptionalAttribute var safeAreaInsets: SafeAreaInsets? + + var value: ViewGeometry { + let context = AnyRuleContext(context) + let child = LayoutProxy( + context: context, + layoutComputer: $childLayoutComputer + ) + let placement = layout.placement( + of: child, + in: _PositionAwarePlacementContext( + context: context, + size: _parentSize, + environment: _environment, + transform: _transform, + position: _position, + safeAreaInsets: _safeAreaInsets + ) + ) + var geometry = child.finallyPlaced( + at: placement, + in: parentSize.value, + layoutDirection: layoutDirection + ) + geometry.origin += CGSize(position) + return geometry + } + + var description: String { + "\(L.self) → ViewGeometry" + } +} + +// MARK: - UnaryChildGeometry + +/// Computes the geometry for a child view in a unary layout without position awareness. +/// This handles the placement and sizing of child views in standard layouts. private struct UnaryChildGeometry: Rule, AsyncAttribute, CustomStringConvertible where L: UnaryLayout, L.PlacementContextType == PlacementContext { @Attribute var parentSize: ViewSize @@ -84,11 +235,11 @@ private struct UnaryChildGeometry: Rule, AsyncAttribute, CustomStringConverti let placement = computer.withMutableEngine(type: UnaryLayoutEngine.self) { engine in engine.childPlacement(at: parentSize) } - let childProxy = LayoutProxy( + let child = LayoutProxy( context: AnyRuleContext(context), layoutComputer: $childLayoutComputer ) - return childProxy.finallyPlaced( + return child.finallyPlaced( at: placement, in: parentSize.value, layoutDirection: layoutDirection @@ -100,6 +251,10 @@ private struct UnaryChildGeometry: Rule, AsyncAttribute, CustomStringConverti } } +// MARK: - UnaryLayoutComputer + +/// A layout computer for standard unary layouts without position awareness. +/// This manages the layout computation for layouts that don't need safe area or transform information. private struct UnaryLayoutComputer: StatefulRule, AsyncAttribute, CustomStringConvertible where L: UnaryLayout, L.PlacementContextType == PlacementContext { @Attribute var layout: L @@ -123,16 +278,55 @@ private struct UnaryLayoutComputer: StatefulRule, AsyncAttribute, CustomStrin } } +// MARK: - UnaryPositionAwareLayoutEngine + +/// Layout engine for position-aware unary layouts. +/// This engine manages the layout computation for layouts that need position and safe area information. +private struct UnaryPositionAwareLayoutEngine: LayoutEngine + where L: UnaryLayout, L.PlacementContextType == _PositionAwarePlacementContext { + let layout: L + let layoutContext: SizeAndSpacingContext + let child: LayoutProxy + var cache: ViewSizeCache = .init() + + init( + layout: L, + layoutContext: SizeAndSpacingContext, + child: LayoutProxy + ) { + self.layout = layout + self.layoutContext = layoutContext + self.child = child + } + + func layoutPriority() -> Double { + layout.layoutPriority(child: child) + } + + mutating func sizeThatFits(_ proposedSize: _ProposedSize) -> CGSize { + let layout = layout + let context = layoutContext + let child = child + return cache.get(proposedSize) { + layout.sizeThatFits( + in: proposedSize, + context: context, + child: child + ) + } + } +} + +// MARK: - UnaryLayoutEngine + +/// Layout engine for standard unary layouts without position awareness. +/// This engine manages the basic layout computation including sizing, placement, and alignment. private struct UnaryLayoutEngine: LayoutEngine where L: UnaryLayout, L.PlacementContextType == PlacementContext { let layout: L - let layoutContext: SizeAndSpacingContext - let child: LayoutProxy - var dimensionsCache: ViewSizeCache = .init() - var placementCache: Cache3 = .init() init(layout: L, layoutContext: SizeAndSpacingContext, child: LayoutProxy) { @@ -186,27 +380,3 @@ private struct UnaryLayoutEngine: LayoutEngine } } } - -// MARK: - LayoutPositionQuery [6.4.41] - -package struct LayoutPositionQuery: Rule, AsyncAttribute { - @Attribute private var parentPosition: ViewOrigin - @Attribute private var localPosition: ViewOrigin - - package init( - parentPosition: Attribute, - localPosition: Attribute, - ) { - _parentPosition = parentPosition - _localPosition = localPosition - } - - package var value: ViewOrigin { - let localPosition = localPosition - let parentPosition = parentPosition - return ViewOrigin( - x: localPosition.x + parentPosition.x, - y: localPosition.y + parentPosition.y - ) - } -} diff --git a/Sources/OpenSwiftUICore/Layout/SafeAreaIgnoringLayout.swift b/Sources/OpenSwiftUICore/Layout/SafeAreaIgnoringLayout.swift new file mode 100644 index 000000000..3a8e03827 --- /dev/null +++ b/Sources/OpenSwiftUICore/Layout/SafeAreaIgnoringLayout.swift @@ -0,0 +1,171 @@ +// +// SafeAreaIgnoringLayout.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +import OpenAttributeGraph +package import OpenCoreGraphicsShims + +@available(OpenSwiftUI_v1_0, *) +@frozen +public struct _SafeAreaIgnoringLayout: UnaryLayout { + public var edges: Edge.Set + + @inlinable + public init(edges: Edge.Set = .all) { + self.edges = edges + } + + package func placement( + of child: LayoutProxy, + in context: _PositionAwarePlacementContext + ) -> _Placement { + let insets = context.safeAreaInsets().in(edges) + let size = context.proposedSize.inset(by: insets) + return _Placement( + proposedSize: size, + anchoring: .topLeading, + at: .zero - insets.originOffset + ) + } + + package func sizeThatFits( + in proposedSize: _ProposedSize, + context: SizeAndSpacingContext, + child: LayoutProxy + ) -> CGSize { + child.size(in: proposedSize) + } + + package func ignoresAutomaticPadding(child: LayoutProxy) -> Bool { + true + } + + package typealias PlacementContextType = _PositionAwarePlacementContext +} + + +// MARK: - _SafeAreaRegionsIgnoringLayout + +@available(OpenSwiftUI_v2_0, *) +@frozen +public struct _SafeAreaRegionsIgnoringLayout: UnaryLayout { + public var regions: SafeAreaRegions + + public var edges: Edge.Set + + @inlinable + package init(regions: SafeAreaRegions, edges: Edge.Set) { + self.regions = regions + self.edges = edges + } + + package func placement( + of child: LayoutProxy, + in context: _PositionAwarePlacementContext + ) -> _Placement { + let insets = context.safeAreaInsets(matching: regions).in(edges) + let size = context.proposedSize.inset(by: -insets) + return _Placement( + proposedSize: size, + anchoring: .topLeading, + at: .zero - insets.originOffset + ) + } + + package func sizeThatFits( + in proposedSize: _ProposedSize, + context: SizeAndSpacingContext, + child: LayoutProxy + ) -> CGSize { + child.size(in: proposedSize) + } + + package func ignoresAutomaticPadding(child: LayoutProxy) -> Bool { + true + } + + package typealias PlacementContextType = _PositionAwarePlacementContext +} + +@available(OpenSwiftUI_v1_0, *) +extension View { + + /// Changes the view's proposed area to extend outside the screen's safe + /// areas. + /// + /// Use `edgesIgnoringSafeArea(_:)` to change the area proposed for this + /// view so that — were the proposal accepted — this view could extend + /// outside the safe area to the bounds of the screen for the specified + /// edges. + /// + /// For example, you can propose that a text view ignore the safe area's top + /// inset: + /// + /// VStack { + /// Text("This text is outside of the top safe area.") + /// .edgesIgnoringSafeArea([.top]) + /// .border(Color.purple) + /// Text("This text is inside VStack.") + /// .border(Color.yellow) + /// } + /// .border(Color.gray) + /// + /// ![A screenshot showing a view whose bounds exceed the safe area of the + /// screen.](OpenSwiftUI-View-edgesIgnoringSafeArea.png) + /// + /// Depending on the surrounding view hierarchy, OpenSwiftUI may not honor an + /// `edgesIgnoringSafeArea(_:)` request. This can happen, for example, if + /// the view is inside a container that respects the screen's safe area. In + /// that case you may need to apply `edgesIgnoringSafeArea(_:)` to the + /// container instead. + /// + /// - Parameter edges: The set of the edges in which to expand the size + /// requested for this view. + /// + /// - Returns: A view that may extend outside of the screen's safe area + /// on the edges specified by `edges`. + @available(*, deprecated, message: "Use ignoresSafeArea(_:edges:) instead.") + @inlinable + nonisolated public func edgesIgnoringSafeArea(_ edges: Edge.Set) -> some View { + return modifier(_SafeAreaIgnoringLayout(edges: edges)) + } +} + +@available(OpenSwiftUI_v2_0, *) +extension View { + + /// Expands the safe area of a view. + /// + /// By default, the OpenSwiftUI layout system sizes and positions views to + /// avoid certain safe areas. This ensures that system content like the + /// software keyboard or edges of the device don’t obstruct your + /// views. To extend your content into these regions, you can ignore + /// safe areas on specific edges by applying this modifier. + /// + /// For examples of how to use this modifier, + /// see . + /// + /// - Parameters: + /// - regions: The regions to expand the view's safe area into. The + /// modifier expands into all safe area region types by default. + /// - edges: The set of edges to expand. Any edges that you + /// don't include in this set remain unchanged. The set includes all + /// edges by default. + /// + /// - Returns: A view with an expanded safe area. + @inlinable + nonisolated public func ignoresSafeArea( + _ regions: SafeAreaRegions = .all, + edges: Edge.Set = .all + ) -> some View { + modifier( + _SafeAreaRegionsIgnoringLayout( + regions: regions, + edges: edges + ) + ) + } +} diff --git a/Sources/OpenSwiftUICore/Layout/SafeAreaInsets.swift b/Sources/OpenSwiftUICore/Layout/SafeAreaInsets.swift index bac3e7581..0aa4492a5 100644 --- a/Sources/OpenSwiftUICore/Layout/SafeAreaInsets.swift +++ b/Sources/OpenSwiftUICore/Layout/SafeAreaInsets.swift @@ -2,14 +2,14 @@ // SafeAreaInsets.swift // OpenSwiftUICore // -// Audited for iOS 18.0 -// Status: WIP +// Audited for 6.5.4 +// Status: Blocked by ViewTransform+CoordinateSpace // ID: C4DC82F2A500E9B6DEA3064A36584B42 (SwiftUICore) import Foundation package import OpenAttributeGraphShims -// MARK: - SafeAreaRegions [6.4.41] +// MARK: - SafeAreaRegions /// A set of symbolic safe area regions. @frozen @@ -70,16 +70,86 @@ package struct SafeAreaInsets: Equatable { } package func resolve(regions: SafeAreaRegions, in ctx: _PositionAwarePlacementContext) -> EdgeInsets { - // _openSwiftUIUnimplementedFailure() - .zero + let size = ctx.size + let rect = CGRect(origin: .zero, size: size) + var adjustedRect = rect + adjust(&adjustedRect, regions: regions, to: ctx) + var next = next + while case let .insets(nextInsets) = next { + nextInsets.adjust(&adjustedRect, regions: regions, to: ctx) + next = nextInsets.next + } + var insets = EdgeInsets.zero + insets.top = rect.minY - adjustedRect.minY + insets.leading = rect.minX - adjustedRect.minX + insets.bottom = adjustedRect.maxY - rect.maxY + insets.trailing = adjustedRect.maxX - rect.maxX + insets.xFlipIfRightToLeft { ctx.layoutDirection } + return insets } + // FIXME: This does not handle coordinate space conversions. private func adjust( _ rect: inout CGRect, regions: SafeAreaRegions, - to: _PositionAwarePlacementContext - ) { - // _openSwiftUIUnimplementedFailure() + to context: _PositionAwarePlacementContext + ) { + let (selectedInsets, _) = mergedInsets(regions: regions) + guard !selectedInsets.isEmpty else { return } + // TODO: ViewTransform coordinate space conversion + rect.origin.x -= selectedInsets.leading + rect.origin.y -= selectedInsets.top + rect.size.width += (selectedInsets.leading + selectedInsets.trailing) + rect.size.height += (selectedInsets.top + selectedInsets.bottom) + } + + private func mergedInsets(regions: SafeAreaRegions) -> (selected: EdgeInsets, total: EdgeInsets) { + guard !elements.isEmpty else { + return (.zero, .zero) + } + var selected: EdgeInsets = .zero + var total: EdgeInsets = .zero + + // Track which edges can still contribute to the selected insets. + // This prevents inner safe area modifiers from overriding outer ones. + // For example, if an outer modifier sets a top inset for a different region, + // an inner modifier matching our region shouldn't override that top edge. + var availableEdges: Edge.Set = .all + + // Iterate through elements in reverse order (from innermost to outermost modifier). + // This ensures that outer modifiers take precedence over inner ones for each edge. + for element in elements.reversed() { + let insets = element.insets + if element.regions.isDisjoint(with: regions) { + if insets.leading != 0 { + availableEdges.remove(.leading) + } + if insets.trailing != 0 { + availableEdges.remove(.trailing) + } + if insets.top != 0 { + availableEdges.remove(.top) + } + if insets.bottom != 0 { + availableEdges.remove(.bottom) + } + } else { + if availableEdges.contains(.top) { + selected.top += insets.top + } + if availableEdges.contains(.leading) { + selected.leading += insets.leading + } + if availableEdges.contains(.bottom) { + selected.bottom += insets.bottom + } + if availableEdges.contains(.trailing) { + selected.trailing += insets.trailing + } + } + total += insets + } + return (selected, total) } } diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift index 952c939fb..639852364 100644 --- a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift +++ b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift @@ -64,7 +64,7 @@ package final class ViewGraph: GraphHost { @Attribute var position: ViewOrigin @Attribute var dimensions: ViewSize - @OptionalAttribute var scrollableContainerSize: ViewSize? + @OptionalAttribute var containerSize: ViewSize? @Attribute var gestureTime: Time @Attribute var gestureEvents: [EventID : EventType] @@ -154,7 +154,7 @@ package final class ViewGraph: GraphHost { _transform = _rootTransform _zeroPoint = Attribute(value: ViewOrigin()) _proposedSize = Attribute(value: .zero) - _scrollableContainerSize = requestedOutputs.contains(.layout) ? OptionalAttribute(Attribute(value: .zero)) : OptionalAttribute() + _containerSize = requestedOutputs.contains(.layout) ? OptionalAttribute(Attribute(value: .zero)) : OptionalAttribute() _safeAreaInsets = Attribute(value: _SafeAreaInsetsModifier(elements: [.init(regions: .container, insets: .zero)])) _defaultLayoutComputer = Attribute(value: .defaultValue) _gestureTime = Attribute(value: .zero) @@ -222,7 +222,7 @@ package final class ViewGraph: GraphHost { ) if requestedOutputs.contains(.layout) { inputs.base.options.formUnion([.viewRequestsLayoutComputer, .viewNeedsGeometry]) - inputs.scrollableContainerSize = _scrollableContainerSize + inputs.containerSize = _containerSize } requestedOutputs.addRequestedPreferences(to: &inputs) if let preferenceBridge { @@ -352,11 +352,11 @@ extension ViewGraph { return hasChange } - package func setScrollableContainerSize(_ size: ViewSize) { - guard let $scrollableContainerSize else { + package func setContainerSize(_ size: ViewSize) { + guard let $containerSize else { return } - let hasChange = $scrollableContainerSize.setValue(size) + let hasChange = $containerSize.setValue(size) if hasChange { delegate?.graphDidChange() } diff --git a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift index a11623e7d..fe9ab4b32 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift @@ -82,7 +82,7 @@ public struct _ViewInputs { package var safeAreaInsets: OptionalAttribute - package var scrollableContainerSize: OptionalAttribute + package var containerSize: OptionalAttribute // MARK: - base.options @@ -153,7 +153,7 @@ public struct _ViewInputs { self.containerPosition = containerPosition self.size = size self.safeAreaInsets = OptionalAttribute() - self.scrollableContainerSize = OptionalAttribute() + self.containerSize = OptionalAttribute() } package static func invalidInputs(_ base: _GraphInputs) -> _ViewInputs { @@ -258,7 +258,7 @@ extension _ViewInputs { self.containerPosition = containerPosition self.size = size self.safeAreaInsets = .init() - self.scrollableContainerSize = .init() + self.containerSize = .init() } } diff --git a/Tests/OpenSwiftUICoreTests/Layout/SafeAreaInsetsTests.swift b/Tests/OpenSwiftUICoreTests/Layout/SafeAreaInsetsTests.swift new file mode 100644 index 000000000..bc6bfb8ff --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Layout/SafeAreaInsetsTests.swift @@ -0,0 +1,39 @@ +// +// SafeAreaInsetsTests.swift +// OpenSwiftUICoreTests + +import OpenAttributeGraphShims +@_spi(ForOpenSwiftUIOnly) +import OpenSwiftUICore +import Testing + +@MainActor +@Suite(.enabled(if: attributeGraphEnabled, "OpenAttributeGraph is not ready yet")) +struct SafeAreaInsetsTests { + @Test + func insets() { + let insets = SafeAreaInsets( + space: .init(), + elements: [.init( + regions: .all, + insets: .init(top: 1, leading: 2, bottom: 3, trailing: 4) + )] + ) + let graph = ViewGraph(rootViewType: EmptyView.self) + let resolved = graph.rootSubgraph.apply { + insets + .resolve( + regions: .all, + in: .init( + context: AnyRuleContext(attribute: graph.rootView), + size: .init(value: graph.size), + environment: .init(value: graph.environment), + transform: .init(value: graph.transform), + position: .init(value: graph.zeroPoint), + safeAreaInsets: .init() + ) + ) + } + #expect(resolved == .init(top: 1, leading: 2, bottom: 3, trailing: 4)) + } +} diff --git a/Tests/OpenSwiftUICoreTests/Layout/ZIndexTests.swift b/Tests/OpenSwiftUICoreTests/Layout/ZIndexTests.swift index c7ec5ef2b..2cda207d6 100644 --- a/Tests/OpenSwiftUICoreTests/Layout/ZIndexTests.swift +++ b/Tests/OpenSwiftUICoreTests/Layout/ZIndexTests.swift @@ -2,9 +2,9 @@ // ZIndexTests.swift // OpenSwiftUICoreTests +import OpenAttributeGraphShims import OpenSwiftUICore import Testing -import OpenAttributeGraphShims @MainActor struct ZIndexTests {