react-native-smooth-clip-view 0.1.0 → 0.2.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 +164 -43
- package/SmoothClipView.podspec +8 -2
- package/android/CMakeLists.txt +39 -0
- package/android/build.gradle +69 -2
- package/android/proguard-rules.pro +6 -0
- package/android/src/main/cpp/SmoothClipAndroid.h +59 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +428 -0
- package/android/src/main/cpp/SmoothClipRegistry.cpp +913 -0
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +25 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +55 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +128 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +164 -30
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +107 -10
- package/android/src/main/java/com/smoothclipview/SmoothClipViewPackage.kt +18 -2
- package/cpp/SmoothClipRegistry.h +93 -0
- package/cpp/SmoothClipSharedGeometry.h +50 -0
- package/ios/SmoothClipModuleProvider.h +9 -0
- package/ios/SmoothClipModuleProvider.mm +16 -0
- package/ios/SmoothClipRegistry.mm +717 -0
- package/ios/SmoothClipTurboModule.cpp +290 -0
- package/ios/SmoothClipTurboModule.h +132 -0
- package/ios/SmoothClipView.mm +976 -85
- package/ios/SmoothClipViewRegistry.h +27 -0
- package/lib/module/NativeSmoothClipModule.js +5 -0
- package/lib/module/NativeSmoothClipModule.js.map +1 -0
- package/lib/module/SmoothClipView.android.js +4 -0
- package/lib/module/SmoothClipView.android.js.map +1 -0
- package/lib/module/SmoothClipView.ios.js +34 -0
- package/lib/module/SmoothClipView.ios.js.map +1 -0
- package/lib/module/SmoothClipView.js +26 -12
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipView.web.js +28 -12
- package/lib/module/SmoothClipView.web.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +14 -1
- package/lib/module/driverState.js +102 -0
- package/lib/module/driverState.js.map +1 -0
- package/lib/module/driverTypes.js +4 -0
- package/lib/module/driverTypes.js.map +1 -0
- package/lib/module/drivers.android.js +4 -0
- package/lib/module/drivers.android.js.map +1 -0
- package/lib/module/drivers.ios.js +380 -0
- package/lib/module/drivers.ios.js.map +1 -0
- package/lib/module/drivers.js +276 -0
- package/lib/module/drivers.js.map +1 -0
- package/lib/module/geometry.js +19 -0
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +2 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/nativeCompletion.js +21 -0
- package/lib/module/nativeCompletion.js.map +1 -0
- package/lib/module/reactRequests.js +50 -0
- package/lib/module/reactRequests.js.map +1 -0
- package/lib/module/smoothClipNative.android.js +15 -0
- package/lib/module/smoothClipNative.android.js.map +1 -0
- package/lib/module/smoothClipNative.js +8 -0
- package/lib/module/smoothClipNative.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +19 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.android.d.ts +3 -0
- package/lib/typescript/src/SmoothClipView.android.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.d.ts +5 -6
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +9 -0
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.web.d.ts +5 -7
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +4 -0
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/driverState.d.ts +31 -0
- package/lib/typescript/src/driverState.d.ts.map +1 -0
- package/lib/typescript/src/driverTypes.d.ts +61 -0
- package/lib/typescript/src/driverTypes.d.ts.map +1 -0
- package/lib/typescript/src/drivers.android.d.ts +3 -0
- package/lib/typescript/src/drivers.android.d.ts.map +1 -0
- package/lib/typescript/src/drivers.d.ts +5 -0
- package/lib/typescript/src/drivers.d.ts.map +1 -0
- package/lib/typescript/src/drivers.ios.d.ts +5 -0
- package/lib/typescript/src/drivers.ios.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +8 -0
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +4 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/nativeCompletion.d.ts +7 -0
- package/lib/typescript/src/nativeCompletion.d.ts.map +1 -0
- package/lib/typescript/src/reactRequests.d.ts +7 -0
- package/lib/typescript/src/reactRequests.d.ts.map +1 -0
- package/lib/typescript/src/smoothClipNative.android.d.ts +4 -0
- package/lib/typescript/src/smoothClipNative.android.d.ts.map +1 -0
- package/lib/typescript/src/smoothClipNative.d.ts +3 -0
- package/lib/typescript/src/smoothClipNative.d.ts.map +1 -0
- package/package.json +12 -7
- package/src/NativeSmoothClipModule.ts +104 -0
- package/src/SmoothClipView.android.tsx +2 -0
- package/src/SmoothClipView.ios.tsx +36 -0
- package/src/SmoothClipView.tsx +31 -27
- package/src/SmoothClipView.web.tsx +26 -22
- package/src/SmoothClipViewNativeComponent.ts +14 -1
- package/src/driverState.ts +150 -0
- package/src/driverTypes.ts +91 -0
- package/src/drivers.android.ts +6 -0
- package/src/drivers.ios.ts +665 -0
- package/src/drivers.ts +416 -0
- package/src/geometry.ts +39 -0
- package/src/index.ts +19 -1
- package/src/index.web.ts +19 -1
- package/src/nativeCompletion.ts +37 -0
- package/src/reactRequests.ts +64 -0
- package/src/smoothClipNative.android.ts +20 -0
- package/src/smoothClipNative.ts +5 -0
package/ios/SmoothClipView.mm
CHANGED
|
@@ -1,26 +1,97 @@
|
|
|
1
1
|
#import "SmoothClipView.h"
|
|
2
|
+
|
|
2
3
|
#import "SmoothClipGeometry.h"
|
|
4
|
+
#import "SmoothClipViewRegistry.h"
|
|
3
5
|
|
|
4
6
|
#import <QuartzCore/QuartzCore.h>
|
|
5
7
|
#import <React/RCTConversions.h>
|
|
6
8
|
#import <React/RCTFabricComponentsPlugins.h>
|
|
9
|
+
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
10
|
+
#import <os/signpost.h>
|
|
11
|
+
#endif
|
|
7
12
|
#import <react/renderer/components/SmoothClipViewSpec/ComponentDescriptors.h>
|
|
8
13
|
#import <react/renderer/components/SmoothClipViewSpec/Props.h>
|
|
9
14
|
#import <react/renderer/components/SmoothClipViewSpec/RCTComponentViewHelpers.h>
|
|
10
15
|
|
|
16
|
+
#include <cfloat>
|
|
17
|
+
|
|
11
18
|
using namespace facebook::react;
|
|
12
19
|
|
|
20
|
+
@class SmoothClipView;
|
|
21
|
+
|
|
22
|
+
@interface SmoothClipAnimationDelegate : NSObject <CAAnimationDelegate>
|
|
23
|
+
@property(nonatomic, weak) SmoothClipView *view;
|
|
24
|
+
@property(nonatomic, assign) uint64_t driverId;
|
|
25
|
+
@property(nonatomic, assign) int32_t animationId;
|
|
26
|
+
// Set when the animation is torn down deliberately. The running CAAnimation
|
|
27
|
+
// copies share this delegate object, so this suppresses their didStop even
|
|
28
|
+
// when Core Animation delivers it after the teardown returns.
|
|
29
|
+
@property(nonatomic, assign) BOOL invalidated;
|
|
30
|
+
@end
|
|
31
|
+
|
|
32
|
+
@interface SmoothClipContainerView : UIView
|
|
33
|
+
@end
|
|
34
|
+
|
|
35
|
+
@implementation SmoothClipContainerView
|
|
36
|
+
|
|
37
|
+
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
|
|
38
|
+
// Geometry is always driven explicitly, either by a HostFunction update or
|
|
39
|
+
// by the animations installed below. Avoid a CATransaction on every frame.
|
|
40
|
+
return (id<CAAction>)[NSNull null];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@end
|
|
44
|
+
|
|
13
45
|
@interface SmoothClipView () <RCTSmoothClipViewViewProtocol>
|
|
46
|
+
- (void)smoothClipAnimationDidStopWithDriverId:(uint64_t)driverId
|
|
47
|
+
animationId:(int32_t)animationId
|
|
48
|
+
finished:(BOOL)finished;
|
|
14
49
|
@end
|
|
15
50
|
|
|
16
51
|
@implementation SmoothClipView {
|
|
17
|
-
|
|
52
|
+
SmoothClipContainerView *_clipContainer;
|
|
53
|
+
SmoothClipContainerView *_contentContainer;
|
|
18
54
|
CGRect _requestedClip;
|
|
19
55
|
CGRect _normalizedClip;
|
|
20
56
|
CGFloat _requestedRadius;
|
|
21
57
|
CGFloat _normalizedRadius;
|
|
58
|
+
CGPoint _requestedContentTranslation;
|
|
59
|
+
CGPoint _normalizedContentTranslation;
|
|
60
|
+
uint64_t _driverId;
|
|
22
61
|
BOOL _hasLayout;
|
|
23
62
|
BOOL _commandIsAuthoritative;
|
|
63
|
+
// Mirrors _clipContainer.hidden/.accessibilityElementsHidden so the hot
|
|
64
|
+
// path can skip UIKit getters; every write goes through setClipContainerHidden.
|
|
65
|
+
BOOL _clipHidden;
|
|
66
|
+
BOOL _ignoreAnimationCallback;
|
|
67
|
+
|
|
68
|
+
int32_t _activeAnimationId;
|
|
69
|
+
NSInteger _activeAnimationKind;
|
|
70
|
+
// Set when a native animation install arrived before the first layout.
|
|
71
|
+
// The install is deferred and re-run through the registry at first layout
|
|
72
|
+
// so it joins from live presentation geometry with the true remaining time.
|
|
73
|
+
BOOL _pendingAnimationInstall;
|
|
74
|
+
CFTimeInterval _animationStartedAt;
|
|
75
|
+
CFTimeInterval _animationDuration;
|
|
76
|
+
smoothclip::TimingAnimation _timingAnimation;
|
|
77
|
+
smoothclip::SpringAnimation _springAnimation;
|
|
78
|
+
SmoothClipAnimationDelegate *_animationDelegate;
|
|
79
|
+
BOOL _animationPaused;
|
|
80
|
+
CGRect _pausedRect;
|
|
81
|
+
CGFloat _pausedRadius;
|
|
82
|
+
CGPoint _pausedContentTranslation;
|
|
83
|
+
CFTimeInterval _pausedRemaining;
|
|
84
|
+
|
|
85
|
+
BOOL _hasPreviousInteractiveSample;
|
|
86
|
+
BOOL _hasInteractiveSample;
|
|
87
|
+
CGRect _previousInteractiveRect;
|
|
88
|
+
CGRect _interactiveRect;
|
|
89
|
+
CGFloat _previousInteractiveRadius;
|
|
90
|
+
CGFloat _interactiveRadius;
|
|
91
|
+
CGPoint _previousInteractiveContentTranslation;
|
|
92
|
+
CGPoint _interactiveContentTranslation;
|
|
93
|
+
CFTimeInterval _previousInteractiveTime;
|
|
94
|
+
CFTimeInterval _interactiveTime;
|
|
24
95
|
}
|
|
25
96
|
|
|
26
97
|
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
@@ -31,110 +102,790 @@ using namespace facebook::react;
|
|
|
31
102
|
return NO;
|
|
32
103
|
}
|
|
33
104
|
|
|
105
|
+
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
106
|
+
+ (os_log_t)signpostLog {
|
|
107
|
+
static os_log_t log = os_log_create(
|
|
108
|
+
"com.smoothclipview", "clip-view");
|
|
109
|
+
return log;
|
|
110
|
+
}
|
|
111
|
+
#endif
|
|
112
|
+
|
|
34
113
|
- (instancetype)initWithFrame:(CGRect)frame {
|
|
35
114
|
if (self = [super initWithFrame:frame]) {
|
|
36
115
|
static const auto defaultProps =
|
|
37
116
|
std::make_shared<const SmoothClipViewProps>();
|
|
38
117
|
_props = defaultProps;
|
|
39
118
|
|
|
40
|
-
_clipContainer = [[
|
|
41
|
-
_clipContainer.
|
|
119
|
+
_clipContainer = [[SmoothClipContainerView alloc] initWithFrame:CGRectZero];
|
|
120
|
+
_clipContainer.layer.masksToBounds = YES;
|
|
121
|
+
_clipContainer.layer.needsDisplayOnBoundsChange = NO;
|
|
122
|
+
_clipContainer.autoresizesSubviews = NO;
|
|
42
123
|
_clipContainer.hidden = YES;
|
|
43
124
|
_clipContainer.accessibilityElementsHidden = YES;
|
|
125
|
+
_clipHidden = YES;
|
|
126
|
+
_contentContainer =
|
|
127
|
+
[[SmoothClipContainerView alloc] initWithFrame:CGRectZero];
|
|
128
|
+
_contentContainer.layer.needsDisplayOnBoundsChange = NO;
|
|
129
|
+
_contentContainer.autoresizesSubviews = NO;
|
|
130
|
+
[_clipContainer addSubview:_contentContainer];
|
|
44
131
|
[self addSubview:_clipContainer];
|
|
45
132
|
|
|
46
133
|
_requestedClip = CGRectZero;
|
|
47
134
|
_normalizedClip = CGRectZero;
|
|
48
135
|
_requestedRadius = 0;
|
|
49
136
|
_normalizedRadius = 0;
|
|
137
|
+
_requestedContentTranslation = CGPointZero;
|
|
138
|
+
_normalizedContentTranslation = CGPointZero;
|
|
139
|
+
_driverId = 0;
|
|
50
140
|
_hasLayout = NO;
|
|
51
141
|
_commandIsAuthoritative = NO;
|
|
142
|
+
_ignoreAnimationCallback = NO;
|
|
143
|
+
_activeAnimationId = 0;
|
|
144
|
+
_activeAnimationKind = 0;
|
|
145
|
+
_pendingAnimationInstall = NO;
|
|
146
|
+
_animationPaused = NO;
|
|
147
|
+
_hasPreviousInteractiveSample = NO;
|
|
148
|
+
_hasInteractiveSample = NO;
|
|
149
|
+
[[NSNotificationCenter defaultCenter]
|
|
150
|
+
addObserver:self
|
|
151
|
+
selector:@selector(applicationWillResignActive)
|
|
152
|
+
name:UIApplicationWillResignActiveNotification
|
|
153
|
+
object:nil];
|
|
154
|
+
[[NSNotificationCenter defaultCenter]
|
|
155
|
+
addObserver:self
|
|
156
|
+
selector:@selector(applicationDidBecomeActive)
|
|
157
|
+
name:UIApplicationDidBecomeActiveNotification
|
|
158
|
+
object:nil];
|
|
52
159
|
}
|
|
53
160
|
return self;
|
|
54
161
|
}
|
|
55
162
|
|
|
163
|
+
- (void)dealloc {
|
|
164
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
165
|
+
if (_driverId != 0) {
|
|
166
|
+
smoothclip::unregisterView(_driverId, self);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
56
170
|
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView
|
|
57
171
|
index:(NSInteger)index {
|
|
58
172
|
NSAssert(childComponentView.superview == nil,
|
|
59
173
|
@"SmoothClipView attempted to mount an already-mounted child");
|
|
60
|
-
[
|
|
174
|
+
[_contentContainer insertSubview:childComponentView atIndex:index];
|
|
61
175
|
}
|
|
62
176
|
|
|
63
177
|
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView
|
|
64
178
|
index:(NSInteger)index {
|
|
65
|
-
NSAssert(childComponentView.superview ==
|
|
179
|
+
NSAssert(childComponentView.superview == _contentContainer,
|
|
66
180
|
@"SmoothClipView attempted to unmount a child from another parent");
|
|
67
|
-
NSAssert(index <
|
|
68
|
-
|
|
181
|
+
NSAssert(index < _contentContainer.subviews.count &&
|
|
182
|
+
_contentContainer.subviews[index] == childComponentView,
|
|
69
183
|
@"SmoothClipView child index mismatch");
|
|
70
184
|
[childComponentView removeFromSuperview];
|
|
71
185
|
}
|
|
72
186
|
|
|
73
|
-
- (void)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
187
|
+
- (void)storeRequestedPresentation:(smoothclip::Presentation)presentation {
|
|
188
|
+
const smoothclip::Geometry geometry = presentation.clip;
|
|
189
|
+
_requestedClip = CGRectMake(
|
|
190
|
+
geometry.x,
|
|
191
|
+
geometry.y,
|
|
192
|
+
MAX(0, geometry.width),
|
|
193
|
+
MAX(0, geometry.height));
|
|
194
|
+
_requestedRadius = MAX(0, geometry.radius);
|
|
195
|
+
_requestedContentTranslation = CGPointMake(
|
|
196
|
+
presentation.contentTranslateX,
|
|
197
|
+
presentation.contentTranslateY);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
- (BOOL)normalizedRequestedGeometry:(SmoothNormalizedClipGeometry *)geometry {
|
|
201
|
+
if (!_hasLayout) return NO;
|
|
202
|
+
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
203
|
+
const bool signpostsEnabled =
|
|
204
|
+
os_signpost_enabled([SmoothClipView signpostLog]);
|
|
205
|
+
os_signpost_id_t identifier = OS_SIGNPOST_ID_NULL;
|
|
206
|
+
if (signpostsEnabled) {
|
|
207
|
+
identifier = os_signpost_id_generate([SmoothClipView signpostLog]);
|
|
208
|
+
os_signpost_interval_begin(
|
|
209
|
+
[SmoothClipView signpostLog], identifier, "normalization");
|
|
81
210
|
}
|
|
211
|
+
#endif
|
|
212
|
+
const CGSize hostSize = self.bounds.size;
|
|
213
|
+
const BOOL valid = SmoothClipNormalizeGeometry(
|
|
214
|
+
CGRectGetMinX(_requestedClip),
|
|
215
|
+
CGRectGetMinY(_requestedClip),
|
|
216
|
+
CGRectGetWidth(_requestedClip),
|
|
217
|
+
CGRectGetHeight(_requestedClip),
|
|
218
|
+
_requestedRadius,
|
|
219
|
+
CGSizeMake(MAX(0, hostSize.width), MAX(0, hostSize.height)),
|
|
220
|
+
geometry);
|
|
221
|
+
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
222
|
+
if (signpostsEnabled) {
|
|
223
|
+
os_signpost_interval_end(
|
|
224
|
+
[SmoothClipView signpostLog], identifier, "normalization");
|
|
225
|
+
}
|
|
226
|
+
#endif
|
|
227
|
+
return valid;
|
|
228
|
+
}
|
|
82
229
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
230
|
+
- (void)setClipContainerHidden:(BOOL)hidden {
|
|
231
|
+
if (_clipHidden == hidden) return;
|
|
232
|
+
_clipHidden = hidden;
|
|
233
|
+
_clipContainer.hidden = hidden;
|
|
234
|
+
_clipContainer.accessibilityElementsHidden = hidden;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
- (void)syncVisibilityForRect:(CGRect)rect {
|
|
238
|
+
[self setClipContainerHidden:CGRectIsEmpty(rect)];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
- (void)writeLayerRect:(CGRect)rect radius:(CGFloat)radius {
|
|
242
|
+
const BOOL rectChanged = !CGRectEqualToRect(_normalizedClip, rect);
|
|
243
|
+
const BOOL radiusChanged = _normalizedRadius != radius;
|
|
244
|
+
_normalizedClip = rect;
|
|
245
|
+
_normalizedRadius = radius;
|
|
246
|
+
if (!rectChanged && !radiusChanged) return;
|
|
247
|
+
|
|
248
|
+
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
249
|
+
const bool signpostsEnabled =
|
|
250
|
+
os_signpost_enabled([SmoothClipView signpostLog]);
|
|
251
|
+
os_signpost_id_t identifier = OS_SIGNPOST_ID_NULL;
|
|
252
|
+
if (signpostsEnabled) {
|
|
253
|
+
identifier = os_signpost_id_generate([SmoothClipView signpostLog]);
|
|
254
|
+
os_signpost_interval_begin(
|
|
255
|
+
[SmoothClipView signpostLog], identifier, "layer-application");
|
|
256
|
+
}
|
|
257
|
+
#endif
|
|
258
|
+
CALayer *layer = _clipContainer.layer;
|
|
259
|
+
if (rectChanged) {
|
|
260
|
+
layer.bounds = rect;
|
|
261
|
+
layer.position = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
|
|
262
|
+
}
|
|
263
|
+
if (radiusChanged) {
|
|
264
|
+
layer.cornerRadius = radius;
|
|
265
|
+
}
|
|
266
|
+
#if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
|
|
267
|
+
if (signpostsEnabled) {
|
|
268
|
+
os_signpost_interval_end(
|
|
269
|
+
[SmoothClipView signpostLog], identifier, "layer-application");
|
|
270
|
+
}
|
|
271
|
+
#endif
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
- (void)writeContentTranslation:(CGPoint)translation {
|
|
275
|
+
if (CGPointEqualToPoint(_normalizedContentTranslation, translation)) return;
|
|
276
|
+
_normalizedContentTranslation = translation;
|
|
277
|
+
_contentContainer.layer.affineTransform =
|
|
278
|
+
CGAffineTransformMakeTranslation(translation.x, translation.y);
|
|
86
279
|
}
|
|
87
280
|
|
|
88
281
|
- (void)applyRequestedClip {
|
|
89
|
-
|
|
282
|
+
SmoothNormalizedClipGeometry geometry;
|
|
283
|
+
if (![self normalizedRequestedGeometry:&geometry]) return;
|
|
284
|
+
|
|
285
|
+
const BOOL visibilityChanged = _clipHidden != CGRectIsEmpty(geometry.rect);
|
|
286
|
+
if (CGRectEqualToRect(_normalizedClip, geometry.rect) &&
|
|
287
|
+
_normalizedRadius == geometry.radius &&
|
|
288
|
+
CGPointEqualToPoint(
|
|
289
|
+
_normalizedContentTranslation, _requestedContentTranslation) &&
|
|
290
|
+
!visibilityChanged) {
|
|
90
291
|
return;
|
|
91
292
|
}
|
|
293
|
+
[self writeLayerRect:geometry.rect radius:geometry.radius];
|
|
294
|
+
[self writeContentTranslation:_requestedContentTranslation];
|
|
295
|
+
[self syncVisibilityForRect:geometry.rect];
|
|
296
|
+
}
|
|
92
297
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
_requestedRadius,
|
|
102
|
-
CGSizeMake(hostWidth, hostHeight),
|
|
103
|
-
&geometry)) {
|
|
298
|
+
- (void)recordInteractiveRect:(CGRect)rect
|
|
299
|
+
radius:(CGFloat)radius
|
|
300
|
+
contentTranslation:(CGPoint)contentTranslation {
|
|
301
|
+
if (_hasInteractiveSample &&
|
|
302
|
+
CGRectEqualToRect(_interactiveRect, rect) &&
|
|
303
|
+
_interactiveRadius == radius &&
|
|
304
|
+
CGPointEqualToPoint(
|
|
305
|
+
_interactiveContentTranslation, contentTranslation)) {
|
|
104
306
|
return;
|
|
105
307
|
}
|
|
308
|
+
if (_hasInteractiveSample) {
|
|
309
|
+
_hasPreviousInteractiveSample = YES;
|
|
310
|
+
_previousInteractiveRect = _interactiveRect;
|
|
311
|
+
_previousInteractiveRadius = _interactiveRadius;
|
|
312
|
+
_previousInteractiveContentTranslation =
|
|
313
|
+
_interactiveContentTranslation;
|
|
314
|
+
_previousInteractiveTime = _interactiveTime;
|
|
315
|
+
}
|
|
316
|
+
_hasInteractiveSample = YES;
|
|
317
|
+
_interactiveRect = rect;
|
|
318
|
+
_interactiveRadius = radius;
|
|
319
|
+
_interactiveContentTranslation = contentTranslation;
|
|
320
|
+
_interactiveTime = CACurrentMediaTime();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
- (CGFloat)inheritedVelocityToRect:(CGRect)target
|
|
324
|
+
radius:(CGFloat)targetRadius
|
|
325
|
+
contentTranslation:(CGPoint)targetContentTranslation {
|
|
326
|
+
if (!_hasPreviousInteractiveSample || !_hasInteractiveSample) return 0;
|
|
327
|
+
const CFTimeInterval elapsed =
|
|
328
|
+
_interactiveTime - _previousInteractiveTime;
|
|
329
|
+
if (elapsed <= 0 || CACurrentMediaTime() - _interactiveTime > 0.1) return 0;
|
|
330
|
+
|
|
331
|
+
const CGFloat current[] = {
|
|
332
|
+
CGRectGetMinX(_interactiveRect),
|
|
333
|
+
CGRectGetMinY(_interactiveRect),
|
|
334
|
+
CGRectGetWidth(_interactiveRect),
|
|
335
|
+
CGRectGetHeight(_interactiveRect),
|
|
336
|
+
_interactiveRadius,
|
|
337
|
+
_interactiveContentTranslation.x,
|
|
338
|
+
_interactiveContentTranslation.y};
|
|
339
|
+
const CGFloat previous[] = {
|
|
340
|
+
CGRectGetMinX(_previousInteractiveRect),
|
|
341
|
+
CGRectGetMinY(_previousInteractiveRect),
|
|
342
|
+
CGRectGetWidth(_previousInteractiveRect),
|
|
343
|
+
CGRectGetHeight(_previousInteractiveRect),
|
|
344
|
+
_previousInteractiveRadius,
|
|
345
|
+
_previousInteractiveContentTranslation.x,
|
|
346
|
+
_previousInteractiveContentTranslation.y};
|
|
347
|
+
const CGFloat destination[] = {
|
|
348
|
+
CGRectGetMinX(target),
|
|
349
|
+
CGRectGetMinY(target),
|
|
350
|
+
CGRectGetWidth(target),
|
|
351
|
+
CGRectGetHeight(target),
|
|
352
|
+
targetRadius,
|
|
353
|
+
targetContentTranslation.x,
|
|
354
|
+
targetContentTranslation.y};
|
|
355
|
+
double numerator = 0;
|
|
356
|
+
double denominator = 0;
|
|
357
|
+
for (NSUInteger index = 0; index < 7; index += 1) {
|
|
358
|
+
const double velocity = (current[index] - previous[index]) / elapsed;
|
|
359
|
+
const double displacement = destination[index] - current[index];
|
|
360
|
+
numerator += velocity * displacement;
|
|
361
|
+
denominator += displacement * displacement;
|
|
362
|
+
}
|
|
363
|
+
if (denominator <= DBL_EPSILON) return 0;
|
|
364
|
+
const double result = numerator / denominator;
|
|
365
|
+
return isfinite(result) ? result : 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
- (void)smoothClipApplyPresentation:(smoothclip::Presentation)presentation {
|
|
369
|
+
_animationPaused = NO;
|
|
370
|
+
[self stopLayerAnimationWithoutCallback];
|
|
371
|
+
[self storeRequestedPresentation:presentation];
|
|
372
|
+
[self applyRequestedClip];
|
|
373
|
+
if (_hasLayout) {
|
|
374
|
+
[self recordInteractiveRect:_normalizedClip
|
|
375
|
+
radius:_normalizedRadius
|
|
376
|
+
contentTranslation:_normalizedContentTranslation];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
- (smoothclip::Presentation)smoothClipCurrentPresentation {
|
|
381
|
+
CGFloat visibleRadius = _normalizedRadius;
|
|
382
|
+
const CGRect visibleRect = _activeAnimationId != 0
|
|
383
|
+
? [self presentationRectWithRadius:&visibleRadius]
|
|
384
|
+
: _normalizedClip;
|
|
385
|
+
CALayer *contentLayer = _activeAnimationId != 0
|
|
386
|
+
? (CALayer *)_contentContainer.layer.presentationLayer
|
|
387
|
+
: _contentContainer.layer;
|
|
388
|
+
if (contentLayer == nil) contentLayer = _contentContainer.layer;
|
|
389
|
+
const CGAffineTransform transform = contentLayer.affineTransform;
|
|
390
|
+
return {
|
|
391
|
+
{CGRectGetMinX(visibleRect),
|
|
392
|
+
CGRectGetMinY(visibleRect),
|
|
393
|
+
CGRectGetWidth(visibleRect),
|
|
394
|
+
CGRectGetHeight(visibleRect),
|
|
395
|
+
visibleRadius},
|
|
396
|
+
transform.tx,
|
|
397
|
+
transform.ty};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
- (BOOL)smoothClipIsJoinable {
|
|
401
|
+
// A view without layout reports zero geometry from
|
|
402
|
+
// smoothClipCurrentPresentation and must not be used as a join reference.
|
|
403
|
+
return _hasLayout;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
- (smoothclip::Presentation)smoothClipFreezePresentation {
|
|
407
|
+
const smoothclip::Presentation visible =
|
|
408
|
+
[self smoothClipCurrentPresentation];
|
|
409
|
+
const CGRect visibleRect = CGRectMake(
|
|
410
|
+
visible.clip.x,
|
|
411
|
+
visible.clip.y,
|
|
412
|
+
visible.clip.width,
|
|
413
|
+
visible.clip.height);
|
|
414
|
+
const CGPoint visibleContentTranslation = CGPointMake(
|
|
415
|
+
visible.contentTranslateX,
|
|
416
|
+
visible.contentTranslateY);
|
|
417
|
+
_animationPaused = NO;
|
|
418
|
+
[self stopLayerAnimationWithoutCallback];
|
|
419
|
+
_requestedClip = visibleRect;
|
|
420
|
+
_requestedRadius = visible.clip.radius;
|
|
421
|
+
_requestedContentTranslation = visibleContentTranslation;
|
|
422
|
+
[self writeLayerRect:visibleRect radius:visible.clip.radius];
|
|
423
|
+
[self writeContentTranslation:visibleContentTranslation];
|
|
424
|
+
[self syncVisibilityForRect:visibleRect];
|
|
425
|
+
[self recordInteractiveRect:visibleRect
|
|
426
|
+
radius:visible.clip.radius
|
|
427
|
+
contentTranslation:visibleContentTranslation];
|
|
428
|
+
return visible;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
- (CGRect)presentationRectWithRadius:(CGFloat *)radius {
|
|
432
|
+
CALayer *layer = (CALayer *)_clipContainer.layer.presentationLayer;
|
|
433
|
+
if (layer == nil) layer = _clipContainer.layer;
|
|
434
|
+
const CGRect bounds = layer.bounds;
|
|
435
|
+
const CGPoint position = layer.position;
|
|
436
|
+
if (radius != nullptr) *radius = layer.cornerRadius;
|
|
437
|
+
return CGRectMake(
|
|
438
|
+
position.x - bounds.size.width * layer.anchorPoint.x,
|
|
439
|
+
position.y - bounds.size.height * layer.anchorPoint.y,
|
|
440
|
+
bounds.size.width,
|
|
441
|
+
bounds.size.height);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
- (void)stopLayerAnimationWithoutCallback {
|
|
445
|
+
_pendingAnimationInstall = NO;
|
|
446
|
+
if (_activeAnimationId == 0) return;
|
|
447
|
+
// Invalidate before removing: CA may deliver didStop for the removed
|
|
448
|
+
// animation after this method returns, when _ignoreAnimationCallback has
|
|
449
|
+
// already been reset — and the id-preserving reinstall paths reuse the
|
|
450
|
+
// same animation id, so the id check alone cannot filter it.
|
|
451
|
+
_animationDelegate.invalidated = YES;
|
|
452
|
+
_ignoreAnimationCallback = YES;
|
|
453
|
+
[_clipContainer.layer removeAnimationForKey:@"smoothClip.geometry"];
|
|
454
|
+
[_contentContainer.layer removeAnimationForKey:@"smoothClip.content"];
|
|
455
|
+
_ignoreAnimationCallback = NO;
|
|
456
|
+
_activeAnimationId = 0;
|
|
457
|
+
_activeAnimationKind = 0;
|
|
458
|
+
_animationPaused = NO;
|
|
459
|
+
_animationDelegate = nil;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
- (CABasicAnimation *)basicAnimationForKeyPath:(NSString *)keyPath
|
|
463
|
+
fromValue:(id)fromValue
|
|
464
|
+
toValue:(id)toValue
|
|
465
|
+
timingFunction:(CAMediaTimingFunction *)timing {
|
|
466
|
+
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
|
|
467
|
+
animation.fromValue = fromValue;
|
|
468
|
+
animation.toValue = toValue;
|
|
469
|
+
animation.timingFunction = timing;
|
|
470
|
+
return animation;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
- (CFTimeInterval)springSettlingDurationWithVelocity:(double)velocity {
|
|
474
|
+
// Every key path in the group shares mass/stiffness/damping and the same
|
|
475
|
+
// normalized initialVelocity, so one settling solve covers all of them.
|
|
476
|
+
CASpringAnimation *probe =
|
|
477
|
+
[CASpringAnimation animationWithKeyPath:@"bounds.origin.x"];
|
|
478
|
+
probe.mass = _springAnimation.mass;
|
|
479
|
+
probe.stiffness = _springAnimation.stiffness;
|
|
480
|
+
probe.damping = _springAnimation.damping;
|
|
481
|
+
probe.initialVelocity = velocity;
|
|
482
|
+
return probe.settlingDuration;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
- (CASpringAnimation *)springAnimationForKeyPath:(NSString *)keyPath
|
|
486
|
+
fromValue:(id)fromValue
|
|
487
|
+
toValue:(id)toValue
|
|
488
|
+
velocity:(double)velocity
|
|
489
|
+
duration:(CFTimeInterval)duration {
|
|
490
|
+
CASpringAnimation *animation =
|
|
491
|
+
[CASpringAnimation animationWithKeyPath:keyPath];
|
|
492
|
+
animation.fromValue = fromValue;
|
|
493
|
+
animation.toValue = toValue;
|
|
494
|
+
animation.mass = _springAnimation.mass;
|
|
495
|
+
animation.stiffness = _springAnimation.stiffness;
|
|
496
|
+
animation.damping = _springAnimation.damping;
|
|
497
|
+
animation.initialVelocity = velocity;
|
|
498
|
+
animation.duration = duration;
|
|
499
|
+
return animation;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
- (void)installAnimationFromRect:(CGRect)fromRect
|
|
503
|
+
fromRadius:(CGFloat)fromRadius
|
|
504
|
+
fromContentTranslation:(CGPoint)fromContentTranslation
|
|
505
|
+
toRect:(CGRect)toRect
|
|
506
|
+
toRadius:(CGFloat)toRadius
|
|
507
|
+
toContentTranslation:(CGPoint)toContentTranslation
|
|
508
|
+
duration:(CFTimeInterval)duration {
|
|
509
|
+
const int32_t animationId = _activeAnimationId;
|
|
510
|
+
const NSInteger animationKind = _activeAnimationKind;
|
|
511
|
+
[self stopLayerAnimationWithoutCallback];
|
|
512
|
+
_activeAnimationId = animationId;
|
|
513
|
+
_activeAnimationKind = animationKind;
|
|
514
|
+
// Unhide while any animated frame can show content; an empty-to-empty
|
|
515
|
+
// transition must stay hidden for accessibility.
|
|
516
|
+
if (!CGRectIsEmpty(toRect) || !CGRectIsEmpty(fromRect)) {
|
|
517
|
+
[self setClipContainerHidden:NO];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
CALayer *layer = _clipContainer.layer;
|
|
521
|
+
const CGRect toBounds = toRect;
|
|
522
|
+
const CGPoint fromPosition =
|
|
523
|
+
CGPointMake(CGRectGetMidX(fromRect), CGRectGetMidY(fromRect));
|
|
524
|
+
const CGPoint toPosition =
|
|
525
|
+
CGPointMake(CGRectGetMidX(toRect), CGRectGetMidY(toRect));
|
|
526
|
+
layer.bounds = toBounds;
|
|
527
|
+
layer.position = toPosition;
|
|
528
|
+
layer.cornerRadius = toRadius;
|
|
529
|
+
_contentContainer.layer.affineTransform = CGAffineTransformMakeTranslation(
|
|
530
|
+
toContentTranslation.x,
|
|
531
|
+
toContentTranslation.y);
|
|
532
|
+
_normalizedClip = toRect;
|
|
533
|
+
_normalizedRadius = toRadius;
|
|
534
|
+
_normalizedContentTranslation = toContentTranslation;
|
|
535
|
+
|
|
536
|
+
CAAnimationGroup *group = [CAAnimationGroup animation];
|
|
537
|
+
CAAnimationGroup *contentGroup = [CAAnimationGroup animation];
|
|
538
|
+
if (_activeAnimationKind == 1) {
|
|
539
|
+
CAMediaTimingFunction *timing = [CAMediaTimingFunction
|
|
540
|
+
functionWithControlPoints:_timingAnimation.controlPoint1X
|
|
541
|
+
:_timingAnimation.controlPoint1Y
|
|
542
|
+
:_timingAnimation.controlPoint2X
|
|
543
|
+
:_timingAnimation.controlPoint2Y];
|
|
544
|
+
group.animations = @[
|
|
545
|
+
[self basicAnimationForKeyPath:@"bounds"
|
|
546
|
+
fromValue:[NSValue valueWithCGRect:fromRect]
|
|
547
|
+
toValue:[NSValue valueWithCGRect:toBounds]
|
|
548
|
+
timingFunction:timing],
|
|
549
|
+
[self basicAnimationForKeyPath:@"position"
|
|
550
|
+
fromValue:[NSValue valueWithCGPoint:fromPosition]
|
|
551
|
+
toValue:[NSValue valueWithCGPoint:toPosition]
|
|
552
|
+
timingFunction:timing],
|
|
553
|
+
[self basicAnimationForKeyPath:@"cornerRadius"
|
|
554
|
+
fromValue:@(fromRadius)
|
|
555
|
+
toValue:@(toRadius)
|
|
556
|
+
timingFunction:timing],
|
|
557
|
+
];
|
|
558
|
+
group.duration = duration;
|
|
559
|
+
contentGroup.animations = @[
|
|
560
|
+
[self basicAnimationForKeyPath:@"transform.translation.x"
|
|
561
|
+
fromValue:@(fromContentTranslation.x)
|
|
562
|
+
toValue:@(toContentTranslation.x)
|
|
563
|
+
timingFunction:timing],
|
|
564
|
+
[self basicAnimationForKeyPath:@"transform.translation.y"
|
|
565
|
+
fromValue:@(fromContentTranslation.y)
|
|
566
|
+
toValue:@(toContentTranslation.y)
|
|
567
|
+
timingFunction:timing],
|
|
568
|
+
];
|
|
569
|
+
contentGroup.duration = duration;
|
|
570
|
+
} else {
|
|
571
|
+
// `_springAnimation.initialVelocity` is the interactive motion projected
|
|
572
|
+
// onto the current-to-target trajectory, normalized by that distance
|
|
573
|
+
// (units 1/s). CASpringAnimation.initialVelocity uses the same normalized
|
|
574
|
+
// convention — its settlingDuration is independent of the from/to
|
|
575
|
+
// distance — so every key path receives the scalar unchanged. Multiplying
|
|
576
|
+
// by per-property deltas would overstate the launch velocity by the pixel
|
|
577
|
+
// distance and blow out the settling time.
|
|
578
|
+
const double velocity = _springAnimation.initialVelocity;
|
|
579
|
+
const CFTimeInterval springDuration =
|
|
580
|
+
[self springSettlingDurationWithVelocity:velocity];
|
|
581
|
+
group.animations = @[
|
|
582
|
+
[self springAnimationForKeyPath:@"bounds.origin.x"
|
|
583
|
+
fromValue:@(fromRect.origin.x)
|
|
584
|
+
toValue:@(toBounds.origin.x)
|
|
585
|
+
velocity:velocity
|
|
586
|
+
duration:springDuration],
|
|
587
|
+
[self springAnimationForKeyPath:@"bounds.origin.y"
|
|
588
|
+
fromValue:@(fromRect.origin.y)
|
|
589
|
+
toValue:@(toBounds.origin.y)
|
|
590
|
+
velocity:velocity
|
|
591
|
+
duration:springDuration],
|
|
592
|
+
[self springAnimationForKeyPath:@"bounds.size.width"
|
|
593
|
+
fromValue:@(fromRect.size.width)
|
|
594
|
+
toValue:@(toBounds.size.width)
|
|
595
|
+
velocity:velocity
|
|
596
|
+
duration:springDuration],
|
|
597
|
+
[self springAnimationForKeyPath:@"bounds.size.height"
|
|
598
|
+
fromValue:@(fromRect.size.height)
|
|
599
|
+
toValue:@(toBounds.size.height)
|
|
600
|
+
velocity:velocity
|
|
601
|
+
duration:springDuration],
|
|
602
|
+
[self springAnimationForKeyPath:@"position.x"
|
|
603
|
+
fromValue:@(fromPosition.x)
|
|
604
|
+
toValue:@(toPosition.x)
|
|
605
|
+
velocity:velocity
|
|
606
|
+
duration:springDuration],
|
|
607
|
+
[self springAnimationForKeyPath:@"position.y"
|
|
608
|
+
fromValue:@(fromPosition.y)
|
|
609
|
+
toValue:@(toPosition.y)
|
|
610
|
+
velocity:velocity
|
|
611
|
+
duration:springDuration],
|
|
612
|
+
[self springAnimationForKeyPath:@"cornerRadius"
|
|
613
|
+
fromValue:@(fromRadius)
|
|
614
|
+
toValue:@(toRadius)
|
|
615
|
+
velocity:velocity
|
|
616
|
+
duration:springDuration],
|
|
617
|
+
];
|
|
618
|
+
// A rebuilt spring (layout change, foreground resume) must run its own
|
|
619
|
+
// settling time; clamping to the interrupted run's remaining wall-clock
|
|
620
|
+
// time would truncate the oscillation and snap to the target.
|
|
621
|
+
group.duration = springDuration;
|
|
622
|
+
contentGroup.animations = @[
|
|
623
|
+
[self springAnimationForKeyPath:@"transform.translation.x"
|
|
624
|
+
fromValue:@(fromContentTranslation.x)
|
|
625
|
+
toValue:@(toContentTranslation.x)
|
|
626
|
+
velocity:velocity
|
|
627
|
+
duration:springDuration],
|
|
628
|
+
[self springAnimationForKeyPath:@"transform.translation.y"
|
|
629
|
+
fromValue:@(fromContentTranslation.y)
|
|
630
|
+
toValue:@(toContentTranslation.y)
|
|
631
|
+
velocity:velocity
|
|
632
|
+
duration:springDuration],
|
|
633
|
+
];
|
|
634
|
+
contentGroup.duration = springDuration;
|
|
635
|
+
}
|
|
106
636
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
637
|
+
_animationDuration = group.duration;
|
|
638
|
+
_animationStartedAt = CACurrentMediaTime();
|
|
639
|
+
_animationDelegate = [SmoothClipAnimationDelegate new];
|
|
640
|
+
_animationDelegate.view = self;
|
|
641
|
+
_animationDelegate.driverId = _driverId;
|
|
642
|
+
_animationDelegate.animationId = _activeAnimationId;
|
|
643
|
+
group.delegate = _animationDelegate;
|
|
644
|
+
[layer addAnimation:group forKey:@"smoothClip.geometry"];
|
|
645
|
+
[_contentContainer.layer addAnimation:contentGroup
|
|
646
|
+
forKey:@"smoothClip.content"];
|
|
647
|
+
}
|
|
113
648
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
!accessibilityChanged) {
|
|
649
|
+
- (void)startAnimationToRequestedGeometryWithDuration:(CFTimeInterval)duration {
|
|
650
|
+
if (!_hasLayout) {
|
|
651
|
+
_pendingAnimationInstall = YES;
|
|
118
652
|
return;
|
|
119
653
|
}
|
|
654
|
+
SmoothNormalizedClipGeometry target;
|
|
655
|
+
if (![self normalizedRequestedGeometry:&target]) return;
|
|
656
|
+
const smoothclip::Presentation current = [self smoothClipCurrentPresentation];
|
|
657
|
+
[self installAnimationFromRect:CGRectMake(
|
|
658
|
+
current.clip.x,
|
|
659
|
+
current.clip.y,
|
|
660
|
+
current.clip.width,
|
|
661
|
+
current.clip.height)
|
|
662
|
+
fromRadius:current.clip.radius
|
|
663
|
+
fromContentTranslation:CGPointMake(
|
|
664
|
+
current.contentTranslateX,
|
|
665
|
+
current.contentTranslateY)
|
|
666
|
+
toRect:target.rect
|
|
667
|
+
toRadius:target.radius
|
|
668
|
+
toContentTranslation:_requestedContentTranslation
|
|
669
|
+
duration:duration];
|
|
670
|
+
}
|
|
120
671
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
672
|
+
- (void)smoothClipAnimateTiming:(smoothclip::Presentation)presentation
|
|
673
|
+
animation:(smoothclip::TimingAnimation)animation
|
|
674
|
+
animationId:(int32_t)animationId {
|
|
675
|
+
[self storeRequestedPresentation:presentation];
|
|
676
|
+
_timingAnimation = animation;
|
|
677
|
+
_activeAnimationKind = 1;
|
|
678
|
+
_activeAnimationId = animationId;
|
|
679
|
+
[self startAnimationToRequestedGeometryWithDuration:
|
|
680
|
+
MAX(0, animation.durationMs) / 1000.0];
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
- (void)smoothClipAnimateSpring:(smoothclip::Presentation)presentation
|
|
684
|
+
animation:(smoothclip::SpringAnimation)animation
|
|
685
|
+
animationId:(int32_t)animationId {
|
|
686
|
+
[self storeRequestedPresentation:presentation];
|
|
687
|
+
_springAnimation = animation;
|
|
688
|
+
if (animation.inheritVelocity) {
|
|
689
|
+
SmoothNormalizedClipGeometry target;
|
|
690
|
+
if ([self normalizedRequestedGeometry:&target]) {
|
|
691
|
+
_springAnimation.initialVelocity =
|
|
692
|
+
[self inheritedVelocityToRect:target.rect
|
|
693
|
+
radius:target.radius
|
|
694
|
+
contentTranslation:_requestedContentTranslation];
|
|
695
|
+
} else {
|
|
696
|
+
_springAnimation.initialVelocity = 0;
|
|
697
|
+
}
|
|
127
698
|
}
|
|
128
|
-
|
|
129
|
-
|
|
699
|
+
_activeAnimationKind = 2;
|
|
700
|
+
_activeAnimationId = animationId;
|
|
701
|
+
[self startAnimationToRequestedGeometryWithDuration:0];
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
- (CAKeyframeAnimation *)keyframeAnimationForKeyPath:(NSString *)keyPath
|
|
705
|
+
values:(NSArray *)values
|
|
706
|
+
keyTimes:(NSArray<NSNumber *> *)keyTimes {
|
|
707
|
+
CAKeyframeAnimation *animation =
|
|
708
|
+
[CAKeyframeAnimation animationWithKeyPath:keyPath];
|
|
709
|
+
animation.values = values;
|
|
710
|
+
animation.keyTimes = keyTimes;
|
|
711
|
+
animation.calculationMode = kCAAnimationLinear;
|
|
712
|
+
return animation;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
- (void)smoothClipAnimateKeyframes:(smoothclip::Presentation)presentation
|
|
716
|
+
keyframes:(const std::vector<smoothclip::Keyframe> &)keyframes
|
|
717
|
+
durationMs:(double)durationMs
|
|
718
|
+
animationId:(int32_t)animationId {
|
|
719
|
+
if (keyframes.size() < 2) return;
|
|
720
|
+
[self storeRequestedPresentation:presentation];
|
|
721
|
+
_activeAnimationKind = 3;
|
|
722
|
+
_activeAnimationId = animationId;
|
|
723
|
+
if (!_hasLayout) {
|
|
724
|
+
_pendingAnimationInstall = YES;
|
|
725
|
+
return;
|
|
130
726
|
}
|
|
131
|
-
|
|
132
|
-
|
|
727
|
+
[self stopLayerAnimationWithoutCallback];
|
|
728
|
+
_activeAnimationKind = 3;
|
|
729
|
+
_activeAnimationId = animationId;
|
|
730
|
+
|
|
731
|
+
const NSUInteger frameCount = keyframes.size();
|
|
732
|
+
const CGSize hostSize = self.bounds.size;
|
|
733
|
+
NSMutableArray<NSNumber *> *times =
|
|
734
|
+
[NSMutableArray arrayWithCapacity:frameCount];
|
|
735
|
+
NSMutableArray *boundsValues = [NSMutableArray arrayWithCapacity:frameCount];
|
|
736
|
+
NSMutableArray *positionValues =
|
|
737
|
+
[NSMutableArray arrayWithCapacity:frameCount];
|
|
738
|
+
NSMutableArray *radiusValues = [NSMutableArray arrayWithCapacity:frameCount];
|
|
739
|
+
NSMutableArray *translateXValues =
|
|
740
|
+
[NSMutableArray arrayWithCapacity:frameCount];
|
|
741
|
+
NSMutableArray *translateYValues =
|
|
742
|
+
[NSMutableArray arrayWithCapacity:frameCount];
|
|
743
|
+
for (const smoothclip::Keyframe &frame : keyframes) {
|
|
744
|
+
SmoothNormalizedClipGeometry normalized;
|
|
745
|
+
if (!SmoothClipNormalizeGeometry(
|
|
746
|
+
frame.presentation.clip.x,
|
|
747
|
+
frame.presentation.clip.y,
|
|
748
|
+
frame.presentation.clip.width,
|
|
749
|
+
frame.presentation.clip.height,
|
|
750
|
+
frame.presentation.clip.radius,
|
|
751
|
+
hostSize,
|
|
752
|
+
&normalized)) return;
|
|
753
|
+
[times addObject:@(frame.offset)];
|
|
754
|
+
[boundsValues addObject:[NSValue valueWithCGRect:normalized.rect]];
|
|
755
|
+
[positionValues addObject:[NSValue valueWithCGPoint:CGPointMake(
|
|
756
|
+
CGRectGetMidX(normalized.rect), CGRectGetMidY(normalized.rect))]];
|
|
757
|
+
[radiusValues addObject:@(normalized.radius)];
|
|
758
|
+
[translateXValues addObject:@(frame.presentation.contentTranslateX)];
|
|
759
|
+
[translateYValues addObject:@(frame.presentation.contentTranslateY)];
|
|
133
760
|
}
|
|
134
|
-
|
|
135
|
-
|
|
761
|
+
|
|
762
|
+
SmoothNormalizedClipGeometry target;
|
|
763
|
+
if (![self normalizedRequestedGeometry:&target]) return;
|
|
764
|
+
[self writeLayerRect:target.rect radius:target.radius];
|
|
765
|
+
[self writeContentTranslation:_requestedContentTranslation];
|
|
766
|
+
[self setClipContainerHidden:NO];
|
|
767
|
+
|
|
768
|
+
CAAnimationGroup *group = [CAAnimationGroup animation];
|
|
769
|
+
group.animations = @[
|
|
770
|
+
[self keyframeAnimationForKeyPath:@"bounds"
|
|
771
|
+
values:boundsValues
|
|
772
|
+
keyTimes:times],
|
|
773
|
+
[self keyframeAnimationForKeyPath:@"position"
|
|
774
|
+
values:positionValues
|
|
775
|
+
keyTimes:times],
|
|
776
|
+
[self keyframeAnimationForKeyPath:@"cornerRadius"
|
|
777
|
+
values:radiusValues
|
|
778
|
+
keyTimes:times],
|
|
779
|
+
];
|
|
780
|
+
group.duration = MAX(0, durationMs) / 1000.0;
|
|
781
|
+
CAAnimationGroup *contentGroup = [CAAnimationGroup animation];
|
|
782
|
+
contentGroup.animations = @[
|
|
783
|
+
[self keyframeAnimationForKeyPath:@"transform.translation.x"
|
|
784
|
+
values:translateXValues
|
|
785
|
+
keyTimes:times],
|
|
786
|
+
[self keyframeAnimationForKeyPath:@"transform.translation.y"
|
|
787
|
+
values:translateYValues
|
|
788
|
+
keyTimes:times],
|
|
789
|
+
];
|
|
790
|
+
contentGroup.duration = group.duration;
|
|
791
|
+
|
|
792
|
+
_animationDuration = group.duration;
|
|
793
|
+
_animationStartedAt = CACurrentMediaTime();
|
|
794
|
+
_animationDelegate = [SmoothClipAnimationDelegate new];
|
|
795
|
+
_animationDelegate.view = self;
|
|
796
|
+
_animationDelegate.driverId = _driverId;
|
|
797
|
+
_animationDelegate.animationId = _activeAnimationId;
|
|
798
|
+
group.delegate = _animationDelegate;
|
|
799
|
+
[_clipContainer.layer addAnimation:group forKey:@"smoothClip.geometry"];
|
|
800
|
+
[_contentContainer.layer addAnimation:contentGroup
|
|
801
|
+
forKey:@"smoothClip.content"];
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
- (void)smoothClipCancelAnimationUsingTarget:(BOOL)useTarget {
|
|
805
|
+
if (_activeAnimationId == 0) return;
|
|
806
|
+
const smoothclip::Presentation visible =
|
|
807
|
+
[self smoothClipCurrentPresentation];
|
|
808
|
+
const CGRect visibleRect = CGRectMake(
|
|
809
|
+
visible.clip.x,
|
|
810
|
+
visible.clip.y,
|
|
811
|
+
visible.clip.width,
|
|
812
|
+
visible.clip.height);
|
|
813
|
+
const CGPoint visibleContentTranslation = CGPointMake(
|
|
814
|
+
visible.contentTranslateX,
|
|
815
|
+
visible.contentTranslateY);
|
|
816
|
+
_animationPaused = NO;
|
|
817
|
+
[self stopLayerAnimationWithoutCallback];
|
|
818
|
+
if (useTarget) {
|
|
819
|
+
[self applyRequestedClip];
|
|
820
|
+
} else {
|
|
821
|
+
_requestedClip = visibleRect;
|
|
822
|
+
_requestedRadius = visible.clip.radius;
|
|
823
|
+
_requestedContentTranslation = visibleContentTranslation;
|
|
824
|
+
[self writeLayerRect:visibleRect radius:visible.clip.radius];
|
|
825
|
+
[self writeContentTranslation:visibleContentTranslation];
|
|
826
|
+
[self syncVisibilityForRect:visibleRect];
|
|
827
|
+
[self recordInteractiveRect:visibleRect
|
|
828
|
+
radius:visible.clip.radius
|
|
829
|
+
contentTranslation:visibleContentTranslation];
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
- (void)applicationWillResignActive {
|
|
834
|
+
if (_activeAnimationId == 0 || _animationPaused) return;
|
|
835
|
+
const smoothclip::Presentation visible =
|
|
836
|
+
[self smoothClipCurrentPresentation];
|
|
837
|
+
_pausedRect = CGRectMake(
|
|
838
|
+
visible.clip.x,
|
|
839
|
+
visible.clip.y,
|
|
840
|
+
visible.clip.width,
|
|
841
|
+
visible.clip.height);
|
|
842
|
+
_pausedRadius = visible.clip.radius;
|
|
843
|
+
_pausedContentTranslation = CGPointMake(
|
|
844
|
+
visible.contentTranslateX,
|
|
845
|
+
visible.contentTranslateY);
|
|
846
|
+
_pausedRemaining = MAX(
|
|
847
|
+
0, _animationDuration - (CACurrentMediaTime() - _animationStartedAt));
|
|
848
|
+
const int32_t animationId = _activeAnimationId;
|
|
849
|
+
const NSInteger animationKind = _activeAnimationKind;
|
|
850
|
+
[self stopLayerAnimationWithoutCallback];
|
|
851
|
+
_activeAnimationId = animationId;
|
|
852
|
+
_activeAnimationKind = animationKind;
|
|
853
|
+
[self writeLayerRect:_pausedRect radius:_pausedRadius];
|
|
854
|
+
[self writeContentTranslation:_pausedContentTranslation];
|
|
855
|
+
_animationPaused = YES;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
- (void)applicationDidBecomeActive {
|
|
859
|
+
if (!_animationPaused || _activeAnimationId == 0) return;
|
|
860
|
+
_animationPaused = NO;
|
|
861
|
+
SmoothNormalizedClipGeometry target;
|
|
862
|
+
if ([self normalizedRequestedGeometry:&target]) {
|
|
863
|
+
if (_activeAnimationKind == 3) {
|
|
864
|
+
_activeAnimationKind = 1;
|
|
865
|
+
_timingAnimation = {
|
|
866
|
+
_pausedRemaining * 1000.0, 0, 0, 1, 1, 2};
|
|
867
|
+
}
|
|
868
|
+
[self installAnimationFromRect:_pausedRect
|
|
869
|
+
fromRadius:_pausedRadius
|
|
870
|
+
fromContentTranslation:_pausedContentTranslation
|
|
871
|
+
toRect:target.rect
|
|
872
|
+
toRadius:target.radius
|
|
873
|
+
toContentTranslation:_requestedContentTranslation
|
|
874
|
+
duration:_pausedRemaining];
|
|
136
875
|
}
|
|
137
|
-
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
- (void)smoothClipAnimationDidStopWithDriverId:(uint64_t)driverId
|
|
879
|
+
animationId:(int32_t)animationId
|
|
880
|
+
finished:(BOOL)finished {
|
|
881
|
+
if (_ignoreAnimationCallback || animationId != _activeAnimationId) return;
|
|
882
|
+
_activeAnimationId = 0;
|
|
883
|
+
_activeAnimationKind = 0;
|
|
884
|
+
_animationPaused = NO;
|
|
885
|
+
_animationDelegate = nil;
|
|
886
|
+
[self syncVisibilityForRect:_normalizedClip];
|
|
887
|
+
smoothclip::viewAnimationDidStop(
|
|
888
|
+
driverId, animationId, self, finished);
|
|
138
889
|
}
|
|
139
890
|
|
|
140
891
|
- (void)updateProps:(const Props::Shared &)props
|
|
@@ -143,38 +894,143 @@ using namespace facebook::react;
|
|
|
143
894
|
*std::static_pointer_cast<const SmoothClipViewProps>(props);
|
|
144
895
|
[super updateProps:props oldProps:oldProps];
|
|
145
896
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
897
|
+
const uint64_t nextDriverId =
|
|
898
|
+
isfinite(newProps.driverId) && newProps.driverId > 0
|
|
899
|
+
? static_cast<uint64_t>(newProps.driverId)
|
|
900
|
+
: 0;
|
|
901
|
+
const smoothclip::Presentation initial{
|
|
902
|
+
{newProps.initialClipX,
|
|
903
|
+
newProps.initialClipY,
|
|
904
|
+
newProps.initialClipWidth,
|
|
905
|
+
newProps.initialClipHeight,
|
|
906
|
+
newProps.initialClipRadius},
|
|
907
|
+
newProps.initialContentTranslateX,
|
|
908
|
+
newProps.initialContentTranslateY};
|
|
909
|
+
|
|
910
|
+
if (nextDriverId != _driverId) {
|
|
911
|
+
if (_driverId != 0) {
|
|
912
|
+
smoothclip::unregisterView(_driverId, self);
|
|
913
|
+
}
|
|
914
|
+
_driverId = nextDriverId;
|
|
915
|
+
_commandIsAuthoritative = NO;
|
|
916
|
+
if (_driverId != 0) {
|
|
917
|
+
smoothclip::registerView(_driverId, self, initial);
|
|
918
|
+
}
|
|
919
|
+
} else if (_driverId == 0 && !_commandIsAuthoritative) {
|
|
920
|
+
[self smoothClipApplyPresentation:initial];
|
|
152
921
|
}
|
|
153
922
|
}
|
|
154
923
|
|
|
924
|
+
- (void)layoutContentContainer {
|
|
925
|
+
_contentContainer.layer.bounds = CGRectMake(
|
|
926
|
+
0, 0, MAX(0, self.bounds.size.width), MAX(0, self.bounds.size.height));
|
|
927
|
+
_contentContainer.layer.position = CGPointMake(
|
|
928
|
+
MAX(0, self.bounds.size.width) / 2,
|
|
929
|
+
MAX(0, self.bounds.size.height) / 2);
|
|
930
|
+
}
|
|
931
|
+
|
|
155
932
|
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
|
|
156
|
-
|
|
933
|
+
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
|
|
934
|
+
if (!_hasLayout && _pendingAnimationInstall && _driverId != 0) {
|
|
935
|
+
// A native animation install arrived before the first layout. Join it now
|
|
936
|
+
// through the registry so it starts from live presentation geometry with
|
|
937
|
+
// the true remaining duration instead of jumping to the target.
|
|
938
|
+
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
|
|
939
|
+
_hasLayout = YES;
|
|
940
|
+
_pendingAnimationInstall = NO;
|
|
941
|
+
[self layoutContentContainer];
|
|
942
|
+
if (!smoothclip::joinActiveAnimation(_driverId, self)) {
|
|
943
|
+
// The animation finished or was cancelled while awaiting layout.
|
|
944
|
+
_activeAnimationId = 0;
|
|
945
|
+
_activeAnimationKind = 0;
|
|
946
|
+
[self applyRequestedClip];
|
|
947
|
+
}
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
CGFloat visibleRadius = 0;
|
|
951
|
+
const BOOL wasAnimating = _activeAnimationId != 0;
|
|
952
|
+
const smoothclip::Presentation visible = wasAnimating
|
|
953
|
+
? [self smoothClipCurrentPresentation]
|
|
954
|
+
: smoothclip::Presentation{{0, 0, 0, 0, 0}, 0, 0};
|
|
955
|
+
const CGRect visibleRect = CGRectMake(
|
|
956
|
+
visible.clip.x,
|
|
957
|
+
visible.clip.y,
|
|
958
|
+
visible.clip.width,
|
|
959
|
+
visible.clip.height);
|
|
960
|
+
visibleRadius = visible.clip.radius;
|
|
961
|
+
const CGPoint visibleContentTranslation = CGPointMake(
|
|
962
|
+
visible.contentTranslateX,
|
|
963
|
+
visible.contentTranslateY);
|
|
964
|
+
const CFTimeInterval remaining = wasAnimating
|
|
965
|
+
? MAX(0, _animationDuration -
|
|
966
|
+
(CACurrentMediaTime() - _animationStartedAt))
|
|
967
|
+
: 0;
|
|
968
|
+
|
|
157
969
|
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
|
|
158
970
|
_hasLayout = YES;
|
|
159
|
-
[self
|
|
971
|
+
[self layoutContentContainer];
|
|
972
|
+
if (wasAnimating && _animationPaused) {
|
|
973
|
+
SmoothNormalizedClipGeometry paused;
|
|
974
|
+
if (SmoothClipNormalizeGeometry(
|
|
975
|
+
CGRectGetMinX(_pausedRect),
|
|
976
|
+
CGRectGetMinY(_pausedRect),
|
|
977
|
+
CGRectGetWidth(_pausedRect),
|
|
978
|
+
CGRectGetHeight(_pausedRect),
|
|
979
|
+
_pausedRadius,
|
|
980
|
+
self.bounds.size,
|
|
981
|
+
&paused)) {
|
|
982
|
+
_pausedRect = paused.rect;
|
|
983
|
+
_pausedRadius = paused.radius;
|
|
984
|
+
[self writeLayerRect:paused.rect radius:paused.radius];
|
|
985
|
+
}
|
|
986
|
+
} else if (wasAnimating) {
|
|
987
|
+
const int32_t animationId = _activeAnimationId;
|
|
988
|
+
const NSInteger animationKind = _activeAnimationKind;
|
|
989
|
+
[self stopLayerAnimationWithoutCallback];
|
|
990
|
+
_activeAnimationId = animationId;
|
|
991
|
+
_activeAnimationKind = animationKind;
|
|
992
|
+
if (_activeAnimationKind == 3) {
|
|
993
|
+
_activeAnimationKind = 1;
|
|
994
|
+
_timingAnimation = {remaining * 1000.0, 0, 0, 1, 1, 2};
|
|
995
|
+
}
|
|
996
|
+
SmoothNormalizedClipGeometry current;
|
|
997
|
+
SmoothClipNormalizeGeometry(
|
|
998
|
+
CGRectGetMinX(visibleRect),
|
|
999
|
+
CGRectGetMinY(visibleRect),
|
|
1000
|
+
CGRectGetWidth(visibleRect),
|
|
1001
|
+
CGRectGetHeight(visibleRect),
|
|
1002
|
+
visibleRadius,
|
|
1003
|
+
self.bounds.size,
|
|
1004
|
+
¤t);
|
|
1005
|
+
SmoothNormalizedClipGeometry target;
|
|
1006
|
+
if ([self normalizedRequestedGeometry:&target]) {
|
|
1007
|
+
[self installAnimationFromRect:current.rect
|
|
1008
|
+
fromRadius:current.radius
|
|
1009
|
+
fromContentTranslation:visibleContentTranslation
|
|
1010
|
+
toRect:target.rect
|
|
1011
|
+
toRadius:target.radius
|
|
1012
|
+
toContentTranslation:_requestedContentTranslation
|
|
1013
|
+
duration:remaining];
|
|
1014
|
+
}
|
|
1015
|
+
} else {
|
|
1016
|
+
[self applyRequestedClip];
|
|
1017
|
+
}
|
|
160
1018
|
}
|
|
161
1019
|
|
|
162
1020
|
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
|
|
163
|
-
if (![super pointInside:point withEvent:event]
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (radius <= 0)
|
|
171
|
-
return YES;
|
|
172
|
-
}
|
|
1021
|
+
if (![super pointInside:point withEvent:event]) return NO;
|
|
1022
|
+
CGFloat radius = 0;
|
|
1023
|
+
const CGRect clip = _activeAnimationId != 0
|
|
1024
|
+
? [self presentationRectWithRadius:&radius]
|
|
1025
|
+
: _normalizedClip;
|
|
1026
|
+
if (_activeAnimationId == 0) radius = _normalizedRadius;
|
|
1027
|
+
if (CGRectIsEmpty(clip) || !CGRectContainsPoint(clip, point)) return NO;
|
|
1028
|
+
if (radius <= 0) return YES;
|
|
173
1029
|
|
|
174
|
-
const CGFloat innerLeft = CGRectGetMinX(
|
|
175
|
-
const CGFloat innerRight = CGRectGetMaxX(
|
|
176
|
-
const CGFloat innerTop = CGRectGetMinY(
|
|
177
|
-
const CGFloat innerBottom = CGRectGetMaxY(
|
|
1030
|
+
const CGFloat innerLeft = CGRectGetMinX(clip) + radius;
|
|
1031
|
+
const CGFloat innerRight = CGRectGetMaxX(clip) - radius;
|
|
1032
|
+
const CGFloat innerTop = CGRectGetMinY(clip) + radius;
|
|
1033
|
+
const CGFloat innerBottom = CGRectGetMaxY(clip) - radius;
|
|
178
1034
|
if ((point.x >= innerLeft && point.x <= innerRight) ||
|
|
179
1035
|
(point.y >= innerTop && point.y <= innerBottom)) {
|
|
180
1036
|
return YES;
|
|
@@ -187,7 +1043,7 @@ using namespace facebook::react;
|
|
|
187
1043
|
return dx * dx + dy * dy <= radius * radius;
|
|
188
1044
|
}
|
|
189
1045
|
|
|
190
|
-
#pragma mark -
|
|
1046
|
+
#pragma mark - Legacy native command (Android parity and benchmark baseline)
|
|
191
1047
|
|
|
192
1048
|
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
|
|
193
1049
|
RCTSmoothClipViewHandleCommand(self, commandName, args);
|
|
@@ -199,26 +1055,61 @@ using namespace facebook::react;
|
|
|
199
1055
|
height:(double)height
|
|
200
1056
|
radius:(double)radius {
|
|
201
1057
|
_commandIsAuthoritative = YES;
|
|
202
|
-
[self
|
|
1058
|
+
[self smoothClipApplyPresentation:
|
|
1059
|
+
{{x, y, width, height, radius}, 0, 0}];
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
- (void)setClipPresentation:(double)x
|
|
1063
|
+
y:(double)y
|
|
1064
|
+
width:(double)width
|
|
1065
|
+
height:(double)height
|
|
1066
|
+
radius:(double)radius
|
|
1067
|
+
contentTranslateX:(double)contentTranslateX
|
|
1068
|
+
contentTranslateY:(double)contentTranslateY {
|
|
1069
|
+
_commandIsAuthoritative = YES;
|
|
1070
|
+
[self smoothClipApplyPresentation:
|
|
1071
|
+
{{x, y, width, height, radius},
|
|
1072
|
+
contentTranslateX,
|
|
1073
|
+
contentTranslateY}];
|
|
203
1074
|
}
|
|
204
1075
|
|
|
205
1076
|
- (void)prepareForRecycle {
|
|
1077
|
+
if (_driverId != 0) {
|
|
1078
|
+
smoothclip::unregisterView(_driverId, self);
|
|
1079
|
+
_driverId = 0;
|
|
1080
|
+
}
|
|
1081
|
+
[self stopLayerAnimationWithoutCallback];
|
|
1082
|
+
_pendingAnimationInstall = NO;
|
|
1083
|
+
_animationPaused = NO;
|
|
1084
|
+
_hasPreviousInteractiveSample = NO;
|
|
1085
|
+
_hasInteractiveSample = NO;
|
|
206
1086
|
[super prepareForRecycle];
|
|
207
1087
|
_requestedClip = CGRectZero;
|
|
208
1088
|
_normalizedClip = CGRectZero;
|
|
209
1089
|
_requestedRadius = 0;
|
|
210
1090
|
_normalizedRadius = 0;
|
|
1091
|
+
_requestedContentTranslation = CGPointZero;
|
|
1092
|
+
_normalizedContentTranslation = CGPointZero;
|
|
211
1093
|
_hasLayout = NO;
|
|
212
1094
|
_commandIsAuthoritative = NO;
|
|
213
1095
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
1096
|
+
CALayer *layer = _clipContainer.layer;
|
|
1097
|
+
layer.bounds = CGRectZero;
|
|
1098
|
+
layer.position = CGPointZero;
|
|
1099
|
+
layer.cornerRadius = 0;
|
|
1100
|
+
_contentContainer.layer.affineTransform = CGAffineTransformIdentity;
|
|
1101
|
+
[self setClipContainerHidden:YES];
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
@end
|
|
1105
|
+
|
|
1106
|
+
@implementation SmoothClipAnimationDelegate
|
|
1107
|
+
|
|
1108
|
+
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished {
|
|
1109
|
+
if (self.invalidated) return;
|
|
1110
|
+
[self.view smoothClipAnimationDidStopWithDriverId:self.driverId
|
|
1111
|
+
animationId:self.animationId
|
|
1112
|
+
finished:finished];
|
|
222
1113
|
}
|
|
223
1114
|
|
|
224
1115
|
@end
|