react-native-smooth-clip-view 0.2.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -349
- package/android/build.gradle +8 -4
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +13 -6
- package/android/src/main/cpp/SmoothClipBindings.cpp +605 -123
- package/android/src/main/cpp/SmoothClipRegistry.cpp +975 -209
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +192 -118
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +22 -4
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +81 -56
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +449 -240
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +150 -38
- package/cpp/SmoothClipAnimationCurve.h +317 -397
- package/cpp/SmoothClipRegistry.h +84 -20
- package/cpp/SmoothClipSharedGeometry.h +131 -22
- package/cpp/SmoothClipVelocityTracker.h +16 -12
- package/ios/SmoothClipGeometry.h +188 -21
- package/ios/SmoothClipRegistry.mm +869 -368
- package/ios/SmoothClipRegistrySnapshot.h +13 -0
- package/ios/SmoothClipTurboModule.cpp +513 -156
- package/ios/SmoothClipTurboModule.h +123 -56
- package/ios/SmoothClipView.mm +1074 -449
- package/ios/SmoothClipViewRegistry.h +3 -7
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +94 -11
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -46
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +32 -12
- package/lib/module/capabilities.js +4 -0
- package/lib/module/capabilities.js.map +1 -0
- package/lib/module/capabilities.native.js +18 -0
- package/lib/module/capabilities.native.js.map +1 -0
- package/lib/module/capabilityTypes.js +2 -0
- package/lib/module/capabilityTypes.js.map +1 -0
- package/lib/module/controllerInternals.js +28 -0
- package/lib/module/controllerInternals.js.map +1 -0
- package/lib/module/controllerLifecycle.js +9 -0
- package/lib/module/controllerLifecycle.js.map +1 -0
- package/lib/module/controllerTypes.js +4 -0
- package/lib/module/controllerTypes.js.map +1 -0
- package/lib/module/controllers.android.js +4 -0
- package/lib/module/controllers.android.js.map +1 -0
- package/lib/module/controllers.ios.js +4 -0
- package/lib/module/controllers.ios.js.map +1 -0
- package/lib/module/controllers.js +4 -0
- package/lib/module/controllers.js.map +1 -0
- package/lib/module/controllers.native.js +89 -0
- package/lib/module/controllers.native.js.map +1 -0
- package/lib/module/geometry.js +180 -30
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupTypes.js +4 -0
- package/lib/module/groupTypes.js.map +1 -0
- package/lib/module/groups.android.js +4 -0
- package/lib/module/groups.android.js.map +1 -0
- package/lib/module/groups.ios.js +4 -0
- package/lib/module/groups.ios.js.map +1 -0
- package/lib/module/groups.js +4 -0
- package/lib/module/groups.js.map +1 -0
- package/lib/module/groups.native.js +399 -0
- package/lib/module/groups.native.js.map +1 -0
- package/lib/module/ids.js +8 -0
- package/lib/module/ids.js.map +1 -0
- package/lib/module/index.js +4 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/nativeCompletion.js +37 -16
- package/lib/module/nativeCompletion.js.map +1 -1
- package/lib/module/presentationCodec.js +81 -0
- package/lib/module/presentationCodec.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +20 -4
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +2 -10
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +139 -4
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +16 -3
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.d.ts +3 -0
- package/lib/typescript/src/capabilities.d.ts.map +1 -0
- package/lib/typescript/src/capabilities.native.d.ts +3 -0
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
- package/lib/typescript/src/capabilityTypes.d.ts +4 -0
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
- package/lib/typescript/src/controllerInternals.d.ts +14 -0
- package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
- package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
- package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/controllerTypes.d.ts +62 -0
- package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
- package/lib/typescript/src/controllers.android.d.ts +2 -0
- package/lib/typescript/src/controllers.android.d.ts.map +1 -0
- package/lib/typescript/src/controllers.d.ts +2 -0
- package/lib/typescript/src/controllers.d.ts.map +1 -0
- package/lib/typescript/src/controllers.ios.d.ts +2 -0
- package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
- package/lib/typescript/src/controllers.native.d.ts +3 -0
- package/lib/typescript/src/controllers.native.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +49 -5
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupTypes.d.ts +31 -0
- package/lib/typescript/src/groupTypes.d.ts.map +1 -0
- package/lib/typescript/src/groups.android.d.ts +2 -0
- package/lib/typescript/src/groups.android.d.ts.map +1 -0
- package/lib/typescript/src/groups.d.ts +2 -0
- package/lib/typescript/src/groups.d.ts.map +1 -0
- package/lib/typescript/src/groups.ios.d.ts +2 -0
- package/lib/typescript/src/groups.ios.d.ts.map +1 -0
- package/lib/typescript/src/groups.native.d.ts +3 -0
- package/lib/typescript/src/groups.native.d.ts.map +1 -0
- package/lib/typescript/src/ids.d.ts +2 -0
- package/lib/typescript/src/ids.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +8 -5
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/nativeCompletion.d.ts +16 -6
- package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
- package/lib/typescript/src/presentationCodec.d.ts +10 -0
- package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
- package/package.json +17 -13
- package/src/NativeSmoothClipModule.ts +80 -56
- package/src/SmoothClipView.ios.tsx +142 -16
- package/src/SmoothClipView.tsx +6 -76
- package/src/SmoothClipViewNativeComponent.ts +32 -12
- package/src/capabilities.native.ts +19 -0
- package/src/capabilities.ts +2 -0
- package/src/capabilityTypes.ts +3 -0
- package/src/controllerInternals.ts +52 -0
- package/src/controllerLifecycle.ts +6 -0
- package/src/controllerTypes.ts +87 -0
- package/src/controllers.android.ts +1 -0
- package/src/controllers.ios.ts +1 -0
- package/src/controllers.native.ts +104 -0
- package/src/controllers.ts +1 -0
- package/src/geometry.ts +293 -45
- package/src/groupTypes.ts +53 -0
- package/src/groups.android.ts +1 -0
- package/src/groups.ios.ts +1 -0
- package/src/groups.native.ts +604 -0
- package/src/groups.ts +1 -0
- package/src/ids.ts +6 -0
- package/src/index.ts +32 -12
- package/src/nativeCompletion.ts +67 -32
- package/src/presentationCodec.ts +134 -0
- package/lib/module/SmoothClipView.web.js +0 -54
- package/lib/module/SmoothClipView.web.js.map +0 -1
- package/lib/module/driverState.js +0 -110
- package/lib/module/driverState.js.map +0 -1
- package/lib/module/driverTypes.js +0 -4
- package/lib/module/driverTypes.js.map +0 -1
- package/lib/module/drivers.android.js +0 -6
- package/lib/module/drivers.android.js.map +0 -1
- package/lib/module/drivers.ios.js +0 -6
- package/lib/module/drivers.ios.js.map +0 -1
- package/lib/module/drivers.js +0 -284
- package/lib/module/drivers.js.map +0 -1
- package/lib/module/drivers.native.js +0 -556
- package/lib/module/drivers.native.js.map +0 -1
- package/lib/module/index.web.js +0 -8
- package/lib/module/index.web.js.map +0 -1
- package/lib/module/reactRequests.js +0 -56
- package/lib/module/reactRequests.js.map +0 -1
- package/lib/module/webClipPath.js +0 -25
- package/lib/module/webClipPath.js.map +0 -1
- package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
- package/lib/typescript/src/driverState.d.ts +0 -31
- package/lib/typescript/src/driverState.d.ts.map +0 -1
- package/lib/typescript/src/driverTypes.d.ts +0 -107
- package/lib/typescript/src/driverTypes.d.ts.map +0 -1
- package/lib/typescript/src/drivers.android.d.ts +0 -3
- package/lib/typescript/src/drivers.android.d.ts.map +0 -1
- package/lib/typescript/src/drivers.d.ts +0 -5
- package/lib/typescript/src/drivers.d.ts.map +0 -1
- package/lib/typescript/src/drivers.ios.d.ts +0 -3
- package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/drivers.native.d.ts +0 -5
- package/lib/typescript/src/drivers.native.d.ts.map +0 -1
- package/lib/typescript/src/index.web.d.ts +0 -9
- package/lib/typescript/src/index.web.d.ts.map +0 -1
- package/lib/typescript/src/reactRequests.d.ts +0 -9
- package/lib/typescript/src/reactRequests.d.ts.map +0 -1
- package/lib/typescript/src/webClipPath.d.ts +0 -7
- package/lib/typescript/src/webClipPath.d.ts.map +0 -1
- package/src/SmoothClipView.web.tsx +0 -66
- package/src/driverState.ts +0 -158
- package/src/driverTypes.ts +0 -140
- package/src/drivers.android.ts +0 -8
- package/src/drivers.ios.ts +0 -8
- package/src/drivers.native.ts +0 -899
- package/src/drivers.ts +0 -427
- package/src/index.web.ts +0 -23
- package/src/reactRequests.ts +0 -76
- package/src/webClipPath.ts +0 -30
package/ios/SmoothClipView.mm
CHANGED
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
#import <react/renderer/components/SmoothClipViewSpec/Props.h>
|
|
17
17
|
#import <react/renderer/components/SmoothClipViewSpec/RCTComponentViewHelpers.h>
|
|
18
18
|
|
|
19
|
+
#include <algorithm>
|
|
19
20
|
#include <cfloat>
|
|
21
|
+
#include <cmath>
|
|
20
22
|
|
|
21
23
|
using namespace facebook::react;
|
|
22
24
|
|
|
@@ -51,26 +53,305 @@ using namespace facebook::react;
|
|
|
51
53
|
finished:(BOOL)finished;
|
|
52
54
|
@end
|
|
53
55
|
|
|
54
|
-
static
|
|
55
|
-
|
|
56
|
+
static SmoothClipCornerRadii SmoothClipPresentationRadii(
|
|
57
|
+
const smoothclip::Geometry &geometry) {
|
|
58
|
+
return {
|
|
59
|
+
(CGFloat)smoothclip::resolvedRadius(
|
|
60
|
+
geometry.topLeftRadius, geometry.radius),
|
|
61
|
+
(CGFloat)smoothclip::resolvedRadius(
|
|
62
|
+
geometry.topRightRadius, geometry.radius),
|
|
63
|
+
(CGFloat)smoothclip::resolvedRadius(
|
|
64
|
+
geometry.bottomRightRadius, geometry.radius),
|
|
65
|
+
(CGFloat)smoothclip::resolvedRadius(
|
|
66
|
+
geometry.bottomLeftRadius, geometry.radius),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static BOOL SmoothClipPresentationHasExplicitRadii(
|
|
71
|
+
const smoothclip::Geometry &geometry) {
|
|
72
|
+
return isfinite(geometry.topLeftRadius) ||
|
|
73
|
+
isfinite(geometry.topRightRadius) ||
|
|
74
|
+
isfinite(geometry.bottomRightRadius) ||
|
|
75
|
+
isfinite(geometry.bottomLeftRadius);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static SmoothClipCornerCurve SmoothClipPresentationCurve(
|
|
79
|
+
const smoothclip::Geometry &geometry) {
|
|
80
|
+
return geometry.curve == smoothclip::ClipCurve::Continuous
|
|
81
|
+
? SmoothClipCornerCurveContinuous
|
|
82
|
+
: SmoothClipCornerCurveCircular;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static bool SmoothClipBuildPresentation(
|
|
86
|
+
double x,
|
|
87
|
+
double y,
|
|
88
|
+
double width,
|
|
89
|
+
double height,
|
|
90
|
+
double topLeftRadius,
|
|
91
|
+
double topRightRadius,
|
|
92
|
+
double bottomRightRadius,
|
|
93
|
+
double bottomLeftRadius,
|
|
94
|
+
NSInteger curveCode,
|
|
95
|
+
double contentTranslateX,
|
|
96
|
+
double contentTranslateY,
|
|
97
|
+
double contentScale,
|
|
98
|
+
BOOL shadowEnabled,
|
|
99
|
+
double shadowRed,
|
|
100
|
+
double shadowGreen,
|
|
101
|
+
double shadowBlue,
|
|
102
|
+
double shadowAlpha,
|
|
103
|
+
double shadowOffsetX,
|
|
104
|
+
double shadowOffsetY,
|
|
105
|
+
double shadowBlurRadius,
|
|
106
|
+
double shadowSpreadDistance,
|
|
107
|
+
smoothclip::Presentation *result) {
|
|
108
|
+
if (result == nullptr || !isfinite(x) || !isfinite(y) ||
|
|
109
|
+
!isfinite(width) || !isfinite(height) ||
|
|
110
|
+
!isfinite(topLeftRadius) || !isfinite(topRightRadius) ||
|
|
111
|
+
!isfinite(bottomRightRadius) || !isfinite(bottomLeftRadius) ||
|
|
112
|
+
!isfinite(contentTranslateX) || !isfinite(contentTranslateY) ||
|
|
113
|
+
!isfinite(contentScale) || !isfinite(shadowRed) ||
|
|
114
|
+
!isfinite(shadowGreen) || !isfinite(shadowBlue) ||
|
|
115
|
+
!isfinite(shadowAlpha) ||
|
|
116
|
+
!isfinite(shadowOffsetX) || !isfinite(shadowOffsetY) ||
|
|
117
|
+
!isfinite(shadowBlurRadius) || !isfinite(shadowSpreadDistance) ||
|
|
118
|
+
contentScale <= 0 || shadowRed < 0 || shadowRed > 1 ||
|
|
119
|
+
shadowGreen < 0 || shadowGreen > 1 ||
|
|
120
|
+
shadowBlue < 0 || shadowBlue > 1 ||
|
|
121
|
+
shadowAlpha < 0 || shadowAlpha > 1 || shadowBlurRadius < 0 ||
|
|
122
|
+
(curveCode != static_cast<NSInteger>(smoothclip::ClipCurve::Circular) &&
|
|
123
|
+
curveCode != static_cast<NSInteger>(smoothclip::ClipCurve::Continuous))) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const bool uniform = topLeftRadius == topRightRadius &&
|
|
128
|
+
topLeftRadius == bottomRightRadius &&
|
|
129
|
+
topLeftRadius == bottomLeftRadius;
|
|
130
|
+
smoothclip::Geometry geometry{
|
|
131
|
+
x, y, width, height, uniform ? topLeftRadius : 0};
|
|
132
|
+
geometry.topLeftRadius = topLeftRadius;
|
|
133
|
+
geometry.topRightRadius = topRightRadius;
|
|
134
|
+
geometry.bottomRightRadius = bottomRightRadius;
|
|
135
|
+
geometry.bottomLeftRadius = bottomLeftRadius;
|
|
136
|
+
geometry.curve = static_cast<smoothclip::ClipCurve>(curveCode);
|
|
137
|
+
const smoothclip::Shadow shadow{
|
|
138
|
+
shadowEnabled,
|
|
139
|
+
shadowRed,
|
|
140
|
+
shadowGreen,
|
|
141
|
+
shadowBlue,
|
|
142
|
+
shadowAlpha,
|
|
143
|
+
shadowOffsetX,
|
|
144
|
+
shadowOffsetY,
|
|
145
|
+
shadowBlurRadius,
|
|
146
|
+
shadowSpreadDistance};
|
|
147
|
+
*result = {
|
|
148
|
+
geometry, contentTranslateX, contentTranslateY, contentScale, shadow};
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static bool SmoothBoxShadowEqual(
|
|
153
|
+
const smoothclip::Shadow &first,
|
|
154
|
+
const smoothclip::Shadow &second) {
|
|
155
|
+
return first.enabled == second.enabled &&
|
|
156
|
+
first.red == second.red && first.green == second.green &&
|
|
157
|
+
first.blue == second.blue && first.alpha == second.alpha &&
|
|
158
|
+
first.offsetX == second.offsetX && first.offsetY == second.offsetY &&
|
|
159
|
+
first.blurRadius == second.blurRadius &&
|
|
160
|
+
first.spreadDistance == second.spreadDistance;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static bool SmoothBoxShadowVisible(
|
|
164
|
+
SmoothClipCanonicalGeometry geometry,
|
|
165
|
+
const smoothclip::Shadow &shadow) {
|
|
166
|
+
return shadow.enabled && shadow.alpha > 0 && !CGRectIsEmpty(geometry.rect);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static smoothclip::Shadow SmoothBoxShadowWithEffectiveAlpha(
|
|
170
|
+
SmoothClipCanonicalGeometry geometry,
|
|
171
|
+
smoothclip::Shadow shadow) {
|
|
172
|
+
if (!SmoothBoxShadowVisible(geometry, shadow)) {
|
|
173
|
+
shadow.alpha = 0;
|
|
174
|
+
}
|
|
175
|
+
return shadow;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static void SmoothBoxShadowNormalizeAnimationEndpoints(
|
|
179
|
+
SmoothClipCanonicalGeometry fromGeometry,
|
|
180
|
+
smoothclip::Shadow *fromShadow,
|
|
181
|
+
SmoothClipCanonicalGeometry toGeometry,
|
|
182
|
+
smoothclip::Shadow *toShadow) {
|
|
183
|
+
const bool fromVisible = SmoothBoxShadowVisible(fromGeometry, *fromShadow);
|
|
184
|
+
const bool toVisible = SmoothBoxShadowVisible(toGeometry, *toShadow);
|
|
185
|
+
if (fromVisible == toVisible) {
|
|
186
|
+
*fromShadow = SmoothBoxShadowWithEffectiveAlpha(
|
|
187
|
+
fromGeometry, *fromShadow);
|
|
188
|
+
*toShadow = SmoothBoxShadowWithEffectiveAlpha(toGeometry, *toShadow);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
smoothclip::Shadow *invisible = fromVisible ? toShadow : fromShadow;
|
|
193
|
+
const smoothclip::Shadow &visible = fromVisible ? *fromShadow : *toShadow;
|
|
194
|
+
invisible->enabled = true;
|
|
195
|
+
invisible->red = visible.red;
|
|
196
|
+
invisible->green = visible.green;
|
|
197
|
+
invisible->blue = visible.blue;
|
|
198
|
+
invisible->alpha = 0;
|
|
199
|
+
invisible->offsetX = visible.offsetX;
|
|
200
|
+
invisible->offsetY = visible.offsetY;
|
|
201
|
+
invisible->blurRadius = visible.blurRadius;
|
|
202
|
+
invisible->spreadDistance = visible.spreadDistance;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static bool SmoothBoxShadowColorEqual(
|
|
206
|
+
const smoothclip::Shadow &first,
|
|
207
|
+
const smoothclip::Shadow &second) {
|
|
208
|
+
return first.red == second.red && first.green == second.green &&
|
|
209
|
+
first.blue == second.blue && first.alpha == second.alpha;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static bool SmoothBoxShadowPathInputEqual(
|
|
213
|
+
SmoothClipCanonicalGeometry firstGeometry,
|
|
214
|
+
const smoothclip::Shadow &firstShadow,
|
|
215
|
+
SmoothClipCanonicalGeometry secondGeometry,
|
|
216
|
+
const smoothclip::Shadow &secondShadow) {
|
|
217
|
+
return CGRectEqualToRect(firstGeometry.rect, secondGeometry.rect) &&
|
|
218
|
+
SmoothClipCornerRadiiEqual(
|
|
219
|
+
firstGeometry.radii, secondGeometry.radii) &&
|
|
220
|
+
firstGeometry.curve == secondGeometry.curve &&
|
|
221
|
+
firstShadow.spreadDistance == secondShadow.spreadDistance;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
static CGFloat SmoothClipAdjustedShadowRadius(
|
|
225
|
+
CGFloat radius,
|
|
226
|
+
CGFloat spread) {
|
|
227
|
+
const CGFloat magnitude = ABS(spread);
|
|
228
|
+
CGFloat multiplier = 1;
|
|
229
|
+
if (magnitude > 0 && radius < magnitude) {
|
|
230
|
+
multiplier = 1 + pow(radius / magnitude - 1, 3);
|
|
231
|
+
}
|
|
232
|
+
return MAX(0, radius + spread * multiplier);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static CGPathRef SmoothClipCreateShadowPath(
|
|
236
|
+
SmoothClipCanonicalGeometry geometry,
|
|
237
|
+
const smoothclip::Shadow &shadow) {
|
|
238
|
+
const CGFloat spread = shadow.spreadDistance;
|
|
239
|
+
CGRect rect = CGRectInset(geometry.rect, -spread, -spread);
|
|
240
|
+
if (CGRectGetWidth(rect) <= 0 || CGRectGetHeight(rect) <= 0) {
|
|
241
|
+
rect = CGRectZero;
|
|
242
|
+
}
|
|
243
|
+
const SmoothClipCornerRadii radii = {
|
|
244
|
+
SmoothClipAdjustedShadowRadius(geometry.radii.topLeft, spread),
|
|
245
|
+
SmoothClipAdjustedShadowRadius(geometry.radii.topRight, spread),
|
|
246
|
+
SmoothClipAdjustedShadowRadius(geometry.radii.bottomRight, spread),
|
|
247
|
+
SmoothClipAdjustedShadowRadius(geometry.radii.bottomLeft, spread),
|
|
248
|
+
};
|
|
249
|
+
return SmoothClipCreateRoundedRectPath(rect, radii, geometry.curve);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static NSString *SmoothClipCALayerCornerCurve(
|
|
253
|
+
SmoothClipCornerCurve curve) API_AVAILABLE(ios(13.0)) {
|
|
254
|
+
return curve == SmoothClipCornerCurveContinuous
|
|
255
|
+
? kCACornerCurveContinuous
|
|
256
|
+
: kCACornerCurveCircular;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static CGAffineTransform SmoothClipContentTransform(
|
|
260
|
+
CGFloat scale,
|
|
261
|
+
CGPoint translation) {
|
|
262
|
+
// Construct the matrix directly. Concatenating a translation and a scale is
|
|
263
|
+
// order-sensitive and can multiply tx/ty; the presentation contract requires
|
|
264
|
+
// a centered uniform scale with translation remaining in unscaled points.
|
|
265
|
+
return CGAffineTransformMake(
|
|
266
|
+
scale, 0, 0, scale, translation.x, translation.y);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
struct SmoothClipPathEndpoints {
|
|
270
|
+
CGPoint points[9];
|
|
271
|
+
size_t count;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
static void SmoothClipCollectPathEndpoints(
|
|
275
|
+
void *context,
|
|
276
|
+
const CGPathElement *element) {
|
|
277
|
+
SmoothClipPathEndpoints *endpoints =
|
|
278
|
+
static_cast<SmoothClipPathEndpoints *>(context);
|
|
279
|
+
if (endpoints->count >= 9 ||
|
|
280
|
+
element->type == kCGPathElementCloseSubpath) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const size_t endpointIndex =
|
|
284
|
+
element->type == kCGPathElementAddCurveToPoint ? 2 :
|
|
285
|
+
element->type == kCGPathElementAddQuadCurveToPoint ? 1 : 0;
|
|
286
|
+
endpoints->points[endpoints->count++] = element->points[endpointIndex];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
static SmoothClipCornerRadii SmoothClipRadiiFromFixedPath(
|
|
290
|
+
CGPathRef path,
|
|
291
|
+
CGRect rect,
|
|
292
|
+
SmoothClipCornerRadii fallback) {
|
|
293
|
+
if (path == nil) return fallback;
|
|
294
|
+
SmoothClipPathEndpoints endpoints{};
|
|
295
|
+
CGPathApply(path, &endpoints, SmoothClipCollectPathEndpoints);
|
|
296
|
+
if (endpoints.count != 9) return fallback;
|
|
297
|
+
return {
|
|
298
|
+
MAX(0, endpoints.points[0].x - CGRectGetMinX(rect)),
|
|
299
|
+
MAX(0, CGRectGetMaxX(rect) - endpoints.points[1].x),
|
|
300
|
+
MAX(0, CGRectGetMaxY(rect) - endpoints.points[3].y),
|
|
301
|
+
MAX(0, endpoints.points[5].x - CGRectGetMinX(rect)),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
static std::array<double, 11> SmoothClipVelocityChannels(
|
|
306
|
+
CGRect rect,
|
|
307
|
+
SmoothClipCornerRadii radii,
|
|
308
|
+
CGPoint contentTranslation,
|
|
309
|
+
CGFloat contentScale) {
|
|
56
310
|
return {CGRectGetMinX(rect),
|
|
57
311
|
CGRectGetMinY(rect),
|
|
58
312
|
CGRectGetWidth(rect),
|
|
59
313
|
CGRectGetHeight(rect),
|
|
60
|
-
|
|
314
|
+
radii.topLeft,
|
|
315
|
+
radii.topRight,
|
|
316
|
+
radii.bottomRight,
|
|
317
|
+
radii.bottomLeft,
|
|
61
318
|
contentTranslation.x,
|
|
62
|
-
contentTranslation.y
|
|
319
|
+
contentTranslation.y,
|
|
320
|
+
contentScale};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
static BOOL SmoothClipRectIntersectsHost(CGRect rect, CGRect host) {
|
|
324
|
+
const CGRect intersection = CGRectIntersection(rect, host);
|
|
325
|
+
return !CGRectIsNull(intersection) && !CGRectIsEmpty(intersection);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
329
|
+
smoothclip::Presentation presentation) {
|
|
330
|
+
smoothclip::canonicalizePresentation(presentation);
|
|
331
|
+
return presentation;
|
|
63
332
|
}
|
|
64
333
|
|
|
65
334
|
@implementation SmoothClipView {
|
|
335
|
+
CALayer *_shadowLayer;
|
|
66
336
|
SmoothClipContainerView *_clipContainer;
|
|
67
337
|
SmoothClipContainerView *_contentContainer;
|
|
338
|
+
CAShapeLayer *_unequalCornerMask;
|
|
68
339
|
CGRect _requestedClip;
|
|
69
|
-
CGRect
|
|
340
|
+
CGRect _canonicalClip;
|
|
70
341
|
CGFloat _requestedRadius;
|
|
71
|
-
CGFloat
|
|
342
|
+
CGFloat _canonicalRadius;
|
|
343
|
+
SmoothClipCornerRadii _requestedRadii;
|
|
344
|
+
SmoothClipCornerRadii _canonicalRadii;
|
|
345
|
+
SmoothClipCornerCurve _requestedCurve;
|
|
346
|
+
SmoothClipCornerCurve _canonicalCurve;
|
|
347
|
+
BOOL _requestedHasExplicitRadii;
|
|
348
|
+
BOOL _canonicalHasExplicitRadii;
|
|
72
349
|
CGPoint _requestedContentTranslation;
|
|
73
|
-
CGPoint
|
|
350
|
+
CGPoint _canonicalContentTranslation;
|
|
351
|
+
CGFloat _requestedContentScale;
|
|
352
|
+
CGFloat _canonicalContentScale;
|
|
353
|
+
smoothclip::Shadow _requestedShadow;
|
|
354
|
+
smoothclip::Shadow _canonicalShadow;
|
|
74
355
|
uint64_t _driverId;
|
|
75
356
|
BOOL _hasLayout;
|
|
76
357
|
BOOL _commandIsAuthoritative;
|
|
@@ -81,23 +362,11 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
81
362
|
|
|
82
363
|
int32_t _activeAnimationId;
|
|
83
364
|
NSInteger _activeAnimationKind;
|
|
84
|
-
// Set when a native animation install arrived before the first layout.
|
|
85
|
-
// The install is deferred and re-run through the registry at first layout
|
|
86
|
-
// so it joins from live presentation geometry with the true remaining time.
|
|
87
|
-
BOOL _pendingAnimationInstall;
|
|
88
|
-
CFTimeInterval _animationStartedAt;
|
|
89
|
-
CFTimeInterval _animationDuration;
|
|
90
|
-
// Bumped on every CA install. updateLayoutMetrics captures it on entry so a
|
|
91
|
-
// registry-driven install that happens inside its own lifecycle notification
|
|
92
|
-
// (a latch resume during that very layout pass) is detectable: the local
|
|
93
|
-
// rebuild must then be skipped, because its captured remaining time predates
|
|
94
|
-
// the suspension and would shorten — or zero out — the fresh install.
|
|
95
|
-
uint32_t _animationInstallGeneration;
|
|
96
365
|
smoothclip::TimingAnimation _timingAnimation;
|
|
97
366
|
smoothclip::SpringAnimation _springAnimation;
|
|
98
367
|
SmoothClipAnimationDelegate *_animationDelegate;
|
|
99
368
|
|
|
100
|
-
// 'inherit' velocity samples (
|
|
369
|
+
// 'inherit' velocity samples (raw canonical geometry, per view); recording/
|
|
101
370
|
// coalescing/projection live in the shared cpp/SmoothClipVelocityTracker.h
|
|
102
371
|
// (behavior-paired with Android's per-driver history).
|
|
103
372
|
smoothclip::VelocitySampleHistory _velocitySamples;
|
|
@@ -124,6 +393,9 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
124
393
|
static const auto defaultProps =
|
|
125
394
|
std::make_shared<const SmoothClipViewProps>();
|
|
126
395
|
_props = defaultProps;
|
|
396
|
+
// SmoothClipView is the fixed viewport. Consumer overflow props are
|
|
397
|
+
// intentionally overridden again after every prop update below.
|
|
398
|
+
self.clipsToBounds = YES;
|
|
127
399
|
|
|
128
400
|
_clipContainer = [[SmoothClipContainerView alloc] initWithFrame:CGRectZero];
|
|
129
401
|
_clipContainer.layer.masksToBounds = YES;
|
|
@@ -139,20 +411,40 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
139
411
|
[_clipContainer addSubview:_contentContainer];
|
|
140
412
|
[self addSubview:_clipContainer];
|
|
141
413
|
|
|
414
|
+
_shadowLayer = nil;
|
|
415
|
+
// Created lazily so the common clipping-only path owns no shadow layer.
|
|
416
|
+
|
|
417
|
+
_unequalCornerMask = [CAShapeLayer layer];
|
|
418
|
+
_unequalCornerMask.actions = @{
|
|
419
|
+
@"path": [NSNull null],
|
|
420
|
+
@"bounds": [NSNull null],
|
|
421
|
+
@"position": [NSNull null],
|
|
422
|
+
};
|
|
423
|
+
_unequalCornerMask.fillColor = UIColor.blackColor.CGColor;
|
|
424
|
+
_unequalCornerMask.needsDisplayOnBoundsChange = NO;
|
|
425
|
+
|
|
142
426
|
_requestedClip = CGRectZero;
|
|
143
|
-
|
|
427
|
+
_canonicalClip = CGRectZero;
|
|
144
428
|
_requestedRadius = 0;
|
|
145
|
-
|
|
429
|
+
_canonicalRadius = 0;
|
|
430
|
+
_requestedRadii = {0, 0, 0, 0};
|
|
431
|
+
_canonicalRadii = {0, 0, 0, 0};
|
|
432
|
+
_requestedCurve = SmoothClipCornerCurveCircular;
|
|
433
|
+
_canonicalCurve = SmoothClipCornerCurveCircular;
|
|
434
|
+
_requestedHasExplicitRadii = NO;
|
|
435
|
+
_canonicalHasExplicitRadii = NO;
|
|
146
436
|
_requestedContentTranslation = CGPointZero;
|
|
147
|
-
|
|
437
|
+
_canonicalContentTranslation = CGPointZero;
|
|
438
|
+
_requestedContentScale = 1;
|
|
439
|
+
_canonicalContentScale = 1;
|
|
440
|
+
_requestedShadow = {};
|
|
441
|
+
_canonicalShadow = {};
|
|
148
442
|
_driverId = 0;
|
|
149
443
|
_hasLayout = NO;
|
|
150
444
|
_commandIsAuthoritative = NO;
|
|
151
445
|
_ignoreAnimationCallback = NO;
|
|
152
446
|
_activeAnimationId = 0;
|
|
153
447
|
_activeAnimationKind = 0;
|
|
154
|
-
_pendingAnimationInstall = NO;
|
|
155
|
-
_animationInstallGeneration = 0;
|
|
156
448
|
smoothclip::clearVelocitySamples(_velocitySamples);
|
|
157
449
|
// App-state transitions are observed by the registry itself (see
|
|
158
450
|
// installApplicationStateObservers): the active flag is process-global,
|
|
@@ -198,13 +490,72 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
198
490
|
geometry.y,
|
|
199
491
|
MAX(0, geometry.width),
|
|
200
492
|
MAX(0, geometry.height));
|
|
201
|
-
|
|
493
|
+
_requestedRadii = SmoothClipPresentationRadii(geometry);
|
|
494
|
+
_requestedRadius = SmoothClipCornerRadiiAreUniform(_requestedRadii)
|
|
495
|
+
? MAX(0, _requestedRadii.topLeft)
|
|
496
|
+
: 0;
|
|
497
|
+
_requestedCurve = SmoothClipPresentationCurve(geometry);
|
|
498
|
+
_requestedHasExplicitRadii =
|
|
499
|
+
SmoothClipPresentationHasExplicitRadii(geometry);
|
|
202
500
|
_requestedContentTranslation = CGPointMake(
|
|
203
501
|
presentation.contentTranslateX,
|
|
204
502
|
presentation.contentTranslateY);
|
|
503
|
+
_requestedContentScale = presentation.contentScale;
|
|
504
|
+
_requestedShadow = presentation.shadow;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
- (CGColorRef)colorForShadow:(const smoothclip::Shadow &)shadow {
|
|
508
|
+
return [UIColor colorWithRed:smoothclip::clamp01(shadow.red)
|
|
509
|
+
green:smoothclip::clamp01(shadow.green)
|
|
510
|
+
blue:smoothclip::clamp01(shadow.blue)
|
|
511
|
+
alpha:smoothclip::clamp01(shadow.alpha)].CGColor;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
- (CALayer *)ensureShadowLayer {
|
|
515
|
+
if (_shadowLayer != nil) return _shadowLayer;
|
|
516
|
+
_shadowLayer = [CALayer layer];
|
|
517
|
+
_shadowLayer.masksToBounds = NO;
|
|
518
|
+
_shadowLayer.actions = @{
|
|
519
|
+
@"shadowPath": [NSNull null],
|
|
520
|
+
@"shadowColor": [NSNull null],
|
|
521
|
+
@"shadowOpacity": [NSNull null],
|
|
522
|
+
@"shadowRadius": [NSNull null],
|
|
523
|
+
@"shadowOffset": [NSNull null],
|
|
524
|
+
@"bounds": [NSNull null],
|
|
525
|
+
@"position": [NSNull null],
|
|
526
|
+
};
|
|
527
|
+
[self.layer insertSublayer:_shadowLayer below:_clipContainer.layer];
|
|
528
|
+
return _shadowLayer;
|
|
205
529
|
}
|
|
206
530
|
|
|
207
|
-
- (
|
|
531
|
+
- (void)writeShadowModel:(const smoothclip::Shadow &)shadow
|
|
532
|
+
geometry:(SmoothClipCanonicalGeometry)geometry {
|
|
533
|
+
const smoothclip::Shadow effectiveShadow =
|
|
534
|
+
SmoothBoxShadowWithEffectiveAlpha(geometry, shadow);
|
|
535
|
+
const BOOL visible = SmoothBoxShadowVisible(geometry, effectiveShadow);
|
|
536
|
+
if (_shadowLayer == nil && !visible) return;
|
|
537
|
+
|
|
538
|
+
CALayer *shadowLayer = visible ? [self ensureShadowLayer] : _shadowLayer;
|
|
539
|
+
shadowLayer.bounds = self.bounds;
|
|
540
|
+
shadowLayer.position = CGPointMake(
|
|
541
|
+
CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
|
|
542
|
+
CGPathRef path = SmoothClipCreateShadowPath(geometry, effectiveShadow);
|
|
543
|
+
shadowLayer.shadowPath = path;
|
|
544
|
+
CGPathRelease(path);
|
|
545
|
+
shadowLayer.shadowColor = [self colorForShadow:effectiveShadow];
|
|
546
|
+
shadowLayer.shadowOpacity = visible ? 1 : 0;
|
|
547
|
+
shadowLayer.shadowRadius = MAX(0, effectiveShadow.blurRadius) / 2.0;
|
|
548
|
+
shadowLayer.shadowOffset = CGSizeMake(
|
|
549
|
+
effectiveShadow.offsetX, effectiveShadow.offsetY);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
- (void)writeShadow:(const smoothclip::Shadow &)shadow
|
|
553
|
+
geometry:(SmoothClipCanonicalGeometry)geometry {
|
|
554
|
+
_canonicalShadow = shadow;
|
|
555
|
+
[self writeShadowModel:shadow geometry:geometry];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
- (BOOL)canonicalRequestedGeometry:(SmoothClipCanonicalGeometry *)geometry {
|
|
208
559
|
if (!_hasLayout) return NO;
|
|
209
560
|
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
210
561
|
const bool signpostsEnabled =
|
|
@@ -213,22 +564,24 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
213
564
|
if (signpostsEnabled) {
|
|
214
565
|
identifier = os_signpost_id_generate([SmoothClipView signpostLog]);
|
|
215
566
|
os_signpost_interval_begin(
|
|
216
|
-
[SmoothClipView signpostLog], identifier, "
|
|
567
|
+
[SmoothClipView signpostLog], identifier, "canonicalization");
|
|
217
568
|
}
|
|
218
569
|
#endif
|
|
219
|
-
const
|
|
220
|
-
const BOOL valid = SmoothClipNormalizeGeometry(
|
|
570
|
+
const BOOL valid = SmoothClipCanonicalizeGeometry(
|
|
221
571
|
CGRectGetMinX(_requestedClip),
|
|
222
572
|
CGRectGetMinY(_requestedClip),
|
|
223
573
|
CGRectGetWidth(_requestedClip),
|
|
224
574
|
CGRectGetHeight(_requestedClip),
|
|
225
|
-
|
|
226
|
-
|
|
575
|
+
_requestedRadii.topLeft,
|
|
576
|
+
_requestedRadii.topRight,
|
|
577
|
+
_requestedRadii.bottomRight,
|
|
578
|
+
_requestedRadii.bottomLeft,
|
|
579
|
+
_requestedCurve,
|
|
227
580
|
geometry);
|
|
228
581
|
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
229
582
|
if (signpostsEnabled) {
|
|
230
583
|
os_signpost_interval_end(
|
|
231
|
-
[SmoothClipView signpostLog], identifier, "
|
|
584
|
+
[SmoothClipView signpostLog], identifier, "canonicalization");
|
|
232
585
|
}
|
|
233
586
|
#endif
|
|
234
587
|
return valid;
|
|
@@ -243,14 +596,63 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
243
596
|
|
|
244
597
|
- (void)syncVisibilityForRect:(CGRect)rect {
|
|
245
598
|
[self setClipContainerHidden:CGRectIsEmpty(rect)];
|
|
599
|
+
_clipContainer.accessibilityElementsHidden =
|
|
600
|
+
!SmoothClipRectIntersectsHost(rect, self.bounds);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
- (void)configureUnequalCornerMaskForGeometry:
|
|
604
|
+
(SmoothClipCanonicalGeometry)geometry {
|
|
605
|
+
_unequalCornerMask.bounds = geometry.rect;
|
|
606
|
+
_unequalCornerMask.position = CGPointMake(
|
|
607
|
+
CGRectGetMidX(geometry.rect), CGRectGetMidY(geometry.rect));
|
|
608
|
+
CGPathRef path = SmoothClipCreateRoundedRectPath(
|
|
609
|
+
geometry.rect, geometry.radii, geometry.curve);
|
|
610
|
+
_unequalCornerMask.path = path;
|
|
611
|
+
CGPathRelease(path);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
- (void)applyStaticCornerRepresentation:
|
|
615
|
+
(SmoothClipCanonicalGeometry)geometry {
|
|
616
|
+
CALayer *layer = _clipContainer.layer;
|
|
617
|
+
if (SmoothClipCornerRadiiAreUniform(geometry.radii)) {
|
|
618
|
+
layer.mask = nil;
|
|
619
|
+
layer.cornerRadius = geometry.radii.topLeft;
|
|
620
|
+
layer.cornerCurve = SmoothClipCALayerCornerCurve(geometry.curve);
|
|
621
|
+
} else {
|
|
622
|
+
layer.cornerRadius = 0;
|
|
623
|
+
layer.cornerCurve = kCACornerCurveCircular;
|
|
624
|
+
[self configureUnequalCornerMaskForGeometry:geometry];
|
|
625
|
+
layer.mask = _unequalCornerMask;
|
|
626
|
+
}
|
|
246
627
|
}
|
|
247
628
|
|
|
248
|
-
- (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
629
|
+
- (SmoothClipCanonicalGeometry)canonicalGeometryValue {
|
|
630
|
+
return {
|
|
631
|
+
.rect = _canonicalClip,
|
|
632
|
+
.radius = _canonicalRadius,
|
|
633
|
+
.radii = _canonicalRadii,
|
|
634
|
+
.curve = _canonicalCurve,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
- (void)writeLayerGeometry:(SmoothClipCanonicalGeometry)geometry
|
|
639
|
+
hasExplicitRadii:(BOOL)hasExplicitRadii {
|
|
640
|
+
const BOOL rectChanged =
|
|
641
|
+
!CGRectEqualToRect(_canonicalClip, geometry.rect);
|
|
642
|
+
const BOOL radiiChanged =
|
|
643
|
+
!SmoothClipCornerRadiiEqual(_canonicalRadii, geometry.radii);
|
|
644
|
+
const BOOL curveChanged = _canonicalCurve != geometry.curve;
|
|
645
|
+
const BOOL representationChanged =
|
|
646
|
+
_canonicalHasExplicitRadii != hasExplicitRadii;
|
|
647
|
+
_canonicalClip = geometry.rect;
|
|
648
|
+
_canonicalRadius = geometry.radius;
|
|
649
|
+
_canonicalRadii = geometry.radii;
|
|
650
|
+
_canonicalCurve = geometry.curve;
|
|
651
|
+
_canonicalHasExplicitRadii = hasExplicitRadii;
|
|
652
|
+
if (!rectChanged && !radiiChanged && !curveChanged &&
|
|
653
|
+
!representationChanged) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
254
656
|
|
|
255
657
|
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
256
658
|
const bool signpostsEnabled =
|
|
@@ -264,12 +666,13 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
264
666
|
#endif
|
|
265
667
|
CALayer *layer = _clipContainer.layer;
|
|
266
668
|
if (rectChanged) {
|
|
267
|
-
layer.bounds = rect;
|
|
268
|
-
layer.position = CGPointMake(
|
|
269
|
-
|
|
270
|
-
if (radiusChanged) {
|
|
271
|
-
layer.cornerRadius = radius;
|
|
669
|
+
layer.bounds = geometry.rect;
|
|
670
|
+
layer.position = CGPointMake(
|
|
671
|
+
CGRectGetMidX(geometry.rect), CGRectGetMidY(geometry.rect));
|
|
272
672
|
}
|
|
673
|
+
// The uniform common case stays on CALayer's compositor fast path and uses
|
|
674
|
+
// the platform's exact continuous-corner implementation.
|
|
675
|
+
[self applyStaticCornerRepresentation:geometry];
|
|
273
676
|
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
274
677
|
if (signpostsEnabled) {
|
|
275
678
|
os_signpost_interval_end(
|
|
@@ -278,53 +681,101 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
278
681
|
#endif
|
|
279
682
|
}
|
|
280
683
|
|
|
281
|
-
- (void)writeContentTranslation:(CGPoint)translation
|
|
282
|
-
|
|
283
|
-
|
|
684
|
+
- (void)writeContentTranslation:(CGPoint)translation
|
|
685
|
+
scale:(CGFloat)scale {
|
|
686
|
+
if (CGPointEqualToPoint(_canonicalContentTranslation, translation) &&
|
|
687
|
+
_canonicalContentScale == scale) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
_canonicalContentTranslation = translation;
|
|
691
|
+
_canonicalContentScale = scale;
|
|
284
692
|
_contentContainer.layer.affineTransform =
|
|
285
|
-
|
|
693
|
+
SmoothClipContentTransform(scale, translation);
|
|
286
694
|
}
|
|
287
695
|
|
|
288
696
|
- (void)applyRequestedClip {
|
|
289
|
-
|
|
290
|
-
if (![self
|
|
291
|
-
|
|
292
|
-
const BOOL
|
|
293
|
-
|
|
294
|
-
|
|
697
|
+
SmoothClipCanonicalGeometry geometry;
|
|
698
|
+
if (![self canonicalRequestedGeometry:&geometry]) return;
|
|
699
|
+
|
|
700
|
+
const BOOL accessibilityHidden =
|
|
701
|
+
!SmoothClipRectIntersectsHost(geometry.rect, self.bounds);
|
|
702
|
+
const BOOL visibilityChanged =
|
|
703
|
+
_clipHidden != CGRectIsEmpty(geometry.rect) ||
|
|
704
|
+
_clipContainer.accessibilityElementsHidden != accessibilityHidden;
|
|
705
|
+
if (CGRectEqualToRect(_canonicalClip, geometry.rect) &&
|
|
706
|
+
SmoothClipCornerRadiiEqual(_canonicalRadii, geometry.radii) &&
|
|
707
|
+
_canonicalCurve == geometry.curve &&
|
|
708
|
+
_canonicalHasExplicitRadii == _requestedHasExplicitRadii &&
|
|
295
709
|
CGPointEqualToPoint(
|
|
296
|
-
|
|
710
|
+
_canonicalContentTranslation, _requestedContentTranslation) &&
|
|
711
|
+
_canonicalContentScale == _requestedContentScale &&
|
|
712
|
+
SmoothBoxShadowEqual(_canonicalShadow, _requestedShadow) &&
|
|
297
713
|
!visibilityChanged) {
|
|
298
714
|
return;
|
|
299
715
|
}
|
|
300
|
-
[self
|
|
301
|
-
|
|
716
|
+
[self writeLayerGeometry:geometry
|
|
717
|
+
hasExplicitRadii:_requestedHasExplicitRadii];
|
|
718
|
+
[self writeContentTranslation:_requestedContentTranslation
|
|
719
|
+
scale:_requestedContentScale];
|
|
720
|
+
[self writeShadow:_requestedShadow geometry:geometry];
|
|
302
721
|
[self syncVisibilityForRect:geometry.rect];
|
|
303
722
|
}
|
|
304
723
|
|
|
305
724
|
- (void)recordInteractiveRect:(CGRect)rect
|
|
306
|
-
|
|
307
|
-
contentTranslation:(CGPoint)contentTranslation
|
|
725
|
+
radii:(SmoothClipCornerRadii)radii
|
|
726
|
+
contentTranslation:(CGPoint)contentTranslation
|
|
727
|
+
contentScale:(CGFloat)contentScale {
|
|
308
728
|
// Identical-value dedupe and same-frame coalescing live in the shared
|
|
309
729
|
// tracker; coalescing keeps a distinct re-record issued sub-frame after
|
|
310
730
|
// the last one (e.g. a fused animateTo `from` seed at gesture release)
|
|
311
731
|
// from exploding the inherited velocity.
|
|
312
732
|
smoothclip::recordVelocitySample(
|
|
313
733
|
_velocitySamples,
|
|
314
|
-
SmoothClipVelocityChannels(
|
|
734
|
+
SmoothClipVelocityChannels(
|
|
735
|
+
rect, radii, contentTranslation, contentScale),
|
|
315
736
|
CACurrentMediaTime());
|
|
316
737
|
}
|
|
317
738
|
|
|
318
739
|
- (CGFloat)inheritedVelocityToRect:(CGRect)target
|
|
319
|
-
|
|
320
|
-
contentTranslation:(CGPoint)targetContentTranslation
|
|
740
|
+
radii:(SmoothClipCornerRadii)targetRadii
|
|
741
|
+
contentTranslation:(CGPoint)targetContentTranslation
|
|
742
|
+
contentScale:(CGFloat)targetContentScale {
|
|
321
743
|
return smoothclip::inheritedVelocity(
|
|
322
744
|
_velocitySamples,
|
|
323
745
|
SmoothClipVelocityChannels(
|
|
324
|
-
target,
|
|
746
|
+
target,
|
|
747
|
+
targetRadii,
|
|
748
|
+
targetContentTranslation,
|
|
749
|
+
targetContentScale),
|
|
325
750
|
CACurrentMediaTime());
|
|
326
751
|
}
|
|
327
752
|
|
|
753
|
+
- (double)smoothClipInheritedVelocityToPresentation:
|
|
754
|
+
(smoothclip::Presentation)presentation {
|
|
755
|
+
SmoothClipCanonicalGeometry target;
|
|
756
|
+
const SmoothClipCornerRadii radii =
|
|
757
|
+
SmoothClipPresentationRadii(presentation.clip);
|
|
758
|
+
if (!SmoothClipCanonicalizeGeometry(
|
|
759
|
+
presentation.clip.x,
|
|
760
|
+
presentation.clip.y,
|
|
761
|
+
presentation.clip.width,
|
|
762
|
+
presentation.clip.height,
|
|
763
|
+
radii.topLeft,
|
|
764
|
+
radii.topRight,
|
|
765
|
+
radii.bottomRight,
|
|
766
|
+
radii.bottomLeft,
|
|
767
|
+
SmoothClipPresentationCurve(presentation.clip),
|
|
768
|
+
&target)) {
|
|
769
|
+
return 0;
|
|
770
|
+
}
|
|
771
|
+
return [self inheritedVelocityToRect:target.rect
|
|
772
|
+
radii:target.radii
|
|
773
|
+
contentTranslation:CGPointMake(
|
|
774
|
+
presentation.contentTranslateX,
|
|
775
|
+
presentation.contentTranslateY)
|
|
776
|
+
contentScale:presentation.contentScale];
|
|
777
|
+
}
|
|
778
|
+
|
|
328
779
|
- (void)smoothClipApplyPresentation:(smoothclip::Presentation)presentation {
|
|
329
780
|
[self smoothClipApplyPresentation:presentation recordVelocitySample:YES];
|
|
330
781
|
}
|
|
@@ -334,33 +785,72 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
334
785
|
[self stopLayerAnimationWithoutCallback];
|
|
335
786
|
[self storeRequestedPresentation:presentation];
|
|
336
787
|
[self applyRequestedClip];
|
|
337
|
-
// Skipped for writes that are not interactive motion (a
|
|
788
|
+
// Skipped for writes that are not interactive motion (a pending-run
|
|
338
789
|
// cancel-to-target): they must not enter the 'inherit' history.
|
|
339
790
|
if (recordVelocitySample && _hasLayout) {
|
|
340
|
-
[self recordInteractiveRect:
|
|
341
|
-
|
|
342
|
-
contentTranslation:
|
|
791
|
+
[self recordInteractiveRect:_canonicalClip
|
|
792
|
+
radii:_canonicalRadii
|
|
793
|
+
contentTranslation:_canonicalContentTranslation
|
|
794
|
+
contentScale:_canonicalContentScale];
|
|
343
795
|
}
|
|
344
796
|
}
|
|
345
797
|
|
|
346
798
|
- (smoothclip::Presentation)smoothClipCurrentPresentation {
|
|
347
|
-
|
|
799
|
+
SmoothClipCornerRadii visibleRadii = _canonicalRadii;
|
|
348
800
|
const CGRect visibleRect = _activeAnimationId != 0
|
|
349
|
-
? [self
|
|
350
|
-
:
|
|
801
|
+
? [self presentationRectWithRadii:&visibleRadii]
|
|
802
|
+
: _canonicalClip;
|
|
351
803
|
CALayer *contentLayer = _activeAnimationId != 0
|
|
352
804
|
? (CALayer *)_contentContainer.layer.presentationLayer
|
|
353
805
|
: _contentContainer.layer;
|
|
354
806
|
if (contentLayer == nil) contentLayer = _contentContainer.layer;
|
|
355
807
|
const CGAffineTransform transform = contentLayer.affineTransform;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
808
|
+
const BOOL uniform = SmoothClipCornerRadiiAreUniform(visibleRadii);
|
|
809
|
+
smoothclip::Geometry geometry{
|
|
810
|
+
CGRectGetMinX(visibleRect),
|
|
811
|
+
CGRectGetMinY(visibleRect),
|
|
812
|
+
CGRectGetWidth(visibleRect),
|
|
813
|
+
CGRectGetHeight(visibleRect),
|
|
814
|
+
uniform ? visibleRadii.topLeft : 0};
|
|
815
|
+
if (_canonicalHasExplicitRadii || !uniform) {
|
|
816
|
+
geometry.topLeftRadius = visibleRadii.topLeft;
|
|
817
|
+
geometry.topRightRadius = visibleRadii.topRight;
|
|
818
|
+
geometry.bottomRightRadius = visibleRadii.bottomRight;
|
|
819
|
+
geometry.bottomLeftRadius = visibleRadii.bottomLeft;
|
|
820
|
+
}
|
|
821
|
+
geometry.curve = _canonicalCurve == SmoothClipCornerCurveContinuous
|
|
822
|
+
? smoothclip::ClipCurve::Continuous
|
|
823
|
+
: smoothclip::ClipCurve::Circular;
|
|
824
|
+
const CGFloat contentScale = hypot(transform.a, transform.b);
|
|
825
|
+
smoothclip::Shadow shadow = _canonicalShadow;
|
|
826
|
+
CALayer *shadowLayer = _activeAnimationId != 0 && _shadowLayer != nil
|
|
827
|
+
? (CALayer *)_shadowLayer.presentationLayer
|
|
828
|
+
: _shadowLayer;
|
|
829
|
+
if (shadowLayer == nil) return SmoothClipCanonicalSnapshot({
|
|
830
|
+
geometry, transform.tx, transform.ty, contentScale, shadow});
|
|
831
|
+
shadow.enabled = _canonicalShadow.enabled || shadowLayer.shadowOpacity > 0;
|
|
832
|
+
shadow.blurRadius = shadowLayer.shadowRadius * 2.0;
|
|
833
|
+
shadow.offsetX = shadowLayer.shadowOffset.width;
|
|
834
|
+
shadow.offsetY = shadowLayer.shadowOffset.height;
|
|
835
|
+
CGColorRef color = shadowLayer.shadowColor;
|
|
836
|
+
if (color != nil) {
|
|
837
|
+
UIColor *uiColor = [UIColor colorWithCGColor:color];
|
|
838
|
+
CGFloat red = 0, green = 0, blue = 0, alpha = 0;
|
|
839
|
+
if ([uiColor getRed:&red green:&green blue:&blue alpha:&alpha]) {
|
|
840
|
+
shadow.red = red;
|
|
841
|
+
shadow.green = green;
|
|
842
|
+
shadow.blue = blue;
|
|
843
|
+
shadow.alpha = alpha;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
CGPathRef shadowPath = shadowLayer.shadowPath;
|
|
847
|
+
if (shadowPath != nil && !CGRectIsEmpty(visibleRect)) {
|
|
848
|
+
const CGRect shadowBounds = CGPathGetBoundingBox(shadowPath);
|
|
849
|
+
shadow.spreadDistance =
|
|
850
|
+
CGRectGetMinX(visibleRect) - CGRectGetMinX(shadowBounds);
|
|
851
|
+
}
|
|
852
|
+
return SmoothClipCanonicalSnapshot(
|
|
853
|
+
{geometry, transform.tx, transform.ty, contentScale, shadow});
|
|
364
854
|
}
|
|
365
855
|
|
|
366
856
|
- (BOOL)smoothClipIsJoinable {
|
|
@@ -373,64 +863,54 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
373
863
|
- (BOOL)smoothClipCanDisplay {
|
|
374
864
|
// A CA animation committed while this view's layer tree is detached from
|
|
375
865
|
// the render tree does not survive the later attach commit; installs and
|
|
376
|
-
//
|
|
866
|
+
// pre-ready runs must wait until a frame can actually be produced.
|
|
377
867
|
return [self smoothClipIsJoinable] && self.window != nil &&
|
|
378
868
|
smoothclip::applicationIsActive();
|
|
379
869
|
}
|
|
380
870
|
|
|
381
|
-
- (BOOL)smoothClipHasPendingInstall {
|
|
382
|
-
return _pendingAnimationInstall;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
871
|
- (void)smoothClipClearVelocitySamples {
|
|
386
872
|
smoothclip::clearVelocitySamples(_velocitySamples);
|
|
387
873
|
}
|
|
388
874
|
|
|
389
|
-
- (double)smoothClipSpringContinuationVelocity {
|
|
390
|
-
if (_activeAnimationId == 0 || _activeAnimationKind != 2 ||
|
|
391
|
-
_pendingAnimationInstall) {
|
|
392
|
-
return 0;
|
|
393
|
-
}
|
|
394
|
-
const double elapsed = MAX(0, CACurrentMediaTime() - _animationStartedAt);
|
|
395
|
-
return smoothclip::springContinuationVelocity(_springAnimation, elapsed);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
875
|
- (smoothclip::Presentation)smoothClipFreezePresentation {
|
|
399
876
|
const smoothclip::Presentation visible =
|
|
400
877
|
[self smoothClipCurrentPresentation];
|
|
401
|
-
const CGRect visibleRect = CGRectMake(
|
|
402
|
-
visible.clip.x,
|
|
403
|
-
visible.clip.y,
|
|
404
|
-
visible.clip.width,
|
|
405
|
-
visible.clip.height);
|
|
406
|
-
const CGPoint visibleContentTranslation = CGPointMake(
|
|
407
|
-
visible.contentTranslateX,
|
|
408
|
-
visible.contentTranslateY);
|
|
409
878
|
[self stopLayerAnimationWithoutCallback];
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
_requestedContentTranslation = visibleContentTranslation;
|
|
413
|
-
[self writeLayerRect:visibleRect radius:visible.clip.radius];
|
|
414
|
-
[self writeContentTranslation:visibleContentTranslation];
|
|
415
|
-
[self syncVisibilityForRect:visibleRect];
|
|
879
|
+
[self storeRequestedPresentation:visible];
|
|
880
|
+
[self applyRequestedClip];
|
|
416
881
|
return visible;
|
|
417
882
|
}
|
|
418
883
|
|
|
419
|
-
- (CGRect)
|
|
884
|
+
- (CGRect)presentationRectWithRadii:(SmoothClipCornerRadii *)radii {
|
|
420
885
|
CALayer *layer = (CALayer *)_clipContainer.layer.presentationLayer;
|
|
421
886
|
if (layer == nil) layer = _clipContainer.layer;
|
|
422
887
|
const CGRect bounds = layer.bounds;
|
|
423
888
|
const CGPoint position = layer.position;
|
|
424
|
-
|
|
425
|
-
return CGRectMake(
|
|
889
|
+
const CGRect rect = CGRectMake(
|
|
426
890
|
position.x - bounds.size.width * layer.anchorPoint.x,
|
|
427
891
|
position.y - bounds.size.height * layer.anchorPoint.y,
|
|
428
892
|
bounds.size.width,
|
|
429
893
|
bounds.size.height);
|
|
894
|
+
if (radii != nullptr) {
|
|
895
|
+
if (_clipContainer.layer.mask == _unequalCornerMask) {
|
|
896
|
+
CAShapeLayer *mask =
|
|
897
|
+
(CAShapeLayer *)_unequalCornerMask.presentationLayer;
|
|
898
|
+
if (mask == nil) mask = _unequalCornerMask;
|
|
899
|
+
*radii = SmoothClipRadiiFromFixedPath(
|
|
900
|
+
mask.path, rect, _canonicalRadii);
|
|
901
|
+
} else {
|
|
902
|
+
*radii = {
|
|
903
|
+
layer.cornerRadius,
|
|
904
|
+
layer.cornerRadius,
|
|
905
|
+
layer.cornerRadius,
|
|
906
|
+
layer.cornerRadius,
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return rect;
|
|
430
911
|
}
|
|
431
912
|
|
|
432
913
|
- (void)stopLayerAnimationWithoutCallback {
|
|
433
|
-
_pendingAnimationInstall = NO;
|
|
434
914
|
if (_activeAnimationId == 0) return;
|
|
435
915
|
// Invalidate before removing: CA may deliver didStop for the removed
|
|
436
916
|
// animation after this method returns, when _ignoreAnimationCallback has
|
|
@@ -440,6 +920,9 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
440
920
|
_ignoreAnimationCallback = YES;
|
|
441
921
|
[_clipContainer.layer removeAnimationForKey:@"smoothClip.geometry"];
|
|
442
922
|
[_contentContainer.layer removeAnimationForKey:@"smoothClip.content"];
|
|
923
|
+
[_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
|
|
924
|
+
[_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
|
|
925
|
+
[self applyStaticCornerRepresentation:[self canonicalGeometryValue]];
|
|
443
926
|
_ignoreAnimationCallback = NO;
|
|
444
927
|
_activeAnimationId = 0;
|
|
445
928
|
_activeAnimationKind = 0;
|
|
@@ -458,15 +941,19 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
458
941
|
}
|
|
459
942
|
|
|
460
943
|
- (CFTimeInterval)springSettlingDurationWithVelocity:(double)velocity {
|
|
461
|
-
// Every key path
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
944
|
+
// Every key path shares one normalized trajectory. Derive the terminal
|
|
945
|
+
// duration from the same relative-energy rule as Android/Reanimated instead
|
|
946
|
+
// of asking each Core Animation property for an independent settle time.
|
|
947
|
+
smoothclip::ScalarSpringState state{0, velocity};
|
|
948
|
+
constexpr double step = 1.0 / 120.0;
|
|
949
|
+
double elapsed = 0;
|
|
950
|
+
while (elapsed < 10.0 &&
|
|
951
|
+
smoothclip::relativeSpringEnergy(state, _springAnimation) >
|
|
952
|
+
_springAnimation.energyThreshold) {
|
|
953
|
+
state = smoothclip::advanceScalarSpring(state, _springAnimation, step);
|
|
954
|
+
elapsed += step;
|
|
955
|
+
}
|
|
956
|
+
return elapsed;
|
|
470
957
|
}
|
|
471
958
|
|
|
472
959
|
- (CASpringAnimation *)springAnimationForKeyPath:(NSString *)keyPath
|
|
@@ -486,13 +973,19 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
486
973
|
return animation;
|
|
487
974
|
}
|
|
488
975
|
|
|
489
|
-
- (void)
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
976
|
+
- (void)installAnimationFromGeometry:
|
|
977
|
+
(SmoothClipCanonicalGeometry)fromGeometry
|
|
978
|
+
fromContentTranslation:(CGPoint)fromContentTranslation
|
|
979
|
+
fromContentScale:(CGFloat)fromContentScale
|
|
980
|
+
fromShadow:(smoothclip::Shadow)fromShadow
|
|
981
|
+
toGeometry:
|
|
982
|
+
(SmoothClipCanonicalGeometry)toGeometry
|
|
983
|
+
hasExplicitToRadii:(BOOL)hasExplicitToRadii
|
|
984
|
+
toContentTranslation:(CGPoint)toContentTranslation
|
|
985
|
+
toContentScale:(CGFloat)toContentScale
|
|
986
|
+
toShadow:(smoothclip::Shadow)toShadow
|
|
987
|
+
duration:(CFTimeInterval)duration
|
|
988
|
+
sharedBeginTime:(CFTimeInterval)sharedBeginTime {
|
|
496
989
|
const int32_t animationId = _activeAnimationId;
|
|
497
990
|
const NSInteger animationKind = _activeAnimationKind;
|
|
498
991
|
[self stopLayerAnimationWithoutCallback];
|
|
@@ -500,50 +993,133 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
500
993
|
_activeAnimationKind = animationKind;
|
|
501
994
|
// Unhide while any animated frame can show content; an empty-to-empty
|
|
502
995
|
// transition must stay hidden for accessibility.
|
|
503
|
-
if (!CGRectIsEmpty(
|
|
996
|
+
if (!CGRectIsEmpty(toGeometry.rect) ||
|
|
997
|
+
!CGRectIsEmpty(fromGeometry.rect)) {
|
|
504
998
|
[self setClipContainerHidden:NO];
|
|
505
999
|
}
|
|
1000
|
+
// Autonomous native motion has no per-frame accessibility layout updates.
|
|
1001
|
+
// Hide moving descendants until the endpoint is committed.
|
|
1002
|
+
_clipContainer.accessibilityElementsHidden = YES;
|
|
506
1003
|
|
|
507
1004
|
CALayer *layer = _clipContainer.layer;
|
|
508
|
-
const CGRect toBounds =
|
|
1005
|
+
const CGRect toBounds = toGeometry.rect;
|
|
509
1006
|
const CGPoint fromPosition =
|
|
510
|
-
CGPointMake(
|
|
1007
|
+
CGPointMake(
|
|
1008
|
+
CGRectGetMidX(fromGeometry.rect),
|
|
1009
|
+
CGRectGetMidY(fromGeometry.rect));
|
|
511
1010
|
const CGPoint toPosition =
|
|
512
|
-
CGPointMake(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
1011
|
+
CGPointMake(
|
|
1012
|
+
CGRectGetMidX(toGeometry.rect),
|
|
1013
|
+
CGRectGetMidY(toGeometry.rect));
|
|
1014
|
+
const BOOL usesMask =
|
|
1015
|
+
!SmoothClipCornerRadiiAreUniform(fromGeometry.radii) ||
|
|
1016
|
+
!SmoothClipCornerRadiiAreUniform(toGeometry.radii) ||
|
|
1017
|
+
fromGeometry.curve != toGeometry.curve;
|
|
1018
|
+
[self writeLayerGeometry:toGeometry
|
|
1019
|
+
hasExplicitRadii:hasExplicitToRadii];
|
|
1020
|
+
[self writeContentTranslation:toContentTranslation scale:toContentScale];
|
|
1021
|
+
_canonicalShadow = toShadow;
|
|
1022
|
+
SmoothBoxShadowNormalizeAnimationEndpoints(
|
|
1023
|
+
fromGeometry, &fromShadow, toGeometry, &toShadow);
|
|
1024
|
+
// The target model layer is complete before any presentation animations are
|
|
1025
|
+
// attached. Core Animation may remove the terminal presentation animation
|
|
1026
|
+
// before its delegate runs, so correctness cannot depend on completion-time
|
|
1027
|
+
// cleanup hiding a stale path or opaque color.
|
|
1028
|
+
[self writeShadowModel:toShadow geometry:toGeometry];
|
|
1029
|
+
if (usesMask) {
|
|
1030
|
+
// Keep one mask representation for the full interval, including an
|
|
1031
|
+
// unequal→uniform transition. Switching layer.mask mid-animation would
|
|
1032
|
+
// create a one-frame seam even though the underlying paths are compatible.
|
|
1033
|
+
[self configureUnequalCornerMaskForGeometry:toGeometry];
|
|
1034
|
+
layer.cornerRadius = 0;
|
|
1035
|
+
layer.mask = _unequalCornerMask;
|
|
1036
|
+
}
|
|
522
1037
|
|
|
1038
|
+
CGPathRef fromMaskPath = usesMask
|
|
1039
|
+
? SmoothClipCreateRoundedRectPath(
|
|
1040
|
+
fromGeometry.rect, fromGeometry.radii, fromGeometry.curve)
|
|
1041
|
+
: nil;
|
|
1042
|
+
CGPathRef toMaskPath = usesMask
|
|
1043
|
+
? SmoothClipCreateRoundedRectPath(
|
|
1044
|
+
toGeometry.rect, toGeometry.radii, toGeometry.curve)
|
|
1045
|
+
: nil;
|
|
1046
|
+
const BOOL fromShadowVisible =
|
|
1047
|
+
SmoothBoxShadowVisible(fromGeometry, fromShadow);
|
|
1048
|
+
const BOOL toShadowVisible =
|
|
1049
|
+
SmoothBoxShadowVisible(toGeometry, toShadow);
|
|
1050
|
+
const BOOL hasVisibleShadow = fromShadowVisible || toShadowVisible;
|
|
1051
|
+
const BOOL animatesShadowPath = hasVisibleShadow &&
|
|
1052
|
+
!SmoothBoxShadowPathInputEqual(
|
|
1053
|
+
fromGeometry, fromShadow, toGeometry, toShadow);
|
|
1054
|
+
const BOOL animatesShadowColor = hasVisibleShadow &&
|
|
1055
|
+
!SmoothBoxShadowColorEqual(fromShadow, toShadow);
|
|
1056
|
+
const BOOL animatesShadowRadius = hasVisibleShadow &&
|
|
1057
|
+
fromShadow.blurRadius != toShadow.blurRadius;
|
|
1058
|
+
const BOOL animatesShadowOffset = hasVisibleShadow &&
|
|
1059
|
+
(fromShadow.offsetX != toShadow.offsetX ||
|
|
1060
|
+
fromShadow.offsetY != toShadow.offsetY);
|
|
1061
|
+
const BOOL animatesShadow = animatesShadowPath || animatesShadowColor ||
|
|
1062
|
+
animatesShadowRadius || animatesShadowOffset;
|
|
1063
|
+
if (hasVisibleShadow) {
|
|
1064
|
+
[self ensureShadowLayer];
|
|
1065
|
+
// Reveal opacity is encoded in shadowColor alpha. Animating layer opacity
|
|
1066
|
+
// too would multiply both curves and produce a quadratic-looking fade.
|
|
1067
|
+
_shadowLayer.shadowOpacity = 1;
|
|
1068
|
+
}
|
|
1069
|
+
CGPathRef fromShadowPath = animatesShadowPath
|
|
1070
|
+
? SmoothClipCreateShadowPath(fromGeometry, fromShadow)
|
|
1071
|
+
: nil;
|
|
1072
|
+
CGPathRef toShadowPath = animatesShadowPath
|
|
1073
|
+
? SmoothClipCreateShadowPath(toGeometry, toShadow)
|
|
1074
|
+
: nil;
|
|
523
1075
|
CAAnimationGroup *group = [CAAnimationGroup animation];
|
|
524
1076
|
CAAnimationGroup *contentGroup = [CAAnimationGroup animation];
|
|
1077
|
+
CAAnimationGroup *shadowGroup = animatesShadow
|
|
1078
|
+
? [CAAnimationGroup animation]
|
|
1079
|
+
: nil;
|
|
1080
|
+
CAAnimationGroup *maskAnimation = nil;
|
|
525
1081
|
if (_activeAnimationKind == 1) {
|
|
526
1082
|
CAMediaTimingFunction *timing = [CAMediaTimingFunction
|
|
527
1083
|
functionWithControlPoints:_timingAnimation.controlPoint1X
|
|
528
1084
|
:_timingAnimation.controlPoint1Y
|
|
529
1085
|
:_timingAnimation.controlPoint2X
|
|
530
1086
|
:_timingAnimation.controlPoint2Y];
|
|
531
|
-
|
|
1087
|
+
NSMutableArray<CAAnimation *> *geometryAnimations = [NSMutableArray arrayWithArray:@[
|
|
532
1088
|
[self basicAnimationForKeyPath:@"bounds"
|
|
533
|
-
fromValue:[NSValue valueWithCGRect:
|
|
1089
|
+
fromValue:[NSValue valueWithCGRect:fromGeometry.rect]
|
|
534
1090
|
toValue:[NSValue valueWithCGRect:toBounds]
|
|
535
1091
|
timingFunction:timing],
|
|
536
1092
|
[self basicAnimationForKeyPath:@"position"
|
|
537
1093
|
fromValue:[NSValue valueWithCGPoint:fromPosition]
|
|
538
1094
|
toValue:[NSValue valueWithCGPoint:toPosition]
|
|
539
1095
|
timingFunction:timing],
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1096
|
+
]];
|
|
1097
|
+
if (usesMask) {
|
|
1098
|
+
maskAnimation = [CAAnimationGroup animation];
|
|
1099
|
+
maskAnimation.animations = @[
|
|
1100
|
+
[self basicAnimationForKeyPath:@"path"
|
|
1101
|
+
fromValue:(__bridge id)fromMaskPath
|
|
1102
|
+
toValue:(__bridge id)toMaskPath
|
|
1103
|
+
timingFunction:timing],
|
|
1104
|
+
[self basicAnimationForKeyPath:@"bounds"
|
|
1105
|
+
fromValue:[NSValue valueWithCGRect:fromGeometry.rect]
|
|
1106
|
+
toValue:[NSValue valueWithCGRect:toGeometry.rect]
|
|
1107
|
+
timingFunction:timing],
|
|
1108
|
+
[self basicAnimationForKeyPath:@"position"
|
|
1109
|
+
fromValue:[NSValue valueWithCGPoint:fromPosition]
|
|
1110
|
+
toValue:[NSValue valueWithCGPoint:toPosition]
|
|
1111
|
+
timingFunction:timing],
|
|
1112
|
+
];
|
|
1113
|
+
} else {
|
|
1114
|
+
[geometryAnimations addObject:
|
|
1115
|
+
[self basicAnimationForKeyPath:@"cornerRadius"
|
|
1116
|
+
fromValue:@(fromGeometry.radii.topLeft)
|
|
1117
|
+
toValue:@(toGeometry.radii.topLeft)
|
|
1118
|
+
timingFunction:timing]];
|
|
1119
|
+
}
|
|
1120
|
+
group.animations = geometryAnimations;
|
|
545
1121
|
group.duration = duration;
|
|
546
|
-
|
|
1122
|
+
NSMutableArray<CAAnimation *> *contentAnimations = [NSMutableArray arrayWithArray:@[
|
|
547
1123
|
[self basicAnimationForKeyPath:@"transform.translation.x"
|
|
548
1124
|
fromValue:@(fromContentTranslation.x)
|
|
549
1125
|
toValue:@(toContentTranslation.x)
|
|
@@ -552,8 +1128,43 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
552
1128
|
fromValue:@(fromContentTranslation.y)
|
|
553
1129
|
toValue:@(toContentTranslation.y)
|
|
554
1130
|
timingFunction:timing],
|
|
555
|
-
];
|
|
1131
|
+
]];
|
|
1132
|
+
if (fromContentScale != 1 || toContentScale != 1) {
|
|
1133
|
+
[contentAnimations addObject:
|
|
1134
|
+
[self basicAnimationForKeyPath:@"transform.scale"
|
|
1135
|
+
fromValue:@(fromContentScale)
|
|
1136
|
+
toValue:@(toContentScale)
|
|
1137
|
+
timingFunction:timing]];
|
|
1138
|
+
}
|
|
1139
|
+
contentGroup.animations = contentAnimations;
|
|
556
1140
|
contentGroup.duration = duration;
|
|
1141
|
+
if (animatesShadow) {
|
|
1142
|
+
NSMutableArray<CAAnimation *> *shadowAnimations = [NSMutableArray array];
|
|
1143
|
+
if (animatesShadowPath) [shadowAnimations addObject:
|
|
1144
|
+
[self basicAnimationForKeyPath:@"shadowPath"
|
|
1145
|
+
fromValue:(__bridge id)fromShadowPath
|
|
1146
|
+
toValue:(__bridge id)toShadowPath
|
|
1147
|
+
timingFunction:timing]];
|
|
1148
|
+
if (animatesShadowColor) [shadowAnimations addObject:
|
|
1149
|
+
[self basicAnimationForKeyPath:@"shadowColor"
|
|
1150
|
+
fromValue:(__bridge id)[self colorForShadow:fromShadow]
|
|
1151
|
+
toValue:(__bridge id)[self colorForShadow:toShadow]
|
|
1152
|
+
timingFunction:timing]];
|
|
1153
|
+
if (animatesShadowRadius) [shadowAnimations addObject:
|
|
1154
|
+
[self basicAnimationForKeyPath:@"shadowRadius"
|
|
1155
|
+
fromValue:@(MAX(0, fromShadow.blurRadius) / 2.0)
|
|
1156
|
+
toValue:@(MAX(0, toShadow.blurRadius) / 2.0)
|
|
1157
|
+
timingFunction:timing]];
|
|
1158
|
+
if (animatesShadowOffset) [shadowAnimations addObject:
|
|
1159
|
+
[self basicAnimationForKeyPath:@"shadowOffset"
|
|
1160
|
+
fromValue:[NSValue valueWithCGSize:CGSizeMake(
|
|
1161
|
+
fromShadow.offsetX, fromShadow.offsetY)]
|
|
1162
|
+
toValue:[NSValue valueWithCGSize:CGSizeMake(
|
|
1163
|
+
toShadow.offsetX, toShadow.offsetY)]
|
|
1164
|
+
timingFunction:timing]];
|
|
1165
|
+
shadowGroup.animations = shadowAnimations;
|
|
1166
|
+
shadowGroup.duration = duration;
|
|
1167
|
+
}
|
|
557
1168
|
} else {
|
|
558
1169
|
// `_springAnimation.initialVelocity` is the interactive motion projected
|
|
559
1170
|
// onto the current-to-target trajectory, normalized by that distance
|
|
@@ -565,24 +1176,24 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
565
1176
|
const double velocity = _springAnimation.initialVelocity;
|
|
566
1177
|
const CFTimeInterval springDuration =
|
|
567
1178
|
[self springSettlingDurationWithVelocity:velocity];
|
|
568
|
-
|
|
1179
|
+
NSMutableArray<CAAnimation *> *geometryAnimations = [NSMutableArray arrayWithArray:@[
|
|
569
1180
|
[self springAnimationForKeyPath:@"bounds.origin.x"
|
|
570
|
-
fromValue:@(
|
|
1181
|
+
fromValue:@(fromGeometry.rect.origin.x)
|
|
571
1182
|
toValue:@(toBounds.origin.x)
|
|
572
1183
|
velocity:velocity
|
|
573
1184
|
duration:springDuration],
|
|
574
1185
|
[self springAnimationForKeyPath:@"bounds.origin.y"
|
|
575
|
-
fromValue:@(
|
|
1186
|
+
fromValue:@(fromGeometry.rect.origin.y)
|
|
576
1187
|
toValue:@(toBounds.origin.y)
|
|
577
1188
|
velocity:velocity
|
|
578
1189
|
duration:springDuration],
|
|
579
1190
|
[self springAnimationForKeyPath:@"bounds.size.width"
|
|
580
|
-
fromValue:@(
|
|
1191
|
+
fromValue:@(fromGeometry.rect.size.width)
|
|
581
1192
|
toValue:@(toBounds.size.width)
|
|
582
1193
|
velocity:velocity
|
|
583
1194
|
duration:springDuration],
|
|
584
1195
|
[self springAnimationForKeyPath:@"bounds.size.height"
|
|
585
|
-
fromValue:@(
|
|
1196
|
+
fromValue:@(fromGeometry.rect.size.height)
|
|
586
1197
|
toValue:@(toBounds.size.height)
|
|
587
1198
|
velocity:velocity
|
|
588
1199
|
duration:springDuration],
|
|
@@ -596,17 +1207,59 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
596
1207
|
toValue:@(toPosition.y)
|
|
597
1208
|
velocity:velocity
|
|
598
1209
|
duration:springDuration],
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
1210
|
+
]];
|
|
1211
|
+
if (usesMask) {
|
|
1212
|
+
maskAnimation = [CAAnimationGroup animation];
|
|
1213
|
+
maskAnimation.animations = @[
|
|
1214
|
+
[self springAnimationForKeyPath:@"path"
|
|
1215
|
+
fromValue:(__bridge id)fromMaskPath
|
|
1216
|
+
toValue:(__bridge id)toMaskPath
|
|
1217
|
+
velocity:velocity
|
|
1218
|
+
duration:springDuration],
|
|
1219
|
+
[self springAnimationForKeyPath:@"bounds.origin.x"
|
|
1220
|
+
fromValue:@(fromGeometry.rect.origin.x)
|
|
1221
|
+
toValue:@(toGeometry.rect.origin.x)
|
|
1222
|
+
velocity:velocity
|
|
1223
|
+
duration:springDuration],
|
|
1224
|
+
[self springAnimationForKeyPath:@"bounds.origin.y"
|
|
1225
|
+
fromValue:@(fromGeometry.rect.origin.y)
|
|
1226
|
+
toValue:@(toGeometry.rect.origin.y)
|
|
1227
|
+
velocity:velocity
|
|
1228
|
+
duration:springDuration],
|
|
1229
|
+
[self springAnimationForKeyPath:@"bounds.size.width"
|
|
1230
|
+
fromValue:@(fromGeometry.rect.size.width)
|
|
1231
|
+
toValue:@(toGeometry.rect.size.width)
|
|
1232
|
+
velocity:velocity
|
|
1233
|
+
duration:springDuration],
|
|
1234
|
+
[self springAnimationForKeyPath:@"bounds.size.height"
|
|
1235
|
+
fromValue:@(fromGeometry.rect.size.height)
|
|
1236
|
+
toValue:@(toGeometry.rect.size.height)
|
|
1237
|
+
velocity:velocity
|
|
1238
|
+
duration:springDuration],
|
|
1239
|
+
[self springAnimationForKeyPath:@"position.x"
|
|
1240
|
+
fromValue:@(fromPosition.x)
|
|
1241
|
+
toValue:@(toPosition.x)
|
|
1242
|
+
velocity:velocity
|
|
1243
|
+
duration:springDuration],
|
|
1244
|
+
[self springAnimationForKeyPath:@"position.y"
|
|
1245
|
+
fromValue:@(fromPosition.y)
|
|
1246
|
+
toValue:@(toPosition.y)
|
|
1247
|
+
velocity:velocity
|
|
1248
|
+
duration:springDuration],
|
|
1249
|
+
];
|
|
1250
|
+
} else {
|
|
1251
|
+
[geometryAnimations addObject:
|
|
1252
|
+
[self springAnimationForKeyPath:@"cornerRadius"
|
|
1253
|
+
fromValue:@(fromGeometry.radii.topLeft)
|
|
1254
|
+
toValue:@(toGeometry.radii.topLeft)
|
|
1255
|
+
velocity:velocity
|
|
1256
|
+
duration:springDuration]];
|
|
1257
|
+
}
|
|
1258
|
+
group.animations = geometryAnimations;
|
|
1259
|
+
// Springs use their analytic settling time; a wall-clock duration would
|
|
1260
|
+
// truncate oscillation and snap to the target.
|
|
608
1261
|
group.duration = springDuration;
|
|
609
|
-
|
|
1262
|
+
NSMutableArray<CAAnimation *> *contentAnimations = [NSMutableArray arrayWithArray:@[
|
|
610
1263
|
[self springAnimationForKeyPath:@"transform.translation.x"
|
|
611
1264
|
fromValue:@(fromContentTranslation.x)
|
|
612
1265
|
toValue:@(toContentTranslation.x)
|
|
@@ -617,12 +1270,74 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
617
1270
|
toValue:@(toContentTranslation.y)
|
|
618
1271
|
velocity:velocity
|
|
619
1272
|
duration:springDuration],
|
|
620
|
-
];
|
|
1273
|
+
]];
|
|
1274
|
+
if (fromContentScale != 1 || toContentScale != 1) {
|
|
1275
|
+
[contentAnimations addObject:
|
|
1276
|
+
[self springAnimationForKeyPath:@"transform.scale"
|
|
1277
|
+
fromValue:@(fromContentScale)
|
|
1278
|
+
toValue:@(toContentScale)
|
|
1279
|
+
velocity:velocity
|
|
1280
|
+
duration:springDuration]];
|
|
1281
|
+
}
|
|
1282
|
+
contentGroup.animations = contentAnimations;
|
|
621
1283
|
contentGroup.duration = springDuration;
|
|
1284
|
+
if (animatesShadow) {
|
|
1285
|
+
NSMutableArray<CAAnimation *> *shadowAnimations = [NSMutableArray array];
|
|
1286
|
+
if (animatesShadowPath) [shadowAnimations addObject:
|
|
1287
|
+
[self springAnimationForKeyPath:@"shadowPath"
|
|
1288
|
+
fromValue:(__bridge id)fromShadowPath
|
|
1289
|
+
toValue:(__bridge id)toShadowPath
|
|
1290
|
+
velocity:velocity
|
|
1291
|
+
duration:springDuration]];
|
|
1292
|
+
if (animatesShadowColor) [shadowAnimations addObject:
|
|
1293
|
+
[self springAnimationForKeyPath:@"shadowColor"
|
|
1294
|
+
fromValue:(__bridge id)[self colorForShadow:fromShadow]
|
|
1295
|
+
toValue:(__bridge id)[self colorForShadow:toShadow]
|
|
1296
|
+
velocity:velocity
|
|
1297
|
+
duration:springDuration]];
|
|
1298
|
+
if (animatesShadowRadius) [shadowAnimations addObject:
|
|
1299
|
+
[self springAnimationForKeyPath:@"shadowRadius"
|
|
1300
|
+
fromValue:@(MAX(0, fromShadow.blurRadius) / 2.0)
|
|
1301
|
+
toValue:@(MAX(0, toShadow.blurRadius) / 2.0)
|
|
1302
|
+
velocity:velocity
|
|
1303
|
+
duration:springDuration]];
|
|
1304
|
+
if (animatesShadowOffset) [shadowAnimations addObject:
|
|
1305
|
+
[self springAnimationForKeyPath:@"shadowOffset"
|
|
1306
|
+
fromValue:[NSValue valueWithCGSize:CGSizeMake(
|
|
1307
|
+
fromShadow.offsetX, fromShadow.offsetY)]
|
|
1308
|
+
toValue:[NSValue valueWithCGSize:CGSizeMake(
|
|
1309
|
+
toShadow.offsetX, toShadow.offsetY)]
|
|
1310
|
+
velocity:velocity
|
|
1311
|
+
duration:springDuration]];
|
|
1312
|
+
shadowGroup.animations = shadowAnimations;
|
|
1313
|
+
shadowGroup.duration = springDuration;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (maskAnimation != nil) {
|
|
1318
|
+
maskAnimation.duration = group.duration;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (sharedBeginTime > 0) {
|
|
1322
|
+
// Each layer receives the same absolute media timestamp translated into
|
|
1323
|
+
// its local clock. Group participants therefore advance from one epoch
|
|
1324
|
+
// even though their animations are installed sequentially.
|
|
1325
|
+
group.beginTime = [layer convertTime:sharedBeginTime fromLayer:nil];
|
|
1326
|
+
contentGroup.beginTime = [_contentContainer.layer
|
|
1327
|
+
convertTime:sharedBeginTime
|
|
1328
|
+
fromLayer:nil];
|
|
1329
|
+
if (animatesShadow) {
|
|
1330
|
+
shadowGroup.beginTime = [_shadowLayer
|
|
1331
|
+
convertTime:sharedBeginTime
|
|
1332
|
+
fromLayer:nil];
|
|
1333
|
+
}
|
|
1334
|
+
if (maskAnimation != nil) {
|
|
1335
|
+
maskAnimation.beginTime = [_unequalCornerMask
|
|
1336
|
+
convertTime:sharedBeginTime
|
|
1337
|
+
fromLayer:nil];
|
|
1338
|
+
}
|
|
622
1339
|
}
|
|
623
1340
|
|
|
624
|
-
_animationDuration = group.duration;
|
|
625
|
-
_animationStartedAt = CACurrentMediaTime();
|
|
626
1341
|
_animationDelegate = [SmoothClipAnimationDelegate new];
|
|
627
1342
|
_animationDelegate.view = self;
|
|
628
1343
|
_animationDelegate.driverId = _driverId;
|
|
@@ -631,189 +1346,122 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
631
1346
|
[layer addAnimation:group forKey:@"smoothClip.geometry"];
|
|
632
1347
|
[_contentContainer.layer addAnimation:contentGroup
|
|
633
1348
|
forKey:@"smoothClip.content"];
|
|
634
|
-
|
|
1349
|
+
if (animatesShadow) {
|
|
1350
|
+
[_shadowLayer addAnimation:shadowGroup forKey:@"smoothClip.shadow"];
|
|
1351
|
+
}
|
|
1352
|
+
if (maskAnimation != nil) {
|
|
1353
|
+
[_unequalCornerMask addAnimation:maskAnimation forKey:@"smoothClip.mask"];
|
|
1354
|
+
}
|
|
1355
|
+
if (fromMaskPath != nil) CGPathRelease(fromMaskPath);
|
|
1356
|
+
if (toMaskPath != nil) CGPathRelease(toMaskPath);
|
|
1357
|
+
if (fromShadowPath != nil) CGPathRelease(fromShadowPath);
|
|
1358
|
+
if (toShadowPath != nil) CGPathRelease(toShadowPath);
|
|
635
1359
|
}
|
|
636
1360
|
|
|
637
|
-
- (BOOL)startAnimationToRequestedGeometryWithDuration:(CFTimeInterval)duration
|
|
1361
|
+
- (BOOL)startAnimationToRequestedGeometryWithDuration:(CFTimeInterval)duration
|
|
1362
|
+
sharedBeginTime:
|
|
1363
|
+
(CFTimeInterval)sharedBeginTime {
|
|
638
1364
|
if (![self smoothClipCanDisplay]) {
|
|
639
|
-
_pendingAnimationInstall = YES;
|
|
640
1365
|
return NO;
|
|
641
1366
|
}
|
|
642
|
-
|
|
643
|
-
if (![self
|
|
1367
|
+
SmoothClipCanonicalGeometry target;
|
|
1368
|
+
if (![self canonicalRequestedGeometry:&target]) return NO;
|
|
644
1369
|
const smoothclip::Presentation current = [self smoothClipCurrentPresentation];
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
1370
|
+
const SmoothClipCornerRadii currentRadii =
|
|
1371
|
+
SmoothClipPresentationRadii(current.clip);
|
|
1372
|
+
const SmoothClipCanonicalGeometry fromGeometry = {
|
|
1373
|
+
.rect = CGRectMake(
|
|
1374
|
+
current.clip.x,
|
|
1375
|
+
current.clip.y,
|
|
1376
|
+
current.clip.width,
|
|
1377
|
+
current.clip.height),
|
|
1378
|
+
.radius = SmoothClipCornerRadiiAreUniform(currentRadii)
|
|
1379
|
+
? currentRadii.topLeft
|
|
1380
|
+
: 0,
|
|
1381
|
+
.radii = currentRadii,
|
|
1382
|
+
.curve = SmoothClipPresentationCurve(current.clip),
|
|
1383
|
+
};
|
|
1384
|
+
[self installAnimationFromGeometry:fromGeometry
|
|
1385
|
+
fromContentTranslation:CGPointMake(
|
|
1386
|
+
current.contentTranslateX,
|
|
1387
|
+
current.contentTranslateY)
|
|
1388
|
+
fromContentScale:current.contentScale
|
|
1389
|
+
fromShadow:current.shadow
|
|
1390
|
+
toGeometry:target
|
|
1391
|
+
hasExplicitToRadii:_requestedHasExplicitRadii
|
|
1392
|
+
toContentTranslation:_requestedContentTranslation
|
|
1393
|
+
toContentScale:_requestedContentScale
|
|
1394
|
+
toShadow:_requestedShadow
|
|
1395
|
+
duration:duration
|
|
1396
|
+
sharedBeginTime:sharedBeginTime];
|
|
658
1397
|
return YES;
|
|
659
1398
|
}
|
|
660
1399
|
|
|
661
1400
|
- (BOOL)smoothClipAnimateTiming:(smoothclip::Presentation)presentation
|
|
662
1401
|
animation:(smoothclip::TimingAnimation)animation
|
|
663
1402
|
animationId:(int32_t)animationId {
|
|
1403
|
+
return [self smoothClipAnimateTiming:presentation
|
|
1404
|
+
animation:animation
|
|
1405
|
+
animationId:animationId
|
|
1406
|
+
sharedBeginTime:0];
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
- (BOOL)smoothClipAnimateTiming:(smoothclip::Presentation)presentation
|
|
1410
|
+
animation:(smoothclip::TimingAnimation)animation
|
|
1411
|
+
animationId:(int32_t)animationId
|
|
1412
|
+
sharedBeginTime:(CFTimeInterval)sharedBeginTime {
|
|
664
1413
|
[self storeRequestedPresentation:presentation];
|
|
665
1414
|
_timingAnimation = animation;
|
|
666
1415
|
_activeAnimationKind = 1;
|
|
667
1416
|
_activeAnimationId = animationId;
|
|
668
1417
|
return [self startAnimationToRequestedGeometryWithDuration:
|
|
669
|
-
MAX(0, animation.durationMs) / 1000.0
|
|
1418
|
+
MAX(0, animation.durationMs) / 1000.0
|
|
1419
|
+
sharedBeginTime:sharedBeginTime];
|
|
670
1420
|
}
|
|
671
1421
|
|
|
672
1422
|
- (BOOL)smoothClipAnimateSpring:(smoothclip::Presentation)presentation
|
|
673
1423
|
animation:(smoothclip::SpringAnimation)animation
|
|
674
1424
|
animationId:(int32_t)animationId {
|
|
1425
|
+
return [self smoothClipAnimateSpring:presentation
|
|
1426
|
+
animation:animation
|
|
1427
|
+
animationId:animationId
|
|
1428
|
+
sharedBeginTime:0];
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
- (BOOL)smoothClipAnimateSpring:(smoothclip::Presentation)presentation
|
|
1432
|
+
animation:(smoothclip::SpringAnimation)animation
|
|
1433
|
+
animationId:(int32_t)animationId
|
|
1434
|
+
sharedBeginTime:(CFTimeInterval)sharedBeginTime {
|
|
675
1435
|
[self storeRequestedPresentation:presentation];
|
|
676
1436
|
_springAnimation = animation;
|
|
677
1437
|
if (animation.inheritVelocity) {
|
|
678
|
-
|
|
679
|
-
if ([self
|
|
1438
|
+
SmoothClipCanonicalGeometry target;
|
|
1439
|
+
if ([self canonicalRequestedGeometry:&target]) {
|
|
680
1440
|
_springAnimation.initialVelocity =
|
|
681
1441
|
[self inheritedVelocityToRect:target.rect
|
|
682
|
-
|
|
683
|
-
contentTranslation:_requestedContentTranslation
|
|
1442
|
+
radii:target.radii
|
|
1443
|
+
contentTranslation:_requestedContentTranslation
|
|
1444
|
+
contentScale:_requestedContentScale];
|
|
684
1445
|
} else {
|
|
685
1446
|
_springAnimation.initialVelocity = 0;
|
|
686
1447
|
}
|
|
687
1448
|
}
|
|
688
1449
|
_activeAnimationKind = 2;
|
|
689
1450
|
_activeAnimationId = animationId;
|
|
690
|
-
return [self startAnimationToRequestedGeometryWithDuration:0
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
- (CAKeyframeAnimation *)keyframeAnimationForKeyPath:(NSString *)keyPath
|
|
694
|
-
values:(NSArray *)values
|
|
695
|
-
keyTimes:(NSArray<NSNumber *> *)keyTimes {
|
|
696
|
-
CAKeyframeAnimation *animation =
|
|
697
|
-
[CAKeyframeAnimation animationWithKeyPath:keyPath];
|
|
698
|
-
animation.values = values;
|
|
699
|
-
animation.keyTimes = keyTimes;
|
|
700
|
-
animation.calculationMode = kCAAnimationLinear;
|
|
701
|
-
return animation;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
- (BOOL)smoothClipAnimateKeyframes:(smoothclip::Presentation)presentation
|
|
705
|
-
keyframes:(const std::vector<smoothclip::Keyframe> &)keyframes
|
|
706
|
-
durationMs:(double)durationMs
|
|
707
|
-
animationId:(int32_t)animationId {
|
|
708
|
-
if (keyframes.size() < 2) return NO;
|
|
709
|
-
[self storeRequestedPresentation:presentation];
|
|
710
|
-
_activeAnimationKind = 3;
|
|
711
|
-
_activeAnimationId = animationId;
|
|
712
|
-
if (![self smoothClipCanDisplay]) {
|
|
713
|
-
_pendingAnimationInstall = YES;
|
|
714
|
-
return NO;
|
|
715
|
-
}
|
|
716
|
-
[self stopLayerAnimationWithoutCallback];
|
|
717
|
-
_activeAnimationKind = 3;
|
|
718
|
-
_activeAnimationId = animationId;
|
|
719
|
-
|
|
720
|
-
const NSUInteger frameCount = keyframes.size();
|
|
721
|
-
const CGSize hostSize = self.bounds.size;
|
|
722
|
-
NSMutableArray<NSNumber *> *times =
|
|
723
|
-
[NSMutableArray arrayWithCapacity:frameCount];
|
|
724
|
-
NSMutableArray *boundsValues = [NSMutableArray arrayWithCapacity:frameCount];
|
|
725
|
-
NSMutableArray *positionValues =
|
|
726
|
-
[NSMutableArray arrayWithCapacity:frameCount];
|
|
727
|
-
NSMutableArray *radiusValues = [NSMutableArray arrayWithCapacity:frameCount];
|
|
728
|
-
NSMutableArray *translateXValues =
|
|
729
|
-
[NSMutableArray arrayWithCapacity:frameCount];
|
|
730
|
-
NSMutableArray *translateYValues =
|
|
731
|
-
[NSMutableArray arrayWithCapacity:frameCount];
|
|
732
|
-
for (const smoothclip::Keyframe &frame : keyframes) {
|
|
733
|
-
SmoothNormalizedClipGeometry normalized;
|
|
734
|
-
if (!SmoothClipNormalizeGeometry(
|
|
735
|
-
frame.presentation.clip.x,
|
|
736
|
-
frame.presentation.clip.y,
|
|
737
|
-
frame.presentation.clip.width,
|
|
738
|
-
frame.presentation.clip.height,
|
|
739
|
-
frame.presentation.clip.radius,
|
|
740
|
-
hostSize,
|
|
741
|
-
&normalized)) return NO;
|
|
742
|
-
[times addObject:@(frame.offset)];
|
|
743
|
-
[boundsValues addObject:[NSValue valueWithCGRect:normalized.rect]];
|
|
744
|
-
[positionValues addObject:[NSValue valueWithCGPoint:CGPointMake(
|
|
745
|
-
CGRectGetMidX(normalized.rect), CGRectGetMidY(normalized.rect))]];
|
|
746
|
-
[radiusValues addObject:@(normalized.radius)];
|
|
747
|
-
[translateXValues addObject:@(frame.presentation.contentTranslateX)];
|
|
748
|
-
[translateYValues addObject:@(frame.presentation.contentTranslateY)];
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
SmoothNormalizedClipGeometry target;
|
|
752
|
-
if (![self normalizedRequestedGeometry:&target]) return NO;
|
|
753
|
-
[self writeLayerRect:target.rect radius:target.radius];
|
|
754
|
-
[self writeContentTranslation:_requestedContentTranslation];
|
|
755
|
-
[self setClipContainerHidden:NO];
|
|
756
|
-
|
|
757
|
-
CAAnimationGroup *group = [CAAnimationGroup animation];
|
|
758
|
-
group.animations = @[
|
|
759
|
-
[self keyframeAnimationForKeyPath:@"bounds"
|
|
760
|
-
values:boundsValues
|
|
761
|
-
keyTimes:times],
|
|
762
|
-
[self keyframeAnimationForKeyPath:@"position"
|
|
763
|
-
values:positionValues
|
|
764
|
-
keyTimes:times],
|
|
765
|
-
[self keyframeAnimationForKeyPath:@"cornerRadius"
|
|
766
|
-
values:radiusValues
|
|
767
|
-
keyTimes:times],
|
|
768
|
-
];
|
|
769
|
-
group.duration = MAX(0, durationMs) / 1000.0;
|
|
770
|
-
CAAnimationGroup *contentGroup = [CAAnimationGroup animation];
|
|
771
|
-
contentGroup.animations = @[
|
|
772
|
-
[self keyframeAnimationForKeyPath:@"transform.translation.x"
|
|
773
|
-
values:translateXValues
|
|
774
|
-
keyTimes:times],
|
|
775
|
-
[self keyframeAnimationForKeyPath:@"transform.translation.y"
|
|
776
|
-
values:translateYValues
|
|
777
|
-
keyTimes:times],
|
|
778
|
-
];
|
|
779
|
-
contentGroup.duration = group.duration;
|
|
780
|
-
|
|
781
|
-
_animationDuration = group.duration;
|
|
782
|
-
_animationStartedAt = CACurrentMediaTime();
|
|
783
|
-
_animationDelegate = [SmoothClipAnimationDelegate new];
|
|
784
|
-
_animationDelegate.view = self;
|
|
785
|
-
_animationDelegate.driverId = _driverId;
|
|
786
|
-
_animationDelegate.animationId = _activeAnimationId;
|
|
787
|
-
group.delegate = _animationDelegate;
|
|
788
|
-
[_clipContainer.layer addAnimation:group forKey:@"smoothClip.geometry"];
|
|
789
|
-
[_contentContainer.layer addAnimation:contentGroup
|
|
790
|
-
forKey:@"smoothClip.content"];
|
|
791
|
-
_animationInstallGeneration += 1;
|
|
792
|
-
return YES;
|
|
1451
|
+
return [self startAnimationToRequestedGeometryWithDuration:0
|
|
1452
|
+
sharedBeginTime:sharedBeginTime];
|
|
793
1453
|
}
|
|
794
1454
|
|
|
795
1455
|
- (void)smoothClipCancelAnimationUsingTarget:(BOOL)useTarget {
|
|
796
1456
|
if (_activeAnimationId == 0) return;
|
|
797
1457
|
const smoothclip::Presentation visible =
|
|
798
1458
|
[self smoothClipCurrentPresentation];
|
|
799
|
-
const CGRect visibleRect = CGRectMake(
|
|
800
|
-
visible.clip.x,
|
|
801
|
-
visible.clip.y,
|
|
802
|
-
visible.clip.width,
|
|
803
|
-
visible.clip.height);
|
|
804
|
-
const CGPoint visibleContentTranslation = CGPointMake(
|
|
805
|
-
visible.contentTranslateX,
|
|
806
|
-
visible.contentTranslateY);
|
|
807
1459
|
[self stopLayerAnimationWithoutCallback];
|
|
808
1460
|
if (useTarget) {
|
|
809
1461
|
[self applyRequestedClip];
|
|
810
1462
|
} else {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
_requestedContentTranslation = visibleContentTranslation;
|
|
814
|
-
[self writeLayerRect:visibleRect radius:visible.clip.radius];
|
|
815
|
-
[self writeContentTranslation:visibleContentTranslation];
|
|
816
|
-
[self syncVisibilityForRect:visibleRect];
|
|
1463
|
+
[self storeRequestedPresentation:visible];
|
|
1464
|
+
[self applyRequestedClip];
|
|
817
1465
|
}
|
|
818
1466
|
}
|
|
819
1467
|
|
|
@@ -824,7 +1472,12 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
824
1472
|
_activeAnimationId = 0;
|
|
825
1473
|
_activeAnimationKind = 0;
|
|
826
1474
|
_animationDelegate = nil;
|
|
827
|
-
[
|
|
1475
|
+
[_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
|
|
1476
|
+
[_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
|
|
1477
|
+
[self applyStaticCornerRepresentation:[self canonicalGeometryValue]];
|
|
1478
|
+
[self writeShadow:_requestedShadow
|
|
1479
|
+
geometry:[self canonicalGeometryValue]];
|
|
1480
|
+
[self syncVisibilityForRect:_canonicalClip];
|
|
828
1481
|
smoothclip::viewAnimationDidStop(
|
|
829
1482
|
driverId, animationId, self, finished);
|
|
830
1483
|
}
|
|
@@ -834,19 +1487,40 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
834
1487
|
const auto &newProps =
|
|
835
1488
|
*std::static_pointer_cast<const SmoothClipViewProps>(props);
|
|
836
1489
|
[super updateProps:props oldProps:oldProps];
|
|
1490
|
+
self.clipsToBounds = YES;
|
|
837
1491
|
|
|
838
1492
|
const uint64_t nextDriverId =
|
|
839
1493
|
isfinite(newProps.driverId) && newProps.driverId > 0
|
|
840
1494
|
? static_cast<uint64_t>(newProps.driverId)
|
|
841
1495
|
: 0;
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1496
|
+
smoothclip::Presentation initial{};
|
|
1497
|
+
if (!SmoothClipBuildPresentation(
|
|
1498
|
+
newProps.initialClipX,
|
|
1499
|
+
newProps.initialClipY,
|
|
1500
|
+
newProps.initialClipWidth,
|
|
1501
|
+
newProps.initialClipHeight,
|
|
1502
|
+
newProps.initialClipTopLeftRadius,
|
|
1503
|
+
newProps.initialClipTopRightRadius,
|
|
1504
|
+
newProps.initialClipBottomRightRadius,
|
|
1505
|
+
newProps.initialClipBottomLeftRadius,
|
|
1506
|
+
newProps.initialClipCurve,
|
|
1507
|
+
newProps.initialContentTranslateX,
|
|
1508
|
+
newProps.initialContentTranslateY,
|
|
1509
|
+
newProps.initialContentScale,
|
|
1510
|
+
newProps.initialClipBoxShadowEnabled,
|
|
1511
|
+
newProps.initialClipBoxShadowRed,
|
|
1512
|
+
newProps.initialClipBoxShadowGreen,
|
|
1513
|
+
newProps.initialClipBoxShadowBlue,
|
|
1514
|
+
newProps.initialClipBoxShadowAlpha,
|
|
1515
|
+
newProps.initialClipBoxShadowOffsetX,
|
|
1516
|
+
newProps.initialClipBoxShadowOffsetY,
|
|
1517
|
+
newProps.initialClipBoxShadowBlurRadius,
|
|
1518
|
+
newProps.initialClipBoxShadowSpreadDistance,
|
|
1519
|
+
&initial)) {
|
|
1520
|
+
// The initial presentation is atomic. Never apply a valid subset when one
|
|
1521
|
+
// field rejects.
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
850
1524
|
|
|
851
1525
|
if (nextDriverId != _driverId) {
|
|
852
1526
|
if (_driverId != 0) {
|
|
@@ -872,160 +1546,95 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
872
1546
|
|
|
873
1547
|
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
|
|
874
1548
|
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
|
|
875
|
-
CGFloat visibleRadius = 0;
|
|
876
|
-
const BOOL wasAnimating = _activeAnimationId != 0;
|
|
877
|
-
const BOOL wasPending = _pendingAnimationInstall;
|
|
878
|
-
const uint32_t installGenerationAtEntry = _animationInstallGeneration;
|
|
879
|
-
const smoothclip::Presentation visible = wasAnimating && !wasPending
|
|
880
|
-
? [self smoothClipCurrentPresentation]
|
|
881
|
-
: smoothclip::Presentation{{0, 0, 0, 0, 0}, 0, 0};
|
|
882
|
-
const CGRect visibleRect = CGRectMake(
|
|
883
|
-
visible.clip.x,
|
|
884
|
-
visible.clip.y,
|
|
885
|
-
visible.clip.width,
|
|
886
|
-
visible.clip.height);
|
|
887
|
-
visibleRadius = visible.clip.radius;
|
|
888
|
-
const CGPoint visibleContentTranslation = CGPointMake(
|
|
889
|
-
visible.contentTranslateX,
|
|
890
|
-
visible.contentTranslateY);
|
|
891
|
-
CFTimeInterval remaining = wasAnimating
|
|
892
|
-
? MAX(0, _animationDuration -
|
|
893
|
-
(CACurrentMediaTime() - _animationStartedAt))
|
|
894
|
-
: 0;
|
|
895
|
-
const double springContinuationVelocity =
|
|
896
|
-
wasAnimating && !wasPending && _activeAnimationKind == 2
|
|
897
|
-
? [self smoothClipSpringContinuationVelocity]
|
|
898
|
-
: 0;
|
|
899
|
-
|
|
900
1549
|
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
|
|
901
1550
|
_hasLayout = YES;
|
|
902
1551
|
[self layoutContentContainer];
|
|
903
1552
|
if (_driverId != 0) {
|
|
904
|
-
// Both edges matter: zero size/detach/background suspend an installed
|
|
905
|
-
// participant; the first positive visible layout resumes the held latch.
|
|
906
1553
|
smoothclip::viewDisplayabilityChanged(_driverId, self);
|
|
907
1554
|
}
|
|
908
|
-
if (
|
|
909
|
-
|
|
910
|
-
// latch. Relayout must not independently restart a background-held id.
|
|
911
|
-
if (_activeAnimationId == 0) [self applyRequestedClip];
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
if (wasAnimating) {
|
|
915
|
-
// A negative transition may have frozen this host and re-latched the
|
|
916
|
-
// registry above. Its non-recording freeze is already the correct model
|
|
917
|
-
// state for the new lifecycle; do not resurrect a local timer.
|
|
918
|
-
if (_activeAnimationId == 0 || ![self smoothClipCanDisplay]) return;
|
|
919
|
-
// The lifecycle notification above may itself have installed this
|
|
920
|
-
// animation from the registry's trimmed remainder (a latch resume inside
|
|
921
|
-
// this very layout pass). That install rebased the clocks; the `remaining`
|
|
922
|
-
// captured on entry predates the suspension and would rebuild a shorter —
|
|
923
|
-
// possibly zero-duration — run on top of the fresh one. The id alone
|
|
924
|
-
// cannot detect this (a resume keeps the animation id), so compare the
|
|
925
|
-
// install generation.
|
|
926
|
-
if (_animationInstallGeneration != installGenerationAtEntry) return;
|
|
927
|
-
const int32_t animationId = _activeAnimationId;
|
|
928
|
-
const NSInteger animationKind = _activeAnimationKind;
|
|
929
|
-
[self stopLayerAnimationWithoutCallback];
|
|
930
|
-
_activeAnimationId = animationId;
|
|
931
|
-
_activeAnimationKind = animationKind;
|
|
932
|
-
if (_activeAnimationKind == 1 && _animationDuration > 0) {
|
|
933
|
-
const double progress = smoothclip::clamp01(
|
|
934
|
-
1 - remaining / _animationDuration);
|
|
935
|
-
_timingAnimation =
|
|
936
|
-
smoothclip::timingRemainder(_timingAnimation, progress).animation;
|
|
937
|
-
remaining = _timingAnimation.durationMs / 1000.0;
|
|
938
|
-
} else if (_activeAnimationKind == 2) {
|
|
939
|
-
_springAnimation.initialVelocity = springContinuationVelocity;
|
|
940
|
-
_springAnimation.inheritVelocity = false;
|
|
941
|
-
} else if (_activeAnimationKind == 3) {
|
|
942
|
-
_activeAnimationKind = 1;
|
|
943
|
-
_timingAnimation = {remaining * 1000.0, 0, 0, 1, 1, 2};
|
|
944
|
-
}
|
|
945
|
-
SmoothNormalizedClipGeometry current;
|
|
946
|
-
SmoothClipNormalizeGeometry(
|
|
947
|
-
CGRectGetMinX(visibleRect),
|
|
948
|
-
CGRectGetMinY(visibleRect),
|
|
949
|
-
CGRectGetWidth(visibleRect),
|
|
950
|
-
CGRectGetHeight(visibleRect),
|
|
951
|
-
visibleRadius,
|
|
952
|
-
self.bounds.size,
|
|
953
|
-
¤t);
|
|
954
|
-
SmoothNormalizedClipGeometry target;
|
|
955
|
-
if ([self normalizedRequestedGeometry:&target]) {
|
|
956
|
-
[self installAnimationFromRect:current.rect
|
|
957
|
-
fromRadius:current.radius
|
|
958
|
-
fromContentTranslation:visibleContentTranslation
|
|
959
|
-
toRect:target.rect
|
|
960
|
-
toRadius:target.radius
|
|
961
|
-
toContentTranslation:_requestedContentTranslation
|
|
962
|
-
duration:remaining];
|
|
963
|
-
}
|
|
964
|
-
} else {
|
|
965
|
-
if (_driverId != 0 && smoothclip::hasActiveAnimation(_driverId)) {
|
|
966
|
-
// Registry-level lifecycle pause owns the frozen model. A layout pass
|
|
967
|
-
// while its latch is held must not apply the requested target or start a
|
|
968
|
-
// per-view timer; foreground/reattach will normalize and resume it once.
|
|
969
|
-
return;
|
|
970
|
-
}
|
|
1555
|
+
if (_activeAnimationId == 0 &&
|
|
1556
|
+
(_driverId == 0 || !smoothclip::hasActiveAnimation(_driverId))) {
|
|
971
1557
|
[self applyRequestedClip];
|
|
972
1558
|
}
|
|
973
1559
|
}
|
|
974
1560
|
|
|
975
1561
|
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
|
|
976
1562
|
if (![super pointInside:point withEvent:event]) return NO;
|
|
977
|
-
|
|
1563
|
+
SmoothClipCornerRadii radii = _canonicalRadii;
|
|
978
1564
|
const CGRect clip = _activeAnimationId != 0
|
|
979
|
-
? [self
|
|
980
|
-
:
|
|
981
|
-
if (_activeAnimationId == 0) radius = _normalizedRadius;
|
|
1565
|
+
? [self presentationRectWithRadii:&radii]
|
|
1566
|
+
: _canonicalClip;
|
|
982
1567
|
if (CGRectIsEmpty(clip) || !CGRectContainsPoint(clip, point)) return NO;
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
return YES;
|
|
1568
|
+
CGPathRef path = nil;
|
|
1569
|
+
if (_clipContainer.layer.mask == _unequalCornerMask) {
|
|
1570
|
+
CAShapeLayer *mask = _activeAnimationId != 0
|
|
1571
|
+
? (CAShapeLayer *)_unequalCornerMask.presentationLayer
|
|
1572
|
+
: _unequalCornerMask;
|
|
1573
|
+
if (mask == nil) mask = _unequalCornerMask;
|
|
1574
|
+
path = mask.path;
|
|
1575
|
+
return path != nil && CGPathContainsPoint(path, NULL, point, NO);
|
|
992
1576
|
}
|
|
993
|
-
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
const CGFloat dy = point.y - centerY;
|
|
998
|
-
return dx * dx + dy * dy <= radius * radius;
|
|
1577
|
+
path = SmoothClipCreateRoundedRectPath(clip, radii, _canonicalCurve);
|
|
1578
|
+
const BOOL contains = CGPathContainsPoint(path, NULL, point, NO);
|
|
1579
|
+
CGPathRelease(path);
|
|
1580
|
+
return contains;
|
|
999
1581
|
}
|
|
1000
1582
|
|
|
1001
|
-
#pragma mark -
|
|
1583
|
+
#pragma mark - Native commands
|
|
1002
1584
|
|
|
1003
1585
|
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
|
|
1004
1586
|
RCTSmoothClipViewHandleCommand(self, commandName, args);
|
|
1005
1587
|
}
|
|
1006
1588
|
|
|
1007
|
-
- (void)setClipGeometry:(double)x
|
|
1008
|
-
y:(double)y
|
|
1009
|
-
width:(double)width
|
|
1010
|
-
height:(double)height
|
|
1011
|
-
radius:(double)radius {
|
|
1012
|
-
_commandIsAuthoritative = YES;
|
|
1013
|
-
[self smoothClipApplyPresentation:
|
|
1014
|
-
{{x, y, width, height, radius}, 0, 0}];
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
1589
|
- (void)setClipPresentation:(double)x
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1590
|
+
y:(double)y
|
|
1591
|
+
width:(double)width
|
|
1592
|
+
height:(double)height
|
|
1593
|
+
topLeftRadius:(double)topLeftRadius
|
|
1594
|
+
topRightRadius:(double)topRightRadius
|
|
1595
|
+
bottomRightRadius:(double)bottomRightRadius
|
|
1596
|
+
bottomLeftRadius:(double)bottomLeftRadius
|
|
1597
|
+
curveCode:(NSInteger)curveCode
|
|
1598
|
+
contentTranslateX:(double)contentTranslateX
|
|
1599
|
+
contentTranslateY:(double)contentTranslateY
|
|
1600
|
+
contentScale:(double)contentScale
|
|
1601
|
+
shadowEnabled:(BOOL)shadowEnabled
|
|
1602
|
+
shadowRed:(double)shadowRed
|
|
1603
|
+
shadowGreen:(double)shadowGreen
|
|
1604
|
+
shadowBlue:(double)shadowBlue
|
|
1605
|
+
shadowAlpha:(double)shadowAlpha
|
|
1606
|
+
shadowOffsetX:(double)shadowOffsetX
|
|
1607
|
+
shadowOffsetY:(double)shadowOffsetY
|
|
1608
|
+
shadowBlurRadius:(double)shadowBlurRadius
|
|
1609
|
+
shadowSpreadDistance:(double)shadowSpreadDistance {
|
|
1610
|
+
smoothclip::Presentation presentation{};
|
|
1611
|
+
if (!SmoothClipBuildPresentation(
|
|
1612
|
+
x,
|
|
1613
|
+
y,
|
|
1614
|
+
width,
|
|
1615
|
+
height,
|
|
1616
|
+
topLeftRadius,
|
|
1617
|
+
topRightRadius,
|
|
1618
|
+
bottomRightRadius,
|
|
1619
|
+
bottomLeftRadius,
|
|
1620
|
+
curveCode,
|
|
1621
|
+
contentTranslateX,
|
|
1622
|
+
contentTranslateY,
|
|
1623
|
+
contentScale,
|
|
1624
|
+
shadowEnabled,
|
|
1625
|
+
shadowRed,
|
|
1626
|
+
shadowGreen,
|
|
1627
|
+
shadowBlue,
|
|
1628
|
+
shadowAlpha,
|
|
1629
|
+
shadowOffsetX,
|
|
1630
|
+
shadowOffsetY,
|
|
1631
|
+
shadowBlurRadius,
|
|
1632
|
+
shadowSpreadDistance,
|
|
1633
|
+
&presentation)) {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1024
1636
|
_commandIsAuthoritative = YES;
|
|
1025
|
-
[self smoothClipApplyPresentation:
|
|
1026
|
-
{{x, y, width, height, radius},
|
|
1027
|
-
contentTranslateX,
|
|
1028
|
-
contentTranslateY}];
|
|
1637
|
+
[self smoothClipApplyPresentation:presentation];
|
|
1029
1638
|
}
|
|
1030
1639
|
|
|
1031
1640
|
- (void)prepareForRecycle {
|
|
@@ -1034,15 +1643,24 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
1034
1643
|
_driverId = 0;
|
|
1035
1644
|
}
|
|
1036
1645
|
[self stopLayerAnimationWithoutCallback];
|
|
1037
|
-
_pendingAnimationInstall = NO;
|
|
1038
1646
|
smoothclip::clearVelocitySamples(_velocitySamples);
|
|
1039
1647
|
[super prepareForRecycle];
|
|
1040
1648
|
_requestedClip = CGRectZero;
|
|
1041
|
-
|
|
1649
|
+
_canonicalClip = CGRectZero;
|
|
1042
1650
|
_requestedRadius = 0;
|
|
1043
|
-
|
|
1651
|
+
_canonicalRadius = 0;
|
|
1652
|
+
_requestedRadii = {0, 0, 0, 0};
|
|
1653
|
+
_canonicalRadii = {0, 0, 0, 0};
|
|
1654
|
+
_requestedCurve = SmoothClipCornerCurveCircular;
|
|
1655
|
+
_canonicalCurve = SmoothClipCornerCurveCircular;
|
|
1656
|
+
_requestedHasExplicitRadii = NO;
|
|
1657
|
+
_canonicalHasExplicitRadii = NO;
|
|
1044
1658
|
_requestedContentTranslation = CGPointZero;
|
|
1045
|
-
|
|
1659
|
+
_canonicalContentTranslation = CGPointZero;
|
|
1660
|
+
_requestedContentScale = 1;
|
|
1661
|
+
_canonicalContentScale = 1;
|
|
1662
|
+
_requestedShadow = {};
|
|
1663
|
+
_canonicalShadow = {};
|
|
1046
1664
|
_hasLayout = NO;
|
|
1047
1665
|
_commandIsAuthoritative = NO;
|
|
1048
1666
|
|
|
@@ -1050,7 +1668,14 @@ static std::array<double, 7> SmoothClipVelocityChannels(
|
|
|
1050
1668
|
layer.bounds = CGRectZero;
|
|
1051
1669
|
layer.position = CGPointZero;
|
|
1052
1670
|
layer.cornerRadius = 0;
|
|
1671
|
+
layer.cornerCurve = kCACornerCurveCircular;
|
|
1672
|
+
layer.mask = nil;
|
|
1673
|
+
_unequalCornerMask.path = nil;
|
|
1053
1674
|
_contentContainer.layer.affineTransform = CGAffineTransformIdentity;
|
|
1675
|
+
_shadowLayer.shadowPath = nil;
|
|
1676
|
+
_shadowLayer.shadowOpacity = 0;
|
|
1677
|
+
_shadowLayer.shadowRadius = 0;
|
|
1678
|
+
_shadowLayer.shadowOffset = CGSizeZero;
|
|
1054
1679
|
[self setClipContainerHidden:YES];
|
|
1055
1680
|
}
|
|
1056
1681
|
|