Skip to content

Commit 561b254

Browse files
committed
Update Animatable related stuff
1 parent 17a5f25 commit 561b254

File tree

10 files changed

+187
-49
lines changed

10 files changed

+187
-49
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// AnimatableArray.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
package struct AnimatableArray<Element>: VectorArithmetic where Element: VectorArithmetic {
9+
package var elements: [Element]
10+
11+
package init(_ elements: [Element]) {
12+
self.elements = elements
13+
}
14+
15+
package static var zero: AnimatableArray<Element> { .init([]) }
16+
17+
package static func += (lhs: inout AnimatableArray<Element>, rhs: AnimatableArray<Element>) {
18+
let count = Swift.min(lhs.elements.count, rhs.elements.count)
19+
for i in 0..<count {
20+
lhs.elements[i] += rhs.elements[i]
21+
}
22+
}
23+
24+
package static func -= (lhs: inout AnimatableArray<Element>, rhs: AnimatableArray<Element>) {
25+
let count = Swift.min(lhs.elements.count, rhs.elements.count)
26+
for i in 0..<count {
27+
lhs.elements[i] -= rhs.elements[i]
28+
}
29+
}
30+
31+
@_transparent
32+
package static func + (lhs: AnimatableArray<Element>, rhs: AnimatableArray<Element>) -> AnimatableArray<Element> {
33+
var result = lhs
34+
result += rhs
35+
return result
36+
}
37+
38+
@_transparent
39+
package static func - (lhs: AnimatableArray<Element>, rhs: AnimatableArray<Element>) -> AnimatableArray<Element> {
40+
var result = lhs
41+
result -= rhs
42+
return result
43+
}
44+
45+
package mutating func scale(by rhs: Double) {
46+
for i in elements.indices {
47+
elements[i].scale(by: rhs)
48+
}
49+
}
50+
51+
package var magnitudeSquared: Double {
52+
elements.reduce(0) { partialResult, element in
53+
partialResult + element.magnitudeSquared
54+
}
55+
}
56+
}
57+
58+
extension Array where Element: Animatable {
59+
package var animatableData: AnimatableArray<Element.AnimatableData> {
60+
get { AnimatableArray(map(\.animatableData)) }
61+
set {
62+
let count = Swift.min(count, newValue.elements.count)
63+
for i in 0..<count {
64+
self[i].animatableData = newValue.elements[i]
65+
}
66+
}
67+
}
68+
}

Sources/OpenSwiftUICore/Animation/AnimatablePair.swift

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
11
//
22
// AnimatablePair.swift
3-
// OpenSwiftUI
3+
// OpenSwiftUICore
44
//
5-
// Audited for iOS 15.5
5+
// Audited for iOS 18.0
66
// Status: Complete
77

88
/// A pair of animatable values, which is itself animatable.
99
@frozen
1010
public struct AnimatablePair<First, Second>: VectorArithmetic where First: VectorArithmetic, Second: VectorArithmetic {
11+
/// The first value.
1112
public var first: First
13+
14+
/// The second value.
1215
public var second: Second
13-
16+
17+
/// Creates an animated pair with the provided values.
1418
@inlinable
1519
public init(_ first: First, _ second: Second) {
1620
self.first = first
1721
self.second = second
1822
}
1923

2024
@inlinable
21-
subscript() -> (First, Second) {
25+
package subscript() -> (First, Second) {
2226
get { (first, second) }
2327
set { (first, second) = newValue }
2428
}
2529

2630
@_transparent
2731
public static var zero: AnimatablePair<First, Second> {
28-
@_transparent
29-
get { .init(First.zero, Second.zero) }
32+
.init(First.zero, Second.zero)
3033
}
3134

3235
@_transparent
@@ -57,13 +60,11 @@ public struct AnimatablePair<First, Second>: VectorArithmetic where First: Vecto
5760
second.scale(by: rhs)
5861
}
5962

63+
/// The dot-product of this animated pair with itself.
6064
@_transparent
6165
public var magnitudeSquared: Double {
62-
@_transparent
63-
get { first.magnitudeSquared + second.magnitudeSquared }
64-
}
65-
66-
public static func == (a: AnimatablePair<First, Second>, b: AnimatablePair<First, Second>) -> Bool {
67-
a.first == b.first && a.second == b.second
66+
first.magnitudeSquared + second.magnitudeSquared
6867
}
6968
}
69+
70+
extension AnimatablePair: Sendable where First: Sendable, Second: Sendable {}

Sources/OpenSwiftUICore/Animation/EmptyAnimatableData.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
//
22
// EmptyAnimatableData.swift
3-
// OpenSwiftUI
3+
// OpenSwiftUICore
44
//
5-
// Audited for iOS 15.5
5+
// Audited for iOS 18.0
66
// Status: Complete
77

8+
/// An empty type for animatable data.
9+
///
10+
/// This type is suitable for use as the `animatableData` property of
11+
/// types that do not have any animatable properties.
812
@frozen
913
public struct EmptyAnimatableData: VectorArithmetic {
1014
@inlinable
@@ -33,3 +37,13 @@ public struct EmptyAnimatableData: VectorArithmetic {
3337

3438
public static func == (_: EmptyAnimatableData, _: EmptyAnimatableData) -> Bool { true }
3539
}
40+
41+
public import Foundation
42+
43+
extension Double: Animatable {
44+
public typealias AnimatableData = Swift.Double
45+
}
46+
47+
extension CGFloat: Animatable {
48+
public typealias AnimatableData = CGFloat
49+
}
File renamed without changes.
Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,51 @@
11
//
22
// VectorArithmetic.swift
3-
// OpenSwiftUI
3+
// OpenSwiftUICore
44
//
5-
// Audited for RELEASE_2023
5+
// Audited for iOS 18.0
66
// Status: Complete
77

88
public import Foundation
99

10+
/// A type that can serve as the animatable data of an animatable type.
11+
///
12+
/// `VectorArithmetic` extends the `AdditiveArithmetic` protocol with scalar
13+
/// multiplication and a way to query the vector magnitude of the value. Use
14+
/// this type as the `animatableData` associated type of a type that conforms to
15+
/// the ``Animatable`` protocol.
1016
public protocol VectorArithmetic: AdditiveArithmetic {
17+
/// Multiplies each component of this value by the given value.
1118
mutating func scale(by rhs: Double)
19+
20+
/// Returns the dot-product of this vector arithmetic instance with itself.
1221
var magnitudeSquared: Double { get }
1322
}
1423

1524
extension VectorArithmetic {
25+
/// Returns a value with each component of this value multiplied by the
26+
/// given value.
1627
@_alwaysEmitIntoClient
1728
public func scaled(by rhs: Double) -> Self {
1829
var result = self
1930
result.scale(by: rhs)
2031
return result
2132
}
2233

34+
/// Interpolates this value with `other` by the specified `amount`.
35+
///
36+
/// This is equivalent to `self = self + (other - self) * amount`.
2337
@_alwaysEmitIntoClient
2438
public mutating func interpolate(towards other: Self, amount: Double) {
25-
// lhs + (rhs - lhs) * t
2639
var result = other
2740
result -= self
2841
result.scale(by: amount)
2942
result += self
3043
self = result
3144
}
3245

46+
/// Returns this value interpolated with `other` by the specified `amount`.
47+
///
48+
/// This result is equivalent to `self + (other - self) * amount`.
3349
@_alwaysEmitIntoClient
3450
public func interpolated(towards other: Self, amount: Double) -> Self {
3551
var result = self
@@ -41,30 +57,38 @@ extension VectorArithmetic {
4157
extension Float: VectorArithmetic {
4258
@_transparent
4359
public mutating func scale(by rhs: Double) { self *= Float(rhs) }
60+
4461
@_transparent
45-
public var magnitudeSquared: Double {
46-
@_transparent
47-
get { Double(self * self) }
48-
}
62+
public var magnitudeSquared: Double { Double(self * self) }
4963
}
5064

5165
extension Double: VectorArithmetic {
5266
@_transparent
5367
public mutating func scale(by rhs: Double) { self *= rhs }
68+
5469
@_transparent
55-
public var magnitudeSquared: Double {
56-
@_transparent
57-
get { self * self }
58-
}
70+
public var magnitudeSquared: Double { self * self }
5971
}
6072

6173
extension CGFloat: VectorArithmetic {
6274
@_transparent
6375
public mutating func scale(by rhs: Double) { self *= CGFloat(rhs) }
6476

6577
@_transparent
66-
public var magnitudeSquared: Double {
67-
@_transparent
68-
get { Double(self * self) }
69-
}
78+
public var magnitudeSquared: Double { Double(self * self) }
79+
}
80+
81+
package func mix<T>(_ lhs: T, _ rhs: T, by t: Double) -> T where T: VectorArithmetic {
82+
var result = rhs
83+
result -= lhs
84+
result.scale(by: t)
85+
result += lhs
86+
return result
87+
}
88+
89+
extension VectorArithmetic {
90+
package static var unitScale: Double { 128.0 }
91+
package static var inverseUnitScale: Swift.Double { 1 / unitScale }
92+
package mutating func applyUnitScale() { scale(by: Self.unitScale) }
93+
package mutating func unapplyUnitScale() { scale(by: Self.inverseUnitScale) }
7094
}

Sources/OpenSwiftUICore/Animation/_VectorMath.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
//
22
// _VectorMath.swift
3-
// OpenSwiftUI
3+
// OpenSwiftUICore
44
//
5-
// Audited for iOS 15.5
5+
// Audited for iOS 18.0
66
// Status: Complete
77

8+
/// Adds the "vector space" numeric operations for any type that
9+
/// conforms to Animatable.
810
public protocol _VectorMath: Animatable {}
911

1012
extension _VectorMath {
@@ -73,3 +75,18 @@ extension _VectorMath {
7375
return result
7476
}
7577
}
78+
79+
extension _VectorMath {
80+
package mutating func normalize() {
81+
let magnitudeSquared = animatableData.magnitudeSquared
82+
if magnitudeSquared != 0 {
83+
self *= (1.0 / magnitudeSquared)
84+
}
85+
}
86+
87+
package func normalized() -> Self {
88+
var result = self
89+
result.normalize()
90+
return result
91+
}
92+
}

Sources/OpenSwiftUICore/Graphic/Color/ColorResolved.swift

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,21 +150,28 @@ extension Color.Resolved : Animatable {
150150
// ResolvedGradient.Color.Space.convertIn(self)
151151
preconditionFailure("TODO")
152152
} else {
153-
let factor: Float = 128.0
154-
return AnimatablePair(linearRed * factor, AnimatablePair(linearGreen * factor, AnimatablePair(linearBlue * factor, opacity * factor)))
153+
return AnimatablePair(
154+
linearRed.scaled(by: .unitScale),
155+
AnimatablePair(
156+
linearGreen.scaled(by: .unitScale),
157+
AnimatablePair(
158+
linearBlue.scaled(by: .unitScale),
159+
opacity.scaled(by: .unitScale)
160+
)
161+
)
162+
)
155163
}
156164
}
157165

158166
set {
159-
let factor: Float = 0.0078125
160167
if Self.legacyInterpolation {
161168
// ResolvedGradient.Color.Space.convertOut(self)
162169
preconditionFailure("TODO")
163170
} else {
164-
linearRed = newValue.first * factor
165-
linearGreen = newValue.second.first * factor
166-
linearBlue = newValue.second.second.first * factor
167-
opacity = newValue.second.second.second * factor
171+
linearRed = newValue.first.scaled(by: .inverseUnitScale)
172+
linearGreen = newValue.second.first.scaled(by: .inverseUnitScale)
173+
linearBlue = newValue.second.second.first.scaled(by: .inverseUnitScale)
174+
opacity = newValue.second.second.second.scaled(by: .inverseUnitScale)
168175
}
169176
}
170177
}
@@ -173,13 +180,20 @@ extension Color.Resolved : Animatable {
173180
extension Color.ResolvedVibrant: Animatable {
174181
package var animatableData: AnimatablePair<Float, AnimatablePair<Float, AnimatablePair<Float, Float>>> {
175182
get {
176-
let factor: Float = 128.0
177-
return AnimatablePair(scale * factor, AnimatablePair(bias.0 * factor, AnimatablePair(bias.1 * factor, bias.2 * factor)))
183+
AnimatablePair(
184+
scale.scaled(by: .unitScale),
185+
AnimatablePair(
186+
bias.0.scaled(by: .unitScale),
187+
AnimatablePair(
188+
bias.1.scaled(by: .unitScale),
189+
bias.2.scaled(by: .unitScale)
190+
)
191+
)
192+
)
178193
}
179194
set {
180-
let factor: Float = 0.0078125
181-
scale = newValue.first * factor
182-
bias = (newValue.second.first * factor, newValue.second.second.first * factor, newValue.second.second.second * factor)
195+
scale = newValue.first.scaled(by: .inverseUnitScale)
196+
bias = (newValue.second.first.scaled(by: .inverseUnitScale), newValue.second.second.first.scaled(by: .inverseUnitScale), newValue.second.second.second.scaled(by: .inverseUnitScale))
183197
}
184198
}
185199
}

Sources/OpenSwiftUICore/Layout/Geometry/Angle.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ extension Angle: Animatable, _VectorMath {
7373
public typealias AnimatableData = Double
7474

7575
public var animatableData: AnimatableData {
76-
get { radians * 128.0 }
77-
set { radians = newValue / 128.0 }
76+
get { radians.scaled(by: .unitScale) }
77+
set { radians = newValue.scaled(by: .inverseUnitScale) }
7878
}
79-
79+
8080
@inlinable
8181
public static var zero: Angle { .init() }
8282
}

Sources/OpenSwiftUICore/Layout/Geometry/UnitPoint.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ extension UnitPoint {
220220

221221
extension UnitPoint: Animatable {
222222
public var animatableData: AnimatablePair<CGFloat, CGFloat> {
223-
get { AnimatablePair(x * 128.0, y * 128.0) }
224-
set { x = newValue.first / 128.0 ; y = newValue.second / 128.0 }
223+
get { AnimatablePair(x.scaled(by: .unitScale), x.scaled(by: .unitScale)) }
224+
set { x = newValue.first.scaled(by: .inverseUnitScale) ; y = newValue.second.scaled(by: .inverseUnitScale) }
225225
}
226226
}
227227

Tests/OpenSwiftUICompatibilityTests/View/Graphic/AngleTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct AngleTests {
99
let a1 = Angle(radians: radians)
1010
#expect(a1.radians == radians)
1111
#expect(a1.degrees == degrees)
12-
#expect(a1.animatableData == radians * 128)
12+
#expect(a1.animatableData == radians * .unitScale)
1313
let a2 = Angle(degrees: degrees)
1414
#expect(a2.radians == radians)
1515
#expect(a2.degrees == degrees)

0 commit comments

Comments
 (0)