diff --git a/Example/OpenSwiftUIUITests/Render/GeometryEffect/IgnoredByLayoutEffectUITests.swift b/Example/OpenSwiftUIUITests/Render/GeometryEffect/IgnoredByLayoutEffectUITests.swift
new file mode 100644
index 000000000..0fc9e38d0
--- /dev/null
+++ b/Example/OpenSwiftUIUITests/Render/GeometryEffect/IgnoredByLayoutEffectUITests.swift
@@ -0,0 +1,40 @@
+//
+// IgnoredByLayoutEffectUITests.swift
+// OpenSwiftUIUITests
+
+import Testing
+import SnapshotTesting
+
+@MainActor
+@Suite(.snapshots(record: .never, diffTool: diffTool))
+struct IgnoredByLayoutEffectUITests {
+ @Test(.disabled("Animation is not implmemented yet"))
+ func offsetIgnoredByLayout() {
+ struct ContentView: View {
+ var body: some View {
+ WobbleColorView()
+ }
+ }
+
+ struct WobbleEffect: GeometryEffect {
+ var amount: CGFloat = 10
+ var shakesPerUnit = 3
+ var animatableData: CGFloat
+
+ nonisolated func effectValue(size: CGSize) -> ProjectionTransform {
+ let translation = amount * sin(animatableData * .pi * CGFloat(shakesPerUnit))
+ return ProjectionTransform(CGAffineTransform(translationX: translation, y: 0))
+ }
+ }
+
+ struct WobbleColorView: View {
+ @State private var wobble = false
+
+ var body: some View {
+ Color.red.frame(width: 200, height: 200)
+ .modifier(_OffsetEffect(offset: CGSize(width: 0, height: 100)).ignoredByLayout())
+ }
+ }
+ openSwiftUIAssertSnapshot(of: ContentView())
+ }
+}
diff --git a/Example/OpenSwiftUIUITests/Render/GeometryEffect/OffsetEffectUITests.swift b/Example/OpenSwiftUIUITests/Render/GeometryEffect/OffsetEffectUITests.swift
new file mode 100644
index 000000000..e58527f07
--- /dev/null
+++ b/Example/OpenSwiftUIUITests/Render/GeometryEffect/OffsetEffectUITests.swift
@@ -0,0 +1,24 @@
+//
+// OffsetEffectUITests.swift
+// OpenSwiftUIUITests
+
+import Testing
+import SnapshotTesting
+
+@MainActor
+@Suite(.snapshots(record: .never, diffTool: diffTool))
+struct OffsetEffectUITests {
+ @Test
+ func offsetWithFrame() {
+ struct ContentView: View {
+ var body: some View {
+ Color.blue
+ .offset(x: 20, y: 15)
+ .frame(width: 80, height: 60)
+ .background(Color.red)
+ .overlay(Color.green.offset(x: 40, y: 30))
+ }
+ }
+ openSwiftUIAssertSnapshot(of: ContentView())
+ }
+}
diff --git a/Sources/OpenSwiftUI/Render/GeometryEffect/IgnoredByLayoutEffect.swift b/Sources/OpenSwiftUI/Render/GeometryEffect/IgnoredByLayoutEffect.swift
new file mode 100644
index 000000000..8a098ab5f
--- /dev/null
+++ b/Sources/OpenSwiftUI/Render/GeometryEffect/IgnoredByLayoutEffect.swift
@@ -0,0 +1,55 @@
+//
+// IgnoredByLayoutEffect.swift
+// OpenSwiftUI
+//
+// Audited for 6.5.4
+// Status: Complete
+
+public import Foundation
+
+/// A geometry effect type that prevents another geometry effect
+/// affecting coordinate space conversions during layout, i.e. the
+/// transform introduced by the other effect is only used when
+/// rendering, not when converting locations from one view to another.
+/// This is often used to disable layout changes during transitions.
+@available(OpenSwiftUI_v1_0, *)
+@frozen
+public struct _IgnoredByLayoutEffect: GeometryEffect where Base: GeometryEffect {
+ public var base: Base
+
+ public static var _affectsLayout: Bool { false }
+
+ @inlinable
+ public init(_ base: Base) {
+ self.base = base
+ }
+
+ public func effectValue(size: CGSize) -> ProjectionTransform {
+ base.effectValue(size: size)
+ }
+
+ public var animatableData: Base.AnimatableData {
+ get { base.animatableData }
+ set { base.animatableData = newValue }
+ }
+}
+
+@available(*, unavailable)
+extension _IgnoredByLayoutEffect: Sendable {}
+
+@available(OpenSwiftUI_v1_0, *)
+extension _IgnoredByLayoutEffect: Equatable where Base: Equatable {}
+
+@available(OpenSwiftUI_v1_0, *)
+extension GeometryEffect {
+ /// Returns an effect that produces the same geometry transform as this
+ /// effect, but only applies the transform while rendering its view.
+ ///
+ /// Use this method to disable layout changes during transitions. The view
+ /// ignores the transform returned by this method while the view is
+ /// performing its layout calculations.
+ @inlinable
+ public func ignoredByLayout() -> _IgnoredByLayoutEffect {
+ return _IgnoredByLayoutEffect(self)
+ }
+}
diff --git a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentAdditions.swift b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentAdditions.swift
index cf7059db8..383d07eb4 100644
--- a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentAdditions.swift
+++ b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentAdditions.swift
@@ -7,6 +7,7 @@
// ID: 1B17C64D9E901A0054B49B69A4A2439D (SwiftUICore)
public import Foundation
+package import OpenGraphShims
// MARK: - EnvironmentValues + Display [6.4.41]
@@ -43,6 +44,12 @@ extension CachedEnvironment.ID {
package static let pixelLength: CachedEnvironment.ID = .init()
}
+extension _ViewInputs {
+ package var pixelLength: Attribute {
+ mapEnvironment(id: .pixelLength) { $0.pixelLength }
+ }
+}
+
private struct DisplayGamutKey: EnvironmentKey {
static var defaultValue: DisplayGamut { .sRGB }
}
diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift
index bc2b63271..68589eb66 100644
--- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift
+++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift
@@ -6,7 +6,9 @@
// Status: WIP
// ID: F37E3733E490AA5E3BDC045E3D34D9F8 (SwiftUICore)
+package import CoreGraphicsShims
package import Foundation
+package import OpenGraphShims
// MARK: - _DisplayList_Identity
@@ -98,7 +100,11 @@ package struct DisplayList: Equatable {
// TODO
items.append(contentsOf: other.items)
// _openSwiftUIUnimplementedFailure()
- }
+ }
+
+ package var isEmpty: Bool {
+ items.isEmpty
+ }
}
@available(*, unavailable)
@@ -204,9 +210,7 @@ extension DisplayList {
}
package enum Transform {
- #if canImport(Darwin)
case affine(CGAffineTransform)
- #endif
case projection(ProjectionTransform)
// case rotation(_RotationEffect.Data)
// case rotation3D(_Rotation3DEffect.Data)
@@ -428,6 +432,30 @@ extension DisplayList {
}
}
+extension PreferencesInputs {
+ @inline(__always)
+ package var requiresDisplayList: Bool {
+ get {
+ contains(DisplayList.Key.self)
+ }
+ set {
+ if newValue {
+ add(DisplayList.Key.self)
+ } else {
+ remove(DisplayList.Key.self)
+ }
+ }
+ }
+}
+
+extension PreferencesOutputs {
+ @inline(__always)
+ package var displayList: Attribute? {
+ get { self[DisplayList.Key.self] }
+ set { self[DisplayList.Key.self] = newValue }
+ }
+}
+
// MARK: - DisplayList.Item + Extension
extension DisplayList.Item {
diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift
index d5ff001f5..b0918b7dd 100644
--- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift
+++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift
@@ -95,12 +95,24 @@ extension _DisplayList_StableIdentityMap: ProtobufMessage {
}
}
-// TODO: Blocked by _ViewInputs
-//extension _ViewInputs {
-// package mutating func configureStableIDs(root: _DisplayList_StableIdentityRoot) {
-// package func pushIdentity(_ identity: _DisplayList_Identity)
-// package func makeStableIdentity() -> _DisplayList_StableIdentity
-//}
+extension _ViewInputs {
+ package mutating func configureStableIDs(root: _DisplayList_StableIdentityRoot) {
+ _openSwiftUIUnimplementedFailure()
+ }
+
+ package func pushIdentity(_ identity: _DisplayList_Identity) {
+
+ guard base.needsStableDisplayListIDs else {
+ return
+ }
+ self[_DisplayList_StableIdentityScope.self].attribute!.value.pushIdentity(identity)
+ }
+
+ package func makeStableIdentity() -> _DisplayList_StableIdentity {
+ // Log.internalError("expected stable IDs to be supported")
+ _openSwiftUIUnimplementedFailure()
+ }
+}
extension _GraphInputs {
private func pushScope(id: ID) where ID: StronglyHashable {
@@ -110,7 +122,7 @@ extension _GraphInputs {
}
package mutating func pushStableID(_ id: ID) where ID: Hashable {
- guard options.contains(.needsStableDisplayListIDs) else {
+ guard needsStableDisplayListIDs else {
return
}
if let stronglyHashable = id as? StronglyHashable {
@@ -121,23 +133,21 @@ extension _GraphInputs {
}
package mutating func pushStableIndex(_ index: Int) {
- guard options.contains(.needsStableDisplayListIDs) else {
+ guard needsStableDisplayListIDs else {
return
}
pushScope(id: index)
}
package mutating func pushStableType(_ type: any Any.Type) {
- #if OPENSWIFTUI_SUPPORT_2024_API
- guard options.contains(.needsStableDisplayListIDs) else {
+ guard needsStableDisplayListIDs else {
return
}
pushScope(id: makeStableTypeData(type))
- #endif
}
package var stableIDScope: WeakAttribute? {
- guard !options.contains(.needsStableDisplayListIDs) else {
+ guard !needsStableDisplayListIDs else { // Question
return nil
}
let result = self[_DisplayList_StableIdentityScope.self]
@@ -145,11 +155,9 @@ extension _GraphInputs {
}
}
-#if OPENSWIFTUI_SUPPORT_2024_API
package func makeStableTypeData(_ type: any Any.Type) -> StrongHash {
unsafeBitCast(Metadata(type).signature, to: StrongHash.self)
}
-#endif
package func makeStableIDData(from id: ID) -> StrongHash? {
guard let encodable = id as? Encodable else {
diff --git a/Sources/OpenSwiftUICore/Render/GeometryEffect/GeometryEffect.swift b/Sources/OpenSwiftUICore/Render/GeometryEffect/GeometryEffect.swift
new file mode 100644
index 000000000..908ea34d0
--- /dev/null
+++ b/Sources/OpenSwiftUICore/Render/GeometryEffect/GeometryEffect.swift
@@ -0,0 +1,286 @@
+//
+// GeometryEffect.swift
+// OpenSwiftUICore
+//
+// Status: WIP
+// ID: 9ED0B9F1F6CE74691B78276C750FEDD3 (SwiftUICore)
+
+public import Foundation
+package import OpenGraphShims
+
+// MARK: - GeometryEffect [6.5.4] [WIP]
+
+/// An effect that changes the visual appearance of a view, largely without
+/// changing its ancestors or descendants.
+///
+/// The only change the effect makes to the view's ancestors and descendants is
+/// to change the coordinate transform to and from them.
+@available(OpenSwiftUI_v1_0, *)
+public protocol GeometryEffect: Animatable, ViewModifier where Body == Never {
+ /// Returns the current value of the effect.
+ func effectValue(size: CGSize) -> ProjectionTransform
+
+ /// If false the effect's transform is not applied to coordinate
+ /// space conversions crossing the view, only to the renderered
+ /// representation of the child view.
+ static var _affectsLayout: Bool { get }
+}
+
+@available(OpenSwiftUI_v1_0, *)
+extension GeometryEffect {
+ public static var _affectsLayout: Bool {
+ true
+ }
+}
+
+@available(OpenSwiftUI_v1_0, *)
+extension GeometryEffect {
+ nonisolated public static func _makeView(
+ modifier: _GraphValue,
+ inputs: _ViewInputs,
+ body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
+ ) -> _ViewOutputs {
+ makeGeometryEffect(modifier: modifier, inputs: inputs, body: body)
+ }
+
+ nonisolated package static func makeGeometryEffect(
+ modifier: _GraphValue,
+ inputs: _ViewInputs,
+ body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
+ ) -> _ViewOutputs {
+ if modifier is _GraphValue<_RotationEffect> {
+ _openSwiftUIUnimplementedFailure()
+ } else if modifier is _GraphValue<_Rotation3DEffect> {
+ _openSwiftUIUnimplementedFailure()
+ } else {
+ DefaultGeometryEffectProvider
+ ._makeGeometryEffect(
+ modifier: modifier,
+ inputs: inputs,
+ body: body
+ )
+ }
+ }
+
+ nonisolated public static func _makeViewList(
+ modifier: _GraphValue,
+ inputs: _ViewListInputs,
+ body: @escaping (_Graph, _ViewListInputs) -> _ViewListOutputs
+ ) -> _ViewListOutputs {
+ makeMultiViewList(modifier: modifier, inputs: inputs, body: body)
+ }
+
+ @available(OpenSwiftUI_v2_0, *)
+ nonisolated public static func _viewListCount(
+ inputs: _ViewListCountInputs,
+ body: (_ViewListCountInputs) -> Int?
+ ) -> Int? {
+ body(inputs)
+ }
+}
+
+// MARK: - GeometryEffectProvider [6.5.4]
+
+protocol GeometryEffectProvider {
+ associatedtype Effect: GeometryEffect
+
+ static func resolve(
+ effect: Effect,
+ origin: inout CGPoint,
+ size: CGSize,
+ layoutDirection: LayoutDirection
+ ) -> DisplayList.Effect
+}
+
+extension GeometryEffectProvider {
+ static func _makeGeometryEffect(
+ modifier: _GraphValue,
+ inputs: _ViewInputs,
+ body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
+ ) -> _ViewOutputs {
+ guard inputs.needsGeometry else {
+ return body(_Graph(), inputs)
+ }
+ let animatableEffect = Effect.makeAnimatable(value: modifier, inputs: inputs.base)
+ let transform = Attribute(
+ GeometryEffectTransform(
+ effect: animatableEffect,
+ size: inputs.animatedCGSize(),
+ position: inputs.animatedPosition(),
+ transform: inputs.transform,
+ layoutDirection: inputs.layoutDirection
+ )
+ )
+ let size = Attribute(
+ RoundedSize(
+ position: inputs.position,
+ size: inputs.size,
+ pixelLength: inputs.pixelLength
+ )
+ )
+ var newInputs = inputs
+ let zeroPoint = ViewGraph.current.$zeroPoint
+ newInputs.transform = transform
+ newInputs.position = zeroPoint
+ newInputs.containerPosition = zeroPoint
+ newInputs.size = size
+ var outputs = body(_Graph(), newInputs)
+ guard inputs.preferences.requiresDisplayList else {
+ return outputs
+ }
+ let identity = DisplayList.Identity()
+ inputs.pushIdentity(identity)
+ let displayList = Attribute(
+ GeometryEffectDisplayList(
+ identity: .init(),
+ effect: animatableEffect,
+ position: inputs.animatedPosition(),
+ size: inputs.animatedCGSize(), // Verify: Still get a new size here
+ layoutDirection: inputs.layoutDirection,
+ containerPosition: inputs.containerPosition,
+ content: .init(outputs.preferences.displayList),
+ options: .init()
+ )
+ )
+ outputs.preferences.displayList = displayList
+ return outputs
+ }
+}
+
+
+// MARK: - RoundedSize [6.5.4]
+
+package struct RoundedSize: Rule, AsyncAttribute {
+ @Attribute var position: CGPoint
+ @Attribute var size: ViewSize
+ @Attribute var pixelLength: CGFloat
+
+ package init(
+ position: Attribute,
+ size: Attribute,
+ pixelLength: Attribute
+ ) {
+ _position = position
+ _size = size
+ _pixelLength = pixelLength
+ }
+
+ package var value: ViewSize {
+ var size = size
+ var rect = CGRect(origin: position, size: size.value)
+ rect.roundCoordinatesToNearestOrUp(toMultipleOf: pixelLength)
+ size.value = rect.size
+ return size
+ }
+}
+
+// MARK: - DefaultGeometryEffectProvider [6.5.4]
+
+struct DefaultGeometryEffectProvider: GeometryEffectProvider where Effect: GeometryEffect {
+ static func resolve(
+ effect: Effect,
+ origin: inout CGPoint,
+ size: CGSize,
+ layoutDirection: LayoutDirection
+ ) -> DisplayList.Effect {
+ var effectValue = effect.effectValue(size: size)
+ if layoutDirection == .rightToLeft {
+ let t = ProjectionTransform(
+ m11: -1, m12: 0, m13: 0,
+ m21: 0, m22: 1, m23: 0,
+ m31: size.width, m32: 0, m33: 1
+ )
+ effectValue = t
+ .concatenating(effectValue)
+ .concatenating(t)
+ }
+ guard effectValue.isInvertible else {
+ Log.externalWarning("ignoring singular matrix: \(effectValue)")
+ return .identity
+ }
+ if effectValue.isAffine {
+ return .transform(.affine(.init(effectValue)))
+ } else {
+ return .transform(.projection(effectValue))
+ }
+ }
+}
+
+// MARK: - GeometryEffectDisplayList [6.5.4]
+
+private struct GeometryEffectDisplayList: Rule, AsyncAttribute, CustomStringConvertible
+ where Provider: GeometryEffectProvider {
+ let identity: DisplayList.Identity
+ @Attribute var effect: Provider.Effect
+ @Attribute var position: CGPoint
+ @Attribute var size: CGSize
+ @Attribute var layoutDirection: LayoutDirection
+ @Attribute var containerPosition: CGPoint
+ @OptionalAttribute var content: DisplayList?
+ let options: DisplayList.Options
+
+ var value: DisplayList {
+ let content = content ?? DisplayList()
+ guard !content.isEmpty else {
+ return content
+ }
+ var origin = CGPoint(position - containerPosition)
+ let displayListEffect = Provider.resolve(
+ effect: effect,
+ origin: &origin,
+ size: size,
+ layoutDirection: layoutDirection
+ )
+ var item = DisplayList.Item(
+ .effect(displayListEffect, content),
+ frame: CGRect(origin: origin, size: size),
+ identity: identity,
+ version: .init(forUpdate: ())
+ )
+ item.canonicalize(options: options)
+ return DisplayList(item)
+ }
+
+ var description: String {
+ "GeometryEffectDisplayList"
+ }
+}
+
+// MARK: - GeometryEffectTransform [6.5.4]
+
+private struct GeometryEffectTransform: Rule, AsyncAttribute where Effect: GeometryEffect {
+ @Attribute var effect: Effect
+ @Attribute var size: CGSize
+ @Attribute var position: CGPoint
+ @Attribute var transform: ViewTransform
+ @Attribute var layoutDirection: LayoutDirection
+
+ typealias Value = ViewTransform
+
+ var value: Value {
+ var transform = transform
+ transform.resetPosition(position)
+ if Effect._affectsLayout {
+ var effectValue = effect.effectValue(size: size)
+ if layoutDirection == .rightToLeft {
+ let t = ProjectionTransform(
+ m11: -1, m12: 0, m13: 0,
+ m21: 0, m22: 1, m23: 0,
+ m31: size.width, m32: 0, m33: 1
+ )
+ effectValue = t
+ .concatenating(effectValue)
+ .concatenating(t)
+ }
+ if effectValue.isInvertible {
+ transform.appendProjectionTransform(
+ effectValue,
+ inverse: true
+ )
+ } else {
+ Log.externalWarning("ignoring singular matrix: \(effectValue)")
+ }
+ }
+ return transform
+ }
+}
diff --git a/Sources/OpenSwiftUICore/Render/GeometryEffect/OffsetEffect.swift b/Sources/OpenSwiftUICore/Render/GeometryEffect/OffsetEffect.swift
new file mode 100644
index 000000000..5f03cf8dc
--- /dev/null
+++ b/Sources/OpenSwiftUICore/Render/GeometryEffect/OffsetEffect.swift
@@ -0,0 +1,129 @@
+//
+// OffsetEffect.swift
+// OpenSwiftUICore
+//
+// Status: Complete
+// ID: 72FB21917F353796516DFC9915156779 (SwiftUICore)
+
+import CoreGraphicsShims
+public import Foundation
+import OpenGraphShims
+
+/// Allows you to redefine origin of the child within its coordinate
+/// space
+@available(OpenSwiftUI_v1_0, *)
+@frozen
+public struct _OffsetEffect: GeometryEffect, Equatable {
+ public var offset: CGSize
+
+ @inlinable
+ nonisolated public init(offset: CGSize) {
+ self.offset = offset
+ }
+
+ public func effectValue(size: CGSize) -> ProjectionTransform {
+ ProjectionTransform(
+ CGAffineTransform(
+ translationX: offset.width,
+ y: offset.height
+ )
+ )
+ }
+
+ public var animatableData: CGSize.AnimatableData {
+ get { offset.animatableData }
+ set { offset.animatableData = newValue }
+ }
+
+ nonisolated public static func _makeView(
+ modifier: _GraphValue<_OffsetEffect>,
+ inputs: _ViewInputs,
+ body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
+ ) -> _ViewOutputs {
+ var inputs = inputs
+ inputs.position = Attribute(
+ OffsetPosition(
+ effect: modifier.value,
+ position: inputs.position,
+ layoutDirection: inputs.layoutDirection
+ )
+ )
+ return body(_Graph(), inputs)
+ }
+}
+
+@available(OpenSwiftUI_v1_0, *)
+extension View {
+ /// Offset this view by the horizontal and vertical amount specified in the
+ /// offset parameter.
+ ///
+ /// Use `offset(_:)` to shift the displayed contents by the amount
+ /// specified in the `offset` parameter.
+ ///
+ /// The original dimensions of the view aren't changed by offsetting the
+ /// contents; in the example below the gray border drawn by this view
+ /// surrounds the original position of the text:
+ ///
+ /// Text("Offset by passing CGSize()")
+ /// .border(Color.green)
+ /// .offset(CGSize(width: 20, height: 25))
+ /// .border(Color.gray)
+ ///
+ /// 
+ ///
+ /// - Parameter offset: The distance to offset this view.
+ ///
+ /// - Returns: A view that offsets this view by `offset`.
+ @inlinable
+ nonisolated public func offset(_ offset: CGSize) -> some View {
+ modifier(_OffsetEffect(offset: offset))
+ }
+
+ /// Offset this view by the specified horizontal and vertical distances.
+ ///
+ /// Use `offset(x:y:)` to shift the displayed contents by the amount
+ /// specified in the `x` and `y` parameters.
+ ///
+ /// The original dimensions of the view aren't changed by offsetting the
+ /// contents; in the example below the gray border drawn by this view
+ /// surrounds the original position of the text:
+ ///
+ /// Text("Offset by passing horizontal & vertical distance")
+ /// .border(Color.green)
+ /// .offset(x: 20, y: 50)
+ /// .border(Color.gray)
+ ///
+ /// 
+ ///
+ /// - Parameters:
+ /// - x: The horizontal distance to offset this view.
+ /// - y: The vertical distance to offset this view.
+ ///
+ /// - Returns: A view that offsets this view by `x` and `y`.
+ @inlinable
+ nonisolated public func offset(x: CGFloat = 0, y: CGFloat = 0) -> some View {
+ offset(CGSize(width: x, height: y))
+ }
+}
+
+private struct OffsetPosition: Rule, AsyncAttribute {
+ @Attribute var effect: _OffsetEffect
+ @Attribute var position: CGPoint
+ @Attribute var layoutDirection: LayoutDirection
+
+ var value: CGPoint {
+ position.resolved(in: layoutDirection) + effect.offset
+ }
+}
+
+extension CGPoint {
+ @inline(__always)
+ fileprivate func resolved(in layoutDirection: LayoutDirection) -> CGPoint {
+ switch layoutDirection {
+ case .leftToRight: CGPoint(x: x, y: y)
+ case .rightToLeft: CGPoint(x: -x, y: y)
+ }
+ }
+}
diff --git a/Sources/OpenSwiftUICore/Render/GeometryEffect/Rotation3DEffect.swift b/Sources/OpenSwiftUICore/Render/GeometryEffect/Rotation3DEffect.swift
new file mode 100644
index 000000000..115c83c27
--- /dev/null
+++ b/Sources/OpenSwiftUICore/Render/GeometryEffect/Rotation3DEffect.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+struct _Rotation3DEffect: GeometryEffect {
+ func effectValue(size: CGSize) -> ProjectionTransform {
+ .init()
+ }
+}
diff --git a/Sources/OpenSwiftUICore/Render/GeometryEffect/RotationEffect.swift b/Sources/OpenSwiftUICore/Render/GeometryEffect/RotationEffect.swift
new file mode 100644
index 000000000..43ed2c895
--- /dev/null
+++ b/Sources/OpenSwiftUICore/Render/GeometryEffect/RotationEffect.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+struct _RotationEffect: GeometryEffect {
+ func effectValue(size: CGSize) -> ProjectionTransform {
+ .init()
+ }
+}
diff --git a/Sources/OpenSwiftUICore/Render/RendererEffect/RendererEffect.swift b/Sources/OpenSwiftUICore/Render/RendererEffect/RendererEffect.swift
new file mode 100644
index 000000000..3ff606281
--- /dev/null
+++ b/Sources/OpenSwiftUICore/Render/RendererEffect/RendererEffect.swift
@@ -0,0 +1,93 @@
+//
+// RendererEffect.swift
+// OpenSwiftUICore
+//
+// Status: WIP
+// ID: 49800242E3DD04CB91F7CE115272DDC3 (SwiftUICore)
+
+package import Foundation
+
+// MARK: - _RendererEffect [6.5.4] [WIP]
+
+package protocol _RendererEffect: MultiViewModifier, PrimitiveViewModifier {
+ func effectValue(size: CGSize) -> DisplayList.Effect
+
+ static var isolatesChildPosition: Bool { get }
+
+ static var disabledForFlattenedContent: Bool { get }
+
+ static var preservesEmptyContent: Bool { get }
+
+ static var isScrapeable: Bool { get }
+
+ // var scrapeableContent: ScrapeableContent.Content? { get }
+}
+
+extension _RendererEffect {
+ package static var isolatesChildPosition: Bool {
+ false
+ }
+
+ package static var disabledForFlattenedContent: Bool {
+ false
+ }
+
+ package static var preservesEmptyContent: Bool {
+ false
+ }
+
+ package static var isScrapeable: Bool {
+ false
+ }
+
+// package var scrapeableContent: ScrapeableContent.Content? {
+// nil
+// }
+
+ package static func _makeRendererEffect(
+ effect: _GraphValue,
+ inputs: _ViewInputs,
+ body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
+ ) -> _ViewOutputs {
+ if isScrapeable {
+ // TOOD: Handle scrapeable content
+ }
+ _openSwiftUIUnimplementedFailure()
+ }
+}
+
+// MARK: - RendererEffect [6.5.4]
+
+package protocol RendererEffect: Animatable, _RendererEffect {}
+
+@available(OpenSwiftUI_v1_0, *)
+extension RendererEffect {
+ package static func makeRendererEffect(
+ effect: _GraphValue,
+ inputs: _ViewInputs,
+ body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
+ ) -> _ViewOutputs {
+ guard inputs.needsGeometry || inputs.preferences.requiresDisplayList else {
+ return body(_Graph(), inputs)
+ }
+ var effect = effect
+ _makeAnimatable(value: &effect, inputs: inputs.base)
+ return _makeRendererEffect(effect: effect, inputs: inputs, body: body)
+ }
+
+ nonisolated public static func _makeView(
+ modifier: _GraphValue,
+ inputs: _ViewInputs,
+ body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
+ ) -> _ViewOutputs {
+ makeRendererEffect(effect: modifier, inputs: inputs, body: body)
+ }
+
+ @available(OpenSwiftUI_v2_0, *)
+ nonisolated public static func _viewListCount(
+ inputs: _ViewListCountInputs,
+ body: (_ViewListCountInputs) -> Int?
+ ) -> Int? {
+ body(inputs)
+ }
+}
diff --git a/Sources/OpenSwiftUICore/Render/RendererLeafView.swift b/Sources/OpenSwiftUICore/Render/RendererLeafView.swift
index f6e408fe9..bcb2fe52d 100644
--- a/Sources/OpenSwiftUICore/Render/RendererLeafView.swift
+++ b/Sources/OpenSwiftUICore/Render/RendererLeafView.swift
@@ -29,7 +29,7 @@ extension RendererLeafView {
// TODO
var outputs = _ViewOutputs()
// FIXME
- outputs.preferences[DisplayList.Key.self] = Attribute(
+ outputs.preferences.displayList = Attribute(
LeafDisplayList(
identity: .init(),
view: view.value,
diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift
index b8b5b13fd..cf420fbfb 100644
--- a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift
+++ b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift
@@ -35,7 +35,7 @@ package final class ViewGraph: GraphHost {
fileprivate func addRequestedPreferences(to inputs: inout _ViewInputs) {
inputs.preferences.add(HostPreferencesKey.self)
if contains(.displayList) {
- inputs.preferences.add(DisplayList.Key.self)
+ inputs.preferences.requiresDisplayList = true
}
if contains(.viewResponders) {
inputs.preferences.requiresViewResponders = true
@@ -257,7 +257,7 @@ package final class ViewGraph: GraphHost {
rootGeometry.$childLayoutComputer = outputs.layoutComputer
}
if requestedOutputs.contains(.displayList) {
- if let displayList = outputs.preferences[DisplayList.Key.self] {
+ if let displayList = outputs.preferences.displayList {
_rootDisplayList = WeakAttribute(rootSubgraph.apply {
Attribute(RootDisplayList(content: displayList, time: data.$time))
})
diff --git a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift
index 801b07c71..5b78ae28a 100644
--- a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift
+++ b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift
@@ -237,7 +237,7 @@ extension _ViewInputs {
inputs.size = viewGraph.intern(ViewSize.zero, id: .defaultValue)
inputs.requestsLayoutComputer = false
inputs.needsGeometry = false
- inputs.preferences.remove(DisplayList.Key.self)
+ inputs.preferences.requiresDisplayList = false
inputs.preferences.requiresViewResponders = false
return inputs
}