Skip to content

Commit af3286a

Browse files
authored
Add RendererEffect support (#379)
* Update DisplayList.Key usage * Add RendererEffect * Add GeometryEffect implementation * Add GeometryEffectProvider._makeGeometryEffect * Add IgnoredByLayoutEffect implementation * Add OffsetEffect implementation * Add OffsetEffectUITests and IgnoredByLayoutEffectUITests * Fix import issue on Linux
1 parent 11dbb21 commit af3286a

File tree

14 files changed

+705
-21
lines changed

14 files changed

+705
-21
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// IgnoredByLayoutEffectUITests.swift
3+
// OpenSwiftUIUITests
4+
5+
import Testing
6+
import SnapshotTesting
7+
8+
@MainActor
9+
@Suite(.snapshots(record: .never, diffTool: diffTool))
10+
struct IgnoredByLayoutEffectUITests {
11+
@Test(.disabled("Animation is not implmemented yet"))
12+
func offsetIgnoredByLayout() {
13+
struct ContentView: View {
14+
var body: some View {
15+
WobbleColorView()
16+
}
17+
}
18+
19+
struct WobbleEffect: GeometryEffect {
20+
var amount: CGFloat = 10
21+
var shakesPerUnit = 3
22+
var animatableData: CGFloat
23+
24+
nonisolated func effectValue(size: CGSize) -> ProjectionTransform {
25+
let translation = amount * sin(animatableData * .pi * CGFloat(shakesPerUnit))
26+
return ProjectionTransform(CGAffineTransform(translationX: translation, y: 0))
27+
}
28+
}
29+
30+
struct WobbleColorView: View {
31+
@State private var wobble = false
32+
33+
var body: some View {
34+
Color.red.frame(width: 200, height: 200)
35+
.modifier(_OffsetEffect(offset: CGSize(width: 0, height: 100)).ignoredByLayout())
36+
}
37+
}
38+
openSwiftUIAssertSnapshot(of: ContentView())
39+
}
40+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// OffsetEffectUITests.swift
3+
// OpenSwiftUIUITests
4+
5+
import Testing
6+
import SnapshotTesting
7+
8+
@MainActor
9+
@Suite(.snapshots(record: .never, diffTool: diffTool))
10+
struct OffsetEffectUITests {
11+
@Test
12+
func offsetWithFrame() {
13+
struct ContentView: View {
14+
var body: some View {
15+
Color.blue
16+
.offset(x: 20, y: 15)
17+
.frame(width: 80, height: 60)
18+
.background(Color.red)
19+
.overlay(Color.green.offset(x: 40, y: 30))
20+
}
21+
}
22+
openSwiftUIAssertSnapshot(of: ContentView())
23+
}
24+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// IgnoredByLayoutEffect.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
public import Foundation
9+
10+
/// A geometry effect type that prevents another geometry effect
11+
/// affecting coordinate space conversions during layout, i.e. the
12+
/// transform introduced by the other effect is only used when
13+
/// rendering, not when converting locations from one view to another.
14+
/// This is often used to disable layout changes during transitions.
15+
@available(OpenSwiftUI_v1_0, *)
16+
@frozen
17+
public struct _IgnoredByLayoutEffect<Base>: GeometryEffect where Base: GeometryEffect {
18+
public var base: Base
19+
20+
public static var _affectsLayout: Bool { false }
21+
22+
@inlinable
23+
public init(_ base: Base) {
24+
self.base = base
25+
}
26+
27+
public func effectValue(size: CGSize) -> ProjectionTransform {
28+
base.effectValue(size: size)
29+
}
30+
31+
public var animatableData: Base.AnimatableData {
32+
get { base.animatableData }
33+
set { base.animatableData = newValue }
34+
}
35+
}
36+
37+
@available(*, unavailable)
38+
extension _IgnoredByLayoutEffect: Sendable {}
39+
40+
@available(OpenSwiftUI_v1_0, *)
41+
extension _IgnoredByLayoutEffect: Equatable where Base: Equatable {}
42+
43+
@available(OpenSwiftUI_v1_0, *)
44+
extension GeometryEffect {
45+
/// Returns an effect that produces the same geometry transform as this
46+
/// effect, but only applies the transform while rendering its view.
47+
///
48+
/// Use this method to disable layout changes during transitions. The view
49+
/// ignores the transform returned by this method while the view is
50+
/// performing its layout calculations.
51+
@inlinable
52+
public func ignoredByLayout() -> _IgnoredByLayoutEffect<Self> {
53+
return _IgnoredByLayoutEffect(self)
54+
}
55+
}

Sources/OpenSwiftUICore/Data/Environment/EnvironmentAdditions.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// ID: 1B17C64D9E901A0054B49B69A4A2439D (SwiftUICore)
88

99
public import Foundation
10+
package import OpenGraphShims
1011

1112
// MARK: - EnvironmentValues + Display [6.4.41]
1213

@@ -43,6 +44,12 @@ extension CachedEnvironment.ID {
4344
package static let pixelLength: CachedEnvironment.ID = .init()
4445
}
4546

47+
extension _ViewInputs {
48+
package var pixelLength: Attribute<CGFloat> {
49+
mapEnvironment(id: .pixelLength) { $0.pixelLength }
50+
}
51+
}
52+
4653
private struct DisplayGamutKey: EnvironmentKey {
4754
static var defaultValue: DisplayGamut { .sRGB }
4855
}

Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
// Status: WIP
77
// ID: F37E3733E490AA5E3BDC045E3D34D9F8 (SwiftUICore)
88

9+
package import CoreGraphicsShims
910
package import Foundation
11+
package import OpenGraphShims
1012

1113
// MARK: - _DisplayList_Identity
1214

@@ -98,7 +100,11 @@ package struct DisplayList: Equatable {
98100
// TODO
99101
items.append(contentsOf: other.items)
100102
// _openSwiftUIUnimplementedFailure()
101-
}
103+
}
104+
105+
package var isEmpty: Bool {
106+
items.isEmpty
107+
}
102108
}
103109

104110
@available(*, unavailable)
@@ -204,9 +210,7 @@ extension DisplayList {
204210
}
205211

206212
package enum Transform {
207-
#if canImport(Darwin)
208213
case affine(CGAffineTransform)
209-
#endif
210214
case projection(ProjectionTransform)
211215
// case rotation(_RotationEffect.Data)
212216
// case rotation3D(_Rotation3DEffect.Data)
@@ -428,6 +432,30 @@ extension DisplayList {
428432
}
429433
}
430434

435+
extension PreferencesInputs {
436+
@inline(__always)
437+
package var requiresDisplayList: Bool {
438+
get {
439+
contains(DisplayList.Key.self)
440+
}
441+
set {
442+
if newValue {
443+
add(DisplayList.Key.self)
444+
} else {
445+
remove(DisplayList.Key.self)
446+
}
447+
}
448+
}
449+
}
450+
451+
extension PreferencesOutputs {
452+
@inline(__always)
453+
package var displayList: Attribute<DisplayList>? {
454+
get { self[DisplayList.Key.self] }
455+
set { self[DisplayList.Key.self] = newValue }
456+
}
457+
}
458+
431459
// MARK: - DisplayList.Item + Extension
432460

433461
extension DisplayList.Item {

Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,24 @@ extension _DisplayList_StableIdentityMap: ProtobufMessage {
9595
}
9696
}
9797

98-
// TODO: Blocked by _ViewInputs
99-
//extension _ViewInputs {
100-
// package mutating func configureStableIDs(root: _DisplayList_StableIdentityRoot) {
101-
// package func pushIdentity(_ identity: _DisplayList_Identity)
102-
// package func makeStableIdentity() -> _DisplayList_StableIdentity
103-
//}
98+
extension _ViewInputs {
99+
package mutating func configureStableIDs(root: _DisplayList_StableIdentityRoot) {
100+
_openSwiftUIUnimplementedFailure()
101+
}
102+
103+
package func pushIdentity(_ identity: _DisplayList_Identity) {
104+
105+
guard base.needsStableDisplayListIDs else {
106+
return
107+
}
108+
self[_DisplayList_StableIdentityScope.self].attribute!.value.pushIdentity(identity)
109+
}
110+
111+
package func makeStableIdentity() -> _DisplayList_StableIdentity {
112+
// Log.internalError("expected stable IDs to be supported")
113+
_openSwiftUIUnimplementedFailure()
114+
}
115+
}
104116

105117
extension _GraphInputs {
106118
private func pushScope<ID>(id: ID) where ID: StronglyHashable {
@@ -110,7 +122,7 @@ extension _GraphInputs {
110122
}
111123

112124
package mutating func pushStableID<ID>(_ id: ID) where ID: Hashable {
113-
guard options.contains(.needsStableDisplayListIDs) else {
125+
guard needsStableDisplayListIDs else {
114126
return
115127
}
116128
if let stronglyHashable = id as? StronglyHashable {
@@ -121,35 +133,31 @@ extension _GraphInputs {
121133
}
122134

123135
package mutating func pushStableIndex(_ index: Int) {
124-
guard options.contains(.needsStableDisplayListIDs) else {
136+
guard needsStableDisplayListIDs else {
125137
return
126138
}
127139
pushScope(id: index)
128140
}
129141

130142
package mutating func pushStableType(_ type: any Any.Type) {
131-
#if OPENSWIFTUI_SUPPORT_2024_API
132-
guard options.contains(.needsStableDisplayListIDs) else {
143+
guard needsStableDisplayListIDs else {
133144
return
134145
}
135146
pushScope(id: makeStableTypeData(type))
136-
#endif
137147
}
138148

139149
package var stableIDScope: WeakAttribute<DisplayList.StableIdentityScope>? {
140-
guard !options.contains(.needsStableDisplayListIDs) else {
150+
guard !needsStableDisplayListIDs else { // Question
141151
return nil
142152
}
143153
let result = self[_DisplayList_StableIdentityScope.self]
144154
return result.attribute == nil ? nil : result
145155
}
146156
}
147157

148-
#if OPENSWIFTUI_SUPPORT_2024_API
149158
package func makeStableTypeData(_ type: any Any.Type) -> StrongHash {
150159
unsafeBitCast(Metadata(type).signature, to: StrongHash.self)
151160
}
152-
#endif
153161

154162
package func makeStableIDData<ID>(from id: ID) -> StrongHash? {
155163
guard let encodable = id as? Encodable else {

0 commit comments

Comments
 (0)