From ed00b6441d6e4966230a410b949785bfa8d118e5 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 31 Jan 2026 23:31:16 +0800 Subject: [PATCH 1/2] Add SceneModifier API support --- Sources/OpenSwiftUI/App/App/AppGraph.swift | 6 +- .../PrimitiveSceneModifier.swift | 8 - .../SceneModifier/SceneModifier.swift | 230 ++++++++++++++++++ .../SceneModifier/TODO/_SceneModifier.swift | 27 -- .../TODO/_SceneModifier_Content.swift | 5 - .../Modifier/CustomViewModifier.swift | 8 +- 6 files changed, 239 insertions(+), 45 deletions(-) delete mode 100644 Sources/OpenSwiftUI/Modifier/SceneModifier/PrimitiveSceneModifier.swift create mode 100644 Sources/OpenSwiftUI/Modifier/SceneModifier/SceneModifier.swift delete mode 100644 Sources/OpenSwiftUI/Modifier/SceneModifier/TODO/_SceneModifier.swift delete mode 100644 Sources/OpenSwiftUI/Modifier/SceneModifier/TODO/_SceneModifier_Content.swift diff --git a/Sources/OpenSwiftUI/App/App/AppGraph.swift b/Sources/OpenSwiftUI/App/App/AppGraph.swift index 8cb41549c..77e6ff1cf 100644 --- a/Sources/OpenSwiftUI/App/App/AppGraph.swift +++ b/Sources/OpenSwiftUI/App/App/AppGraph.swift @@ -145,8 +145,10 @@ protocol AppGraphObserver: AnyObject { // MARK: - AppBodyAccessor -private struct AppBodyAccessor: BodyAccessor { - typealias Body = Container.Body +private struct AppBodyAccessor: BodyAccessor where Application: App { + typealias Container = Application + + typealias Body = Application.Body func updateBody(of container: Container, changed: Bool) { guard changed else { diff --git a/Sources/OpenSwiftUI/Modifier/SceneModifier/PrimitiveSceneModifier.swift b/Sources/OpenSwiftUI/Modifier/SceneModifier/PrimitiveSceneModifier.swift deleted file mode 100644 index 5f5a0bd56..000000000 --- a/Sources/OpenSwiftUI/Modifier/SceneModifier/PrimitiveSceneModifier.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// PrimitiveSceneModifier.swift -// OpenSwiftUI -// -// Audited for 3.5.2 -// Status: Complete - -protocol PrimitiveSceneModifier: _SceneModifier where Body == Never {} diff --git a/Sources/OpenSwiftUI/Modifier/SceneModifier/SceneModifier.swift b/Sources/OpenSwiftUI/Modifier/SceneModifier/SceneModifier.swift new file mode 100644 index 000000000..c9a3a4366 --- /dev/null +++ b/Sources/OpenSwiftUI/Modifier/SceneModifier/SceneModifier.swift @@ -0,0 +1,230 @@ +// +// SceneModifier.swift +// OpenSwiftUI +// +// Audited for 6.5.4 +// Status: Complete +// ID: 88477CCB0AEDAD452D0818B33FCEDC5F (SwiftUI) + +import OpenAttributeGraphShims +import OpenSwiftUICore + +// MARK: - _SceneModifier + +@available(OpenSwiftUI_v2_0, *) +public protocol _SceneModifier { + associatedtype Body: Scene + + @SceneBuilder + func body(content: SceneContent) -> Body + + typealias SceneContent = _SceneModifier_Content + + static func _makeScene( + modifier: _GraphValue, + inputs: _SceneInputs, + body: @escaping (_Graph, _SceneInputs) -> _SceneOutputs + ) -> _SceneOutputs +} + +// MARK: - PrimitiveSceneModifier + +protocol PrimitiveSceneModifier: _SceneModifier where Body == Never {} + +extension _SceneModifier { + func sceneBodyError() -> Never { + preconditionFailure("body() should not be called on \(self).") + } +} + +@available(OpenSwiftUI_v2_0, *) +extension _SceneModifier where Body == Never { + public func body(content: SceneContent) -> Body { + sceneBodyError() + } +} + +// MARK: - _GraphInputsModifier + _SceneModifier + +@available(OpenSwiftUI_v2_0, *) +extension _SceneModifier where Self: _GraphInputsModifier, Body == Never { + public static func _makeScene( + modifier: _GraphValue, + inputs: _SceneInputs, + body: @escaping (_Graph, _SceneInputs) -> _SceneOutputs + ) -> _SceneOutputs { + var inputs = inputs + _makeInputs(modifier: modifier, inputs: &inputs.base) + return body(.init(), inputs) + } +} + +// MARK: - EmptyModifier + _SceneModifier + +@available(OpenSwiftUI_v2_0, *) +extension EmptyModifier: _SceneModifier { + @MainActor + @preconcurrency + public static func _makeScene( + modifier: _GraphValue, + inputs: _SceneInputs, + body: @escaping (_Graph, _SceneInputs) -> _SceneOutputs + ) -> _SceneOutputs { + body(.init(), inputs) + } +} + +// MARK: - Scene + modifier + +@available(OpenSwiftUI_v2_0, *) +extension Scene { + @inlinable + @MainActor + @preconcurrency + internal func modifier(_ modifier: T) -> ModifiedContent { + return .init(content: self, modifier: modifier) + } +} + +@_spi(Private) +@available(OpenSwiftUI_v4_0, *) +extension Scene { + @_spi(Private) + nonisolated public func sceneModifier(_ modifier: T) -> ModifiedContent { + self.modifier(modifier) + } +} + +// MARK: - ModifiedContent + Scene & _SceneModifier + +@available(OpenSwiftUI_v2_0, *) +extension ModifiedContent: Scene where Content: Scene, Modifier: _SceneModifier { + nonisolated public static func _makeScene( + scene: _GraphValue, + inputs: _SceneInputs + ) -> _SceneOutputs { + Modifier._makeScene( + modifier: scene[offset: { .of(&$0.modifier) }], + inputs: inputs + ) { + Content._makeScene( + scene: scene[offset: { .of(&$0.content) }], + inputs: $1 + ) + } + } + + @MainActor + @preconcurrency + public var body: Body { + sceneBodyError() + } +} + +@available(OpenSwiftUI_v2_0, *) +extension ModifiedContent: _SceneModifier where Content: _SceneModifier, Modifier: _SceneModifier { + public static func _makeScene( + modifier: _GraphValue, + inputs: _SceneInputs, + body: @escaping (_Graph, _SceneInputs) -> _SceneOutputs + ) -> _SceneOutputs { + Modifier._makeScene( + modifier: modifier[offset: { .of(&$0.modifier) }], + inputs: inputs + ) { + Content._makeScene( + modifier: modifier[offset: { .of(&$0.content) }], + inputs: $1, + body: body + ) + } + } +} + +// MARK: - _SceneModifier + concat + +@available(OpenSwiftUI_v2_0, *) +extension _SceneModifier { + @inlinable + internal func concat(_ modifier: T) -> ModifiedContent { + return .init(content: self, modifier: modifier) + } +} + +// MARK: - _SceneModifier_Content + +@available(OpenSwiftUI_v2_0, *) +@MainActor +@preconcurrency +public struct _SceneModifier_Content: PrimitiveScene where Modifier: _SceneModifier { + nonisolated public static func _makeScene( + scene: _GraphValue, + inputs: _SceneInputs + ) -> _SceneOutputs { + var inputs = inputs + guard let body = inputs.popLast(BodyInput.self) else { + return .init() + } + return body(.init(), inputs) + } + + fileprivate struct BodyInput: SceneInput { + typealias BodyInputElement = (_Graph, _SceneInputs) -> _SceneOutputs + + static var defaultValue: Stack { .init() } + } +} + +@available(*, unavailable) +extension _SceneModifier_Content: Sendable {} + +// MARK: - _SceneModifier default implementation + +@available(OpenSwiftUI_v2_0, *) +extension _SceneModifier { + public static func _makeScene( + modifier: _GraphValue, + inputs: _SceneInputs, + body: @escaping (_Graph, _SceneInputs) -> _SceneOutputs + ) -> _SceneOutputs { + let fields = DynamicPropertyCache.fields(of: Self.self) + var inputs = inputs + let (scene, buffer) = makeBody(modifier: modifier, inputs: &inputs.base, fields: fields) + inputs.append(body, to: _SceneModifier_Content.BodyInput.self) + let outputs = Body._makeScene(scene: scene, inputs: inputs) + if let buffer { + buffer.traceMountedProperties(to: modifier, fields: fields) + } + return outputs + } + + private static func makeBody( + modifier: _GraphValue, + inputs: inout _GraphInputs, + fields: DynamicPropertyCache.Fields + ) -> (_GraphValue, _DynamicPropertyBuffer?) { + precondition( + Metadata(Self.self).isValueType, + "view modifiers must be value types: \(Self.self)" + ) + let accessor = AppModifierBodyAccessor() + return accessor.makeBody(container: modifier, inputs: &inputs, fields: fields) + } +} + +// MARK: - AppModifierBodyAccessor + +private struct AppModifierBodyAccessor: BodyAccessor where Modifier: _SceneModifier { + typealias Container = Modifier + + typealias Body = Modifier.Body + + func updateBody(of container: Container, changed: Bool) { + guard changed else { + return + } + setBody { + container.body(content: .init()) + } + } +} diff --git a/Sources/OpenSwiftUI/Modifier/SceneModifier/TODO/_SceneModifier.swift b/Sources/OpenSwiftUI/Modifier/SceneModifier/TODO/_SceneModifier.swift deleted file mode 100644 index 9e599a666..000000000 --- a/Sources/OpenSwiftUI/Modifier/SceneModifier/TODO/_SceneModifier.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// _SceneModifier.swift -// OpenSwiftUI -// -// Audited for 3.5.2 -// Status: WIP - -public protocol _SceneModifier { - associatedtype Body: Scene - - @SceneBuilder - func body(content: SceneContent) -> Body - - typealias SceneContent = _SceneModifier_Content -// static func _makeScene(modifier: _GraphValue, inputs: _SceneInputs, body: @escaping (_Graph, _SceneInputs) -> _SceneOutputs) -> _SceneOutputs -} - -extension _SceneModifier { - // public static func _makeScene(modifier: SwiftUI._GraphValue, inputs: SwiftUI._SceneInputs, body: @escaping (SwiftUI._Graph, SwiftUI._SceneInputs) -> SwiftUI._SceneOutputs) -> SwiftUI._SceneOutputs -} - -extension _SceneModifier where Body == Never { - @inline(__always) - public func body(content _: SceneContent) -> Body { - preconditionFailure("body() should not be called on \(Self.self).") - } -} diff --git a/Sources/OpenSwiftUI/Modifier/SceneModifier/TODO/_SceneModifier_Content.swift b/Sources/OpenSwiftUI/Modifier/SceneModifier/TODO/_SceneModifier_Content.swift deleted file mode 100644 index a4319787c..000000000 --- a/Sources/OpenSwiftUI/Modifier/SceneModifier/TODO/_SceneModifier_Content.swift +++ /dev/null @@ -1,5 +0,0 @@ -public struct _SceneModifier_Content: PrimitiveScene { -// public static func _makeScene(scene: _GraphValue<_SceneModifier_Content>, inputs: _SceneInputs) -> _SceneOutputs {} - - public typealias Body = Never -} diff --git a/Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift b/Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift index 8b328d75c..95009ea40 100644 --- a/Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift +++ b/Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift @@ -329,15 +329,17 @@ private struct BodyCountInput: ViewInput { // MARK: - ModifierBodyAccessor -private struct ModifierBodyAccessor: BodyAccessor where Container: ViewModifier { - typealias Body = Container.Body +private struct ModifierBodyAccessor: BodyAccessor where Modifier: ViewModifier { + typealias Container = Modifier + + typealias Body = Modifier.Body func updateBody(of container: Container, changed: Bool) { guard changed else { return } setBody { - container.body(content: Container.Content()) + container.body(content: .init()) } } } From 0acd81a1beb825356689f68a0121e278934c36d2 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 31 Jan 2026 23:36:40 +0800 Subject: [PATCH 2/2] Add documentation for SceneModifier API --- .../Modifier/SceneModifier/SceneModifier.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/OpenSwiftUI/Modifier/SceneModifier/SceneModifier.swift b/Sources/OpenSwiftUI/Modifier/SceneModifier/SceneModifier.swift index c9a3a4366..bf983d10d 100644 --- a/Sources/OpenSwiftUI/Modifier/SceneModifier/SceneModifier.swift +++ b/Sources/OpenSwiftUI/Modifier/SceneModifier/SceneModifier.swift @@ -11,13 +11,21 @@ import OpenSwiftUICore // MARK: - _SceneModifier +/// A modifier that can be applied to a scene or other scene modifier, +/// producing a different version of the original value. @available(OpenSwiftUI_v2_0, *) public protocol _SceneModifier { + + /// The type of scene representing the body of `Self`. associatedtype Body: Scene + /// Returns the current body of `self`. `content` is a proxy for + /// the scene that will have the modifier represented by `Self` + /// applied to it. @SceneBuilder func body(content: SceneContent) -> Body + /// The content scene type passed to `body()`. typealias SceneContent = _SceneModifier_Content static func _makeScene( @@ -78,6 +86,8 @@ extension EmptyModifier: _SceneModifier { @available(OpenSwiftUI_v2_0, *) extension Scene { + /// Returns a new scene representing `self` with `modifier` applied + /// to it. @inlinable @MainActor @preconcurrency @@ -145,6 +155,8 @@ extension ModifiedContent: _SceneModifier where Content: _SceneModifier, Modifie @available(OpenSwiftUI_v2_0, *) extension _SceneModifier { + /// Returns a new modifier that is the result of concatenating + /// `self` with `modifier`. @inlinable internal func concat(_ modifier: T) -> ModifiedContent { return .init(content: self, modifier: modifier)