react-native-smooth-clip-view 0.4.1 → 0.4.3
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/CHANGELOG.md +44 -0
- package/README.md +92 -5
- package/android/src/main/cpp/SmoothClipAndroid.h +2 -1
- package/android/src/main/cpp/SmoothClipBindings.cpp +22 -8
- package/android/src/main/cpp/SmoothClipRegistry.cpp +85 -10
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +23 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +8 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +39 -1
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +124 -32
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +16 -0
- package/cpp/SmoothClipAnimationCurve.h +21 -10
- package/cpp/SmoothClipRegistry.h +2 -0
- package/cpp/SmoothClipSharedGeometry.h +20 -0
- package/cpp/SmoothClipVelocityTracker.h +6 -0
- package/ios/SmoothClipRegistry.mm +173 -17
- package/ios/SmoothClipTurboModule.cpp +17 -6
- package/ios/SmoothClipTurboModule.h +3 -1
- package/ios/SmoothClipView.mm +408 -20
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +3 -0
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +5 -1
- package/lib/module/geometry.js +22 -2
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/presentationCodec.js +5 -3
- package/lib/module/presentationCodec.js.map +1 -1
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +1 -1
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +3 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/controllerTypes.d.ts +5 -1
- package/lib/typescript/src/controllerTypes.d.ts.map +1 -1
- package/lib/typescript/src/geometry.d.ts +9 -0
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/presentationCodec.d.ts +1 -1
- package/lib/typescript/src/presentationCodec.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/NativeSmoothClipModule.ts +3 -1
- package/src/SmoothClipView.ios.tsx +3 -0
- package/src/SmoothClipViewNativeComponent.ts +5 -1
- package/src/controllerTypes.ts +5 -1
- package/src/geometry.ts +35 -0
- package/src/index.ts +1 -0
- package/src/presentationCodec.ts +8 -2
package/ios/SmoothClipView.mm
CHANGED
|
@@ -45,6 +45,36 @@ using namespace facebook::react;
|
|
|
45
45
|
return (id<CAAction>)[NSNull null];
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// UIKit normally hit-tests the target model tree. During native motion, use
|
|
49
|
+
// the visible bounds/transform/opacity instead, including a fade whose target
|
|
50
|
+
// opacity is already zero. These containers have no consumer-owned handlers.
|
|
51
|
+
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
|
|
52
|
+
CALayer *visible = self.layer.animationKeys.count > 0 ? self.layer.presentationLayer : nil;
|
|
53
|
+
return CGRectContainsPoint(visible != nil ? visible.bounds : self.bounds, point);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
|
57
|
+
CALayer *visible = self.layer.animationKeys.count > 0 ? self.layer.presentationLayer : nil;
|
|
58
|
+
if (self.hidden || !self.userInteractionEnabled ||
|
|
59
|
+
(visible != nil ? visible.opacity : self.layer.opacity) <= 0) return nil;
|
|
60
|
+
if (visible != nil && self.layer.animationKeys.count > 0) {
|
|
61
|
+
CGPoint parentPoint = [self.layer convertPoint:point toLayer:self.layer.superlayer];
|
|
62
|
+
point = [visible convertPoint:parentPoint fromLayer:visible.superlayer];
|
|
63
|
+
}
|
|
64
|
+
if (![self pointInside:point withEvent:event]) return nil;
|
|
65
|
+
for (UIView *child in self.subviews.reverseObjectEnumerator) {
|
|
66
|
+
UIView *hit = [child hitTest:[self convertPoint:point toView:child] withEvent:event];
|
|
67
|
+
if (hit != nil) return hit;
|
|
68
|
+
}
|
|
69
|
+
return self;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@end
|
|
73
|
+
|
|
74
|
+
@interface SmoothClipPresentationContainerView : SmoothClipContainerView
|
|
75
|
+
@end
|
|
76
|
+
@implementation SmoothClipPresentationContainerView
|
|
77
|
+
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return YES; }
|
|
48
78
|
@end
|
|
49
79
|
|
|
50
80
|
@interface SmoothClipView () <RCTSmoothClipViewViewProtocol>
|
|
@@ -104,6 +134,8 @@ static bool SmoothClipBuildPresentation(
|
|
|
104
134
|
double shadowOffsetY,
|
|
105
135
|
double shadowBlurRadius,
|
|
106
136
|
double shadowSpreadDistance,
|
|
137
|
+
double rotation,
|
|
138
|
+
double opacity,
|
|
107
139
|
smoothclip::Presentation *result) {
|
|
108
140
|
if (result == nullptr || !isfinite(x) || !isfinite(y) ||
|
|
109
141
|
!isfinite(width) || !isfinite(height) ||
|
|
@@ -115,6 +147,7 @@ static bool SmoothClipBuildPresentation(
|
|
|
115
147
|
!isfinite(shadowAlpha) ||
|
|
116
148
|
!isfinite(shadowOffsetX) || !isfinite(shadowOffsetY) ||
|
|
117
149
|
!isfinite(shadowBlurRadius) || !isfinite(shadowSpreadDistance) ||
|
|
150
|
+
!isfinite(rotation) || !isfinite(opacity) ||
|
|
118
151
|
contentScale <= 0 || shadowRed < 0 || shadowRed > 1 ||
|
|
119
152
|
shadowGreen < 0 || shadowGreen > 1 ||
|
|
120
153
|
shadowBlue < 0 || shadowBlue > 1 ||
|
|
@@ -145,7 +178,8 @@ static bool SmoothClipBuildPresentation(
|
|
|
145
178
|
shadowBlurRadius,
|
|
146
179
|
shadowSpreadDistance};
|
|
147
180
|
*result = {
|
|
148
|
-
geometry, contentTranslateX, contentTranslateY, contentScale, shadow
|
|
181
|
+
geometry, contentTranslateX, contentTranslateY, contentScale, shadow,
|
|
182
|
+
rotation, smoothclip::clamp01(opacity)};
|
|
149
183
|
return true;
|
|
150
184
|
}
|
|
151
185
|
|
|
@@ -320,9 +354,25 @@ static std::array<double, 11> SmoothClipVelocityChannels(
|
|
|
320
354
|
contentScale};
|
|
321
355
|
}
|
|
322
356
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
357
|
+
// Core Animation uses the overdamped physical solution as well as the critical
|
|
358
|
+
// and underdamped ones. This is only a turn-count reference at snapshot time.
|
|
359
|
+
static double SmoothClipCARotationProgress(double elapsed, const smoothclip::SpringAnimation &spring) {
|
|
360
|
+
const double omega = std::sqrt(spring.stiffness / spring.mass);
|
|
361
|
+
const double zeta = spring.damping / (2 * std::sqrt(spring.stiffness * spring.mass));
|
|
362
|
+
if (zeta <= 1) {
|
|
363
|
+
return smoothclip::advanceScalarSpring({0, spring.initialVelocity}, spring, elapsed).position;
|
|
364
|
+
}
|
|
365
|
+
const double root = std::sqrt(zeta * zeta - 1);
|
|
366
|
+
const double slow = -omega / (zeta + root);
|
|
367
|
+
const double fast = -omega * (zeta + root);
|
|
368
|
+
const double a = (spring.initialVelocity + fast) / (slow - fast);
|
|
369
|
+
return 1 + a * std::exp(slow * elapsed) + (-1 - a) * std::exp(fast * elapsed);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
static BOOL SmoothClipRotatedRectIntersectsHost(CGRect rect, CGRect host, double rotation) {
|
|
373
|
+
return smoothclip::rotatedRectIntersectsHost(rect.origin.x - host.origin.x,
|
|
374
|
+
rect.origin.y - host.origin.y, rect.size.width, rect.size.height, rotation,
|
|
375
|
+
host.size.width, host.size.height);
|
|
326
376
|
}
|
|
327
377
|
|
|
328
378
|
static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
@@ -333,6 +383,19 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
333
383
|
|
|
334
384
|
@implementation SmoothClipView {
|
|
335
385
|
CALayer *_shadowLayer;
|
|
386
|
+
SmoothClipPresentationContainerView *_presentationContainer;
|
|
387
|
+
double _requestedRotation;
|
|
388
|
+
double _canonicalRotation;
|
|
389
|
+
double _requestedOpacity;
|
|
390
|
+
double _canonicalOpacity;
|
|
391
|
+
double _animationFromRotation;
|
|
392
|
+
double _animationToRotation;
|
|
393
|
+
BOOL _hasAppearanceAnimation;
|
|
394
|
+
BOOL _hasMaskAnimation;
|
|
395
|
+
BOOL _hasShadowModel;
|
|
396
|
+
SmoothClipCanonicalGeometry _shadowModelGeometry;
|
|
397
|
+
smoothclip::Shadow _shadowModel;
|
|
398
|
+
|
|
336
399
|
SmoothClipContainerView *_clipContainer;
|
|
337
400
|
SmoothClipContainerView *_contentContainer;
|
|
338
401
|
CAShapeLayer *_unequalCornerMask;
|
|
@@ -365,6 +428,9 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
365
428
|
smoothclip::TimingAnimation _timingAnimation;
|
|
366
429
|
smoothclip::SpringAnimation _springAnimation;
|
|
367
430
|
SmoothClipAnimationDelegate *_animationDelegate;
|
|
431
|
+
BOOL _springPausedForInactivity;
|
|
432
|
+
BOOL _pausedSpringHadMaskAnimation;
|
|
433
|
+
BOOL _pausedSpringHadShadowAnimation;
|
|
368
434
|
|
|
369
435
|
// 'inherit' velocity samples (raw canonical geometry, per view); recording/
|
|
370
436
|
// coalescing/projection live in the shared cpp/SmoothClipVelocityTracker.h
|
|
@@ -397,6 +463,12 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
397
463
|
// intentionally overridden again after every prop update below.
|
|
398
464
|
self.clipsToBounds = YES;
|
|
399
465
|
|
|
466
|
+
_presentationContainer = [[SmoothClipPresentationContainerView alloc] initWithFrame:CGRectZero];
|
|
467
|
+
_presentationContainer.layer.anchorPoint = CGPointZero;
|
|
468
|
+
_presentationContainer.layer.allowsGroupOpacity = YES;
|
|
469
|
+
_presentationContainer.autoresizesSubviews = NO;
|
|
470
|
+
[self addSubview:_presentationContainer];
|
|
471
|
+
_requestedOpacity = _canonicalOpacity = 1;
|
|
400
472
|
_clipContainer = [[SmoothClipContainerView alloc] initWithFrame:CGRectZero];
|
|
401
473
|
_clipContainer.layer.masksToBounds = YES;
|
|
402
474
|
_clipContainer.layer.needsDisplayOnBoundsChange = NO;
|
|
@@ -409,7 +481,7 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
409
481
|
_contentContainer.layer.needsDisplayOnBoundsChange = NO;
|
|
410
482
|
_contentContainer.autoresizesSubviews = NO;
|
|
411
483
|
[_clipContainer addSubview:_contentContainer];
|
|
412
|
-
[
|
|
484
|
+
[_presentationContainer addSubview:_clipContainer];
|
|
413
485
|
|
|
414
486
|
_shadowLayer = nil;
|
|
415
487
|
// Created lazily so the common clipping-only path owns no shadow layer.
|
|
@@ -445,6 +517,9 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
445
517
|
_ignoreAnimationCallback = NO;
|
|
446
518
|
_activeAnimationId = 0;
|
|
447
519
|
_activeAnimationKind = 0;
|
|
520
|
+
_springPausedForInactivity = NO;
|
|
521
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
522
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
448
523
|
smoothclip::clearVelocitySamples(_velocitySamples);
|
|
449
524
|
// App-state transitions are observed by the registry itself (see
|
|
450
525
|
// installApplicationStateObservers): the active flag is process-global,
|
|
@@ -502,6 +577,8 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
502
577
|
presentation.contentTranslateY);
|
|
503
578
|
_requestedContentScale = presentation.contentScale;
|
|
504
579
|
_requestedShadow = presentation.shadow;
|
|
580
|
+
_requestedRotation = presentation.rotation;
|
|
581
|
+
_requestedOpacity = smoothclip::clamp01(presentation.opacity);
|
|
505
582
|
}
|
|
506
583
|
|
|
507
584
|
- (CGColorRef)colorForShadow:(const smoothclip::Shadow &)shadow {
|
|
@@ -524,7 +601,7 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
524
601
|
@"bounds": [NSNull null],
|
|
525
602
|
@"position": [NSNull null],
|
|
526
603
|
};
|
|
527
|
-
[
|
|
604
|
+
[_presentationContainer.layer insertSublayer:_shadowLayer below:_clipContainer.layer];
|
|
528
605
|
return _shadowLayer;
|
|
529
606
|
}
|
|
530
607
|
|
|
@@ -539,9 +616,15 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
539
616
|
shadowLayer.bounds = self.bounds;
|
|
540
617
|
shadowLayer.position = CGPointMake(
|
|
541
618
|
CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
619
|
+
if (!_hasShadowModel || !SmoothBoxShadowPathInputEqual(
|
|
620
|
+
_shadowModelGeometry, _shadowModel, geometry, effectiveShadow)) {
|
|
621
|
+
CGPathRef path = SmoothClipCreateShadowPath(geometry, effectiveShadow);
|
|
622
|
+
shadowLayer.shadowPath = path;
|
|
623
|
+
CGPathRelease(path);
|
|
624
|
+
}
|
|
625
|
+
_hasShadowModel = YES;
|
|
626
|
+
_shadowModelGeometry = geometry;
|
|
627
|
+
_shadowModel = effectiveShadow;
|
|
545
628
|
shadowLayer.shadowColor = [self colorForShadow:effectiveShadow];
|
|
546
629
|
shadowLayer.shadowOpacity = visible ? 1 : 0;
|
|
547
630
|
shadowLayer.shadowRadius = MAX(0, effectiveShadow.blurRadius) / 2.0;
|
|
@@ -597,7 +680,7 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
597
680
|
- (void)syncVisibilityForRect:(CGRect)rect {
|
|
598
681
|
[self setClipContainerHidden:CGRectIsEmpty(rect)];
|
|
599
682
|
_clipContainer.accessibilityElementsHidden =
|
|
600
|
-
!
|
|
683
|
+
!SmoothClipRotatedRectIntersectsHost(rect, self.bounds, _canonicalRotation) || _canonicalOpacity <= 0;
|
|
601
684
|
}
|
|
602
685
|
|
|
603
686
|
- (void)configureUnequalCornerMaskForGeometry:
|
|
@@ -681,6 +764,50 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
681
764
|
#endif
|
|
682
765
|
}
|
|
683
766
|
|
|
767
|
+
- (void)writeAppearanceRotation:(double)rotation opacity:(double)opacity rect:(CGRect)rect {
|
|
768
|
+
CALayer *layer = _presentationContainer.layer;
|
|
769
|
+
// A zero-angle group is identity regardless of pivot. Keep it stationary so
|
|
770
|
+
// existing geometry-only streams do not also dirty the parent layer each frame.
|
|
771
|
+
const CGPoint center = rotation == 0 ? CGPointZero :
|
|
772
|
+
CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
|
|
773
|
+
const CGRect bounds = (CGRect){center, self.bounds.size};
|
|
774
|
+
if (!CGRectEqualToRect(layer.bounds, bounds)) layer.bounds = bounds;
|
|
775
|
+
if (!CGPointEqualToPoint(layer.position, center)) layer.position = center;
|
|
776
|
+
if (_canonicalRotation != rotation) layer.affineTransform = CGAffineTransformMakeRotation(rotation);
|
|
777
|
+
if (_canonicalOpacity != opacity) layer.opacity = smoothclip::clamp01(opacity);
|
|
778
|
+
_canonicalRotation = rotation;
|
|
779
|
+
_canonicalOpacity = opacity;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Matrix decomposition loses complete turns. Use the installed native trajectory
|
|
783
|
+
// to choose the equivalent visible angle, including after a paused spring resumes.
|
|
784
|
+
- (double)visibleRotation {
|
|
785
|
+
CAAnimationGroup *group = (CAAnimationGroup *)[_presentationContainer.layer
|
|
786
|
+
animationForKey:@"smoothClip.appearance"];
|
|
787
|
+
CALayer *visible = _presentationContainer.layer.presentationLayer;
|
|
788
|
+
if (_activeAnimationId == 0 || group == nil || visible == nil) return _canonicalRotation;
|
|
789
|
+
const double elapsed = group.speed == 0 ? group.timeOffset :
|
|
790
|
+
std::clamp([_presentationContainer.layer convertTime:CACurrentMediaTime() fromLayer:nil] - group.beginTime,
|
|
791
|
+
0.0, group.duration);
|
|
792
|
+
double progress;
|
|
793
|
+
if (_activeAnimationKind == 1) {
|
|
794
|
+
progress = smoothclip::cubicBezier(_timingAnimation.controlPoint1X,
|
|
795
|
+
_timingAnimation.controlPoint1Y, _timingAnimation.controlPoint2X,
|
|
796
|
+
_timingAnimation.controlPoint2Y, group.duration > 0 ? elapsed / group.duration : 1);
|
|
797
|
+
} else {
|
|
798
|
+
progress = SmoothClipCARotationProgress(elapsed, _springAnimation);
|
|
799
|
+
}
|
|
800
|
+
const double expected = _animationFromRotation +
|
|
801
|
+
(_animationToRotation - _animationFromRotation) * progress;
|
|
802
|
+
const CGAffineTransform matrix = visible.affineTransform;
|
|
803
|
+
return smoothclip::unwrapRotation(std::atan2(matrix.b, matrix.a), expected);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
- (double)visibleOpacity {
|
|
807
|
+
CALayer *layer = _activeAnimationId != 0 ? _presentationContainer.layer.presentationLayer : nil;
|
|
808
|
+
return smoothclip::clamp01(layer == nil ? _canonicalOpacity : layer.opacity);
|
|
809
|
+
}
|
|
810
|
+
|
|
684
811
|
- (void)writeContentTranslation:(CGPoint)translation
|
|
685
812
|
scale:(CGFloat)scale {
|
|
686
813
|
if (CGPointEqualToPoint(_canonicalContentTranslation, translation) &&
|
|
@@ -698,7 +825,7 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
698
825
|
if (![self canonicalRequestedGeometry:&geometry]) return;
|
|
699
826
|
|
|
700
827
|
const BOOL accessibilityHidden =
|
|
701
|
-
!
|
|
828
|
+
!SmoothClipRotatedRectIntersectsHost(geometry.rect, self.bounds, _requestedRotation) || _requestedOpacity <= 0;
|
|
702
829
|
const BOOL visibilityChanged =
|
|
703
830
|
_clipHidden != CGRectIsEmpty(geometry.rect) ||
|
|
704
831
|
_clipContainer.accessibilityElementsHidden != accessibilityHidden;
|
|
@@ -709,15 +836,23 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
709
836
|
CGPointEqualToPoint(
|
|
710
837
|
_canonicalContentTranslation, _requestedContentTranslation) &&
|
|
711
838
|
_canonicalContentScale == _requestedContentScale &&
|
|
839
|
+
_canonicalRotation == _requestedRotation && _canonicalOpacity == _requestedOpacity &&
|
|
712
840
|
SmoothBoxShadowEqual(_canonicalShadow, _requestedShadow) &&
|
|
713
841
|
!visibilityChanged) {
|
|
714
842
|
return;
|
|
715
843
|
}
|
|
844
|
+
const SmoothClipCanonicalGeometry previousGeometry = [self canonicalGeometryValue];
|
|
716
845
|
[self writeLayerGeometry:geometry
|
|
717
846
|
hasExplicitRadii:_requestedHasExplicitRadii];
|
|
718
847
|
[self writeContentTranslation:_requestedContentTranslation
|
|
719
848
|
scale:_requestedContentScale];
|
|
720
|
-
|
|
849
|
+
if (!CGRectEqualToRect(previousGeometry.rect, geometry.rect) ||
|
|
850
|
+
!SmoothClipCornerRadiiEqual(previousGeometry.radii, geometry.radii) ||
|
|
851
|
+
previousGeometry.curve != geometry.curve ||
|
|
852
|
+
!SmoothBoxShadowEqual(_canonicalShadow, _requestedShadow)) {
|
|
853
|
+
[self writeShadow:_requestedShadow geometry:geometry];
|
|
854
|
+
}
|
|
855
|
+
[self writeAppearanceRotation:_requestedRotation opacity:_requestedOpacity rect:geometry.rect];
|
|
721
856
|
[self syncVisibilityForRect:geometry.rect];
|
|
722
857
|
}
|
|
723
858
|
|
|
@@ -827,7 +962,8 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
827
962
|
? (CALayer *)_shadowLayer.presentationLayer
|
|
828
963
|
: _shadowLayer;
|
|
829
964
|
if (shadowLayer == nil) return SmoothClipCanonicalSnapshot({
|
|
830
|
-
geometry, transform.tx, transform.ty, contentScale, shadow
|
|
965
|
+
geometry, transform.tx, transform.ty, contentScale, shadow,
|
|
966
|
+
[self visibleRotation], [self visibleOpacity]});
|
|
831
967
|
shadow.enabled = _canonicalShadow.enabled || shadowLayer.shadowOpacity > 0;
|
|
832
968
|
shadow.blurRadius = shadowLayer.shadowRadius * 2.0;
|
|
833
969
|
shadow.offsetX = shadowLayer.shadowOffset.width;
|
|
@@ -850,7 +986,8 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
850
986
|
CGRectGetMinX(visibleRect) - CGRectGetMinX(shadowBounds);
|
|
851
987
|
}
|
|
852
988
|
return SmoothClipCanonicalSnapshot(
|
|
853
|
-
{geometry, transform.tx, transform.ty, contentScale, shadow
|
|
989
|
+
{geometry, transform.tx, transform.ty, contentScale, shadow,
|
|
990
|
+
[self visibleRotation], [self visibleOpacity]});
|
|
854
991
|
}
|
|
855
992
|
|
|
856
993
|
- (BOOL)smoothClipIsJoinable {
|
|
@@ -921,12 +1058,18 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
921
1058
|
[_clipContainer.layer removeAnimationForKey:@"smoothClip.geometry"];
|
|
922
1059
|
[_contentContainer.layer removeAnimationForKey:@"smoothClip.content"];
|
|
923
1060
|
[_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
|
|
1061
|
+
[_presentationContainer.layer removeAnimationForKey:@"smoothClip.appearance"];
|
|
924
1062
|
[_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
|
|
925
|
-
|
|
1063
|
+
if (_hasMaskAnimation) {
|
|
1064
|
+
[self applyStaticCornerRepresentation:[self canonicalGeometryValue]];
|
|
1065
|
+
}
|
|
926
1066
|
_ignoreAnimationCallback = NO;
|
|
927
1067
|
_activeAnimationId = 0;
|
|
928
1068
|
_activeAnimationKind = 0;
|
|
929
1069
|
_animationDelegate = nil;
|
|
1070
|
+
_springPausedForInactivity = NO;
|
|
1071
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
1072
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
930
1073
|
}
|
|
931
1074
|
|
|
932
1075
|
- (CABasicAnimation *)basicAnimationForKeyPath:(NSString *)keyPath
|
|
@@ -978,12 +1121,16 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
978
1121
|
fromContentTranslation:(CGPoint)fromContentTranslation
|
|
979
1122
|
fromContentScale:(CGFloat)fromContentScale
|
|
980
1123
|
fromShadow:(smoothclip::Shadow)fromShadow
|
|
1124
|
+
fromRotation:(double)fromRotation
|
|
1125
|
+
fromOpacity:(double)fromOpacity
|
|
981
1126
|
toGeometry:
|
|
982
1127
|
(SmoothClipCanonicalGeometry)toGeometry
|
|
983
1128
|
hasExplicitToRadii:(BOOL)hasExplicitToRadii
|
|
984
1129
|
toContentTranslation:(CGPoint)toContentTranslation
|
|
985
1130
|
toContentScale:(CGFloat)toContentScale
|
|
986
1131
|
toShadow:(smoothclip::Shadow)toShadow
|
|
1132
|
+
toRotation:(double)toRotation
|
|
1133
|
+
toOpacity:(double)toOpacity
|
|
987
1134
|
duration:(CFTimeInterval)duration
|
|
988
1135
|
sharedBeginTime:(CFTimeInterval)sharedBeginTime {
|
|
989
1136
|
const int32_t animationId = _activeAnimationId;
|
|
@@ -1011,13 +1158,19 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1011
1158
|
CGPointMake(
|
|
1012
1159
|
CGRectGetMidX(toGeometry.rect),
|
|
1013
1160
|
CGRectGetMidY(toGeometry.rect));
|
|
1014
|
-
const BOOL
|
|
1015
|
-
!
|
|
1016
|
-
!SmoothClipCornerRadiiAreUniform(toGeometry.radii) ||
|
|
1161
|
+
const BOOL changesShape = !CGRectEqualToRect(fromGeometry.rect, toGeometry.rect) ||
|
|
1162
|
+
!SmoothClipCornerRadiiEqual(fromGeometry.radii, toGeometry.radii) ||
|
|
1017
1163
|
fromGeometry.curve != toGeometry.curve;
|
|
1164
|
+
const BOOL usesMask = changesShape &&
|
|
1165
|
+
(!SmoothClipCornerRadiiAreUniform(fromGeometry.radii) ||
|
|
1166
|
+
!SmoothClipCornerRadiiAreUniform(toGeometry.radii) ||
|
|
1167
|
+
fromGeometry.curve != toGeometry.curve);
|
|
1018
1168
|
[self writeLayerGeometry:toGeometry
|
|
1019
1169
|
hasExplicitRadii:hasExplicitToRadii];
|
|
1020
1170
|
[self writeContentTranslation:toContentTranslation scale:toContentScale];
|
|
1171
|
+
[self writeAppearanceRotation:toRotation opacity:toOpacity rect:toGeometry.rect];
|
|
1172
|
+
_animationFromRotation = fromRotation;
|
|
1173
|
+
_animationToRotation = toRotation;
|
|
1021
1174
|
_canonicalShadow = toShadow;
|
|
1022
1175
|
SmoothBoxShadowNormalizeAnimationEndpoints(
|
|
1023
1176
|
fromGeometry, &fromShadow, toGeometry, &toShadow);
|
|
@@ -1078,6 +1231,7 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1078
1231
|
? [CAAnimationGroup animation]
|
|
1079
1232
|
: nil;
|
|
1080
1233
|
CAAnimationGroup *maskAnimation = nil;
|
|
1234
|
+
_hasMaskAnimation = usesMask;
|
|
1081
1235
|
if (_activeAnimationKind == 1) {
|
|
1082
1236
|
CAMediaTimingFunction *timing = [CAMediaTimingFunction
|
|
1083
1237
|
functionWithControlPoints:_timingAnimation.controlPoint1X
|
|
@@ -1318,10 +1472,47 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1318
1472
|
maskAnimation.duration = group.duration;
|
|
1319
1473
|
}
|
|
1320
1474
|
|
|
1475
|
+
CAAnimationGroup *appearanceGroup = [CAAnimationGroup animation];
|
|
1476
|
+
NSMutableArray<CAAnimation *> *appearanceAnimations = [NSMutableArray array];
|
|
1477
|
+
// A zero anchor and moving bounds origin avoid host-size-dependent pivot math.
|
|
1478
|
+
NSArray<NSString *> *keys = @[@"bounds.origin.x", @"bounds.origin.y", @"position.x",
|
|
1479
|
+
@"position.y", @"transform.rotation.z", @"opacity"];
|
|
1480
|
+
NSArray<NSNumber *> *fromValues = @[@(fromPosition.x), @(fromPosition.y), @(fromPosition.x),
|
|
1481
|
+
@(fromPosition.y), @(fromRotation), @(fromOpacity)];
|
|
1482
|
+
NSArray<NSNumber *> *toValues = @[@(toPosition.x), @(toPosition.y), @(toPosition.x),
|
|
1483
|
+
@(toPosition.y), @(toRotation), @(toOpacity)];
|
|
1484
|
+
for (NSUInteger index = 0; index < keys.count; index++) {
|
|
1485
|
+
if ([fromValues[index] isEqual:toValues[index]]) continue;
|
|
1486
|
+
// At zero rotation, origin/position cancel exactly; no pivot animation is needed.
|
|
1487
|
+
if (index < 4 && fromRotation == 0 && toRotation == 0) continue;
|
|
1488
|
+
CAAnimation *animation;
|
|
1489
|
+
if (_activeAnimationKind == 1) {
|
|
1490
|
+
CAMediaTimingFunction *timing = [CAMediaTimingFunction
|
|
1491
|
+
functionWithControlPoints:_timingAnimation.controlPoint1X :_timingAnimation.controlPoint1Y
|
|
1492
|
+
:_timingAnimation.controlPoint2X :_timingAnimation.controlPoint2Y];
|
|
1493
|
+
animation = [self basicAnimationForKeyPath:keys[index] fromValue:fromValues[index]
|
|
1494
|
+
toValue:toValues[index] timingFunction:timing];
|
|
1495
|
+
} else {
|
|
1496
|
+
animation = [self springAnimationForKeyPath:keys[index] fromValue:fromValues[index]
|
|
1497
|
+
toValue:toValues[index] velocity:_springAnimation.initialVelocity duration:group.duration];
|
|
1498
|
+
}
|
|
1499
|
+
[appearanceAnimations addObject:animation];
|
|
1500
|
+
}
|
|
1501
|
+
appearanceGroup.animations = appearanceAnimations;
|
|
1502
|
+
appearanceGroup.duration = group.duration;
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
// An explicit media-time origin is required to pause and resume spring
|
|
1506
|
+
// groups without touching the parent layer clock (which would also pause
|
|
1507
|
+
// consumer animations in the clipped subtree).
|
|
1508
|
+
if (sharedBeginTime <= 0) {
|
|
1509
|
+
sharedBeginTime = CACurrentMediaTime();
|
|
1510
|
+
}
|
|
1321
1511
|
if (sharedBeginTime > 0) {
|
|
1322
1512
|
// Each layer receives the same absolute media timestamp translated into
|
|
1323
1513
|
// its local clock. Group participants therefore advance from one epoch
|
|
1324
1514
|
// even though their animations are installed sequentially.
|
|
1515
|
+
appearanceGroup.beginTime = [_presentationContainer.layer convertTime:sharedBeginTime fromLayer:nil];
|
|
1325
1516
|
group.beginTime = [layer convertTime:sharedBeginTime fromLayer:nil];
|
|
1326
1517
|
contentGroup.beginTime = [_contentContainer.layer
|
|
1327
1518
|
convertTime:sharedBeginTime
|
|
@@ -1343,6 +1534,13 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1343
1534
|
_animationDelegate.driverId = _driverId;
|
|
1344
1535
|
_animationDelegate.animationId = _activeAnimationId;
|
|
1345
1536
|
group.delegate = _animationDelegate;
|
|
1537
|
+
_springPausedForInactivity = NO;
|
|
1538
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
1539
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
1540
|
+
_hasAppearanceAnimation = appearanceAnimations.count > 0;
|
|
1541
|
+
if (_hasAppearanceAnimation) {
|
|
1542
|
+
[_presentationContainer.layer addAnimation:appearanceGroup forKey:@"smoothClip.appearance"];
|
|
1543
|
+
}
|
|
1346
1544
|
[layer addAnimation:group forKey:@"smoothClip.geometry"];
|
|
1347
1545
|
[_contentContainer.layer addAnimation:contentGroup
|
|
1348
1546
|
forKey:@"smoothClip.content"];
|
|
@@ -1387,11 +1585,15 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1387
1585
|
current.contentTranslateY)
|
|
1388
1586
|
fromContentScale:current.contentScale
|
|
1389
1587
|
fromShadow:current.shadow
|
|
1588
|
+
fromRotation:current.rotation
|
|
1589
|
+
fromOpacity:current.opacity
|
|
1390
1590
|
toGeometry:target
|
|
1391
1591
|
hasExplicitToRadii:_requestedHasExplicitRadii
|
|
1392
1592
|
toContentTranslation:_requestedContentTranslation
|
|
1393
1593
|
toContentScale:_requestedContentScale
|
|
1394
1594
|
toShadow:_requestedShadow
|
|
1595
|
+
toRotation:_requestedRotation
|
|
1596
|
+
toOpacity:_requestedOpacity
|
|
1395
1597
|
duration:duration
|
|
1396
1598
|
sharedBeginTime:sharedBeginTime];
|
|
1397
1599
|
return YES;
|
|
@@ -1465,6 +1667,168 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1465
1667
|
}
|
|
1466
1668
|
}
|
|
1467
1669
|
|
|
1670
|
+
- (BOOL)smoothClipPauseSpringAnimation:(int32_t)animationId
|
|
1671
|
+
atMediaTime:(CFTimeInterval)mediaTime {
|
|
1672
|
+
if (_activeAnimationId != animationId || _activeAnimationKind != 2 ||
|
|
1673
|
+
_springPausedForInactivity) {
|
|
1674
|
+
return NO;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
CALayer *geometryLayer = _clipContainer.layer;
|
|
1678
|
+
CALayer *contentLayer = _contentContainer.layer;
|
|
1679
|
+
CAAnimation *installedGeometry =
|
|
1680
|
+
[geometryLayer animationForKey:@"smoothClip.geometry"];
|
|
1681
|
+
CAAnimation *installedContent =
|
|
1682
|
+
[contentLayer animationForKey:@"smoothClip.content"];
|
|
1683
|
+
CAAnimation *installedAppearance = [_presentationContainer.layer animationForKey:@"smoothClip.appearance"];
|
|
1684
|
+
if ((_hasAppearanceAnimation && installedAppearance == nil) || installedGeometry == nil || installedContent == nil) return NO;
|
|
1685
|
+
|
|
1686
|
+
auto pausedCopy = ^CAAnimation *(CALayer *layer, CAAnimation *installed) {
|
|
1687
|
+
if (installed == nil) return (CAAnimation *)nil;
|
|
1688
|
+
CAAnimation *paused = [installed copy];
|
|
1689
|
+
const CFTimeInterval localNow =
|
|
1690
|
+
[layer convertTime:mediaTime fromLayer:nil];
|
|
1691
|
+
const CFTimeInterval elapsed = std::clamp(
|
|
1692
|
+
localNow - installed.beginTime,
|
|
1693
|
+
(CFTimeInterval)0,
|
|
1694
|
+
installed.duration);
|
|
1695
|
+
paused.speed = 0;
|
|
1696
|
+
paused.timeOffset = elapsed;
|
|
1697
|
+
paused.beginTime = 0;
|
|
1698
|
+
return paused;
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
CAAnimation *appearance = pausedCopy(_presentationContainer.layer, installedAppearance);
|
|
1702
|
+
CAAnimation *geometry = pausedCopy(geometryLayer, installedGeometry);
|
|
1703
|
+
CAAnimation *content = pausedCopy(contentLayer, installedContent);
|
|
1704
|
+
CAAnimation *installedMask =
|
|
1705
|
+
[_unequalCornerMask animationForKey:@"smoothClip.mask"];
|
|
1706
|
+
CAAnimation *mask = pausedCopy(_unequalCornerMask, installedMask);
|
|
1707
|
+
CAAnimation *installedShadow =
|
|
1708
|
+
[_shadowLayer animationForKey:@"smoothClip.shadow"];
|
|
1709
|
+
CAAnimation *shadow = pausedCopy(_shadowLayer, installedShadow);
|
|
1710
|
+
|
|
1711
|
+
_animationDelegate.invalidated = YES;
|
|
1712
|
+
_ignoreAnimationCallback = YES;
|
|
1713
|
+
[geometryLayer removeAnimationForKey:@"smoothClip.geometry"];
|
|
1714
|
+
[contentLayer removeAnimationForKey:@"smoothClip.content"];
|
|
1715
|
+
[_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
|
|
1716
|
+
[_presentationContainer.layer removeAnimationForKey:@"smoothClip.appearance"];
|
|
1717
|
+
[_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
|
|
1718
|
+
|
|
1719
|
+
_animationDelegate = [SmoothClipAnimationDelegate new];
|
|
1720
|
+
_animationDelegate.view = self;
|
|
1721
|
+
_animationDelegate.driverId = _driverId;
|
|
1722
|
+
_animationDelegate.animationId = animationId;
|
|
1723
|
+
geometry.delegate = _animationDelegate;
|
|
1724
|
+
if (appearance != nil) {
|
|
1725
|
+
[_presentationContainer.layer addAnimation:appearance forKey:@"smoothClip.appearance"];
|
|
1726
|
+
}
|
|
1727
|
+
[geometryLayer addAnimation:geometry forKey:@"smoothClip.geometry"];
|
|
1728
|
+
[contentLayer addAnimation:content forKey:@"smoothClip.content"];
|
|
1729
|
+
if (mask != nil) {
|
|
1730
|
+
[_unequalCornerMask addAnimation:mask forKey:@"smoothClip.mask"];
|
|
1731
|
+
}
|
|
1732
|
+
if (shadow != nil) {
|
|
1733
|
+
[_shadowLayer addAnimation:shadow forKey:@"smoothClip.shadow"];
|
|
1734
|
+
}
|
|
1735
|
+
_ignoreAnimationCallback = NO;
|
|
1736
|
+
_springPausedForInactivity = YES;
|
|
1737
|
+
_pausedSpringHadMaskAnimation = mask != nil;
|
|
1738
|
+
_pausedSpringHadShadowAnimation = shadow != nil;
|
|
1739
|
+
return YES;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
- (BOOL)smoothClipResumeSpringAnimation:(int32_t)animationId
|
|
1743
|
+
atMediaTime:(CFTimeInterval)mediaTime
|
|
1744
|
+
catchUpBy:(CFTimeInterval)catchUp {
|
|
1745
|
+
if (_activeAnimationId != animationId || _activeAnimationKind != 2 ||
|
|
1746
|
+
!_springPausedForInactivity) {
|
|
1747
|
+
return NO;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
CALayer *geometryLayer = _clipContainer.layer;
|
|
1751
|
+
CALayer *contentLayer = _contentContainer.layer;
|
|
1752
|
+
CAAnimation *installedGeometry =
|
|
1753
|
+
[geometryLayer animationForKey:@"smoothClip.geometry"];
|
|
1754
|
+
CAAnimation *installedContent =
|
|
1755
|
+
[contentLayer animationForKey:@"smoothClip.content"];
|
|
1756
|
+
CAAnimation *installedMask =
|
|
1757
|
+
[_unequalCornerMask animationForKey:@"smoothClip.mask"];
|
|
1758
|
+
CAAnimation *installedShadow =
|
|
1759
|
+
[_shadowLayer animationForKey:@"smoothClip.shadow"];
|
|
1760
|
+
CAAnimation *installedAppearance = [_presentationContainer.layer animationForKey:@"smoothClip.appearance"];
|
|
1761
|
+
if ((_hasAppearanceAnimation && installedAppearance == nil) || installedGeometry == nil || installedContent == nil ||
|
|
1762
|
+
(_pausedSpringHadMaskAnimation && installedMask == nil) ||
|
|
1763
|
+
(_pausedSpringHadShadowAnimation && installedShadow == nil)) {
|
|
1764
|
+
return NO;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
auto resumedCopy = ^CAAnimation *(CALayer *layer, CAAnimation *installed) {
|
|
1768
|
+
if (installed == nil) return (CAAnimation *)nil;
|
|
1769
|
+
CAAnimation *resumed = [installed copy];
|
|
1770
|
+
const CFTimeInterval localNow =
|
|
1771
|
+
[layer convertTime:mediaTime fromLayer:nil];
|
|
1772
|
+
const CFTimeInterval resumedTime = std::min(
|
|
1773
|
+
installed.duration,
|
|
1774
|
+
std::max((CFTimeInterval)0, installed.timeOffset + catchUp));
|
|
1775
|
+
resumed.speed = 1;
|
|
1776
|
+
resumed.timeOffset = 0;
|
|
1777
|
+
resumed.beginTime = localNow - resumedTime;
|
|
1778
|
+
return resumed;
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
CAAnimation *appearance = resumedCopy(_presentationContainer.layer, installedAppearance);
|
|
1782
|
+
CAAnimation *geometry = resumedCopy(geometryLayer, installedGeometry);
|
|
1783
|
+
CAAnimation *content = resumedCopy(contentLayer, installedContent);
|
|
1784
|
+
CAAnimation *mask = resumedCopy(_unequalCornerMask, installedMask);
|
|
1785
|
+
CAAnimation *shadow = resumedCopy(_shadowLayer, installedShadow);
|
|
1786
|
+
|
|
1787
|
+
_animationDelegate.invalidated = YES;
|
|
1788
|
+
_ignoreAnimationCallback = YES;
|
|
1789
|
+
[geometryLayer removeAnimationForKey:@"smoothClip.geometry"];
|
|
1790
|
+
[contentLayer removeAnimationForKey:@"smoothClip.content"];
|
|
1791
|
+
[_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
|
|
1792
|
+
[_presentationContainer.layer removeAnimationForKey:@"smoothClip.appearance"];
|
|
1793
|
+
[_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
|
|
1794
|
+
|
|
1795
|
+
_animationDelegate = [SmoothClipAnimationDelegate new];
|
|
1796
|
+
_animationDelegate.view = self;
|
|
1797
|
+
_animationDelegate.driverId = _driverId;
|
|
1798
|
+
_animationDelegate.animationId = animationId;
|
|
1799
|
+
geometry.delegate = _animationDelegate;
|
|
1800
|
+
if (appearance != nil) {
|
|
1801
|
+
[_presentationContainer.layer addAnimation:appearance forKey:@"smoothClip.appearance"];
|
|
1802
|
+
}
|
|
1803
|
+
[geometryLayer addAnimation:geometry forKey:@"smoothClip.geometry"];
|
|
1804
|
+
[contentLayer addAnimation:content forKey:@"smoothClip.content"];
|
|
1805
|
+
if (mask != nil) {
|
|
1806
|
+
[_unequalCornerMask addAnimation:mask forKey:@"smoothClip.mask"];
|
|
1807
|
+
}
|
|
1808
|
+
if (shadow != nil) {
|
|
1809
|
+
[_shadowLayer addAnimation:shadow forKey:@"smoothClip.shadow"];
|
|
1810
|
+
}
|
|
1811
|
+
_ignoreAnimationCallback = NO;
|
|
1812
|
+
_springPausedForInactivity = NO;
|
|
1813
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
1814
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
1815
|
+
return YES;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
- (BOOL)smoothClipHasInstalledAnimation:(int32_t)animationId {
|
|
1819
|
+
return _activeAnimationId == animationId &&
|
|
1820
|
+
(!_hasAppearanceAnimation ||
|
|
1821
|
+
[_presentationContainer.layer animationForKey:@"smoothClip.appearance"] != nil) &&
|
|
1822
|
+
[_clipContainer.layer animationForKey:@"smoothClip.geometry"] != nil &&
|
|
1823
|
+
[_contentContainer.layer animationForKey:@"smoothClip.content"] != nil &&
|
|
1824
|
+
(!_springPausedForInactivity ||
|
|
1825
|
+
(!_pausedSpringHadMaskAnimation ||
|
|
1826
|
+
[_unequalCornerMask animationForKey:@"smoothClip.mask"] != nil)) &&
|
|
1827
|
+
(!_springPausedForInactivity ||
|
|
1828
|
+
(!_pausedSpringHadShadowAnimation ||
|
|
1829
|
+
[_shadowLayer animationForKey:@"smoothClip.shadow"] != nil));
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1468
1832
|
- (void)smoothClipAnimationDidStopWithDriverId:(uint64_t)driverId
|
|
1469
1833
|
animationId:(int32_t)animationId
|
|
1470
1834
|
finished:(BOOL)finished {
|
|
@@ -1472,9 +1836,15 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1472
1836
|
_activeAnimationId = 0;
|
|
1473
1837
|
_activeAnimationKind = 0;
|
|
1474
1838
|
_animationDelegate = nil;
|
|
1839
|
+
_springPausedForInactivity = NO;
|
|
1840
|
+
_pausedSpringHadMaskAnimation = NO;
|
|
1841
|
+
_pausedSpringHadShadowAnimation = NO;
|
|
1475
1842
|
[_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
|
|
1843
|
+
[_presentationContainer.layer removeAnimationForKey:@"smoothClip.appearance"];
|
|
1476
1844
|
[_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
|
|
1477
|
-
|
|
1845
|
+
if (_hasMaskAnimation) {
|
|
1846
|
+
[self applyStaticCornerRepresentation:[self canonicalGeometryValue]];
|
|
1847
|
+
}
|
|
1478
1848
|
[self writeShadow:_requestedShadow
|
|
1479
1849
|
geometry:[self canonicalGeometryValue]];
|
|
1480
1850
|
[self syncVisibilityForRect:_canonicalClip];
|
|
@@ -1516,6 +1886,8 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1516
1886
|
newProps.initialClipBoxShadowOffsetY,
|
|
1517
1887
|
newProps.initialClipBoxShadowBlurRadius,
|
|
1518
1888
|
newProps.initialClipBoxShadowSpreadDistance,
|
|
1889
|
+
newProps.initialRotation,
|
|
1890
|
+
newProps.initialOpacity,
|
|
1519
1891
|
&initial)) {
|
|
1520
1892
|
// The initial presentation is atomic. Never apply a valid subset when one
|
|
1521
1893
|
// field rejects.
|
|
@@ -1537,6 +1909,7 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1537
1909
|
}
|
|
1538
1910
|
|
|
1539
1911
|
- (void)layoutContentContainer {
|
|
1912
|
+
[self writeAppearanceRotation:_canonicalRotation opacity:_canonicalOpacity rect:_canonicalClip];
|
|
1540
1913
|
_contentContainer.layer.bounds = CGRectMake(
|
|
1541
1914
|
0, 0, MAX(0, self.bounds.size.width), MAX(0, self.bounds.size.height));
|
|
1542
1915
|
_contentContainer.layer.position = CGPointMake(
|
|
@@ -1564,6 +1937,12 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1564
1937
|
const CGRect clip = _activeAnimationId != 0
|
|
1565
1938
|
? [self presentationRectWithRadii:&radii]
|
|
1566
1939
|
: _canonicalClip;
|
|
1940
|
+
if ([self visibleOpacity] <= 0) return NO;
|
|
1941
|
+
const double rotation = [self visibleRotation];
|
|
1942
|
+
const CGPoint center = CGPointMake(CGRectGetMidX(clip), CGRectGetMidY(clip));
|
|
1943
|
+
const double dx = point.x - center.x, dy = point.y - center.y;
|
|
1944
|
+
point = CGPointMake(center.x + cos(rotation) * dx + sin(rotation) * dy,
|
|
1945
|
+
center.y - sin(rotation) * dx + cos(rotation) * dy);
|
|
1567
1946
|
if (CGRectIsEmpty(clip) || !CGRectContainsPoint(clip, point)) return NO;
|
|
1568
1947
|
CGPathRef path = nil;
|
|
1569
1948
|
if (_clipContainer.layer.mask == _unequalCornerMask) {
|
|
@@ -1606,7 +1985,9 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1606
1985
|
shadowOffsetX:(double)shadowOffsetX
|
|
1607
1986
|
shadowOffsetY:(double)shadowOffsetY
|
|
1608
1987
|
shadowBlurRadius:(double)shadowBlurRadius
|
|
1609
|
-
shadowSpreadDistance:(double)shadowSpreadDistance
|
|
1988
|
+
shadowSpreadDistance:(double)shadowSpreadDistance
|
|
1989
|
+
rotation:(double)rotation
|
|
1990
|
+
opacity:(double)opacity {
|
|
1610
1991
|
smoothclip::Presentation presentation{};
|
|
1611
1992
|
if (!SmoothClipBuildPresentation(
|
|
1612
1993
|
x,
|
|
@@ -1630,6 +2011,8 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1630
2011
|
shadowOffsetY,
|
|
1631
2012
|
shadowBlurRadius,
|
|
1632
2013
|
shadowSpreadDistance,
|
|
2014
|
+
rotation,
|
|
2015
|
+
opacity,
|
|
1633
2016
|
&presentation)) {
|
|
1634
2017
|
return;
|
|
1635
2018
|
}
|
|
@@ -1661,6 +2044,10 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1661
2044
|
_canonicalContentScale = 1;
|
|
1662
2045
|
_requestedShadow = {};
|
|
1663
2046
|
_canonicalShadow = {};
|
|
2047
|
+
_requestedRotation = _canonicalRotation = 0;
|
|
2048
|
+
_requestedOpacity = _canonicalOpacity = 1;
|
|
2049
|
+
_presentationContainer.layer.affineTransform = CGAffineTransformIdentity;
|
|
2050
|
+
_presentationContainer.layer.opacity = 1;
|
|
1664
2051
|
_hasLayout = NO;
|
|
1665
2052
|
_commandIsAuthoritative = NO;
|
|
1666
2053
|
|
|
@@ -1672,6 +2059,7 @@ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
|
|
|
1672
2059
|
layer.mask = nil;
|
|
1673
2060
|
_unequalCornerMask.path = nil;
|
|
1674
2061
|
_contentContainer.layer.affineTransform = CGAffineTransformIdentity;
|
|
2062
|
+
_hasShadowModel = NO;
|
|
1675
2063
|
_shadowLayer.shadowPath = nil;
|
|
1676
2064
|
_shadowLayer.shadowOpacity = 0;
|
|
1677
2065
|
_shadowLayer.shadowRadius = 0;
|